diff options
62 files changed, 1097 insertions, 289 deletions
diff --git a/.travis.yml b/.travis.yml index 24237322..f5389f74 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,6 @@ sudo: required services: - docker -language: python +language: generic script: | ./run-tests @@ -29,6 +29,10 @@ features, including: * Finding references (`:ALEFindReferences`) * Hover information (`:ALEHover`) +If you don't care about Language Server Protocol, ALE won't load any of the code +for working with it unless needed. One of ALE's general missions is that you +won't pay for the features that you don't use. + ## Table of Contents 1. [Supported Languages and Tools](#supported-languages) @@ -93,11 +97,12 @@ formatting. | Bash | shell [-n flag](https://www.gnu.org/software/bash/manual/bash.html#index-set), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | | Bourne Shell | shell [-n flag](http://linux.die.net/man/1/sh), [shellcheck](https://www.shellcheck.net/), [shfmt](https://github.com/mvdan/sh) | | C | [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint), [clang](http://clang.llvm.org/), [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | -| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) !!, [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint) !!, [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | +| C++ (filetype cpp) | [clang](http://clang.llvm.org/), [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) !!, [clangtidy](http://clang.llvm.org/extra/clang-tidy/) !!, [clang-format](https://clang.llvm.org/docs/ClangFormat.html), [cppcheck](http://cppcheck.sourceforge.net), [cpplint](https://github.com/google/styleguide/tree/gh-pages/cpplint) !!, [cquery](https://github.com/cquery-project/cquery), [flawfinder](https://www.dwheeler.com/flawfinder/), [gcc](https://gcc.gnu.org/) | | CUDA | [nvcc](http://docs.nvidia.com/cuda/cuda-compiler-driver-nvcc/index.html) | | C# | [mcs](http://www.mono-project.com/docs/about-mono/languages/csharp/) see:`help ale-cs-mcs` for details, [mcsc](http://www.mono-project.com/docs/about-mono/languages/csharp/) !! see:`help ale-cs-mcsc` for details and configuration| | Chef | [foodcritic](http://www.foodcritic.io/) | | Clojure | [joker](https://github.com/candid82/joker) | +| CloudFormation | [cfn-python-lint](https://github.com/awslabs/cfn-python-lint) | | CMake | [cmakelint](https://github.com/richq/cmake-lint) | | CoffeeScript | [coffee](http://coffeescript.org/), [coffeelint](https://www.npmjs.com/package/coffeelint) | | Crystal | [crystal](https://crystal-lang.org/) !! | @@ -164,7 +169,7 @@ formatting. | Rust | cargo !! (see `:help ale-integration-rust` for configuration instructions), [rls](https://github.com/rust-lang-nursery/rls), [rustc](https://www.rust-lang.org/), [rustfmt](https://github.com/rust-lang-nursery/rustfmt) | | SASS | [sass-lint](https://www.npmjs.com/package/sass-lint), [stylelint](https://github.com/stylelint/stylelint) | | SCSS | [prettier](https://github.com/prettier/prettier), [sass-lint](https://www.npmjs.com/package/sass-lint), [scss-lint](https://github.com/brigade/scss-lint), [stylelint](https://github.com/stylelint/stylelint) | -| Scala | [fsc](https://www.scala-lang.org/old/sites/default/files/linuxsoft_archives/docu/files/tools/fsc.html), [scalac](http://scala-lang.org), [scalastyle](http://www.scalastyle.org) | +| Scala | [fsc](https://www.scala-lang.org/old/sites/default/files/linuxsoft_archives/docu/files/tools/fsc.html), [scalac](http://scala-lang.org), [scalafmt](https://scalameta.org/scalafmt/), [scalastyle](http://www.scalastyle.org) | | Slim | [slim-lint](https://github.com/sds/slim-lint) | | SML | [smlnj](http://www.smlnj.org/) | | Solidity | [solhint](https://github.com/protofire/solhint), [solium](https://github.com/duaraghav8/Solium) | @@ -253,7 +258,7 @@ See `:help ale-fix` for complete information on how to fix files with ALE. ALE offers some support for completion via hijacking of omnicompletion while you type. All of ALE's completion information must come from Language Server -Protocol linters, or from `tsserver` for TypeSript. +Protocol linters, or from `tsserver` for TypeScript. ```vim " Enable completion where available. @@ -749,8 +754,9 @@ ALE cannot easily detect which compiler flags to use. Some tools and build configurations can generate [compile_commands.json](https://clang.llvm.org/docs/JSONCompilationDatabase.html) -files. The `cppcheck`, `clangcheck` and `clangtidy` linters can read these -files for automatically determining the appropriate compiler flags to use. +files. The `cppcheck`, `clangcheck`, `clangtidy` and `cquery` linters can read +these files for automatically determining the appropriate compiler flags to +use. For linting with compilers like `gcc` and `clang`, and with other tools, you will need to tell ALE which compiler flags to use yourself. You can use diff --git a/ale_linters/cloudformation/cfn_python_lint.vim b/ale_linters/cloudformation/cfn_python_lint.vim new file mode 100644 index 00000000..25e86ca3 --- /dev/null +++ b/ale_linters/cloudformation/cfn_python_lint.vim @@ -0,0 +1,35 @@ +" Author: Yasuhiro Kiyota <yasuhiroki.duck@gmail.com> +" Description: Support cfn-python-lint for AWS Cloudformation template file + +function! ale_linters#cloudformation#cfn_python_lint#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " sample.template.yaml:96:7:96:15: [E3012] Property Resources/Sample/Properties/FromPort should be of type Integer + let l:pattern = '\v^(.*):(\d+):(\d+):(\d+):(\d+): \[([[:alnum:]]+)\] (.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:code = l:match[6] + + if ale#path#IsBufferPath(a:buffer, l:match[1]) + call add(l:output, { + \ 'lnum': l:match[2] + 0, + \ 'col': l:match[3] + 0, + \ 'end_lnum': l:match[4] + 0, + \ 'end_col': l:match[5] + 0, + \ 'text': l:match[7], + \ 'code': l:code, + \ 'type': l:code[:0] is# 'E' ? 'E' : 'W', + \}) + endif + endfor + + return l:output +endfunction + +call ale#linter#Define('cloudformation', { +\ 'name': 'cloudformation', +\ 'executable': 'cfn-lint', +\ 'command': 'cfn-lint --template %t --format parseable', +\ 'callback': 'ale_linters#cloudformation#cfn_python_lint#Handle', +\}) diff --git a/ale_linters/cpp/cquery.vim b/ale_linters/cpp/cquery.vim new file mode 100644 index 00000000..2fd77d46 --- /dev/null +++ b/ale_linters/cpp/cquery.vim @@ -0,0 +1,34 @@ +" Author: Ben Falconer <ben@falconers.me.uk> +" Description: A language server for C++ + +call ale#Set('cpp_cquery_executable', 'cquery') +call ale#Set('cpp_cquery_cache_directory', expand('~/.cache/cquery')) + +function! ale_linters#cpp#cquery#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile(a:buffer, 'compile_commands.json') + + return !empty(l:project_root) ? fnamemodify(l:project_root, ':h') : '' +endfunction + +function! ale_linters#cpp#cquery#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'cpp_cquery_executable') +endfunction + +function! ale_linters#cpp#cquery#GetCommand(buffer) abort + let l:executable = ale_linters#cpp#cquery#GetExecutable(a:buffer) + return ale#Escape(l:executable) +endfunction + +function! ale_linters#cpp#cquery#GetInitializationOptions(buffer) abort + return {'cacheDirectory': ale#Var(a:buffer, 'cpp_cquery_cache_directory')} +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'cquery', +\ 'lsp': 'stdio', +\ 'executable_callback': 'ale_linters#cpp#cquery#GetExecutable', +\ 'command_callback': 'ale_linters#cpp#cquery#GetCommand', +\ 'project_root_callback': 'ale_linters#cpp#cquery#GetProjectRoot', +\ 'initialization_options_callback': 'ale_linters#cpp#cquery#GetInitializationOptions', +\ 'language': 'cpp', +\}) diff --git a/ale_linters/gitcommit/gitlint.vim b/ale_linters/gitcommit/gitlint.vim index 0b4ca0fc..64731055 100644 --- a/ale_linters/gitcommit/gitlint.vim +++ b/ale_linters/gitcommit/gitlint.vim @@ -28,6 +28,10 @@ function! ale_linters#gitcommit#gitlint#Handle(buffer, lines) abort for l:match in ale#util#GetMatches(a:lines, l:pattern) let l:code = l:match[2] + if l:code is# 'T2' && !ale#Var(a:buffer, 'warn_about_trailing_whitespace') + continue + endif + let l:item = { \ 'lnum': l:match[1] + 0, \ 'text': l:match[3], diff --git a/ale_linters/puppet/puppet.vim b/ale_linters/puppet/puppet.vim index 8beeb61e..4ca0dd55 100644 --- a/ale_linters/puppet/puppet.vim +++ b/ale_linters/puppet/puppet.vim @@ -4,14 +4,15 @@ function! ale_linters#puppet#puppet#Handle(buffer, lines) abort " Matches patterns like the following: " Error: Could not parse for environment production: Syntax error at ':' at /root/puppetcode/modules/nginx/manifests/init.pp:43:12 " Error: Could not parse for environment production: Syntax error at '='; expected '}' at /root/puppetcode/modules/pancakes/manifests/init.pp:5" + " Error: Could not parse for environment production: Syntax error at 'parameter1' (file: /tmp/modules/mariadb/manifests/slave.pp, line: 4, column: 5) - let l:pattern = '^Error: .*: \(.\+\) at .\+\.pp:\(\d\+\):\=\(\d*\)' + let l:pattern = '^Error: .*: \(.\+\) \((file:\|at\) .\+\.pp\(, line: \|:\)\(\d\+\)\(, column: \|:\)\=\(\d*\)' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'lnum': l:match[2] + 0, - \ 'col': l:match[3] + 0, + \ 'lnum': l:match[4] + 0, + \ 'col': l:match[6] + 0, \ 'text': l:match[1], \}) endfor diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim index 7398b1dc..fdc4ac94 100644 --- a/ale_linters/python/flake8.vim +++ b/ale_linters/python/flake8.vim @@ -52,6 +52,10 @@ function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) let l:version = ale#semver#GetVersion(l:executable, a:version_output) + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run flake8' + \ : '' + " Only include the --stdin-display-name argument if we can parse the " flake8 version, and it is recent enough to support it. let l:display_name_args = ale#semver#GTE(l:version, [3, 0, 0]) @@ -61,7 +65,7 @@ function! ale_linters#python#flake8#GetCommand(buffer, version_output) abort let l:options = ale#Var(a:buffer, 'python_flake8_options') return l:cd_string - \ . ale#Escape(l:executable) + \ . ale#Escape(l:executable) . l:exec_args \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --format=default' \ . l:display_name_args . ' -' diff --git a/ale_linters/python/mypy.vim b/ale_linters/python/mypy.vim index e8ceb6a3..b38ccdeb 100644 --- a/ale_linters/python/mypy.vim +++ b/ale_linters/python/mypy.vim @@ -23,10 +23,14 @@ function! ale_linters#python#mypy#GetCommand(buffer) abort let l:dir = s:GetDir(a:buffer) let l:executable = ale_linters#python#mypy#GetExecutable(a:buffer) + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run mypy' + \ : '' + " We have to always switch to an explicit directory for a command so " we can know with certainty the base path for the 'filename' keys below. return ale#path#CdString(l:dir) - \ . ale#Escape(l:executable) + \ . ale#Escape(l:executable) . l:exec_args \ . ' --show-column-numbers ' \ . ale#Var(a:buffer, 'python_mypy_options') \ . ' --shadow-file %s %t %s' diff --git a/ale_linters/python/prospector.vim b/ale_linters/python/prospector.vim index b3d11aa8..eadfee47 100644 --- a/ale_linters/python/prospector.vim +++ b/ale_linters/python/prospector.vim @@ -14,7 +14,14 @@ function! ale_linters#python#prospector#GetExecutable(buffer) abort endfunction function! ale_linters#python#prospector#GetCommand(buffer) abort - return ale#Escape(ale_linters#python#prospector#GetExecutable(a:buffer)) + let l:executable = ale_linters#python#prospector#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run prospector' + \ : '' + + return ale#Escape(l:executable) + \ . l:exec_args \ . ' ' . ale#Var(a:buffer, 'python_prospector_options') \ . ' --messages-only --absolute-paths --zero-exit --output-format json' \ . ' %s' diff --git a/ale_linters/python/pycodestyle.vim b/ale_linters/python/pycodestyle.vim index 9254f4ab..de96363f 100644 --- a/ale_linters/python/pycodestyle.vim +++ b/ale_linters/python/pycodestyle.vim @@ -10,7 +10,13 @@ function! ale_linters#python#pycodestyle#GetExecutable(buffer) abort endfunction function! ale_linters#python#pycodestyle#GetCommand(buffer) abort - return ale#Escape(ale_linters#python#pycodestyle#GetExecutable(a:buffer)) + let l:executable = ale_linters#python#pycodestyle#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pycodestyle' + \ : '' + + return ale#Escape(l:executable) . l:exec_args \ . ' ' \ . ale#Var(a:buffer, 'python_pycodestyle_options') \ . ' -' diff --git a/ale_linters/python/pyflakes.vim b/ale_linters/python/pyflakes.vim index 475c3a6d..86ff8773 100644 --- a/ale_linters/python/pyflakes.vim +++ b/ale_linters/python/pyflakes.vim @@ -11,7 +11,13 @@ endfunction function! ale_linters#python#pyflakes#GetCommand(buffer) abort let l:executable = ale_linters#python#pyflakes#GetExecutable(a:buffer) - return ale#Escape(l:executable) . ' %t' + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pyflakes' + \ : '' + + return ale#Escape(l:executable) + \ . l:exec_args + \ . ' %t' endfunction function! ale_linters#python#pyflakes#Handle(buffer, lines) abort diff --git a/ale_linters/python/pylint.vim b/ale_linters/python/pylint.vim index a6fc8ed4..9239f835 100644 --- a/ale_linters/python/pylint.vim +++ b/ale_linters/python/pylint.vim @@ -15,8 +15,14 @@ function! ale_linters#python#pylint#GetCommand(buffer) abort \ ? ale#path#BufferCdString(a:buffer) \ : '' + let l:executable = ale_linters#python#pylint#GetExecutable(a:buffer) + + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pylint' + \ : '' + return l:cd_string - \ . ale#Escape(ale_linters#python#pylint#GetExecutable(a:buffer)) + \ . ale#Escape(l:executable) . l:exec_args \ . ' ' . ale#Var(a:buffer, 'python_pylint_options') \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n' \ . ' %s' diff --git a/ale_linters/python/pyls.vim b/ale_linters/python/pyls.vim index 883b38f5..010cb31f 100644 --- a/ale_linters/python/pyls.vim +++ b/ale_linters/python/pyls.vim @@ -11,7 +11,11 @@ endfunction function! ale_linters#python#pyls#GetCommand(buffer) abort let l:executable = ale_linters#python#pyls#GetExecutable(a:buffer) - return ale#Escape(l:executable) + let l:exec_args = l:executable =~? 'pipenv$' + \ ? ' run pyls' + \ : '' + + return ale#Escape(l:executable) . l:exec_args endfunction call ale#linter#Define('python', { diff --git a/ale_linters/r/lintr.vim b/ale_linters/r/lintr.vim index 51e5c562..8f74c9b8 100644 --- a/ale_linters/r/lintr.vim +++ b/ale_linters/r/lintr.vim @@ -22,7 +22,7 @@ function! ale_linters#r#lintr#GetCommand(buffer) abort \ . l:lint_cmd return ale#path#BufferCdString(a:buffer) - \ . 'Rscript -e ' + \ . 'Rscript --vanilla -e ' \ . ale#Escape(l:cmd_string) . ' %t' endfunction diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim index 09f41bbb..ef0c3bd1 100644 --- a/ale_linters/rust/cargo.vim +++ b/ale_linters/rust/cargo.vim @@ -4,6 +4,8 @@ call ale#Set('rust_cargo_use_check', 1) call ale#Set('rust_cargo_check_all_targets', 0) +call ale#Set('rust_cargo_check_examples', 0) +call ale#Set('rust_cargo_check_tests', 0) call ale#Set('rust_cargo_default_feature_behavior', 'default') call ale#Set('rust_cargo_include_features', '') @@ -31,6 +33,12 @@ function! ale_linters#rust#cargo#GetCommand(buffer, version_output) abort let l:use_all_targets = l:use_check \ && ale#Var(a:buffer, 'rust_cargo_check_all_targets') \ && ale#semver#GTE(l:version, [0, 22, 0]) + let l:use_examples = l:use_check + \ && ale#Var(a:buffer, 'rust_cargo_check_examples') + \ && ale#semver#GTE(l:version, [0, 22, 0]) + let l:use_tests = l:use_check + \ && ale#Var(a:buffer, 'rust_cargo_check_tests') + \ && ale#semver#GTE(l:version, [0, 22, 0]) let l:include_features = ale#Var(a:buffer, 'rust_cargo_include_features') if !empty(l:include_features) @@ -50,6 +58,8 @@ function! ale_linters#rust#cargo#GetCommand(buffer, version_output) abort return 'cargo ' \ . (l:use_check ? 'check' : 'build') \ . (l:use_all_targets ? ' --all-targets' : '') + \ . (l:use_examples ? ' --examples' : '') + \ . (l:use_tests ? ' --tests' : '') \ . ' --frozen --message-format=json -q' \ . l:default_feature \ . l:include_features diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index 6fa2619b..4823b00c 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -317,7 +317,7 @@ function! ale#completion#ParseLSPCompletions(response) abort \ 'word': l:word, \ 'kind': l:kind, \ 'icase': 1, - \ 'menu': l:item.detail, + \ 'menu': get(l:item, 'detail', ''), \ 'info': get(l:item, 'documentation', ''), \}) endfor @@ -389,14 +389,13 @@ function! s:GetLSPCompletions(linter) abort \ ? function('ale#completion#HandleTSServerResponse') \ : function('ale#completion#HandleLSPResponse') - let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id - let l:root = l:lsp_details.project_root if a:linter.lsp is# 'tsserver' let l:message = ale#lsp#tsserver_message#Completions( @@ -408,7 +407,7 @@ function! s:GetLSPCompletions(linter) abort else " Send a message saying the buffer has changed first, otherwise " completions won't know what text is nearby. - call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root) + call ale#lsp#NotifyForChanges(l:lsp_details) " For LSP completions, we need to clamp the column to the length of " the line. python-language-server and perhaps others do not implement @@ -424,7 +423,7 @@ function! s:GetLSPCompletions(linter) abort \) endif - let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) if l:request_id let b:ale_completion_info.conn_id = l:id diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim index a3bfcd17..6c70b64c 100644 --- a/autoload/ale/definition.vim +++ b/autoload/ale/definition.vim @@ -22,7 +22,7 @@ function! ale#definition#HandleTSServerResponse(conn_id, response) abort \&& has_key(s:go_to_definition_map, a:response.request_seq) let l:options = remove(s:go_to_definition_map, a:response.request_seq) - if get(a:response, 'success', v:false) is v:true + if get(a:response, 'success', v:false) is v:true && !empty(a:response.body) let l:filename = a:response.body[0].file let l:line = a:response.body[0].start.line let l:column = a:response.body[0].start.offset @@ -65,14 +65,13 @@ function! s:GoToLSPDefinition(linter, options) abort \ ? function('ale#definition#HandleTSServerResponse') \ : function('ale#definition#HandleLSPResponse') - let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id - let l:root = l:lsp_details.project_root if a:linter.lsp is# 'tsserver' let l:message = ale#lsp#tsserver_message#Definition( @@ -83,7 +82,7 @@ function! s:GoToLSPDefinition(linter, options) abort else " Send a message saying the buffer has changed first, or the " definition position probably won't make sense. - call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root) + call ale#lsp#NotifyForChanges(l:lsp_details) let l:column = min([l:column, len(getline(l:line))]) @@ -93,7 +92,7 @@ function! s:GoToLSPDefinition(linter, options) abort let l:message = ale#lsp#message#Definition(l:buffer, l:line, l:column) endif - let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) let s:go_to_definition_map[l:request_id] = { \ 'open_in_tab': get(a:options, 'open_in_tab', 0), diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 10589869..563c37a2 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -14,11 +14,6 @@ if !has_key(s:, 'job_info_map') let s:job_info_map = {} endif -" Associates LSP connection IDs with linter names. -if !has_key(s:, 'lsp_linter_map') - let s:lsp_linter_map = {} -endif - if !has_key(s:, 'executable_cache_map') let s:executable_cache_map = {} endif @@ -79,16 +74,6 @@ function! ale#engine#InitBufferInfo(buffer) abort return 0 endfunction -" Clear LSP linter data for the linting engine. -function! ale#engine#ClearLSPData() abort - let s:lsp_linter_map = {} -endfunction - -" Just for tests. -function! ale#engine#SetLSPLinterMap(replacement_map) abort - let s:lsp_linter_map = a:replacement_map -endfunction - " This function is documented and part of the public API. " " Return 1 if ALE is busy checking a given buffer @@ -241,88 +226,6 @@ function! s:HandleExit(job_id, exit_code) abort call ale#engine#HandleLoclist(l:linter.name, l:buffer, l:loclist) endfunction -function! s:HandleLSPDiagnostics(conn_id, response) abort - let l:linter_name = s:lsp_linter_map[a:conn_id] - let l:filename = ale#path#FromURI(a:response.params.uri) - let l:buffer = bufnr(l:filename) - - if l:buffer <= 0 - return - endif - - let l:loclist = ale#lsp#response#ReadDiagnostics(a:response) - - call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist) -endfunction - -function! s:HandleTSServerDiagnostics(response, error_type) abort - let l:buffer = bufnr(a:response.body.file) - let l:info = get(g:ale_buffer_info, l:buffer, {}) - - if empty(l:info) - return - endif - - let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response) - - " tsserver sends syntax and semantic errors in separate messages, so we - " have to collect the messages separately for each buffer and join them - " back together again. - if a:error_type is# 'syntax' - let l:info.syntax_loclist = l:thislist - else - let l:info.semantic_loclist = l:thislist - endif - - let l:loclist = get(l:info, 'semantic_loclist', []) - \ + get(l:info, 'syntax_loclist', []) - - call ale#engine#HandleLoclist('tsserver', l:buffer, l:loclist) -endfunction - -function! s:HandleLSPErrorMessage(linter_name, response) abort - if !g:ale_history_enabled || !g:ale_history_log_output - return - endif - - if empty(a:linter_name) - return - endif - - let l:message = ale#lsp#response#GetErrorMessage(a:response) - - if empty(l:message) - return - endif - - " This global variable is set here so we don't load the debugging.vim file - " until someone uses :ALEInfo. - let g:ale_lsp_error_messages = get(g:, 'ale_lsp_error_messages', {}) - - if !has_key(g:ale_lsp_error_messages, a:linter_name) - let g:ale_lsp_error_messages[a:linter_name] = [] - endif - - call add(g:ale_lsp_error_messages[a:linter_name], l:message) -endfunction - -function! ale#engine#HandleLSPResponse(conn_id, response) abort - let l:method = get(a:response, 'method', '') - let l:linter_name = get(s:lsp_linter_map, a:conn_id, '') - - if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error') - call s:HandleLSPErrorMessage(l:linter_name, a:response) - elseif l:method is# 'textDocument/publishDiagnostics' - call s:HandleLSPDiagnostics(a:conn_id, a:response) - elseif get(a:response, 'type', '') is# 'event' - \&& get(a:response, 'event', '') is# 'semanticDiag' - call s:HandleTSServerDiagnostics(a:response, 'semantic') - elseif get(a:response, 'type', '') is# 'event' - \&& get(a:response, 'event', '') is# 'syntaxDiag' - call s:HandleTSServerDiagnostics(a:response, 'syntax') - endif -endfunction - function! ale#engine#SetResults(buffer, loclist) abort let l:linting_is_done = !ale#engine#IsCheckingBuffer(a:buffer) @@ -739,44 +642,6 @@ function! s:StopCurrentJobs(buffer, include_lint_file_jobs) abort let l:info.active_linter_list = l:new_active_linter_list endfunction -function! s:CheckWithLSP(buffer, linter) abort - let l:info = g:ale_buffer_info[a:buffer] - let l:lsp_details = ale#linter#StartLSP( - \ a:buffer, - \ a:linter, - \ function('ale#engine#HandleLSPResponse'), - \) - - if empty(l:lsp_details) - return 0 - endif - - let l:id = l:lsp_details.connection_id - let l:root = l:lsp_details.project_root - - " Remember the linter this connection is for. - let s:lsp_linter_map[l:id] = a:linter.name - - let l:change_message = a:linter.lsp is# 'tsserver' - \ ? ale#lsp#tsserver_message#Geterr(a:buffer) - \ : ale#lsp#message#DidChange(a:buffer) - let l:request_id = ale#lsp#Send(l:id, l:change_message, l:root) - - " If this was a file save event, also notify the server of that. - if a:linter.lsp isnot# 'tsserver' - \&& getbufvar(a:buffer, 'ale_save_event_fired', 0) - let l:save_message = ale#lsp#message#DidSave(a:buffer) - let l:request_id = ale#lsp#Send(l:id, l:save_message, l:root) - endif - - if l:request_id != 0 - if index(l:info.active_linter_list, a:linter.name) < 0 - call add(l:info.active_linter_list, a:linter.name) - endif - endif - - return l:request_id != 0 -endfunction function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort " Figure out which linters are still enabled, and remove @@ -832,7 +697,7 @@ endfunction " Returns 1 if the linter was successfully run. function! s:RunLinter(buffer, linter) abort if !empty(a:linter.lsp) - return s:CheckWithLSP(a:buffer, a:linter) + return ale#lsp_linter#CheckWithLSP(a:buffer, a:linter) else let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim index 17532b3b..7b55acfc 100644 --- a/autoload/ale/fix/registry.vim +++ b/autoload/ale/fix/registry.vim @@ -95,6 +95,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['ruby'], \ 'description': 'Fix ruby files with rufo', \ }, +\ 'scalafmt': { +\ 'function': 'ale#fixers#scalafmt#Fix', +\ 'suggested_filetypes': ['scala'], +\ 'description': 'Fix Scala files using scalafmt', +\ }, \ 'standard': { \ 'function': 'ale#fixers#standard#Fix', \ 'suggested_filetypes': ['javascript'], @@ -195,6 +200,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['javascript'], \ 'description': 'Fix JavaScript files using xo --fix.', \ }, +\ 'qmlfmt': { +\ 'function': 'ale#fixers#qmlfmt#Fix', +\ 'suggested_filetypes': ['qml'], +\ 'description': 'Fix QML files with qmlfmt.', +\ }, \} " Reset the function registry to the default entries. diff --git a/autoload/ale/fixers/qmlfmt.vim b/autoload/ale/fixers/qmlfmt.vim new file mode 100644 index 00000000..d750d1c4 --- /dev/null +++ b/autoload/ale/fixers/qmlfmt.vim @@ -0,0 +1,11 @@ +call ale#Set('qml_qmlfmt_executable', 'qmlfmt') + +function! ale#fixers#qmlfmt#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'qml_qmlfmt_executable') +endfunction + +function! ale#fixers#qmlfmt#Fix(buffer) abort + return { + \ 'command': ale#Escape(ale#fixers#qmlfmt#GetExecutable(a:buffer)), + \} +endfunction diff --git a/autoload/ale/fixers/scalafmt.vim b/autoload/ale/fixers/scalafmt.vim new file mode 100644 index 00000000..07d28275 --- /dev/null +++ b/autoload/ale/fixers/scalafmt.vim @@ -0,0 +1,26 @@ +" Author: Jeffrey Lau https://github.com/zoonfafer +" Description: Integration of Scalafmt with ALE. + +call ale#Set('scala_scalafmt_executable', 'scalafmt') +call ale#Set('scala_scalafmt_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('scala_scalafmt_options', '') + +function! ale#fixers#scalafmt#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'scala_scalafmt_executable') + let l:options = ale#Var(a:buffer, 'scala_scalafmt_options') + let l:exec_args = l:executable =~? 'ng$' + \ ? ' scalafmt' + \ : '' + + return ale#Escape(l:executable) . l:exec_args + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t' + +endfunction + +function! ale#fixers#scalafmt#Fix(buffer) abort + return { + \ 'command': ale#fixers#scalafmt#GetCommand(a:buffer), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim index 3bf92488..6d131adc 100644 --- a/autoload/ale/hover.vim +++ b/autoload/ale/hover.vim @@ -97,14 +97,14 @@ function! s:ShowDetails(linter, buffer, line, column, opt) abort \ ? function('ale#hover#HandleTSServerResponse') \ : function('ale#hover#HandleLSPResponse') - let l:lsp_details = ale#linter#StartLSP(a:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(a:buffer, a:linter, l:Callback) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id - let l:root = l:lsp_details.project_root + let l:language_id = l:lsp_details.language_id if a:linter.lsp is# 'tsserver' let l:column = a:column @@ -117,14 +117,14 @@ function! s:ShowDetails(linter, buffer, line, column, opt) abort else " Send a message saying the buffer has changed first, or the " hover position probably won't make sense. - call ale#lsp#Send(l:id, ale#lsp#message#DidChange(a:buffer), l:root) + call ale#lsp#NotifyForChanges(l:lsp_details) let l:column = min([a:column, len(getbufline(a:buffer, a:line)[0])]) let l:message = ale#lsp#message#Hover(a:buffer, a:line, l:column) endif - let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) let s:hover_map[l:request_id] = { \ 'buffer': a:buffer, diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index c791ba46..cc7be518 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -227,6 +227,21 @@ function! ale#linter#PreProcess(linter) abort throw '`completion_filter` must be a callback' endif endif + + if has_key(a:linter, 'initialization_options_callback') + if has_key(a:linter, 'initialization_options') + throw 'Only one of `initialization_options` or ' + \ . '`initialization_options_callback` should be set' + endif + + let l:obj.initialization_options_callback = a:linter.initialization_options_callback + + if !s:IsCallback(l:obj.initialization_options_callback) + throw '`initialization_options_callback` must be a callback if defined' + endif + elseif has_key(a:linter, 'initialization_options') + let l:obj.initialization_options = a:linter.initialization_options + endif endif let l:obj.output_stream = get(a:linter, 'output_stream', 'stdout') @@ -436,72 +451,3 @@ function! ale#linter#GetAddress(buffer, linter) abort \ ? ale#util#GetFunction(a:linter.address_callback)(a:buffer) \ : a:linter.address endfunction - -" Given a buffer, an LSP linter, and a callback to register for handling -" messages, start up an LSP linter and get ready to receive errors or -" completions. -function! ale#linter#StartLSP(buffer, linter, callback) abort - let l:command = '' - let l:address = '' - let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) - - if empty(l:root) && a:linter.lsp isnot# 'tsserver' - " If there's no project root, then we can't check files with LSP, - " unless we are using tsserver, which doesn't use project roots. - return {} - endif - - if a:linter.lsp is# 'socket' - let l:address = ale#linter#GetAddress(a:buffer, a:linter) - let l:conn_id = ale#lsp#ConnectToAddress( - \ l:address, - \ l:root, - \ a:callback, - \) - else - let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) - - if !executable(l:executable) - return {} - endif - - let l:command = ale#job#PrepareCommand( - \ a:buffer, - \ ale#linter#GetCommand(a:buffer, a:linter), - \) - let l:conn_id = ale#lsp#StartProgram( - \ l:executable, - \ l:command, - \ l:root, - \ a:callback, - \) - endif - - let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) - - if !l:conn_id - if g:ale_history_enabled && !empty(l:command) - call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command) - endif - - return {} - endif - - if ale#lsp#OpenDocumentIfNeeded(l:conn_id, a:buffer, l:root, l:language_id) - if g:ale_history_enabled && !empty(l:command) - call ale#history#Add(a:buffer, 'started', l:conn_id, l:command) - endif - endif - - " The change message needs to be sent for tsserver before doing anything. - if a:linter.lsp is# 'tsserver' - call ale#lsp#Send(l:conn_id, ale#lsp#tsserver_message#Change(a:buffer)) - endif - - return { - \ 'connection_id': l:conn_id, - \ 'command': l:command, - \ 'project_root': l:root, - \ 'language_id': l:language_id, - \} -endfunction diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 8db9348f..29759f66 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -6,18 +6,22 @@ let s:connections = [] let g:ale_lsp_next_message_id = 1 -function! s:NewConnection() abort +" Exposed only so tests can get at it. +" Do not call this function basically anywhere. +function! ale#lsp#NewConnection(initialization_options) abort " id: The job ID as a Number, or the server address as a string. " data: The message data received so far. " executable: An executable only set for program connections. - " open_documents: A list of buffers we told the server we opened. + " open_documents: A Dictionary mapping buffers to b:changedtick, keeping + " track of when documents were opened, and when we last changed them. " callback_list: A list of callbacks for handling LSP responses. let l:conn = { \ 'id': '', \ 'data': '', \ 'projects': {}, - \ 'open_documents': [], + \ 'open_documents': {}, \ 'callback_list': [], + \ 'initialization_options': a:initialization_options, \} call add(s:connections, l:conn) @@ -25,6 +29,11 @@ function! s:NewConnection() abort return l:conn endfunction +" Remove an LSP connection with a given ID. This is only for tests. +function! ale#lsp#RemoveConnectionWithID(id) abort + call filter(s:connections, 'v:val.id isnot a:id') +endfunction + function! s:FindConnection(key, value) abort for l:conn in s:connections if has_key(l:conn, a:key) && get(l:conn, a:key) == a:value @@ -207,6 +216,11 @@ function! ale#lsp#HandleOtherInitializeResponses(conn, response) abort endfunction function! ale#lsp#HandleMessage(conn, message) abort + if type(a:message) != type('') + " Ignore messages that aren't strings. + return + endif + let a:conn.data .= a:message " Parse the objects now if we can, and keep the remaining text. @@ -266,7 +280,7 @@ endfunction " " The job ID will be returned for for the program if it ran, otherwise " 0 will be returned. -function! ale#lsp#StartProgram(executable, command, project_root, callback) abort +function! ale#lsp#StartProgram(executable, command, project_root, callback, initialization_options) abort if !executable(a:executable) return 0 endif @@ -274,7 +288,7 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback) abor let l:conn = s:FindConnection('executable', a:executable) " Get the current connection or a new one. - let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() + let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:initialization_options) let l:conn.executable = a:executable if !has_key(l:conn, 'id') || !ale#job#IsRunning(l:conn.id) @@ -300,10 +314,10 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback) abor endfunction " Connect to an address and set up a callback for handling responses. -function! ale#lsp#ConnectToAddress(address, project_root, callback) abort +function! ale#lsp#ConnectToAddress(address, project_root, callback, initialization_options) abort let l:conn = s:FindConnection('id', a:address) " Get the current connection or a new one. - let l:conn = !empty(l:conn) ? l:conn : s:NewConnection() + let l:conn = !empty(l:conn) ? l:conn : ale#lsp#NewConnection(a:initialization_options) if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) isnot# 'open' let l:conn.channnel = ch_open(a:address, { @@ -378,7 +392,7 @@ function! ale#lsp#Send(conn_id, message, ...) abort " Only send the init message once. if !l:project.init_request_id let [l:init_id, l:init_data] = ale#lsp#CreateMessageData( - \ ale#lsp#message#Initialize(l:project_root), + \ ale#lsp#message#Initialize(l:project_root, l:conn.initialization_options), \) let l:project.init_request_id = l:init_id @@ -400,21 +414,72 @@ function! ale#lsp#Send(conn_id, message, ...) abort return l:id == 0 ? -1 : l:id endfunction -function! ale#lsp#OpenDocumentIfNeeded(conn_id, buffer, project_root, language_id) abort - let l:conn = s:FindConnection('id', a:conn_id) +" The Document details Dictionary should contain the following keys. +" +" buffer - The buffer number for the document. +" connection_id - The connection ID for the LSP server. +" command - The command to run to start the LSP connection. +" project_root - The project root for the LSP project. +" language_id - The language ID for the project, like 'python', 'rust', etc. + +" Create a new Dictionary containing more connection details, with the +" following information added: +" +" conn - An existing LSP connection for the document. +" document_open - 1 if the document is currently open, 0 otherwise. +function! s:ExtendDocumentDetails(details) abort + let l:extended = copy(a:details) + let l:conn = s:FindConnection('id', a:details.connection_id) + + let l:extended.conn = l:conn + let l:extended.document_open = !empty(l:conn) + \ && has_key(l:conn.open_documents, a:details.buffer) + + return l:extended +endfunction + +" Notify LSP servers or tsserver if a document is opened, if needed. +" If a document is opened, 1 will be returned, otherwise 0 will be returned. +function! ale#lsp#OpenDocument(basic_details) abort + let l:d = s:ExtendDocumentDetails(a:basic_details) let l:opened = 0 - if !empty(l:conn) && index(l:conn.open_documents, a:buffer) < 0 - if empty(a:language_id) - let l:message = ale#lsp#tsserver_message#Open(a:buffer) + if !empty(l:d.conn) && !l:d.document_open + if empty(l:d.language_id) + let l:message = ale#lsp#tsserver_message#Open(l:d.buffer) else - let l:message = ale#lsp#message#DidOpen(a:buffer, a:language_id) + let l:message = ale#lsp#message#DidOpen(l:d.buffer, l:d.language_id) endif - call ale#lsp#Send(a:conn_id, l:message, a:project_root) - call add(l:conn.open_documents, a:buffer) + call ale#lsp#Send(l:d.connection_id, l:message, l:d.project_root) + let l:d.conn.open_documents[l:d.buffer] = getbufvar(l:d.buffer, 'changedtick') let l:opened = 1 endif return l:opened endfunction + +" Notify LSP servers or tsserver that a document has changed, if needed. +" If a notification is sent, 1 will be returned, otherwise 0 will be returned. +function! ale#lsp#NotifyForChanges(basic_details) abort + let l:d = s:ExtendDocumentDetails(a:basic_details) + let l:notified = 0 + + if l:d.document_open + let l:new_tick = getbufvar(l:d.buffer, 'changedtick') + + if l:d.conn.open_documents[l:d.buffer] < l:new_tick + if empty(l:d.language_id) + let l:message = ale#lsp#tsserver_message#Change(l:d.buffer) + else + let l:message = ale#lsp#message#DidChange(l:d.buffer) + endif + + call ale#lsp#Send(l:d.connection_id, l:message, l:d.project_root) + let l:d.conn.open_documents[l:d.buffer] = l:new_tick + let l:notified = 1 + endif + endif + + return l:notified +endfunction diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim index 5637fa2e..9e05156d 100644 --- a/autoload/ale/lsp/message.vim +++ b/autoload/ale/lsp/message.vim @@ -24,12 +24,15 @@ function! ale#lsp#message#GetNextVersionID() abort return l:id endfunction -function! ale#lsp#message#Initialize(root_path) abort +function! ale#lsp#message#Initialize(root_path, initialization_options) abort " TODO: Define needed capabilities. + " NOTE: rootPath is deprecated in favour of rootUri return [0, 'initialize', { \ 'processId': getpid(), \ 'rootPath': a:root_path, \ 'capabilities': {}, + \ 'initializationOptions': a:initialization_options, + \ 'rootUri': ale#path#ToURI(a:root_path), \}] endfunction diff --git a/autoload/ale/lsp/reset.vim b/autoload/ale/lsp/reset.vim index c206ed08..c7c97a47 100644 --- a/autoload/ale/lsp/reset.vim +++ b/autoload/ale/lsp/reset.vim @@ -7,9 +7,9 @@ function! ale#lsp#reset#StopAllLSPs() abort call ale#definition#ClearLSPData() endif - if exists('*ale#engine#ClearLSPData') + if exists('*ale#lsp_linter#ClearLSPData') " Clear the mapping for connections, etc. - call ale#engine#ClearLSPData() + call ale#lsp_linter#ClearLSPData() " Remove the problems for all of the LSP linters in every buffer. for l:buffer_string in keys(g:ale_buffer_info) diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim new file mode 100644 index 00000000..4aef8ff5 --- /dev/null +++ b/autoload/ale/lsp_linter.vim @@ -0,0 +1,228 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: Integration between linters and LSP/tsserver. + +" This code isn't loaded if a user never users LSP features or linters. + +" Associates LSP connection IDs with linter names. +if !has_key(s:, 'lsp_linter_map') + let s:lsp_linter_map = {} +endif + +function! s:HandleLSPDiagnostics(conn_id, response) abort + let l:linter_name = s:lsp_linter_map[a:conn_id] + let l:filename = ale#path#FromURI(a:response.params.uri) + let l:buffer = bufnr(l:filename) + + if l:buffer <= 0 + return + endif + + let l:loclist = ale#lsp#response#ReadDiagnostics(a:response) + + call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist) +endfunction + +function! s:HandleTSServerDiagnostics(response, error_type) abort + let l:buffer = bufnr(a:response.body.file) + let l:info = get(g:ale_buffer_info, l:buffer, {}) + + if empty(l:info) + return + endif + + let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response) + + " tsserver sends syntax and semantic errors in separate messages, so we + " have to collect the messages separately for each buffer and join them + " back together again. + if a:error_type is# 'syntax' + let l:info.syntax_loclist = l:thislist + else + let l:info.semantic_loclist = l:thislist + endif + + let l:loclist = get(l:info, 'semantic_loclist', []) + \ + get(l:info, 'syntax_loclist', []) + + call ale#engine#HandleLoclist('tsserver', l:buffer, l:loclist) +endfunction + +function! s:HandleLSPErrorMessage(linter_name, response) abort + if !g:ale_history_enabled || !g:ale_history_log_output + return + endif + + if empty(a:linter_name) + return + endif + + let l:message = ale#lsp#response#GetErrorMessage(a:response) + + if empty(l:message) + return + endif + + " This global variable is set here so we don't load the debugging.vim file + " until someone uses :ALEInfo. + let g:ale_lsp_error_messages = get(g:, 'ale_lsp_error_messages', {}) + + if !has_key(g:ale_lsp_error_messages, a:linter_name) + let g:ale_lsp_error_messages[a:linter_name] = [] + endif + + call add(g:ale_lsp_error_messages[a:linter_name], l:message) +endfunction + +function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort + let l:method = get(a:response, 'method', '') + let l:linter_name = get(s:lsp_linter_map, a:conn_id, '') + + if get(a:response, 'jsonrpc', '') is# '2.0' && has_key(a:response, 'error') + call s:HandleLSPErrorMessage(l:linter_name, a:response) + elseif l:method is# 'textDocument/publishDiagnostics' + call s:HandleLSPDiagnostics(a:conn_id, a:response) + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'semanticDiag' + call s:HandleTSServerDiagnostics(a:response, 'semantic') + elseif get(a:response, 'type', '') is# 'event' + \&& get(a:response, 'event', '') is# 'syntaxDiag' + call s:HandleTSServerDiagnostics(a:response, 'syntax') + endif +endfunction + +" Given a buffer, an LSP linter, and a callback to register for handling +" messages, start up an LSP linter and get ready to receive errors or +" completions. +function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort + let l:command = '' + let l:address = '' + let l:root = ale#util#GetFunction(a:linter.project_root_callback)(a:buffer) + + if empty(l:root) && a:linter.lsp isnot# 'tsserver' + " If there's no project root, then we can't check files with LSP, + " unless we are using tsserver, which doesn't use project roots. + return {} + endif + + let l:initialization_options = {} + + if has_key(a:linter, 'initialization_options_callback') + let l:initialization_options = ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer) + elseif has_key(a:linter, 'initialization_options') + let l:initialization_options = a:linter.initialization_options + endif + + if a:linter.lsp is# 'socket' + let l:address = ale#linter#GetAddress(a:buffer, a:linter) + let l:conn_id = ale#lsp#ConnectToAddress( + \ l:address, + \ l:root, + \ a:callback, + \ l:initialization_options, + \) + else + let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) + + if !executable(l:executable) + return {} + endif + + let l:command = ale#job#PrepareCommand( + \ a:buffer, + \ ale#linter#GetCommand(a:buffer, a:linter), + \) + let l:conn_id = ale#lsp#StartProgram( + \ l:executable, + \ l:command, + \ l:root, + \ a:callback, + \ l:initialization_options, + \) + endif + + let l:language_id = ale#util#GetFunction(a:linter.language_callback)(a:buffer) + + if !l:conn_id + if g:ale_history_enabled && !empty(l:command) + call ale#history#Add(a:buffer, 'failed', l:conn_id, l:command) + endif + + return {} + endif + + let l:details = { + \ 'buffer': a:buffer, + \ 'connection_id': l:conn_id, + \ 'command': l:command, + \ 'project_root': l:root, + \ 'language_id': l:language_id, + \} + + if ale#lsp#OpenDocument(l:details) + if g:ale_history_enabled && !empty(l:command) + call ale#history#Add(a:buffer, 'started', l:conn_id, l:command) + endif + endif + + " The change message needs to be sent for tsserver before doing anything. + if a:linter.lsp is# 'tsserver' + call ale#lsp#NotifyForChanges(l:details) + endif + + return l:details +endfunction + +function! ale#lsp_linter#CheckWithLSP(buffer, linter) abort + let l:info = g:ale_buffer_info[a:buffer] + let l:lsp_details = ale#lsp_linter#StartLSP( + \ a:buffer, + \ a:linter, + \ function('ale#lsp_linter#HandleLSPResponse'), + \) + + if empty(l:lsp_details) + return 0 + endif + + let l:id = l:lsp_details.connection_id + let l:root = l:lsp_details.project_root + + " Remember the linter this connection is for. + let s:lsp_linter_map[l:id] = a:linter.name + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Geterr(a:buffer) + let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + + let l:notified = l:request_id != 0 + else + let l:notified = ale#lsp#NotifyForChanges(l:lsp_details) + endif + + " If this was a file save event, also notify the server of that. + if a:linter.lsp isnot# 'tsserver' + \&& getbufvar(a:buffer, 'ale_save_event_fired', 0) + let l:save_message = ale#lsp#message#DidSave(a:buffer) + let l:request_id = ale#lsp#Send(l:id, l:save_message, l:root) + + let l:notified = l:request_id != 0 + endif + + if l:notified + if index(l:info.active_linter_list, a:linter.name) < 0 + call add(l:info.active_linter_list, a:linter.name) + endif + endif + + return l:notified +endfunction + +" Clear LSP linter data for the linting engine. +function! ale#lsp_linter#ClearLSPData() abort + let s:lsp_linter_map = {} +endfunction + +" Just for tests. +function! ale#lsp_linter#SetLSPLinterMap(replacement_map) abort + let s:lsp_linter_map = a:replacement_map +endfunction diff --git a/autoload/ale/pattern_options.vim b/autoload/ale/pattern_options.vim index c445a9ed..d1f91785 100644 --- a/autoload/ale/pattern_options.vim +++ b/autoload/ale/pattern_options.vim @@ -23,7 +23,8 @@ function! s:CmpPatterns(left_item, right_item) abort endfunction function! ale#pattern_options#SetOptions(buffer) abort - if !g:ale_pattern_options_enabled || empty(g:ale_pattern_options) + if !get(g:, 'ale_pattern_options_enabled', 0) + \|| empty(get(g:, 'ale_pattern_options', 0)) return endif diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim index 9777519d..89df69eb 100644 --- a/autoload/ale/references.vim +++ b/autoload/ale/references.vim @@ -72,14 +72,13 @@ function! s:FindReferences(linter) abort \ ? function('ale#references#HandleTSServerResponse') \ : function('ale#references#HandleLSPResponse') - let l:lsp_details = ale#linter#StartLSP(l:buffer, a:linter, l:Callback) + let l:lsp_details = ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) if empty(l:lsp_details) return 0 endif let l:id = l:lsp_details.connection_id - let l:root = l:lsp_details.project_root if a:linter.lsp is# 'tsserver' let l:message = ale#lsp#tsserver_message#References( @@ -90,14 +89,14 @@ function! s:FindReferences(linter) abort else " Send a message saying the buffer has changed first, or the " references position probably won't make sense. - call ale#lsp#Send(l:id, ale#lsp#message#DidChange(l:buffer), l:root) + call ale#lsp#NotifyForChanges(l:lsp_details) let l:column = min([l:column, len(getline(l:line))]) let l:message = ale#lsp#message#References(l:buffer, l:line, l:column) endif - let l:request_id = ale#lsp#Send(l:id, l:message, l:root) + let l:request_id = ale#lsp#Send(l:id, l:message, l:lsp_details.project_root) let s:references_map[l:request_id] = {} endfunction diff --git a/doc/ale-cloudformation.txt b/doc/ale-cloudformation.txt new file mode 100644 index 00000000..59c6af06 --- /dev/null +++ b/doc/ale-cloudformation.txt @@ -0,0 +1,14 @@ +=============================================================================== +ALE CloudFormation Integration *ale-cloudformation-options* + + +=============================================================================== +cfn-python-lint *ale-cloudformation-cfn-python-lint* + +cfn-python-lint is a linter for AWS CloudFormation template file. + +https://github.com/awslabs/cfn-python-lint + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: + diff --git a/doc/ale-cpp.txt b/doc/ale-cpp.txt index 05e54799..8bd111e4 100644 --- a/doc/ale-cpp.txt +++ b/doc/ale-cpp.txt @@ -157,6 +157,26 @@ g:ale_cpp_cpplint_options *g:ale_cpp_cpplint_options* =============================================================================== +cquery *ale-cpp-cquery* + +g:ale_cpp_cquery_executable *g:ale_cpp_cquery_executable* + *b:ale_cpp_cquery_executable* + Type: |String| + Default: `'cquery'` + + This variable can be changed to use a different executable for cquery. + + +g:ale_cpp_cquery_cache_directory *g:ale_cpp_cquery_cache_directory* + *b:ale_cpp_cquery_cache_directory* + Type: |String| + Default: `'~/.cache/cquery'` + + This variable can be changed to decide which directory cquery uses for its + cache. + + +=============================================================================== flawfinder *ale-cpp-flawfinder* g:ale_cpp_flawfinder_executable *g:ale_cpp_flawfinder_executable* diff --git a/doc/ale-gitcommit.txt b/doc/ale-gitcommit.txt index 19ba8e94..38f3fd90 100644 --- a/doc/ale-gitcommit.txt +++ b/doc/ale-gitcommit.txt @@ -19,12 +19,14 @@ g:ale_gitcommit_gitlint_options *g:ale_gitcommit_gitlint_options* Default: `''` This variable can be changed to add command-line arguments to the gitlint - invocation. + invocation. For example, you can specify the path to a configuration file. > - For example, to dinamically set the gitlint configuration file path, you - may want to set > + let g:ale_gitcommit_gitlint_options = '-C /home/user/.config/gitlint.ini' +< + You can also disable particular error codes using this option. For example, + you can ignore errors for git commits with a missing body. > - let g:ale_gitcommit_gitlint_options = '-C /home/user/.config/gitlint.ini' + let g:ale_gitcommit_gitlint_options = '--ignore B6' < g:ale_gitcommit_gitlint_use_global *g:ale_gitcommit_gitlint_use_global* diff --git a/doc/ale-python.txt b/doc/ale-python.txt index 55641892..b24b531d 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -100,7 +100,8 @@ g:ale_python_flake8_executable *g:ale_python_flake8_executable* Type: |String| Default: `'flake8'` - This variable can be changed to modify the executable used for flake8. + This variable can be changed to modify the executable used for flake8. Set + this to `'pipenv'` to invoke `'pipenv` `run` `flake8'`. g:ale_python_flake8_options *g:ale_python_flake8_options* @@ -169,6 +170,8 @@ g:ale_python_mypy_executable *g:ale_python_mypy_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `mypy'`. + g:ale_python_mypy_ignore_invalid_syntax *g:ale_python_mypy_ignore_invalid_syntax* *b:ale_python_mypy_ignore_invalid_syntax* @@ -207,6 +210,8 @@ g:ale_python_prospector_executable *g:ale_python_prospector_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `prospector'`. + g:ale_python_prospector_options *g:ale_python_prospector_options* *b:ale_python_prospector_options* @@ -248,6 +253,8 @@ g:ale_python_pycodestyle_executable *g:ale_python_pycodestyle_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `pycodestyle'`. + g:ale_python_pycodestyle_options *g:ale_python_pycodestyle_options* *b:ale_python_pycodestyle_options* @@ -267,6 +274,20 @@ g:ale_python_pycodestyle_use_global *g:ale_python_pycodestyle_use_global* =============================================================================== +pyflakes *ale-python-pyflakes* + + +g:ale_python_pyflakes_executable *g:ale_python_pyflakes_executable* + *b:ale_python_pyflakes_executable* + Type: |String| + Default: `'pyflakes'` + + See |ale-integrations-local-executables| + + Set this to `'pipenv'` to invoke `'pipenv` `run` `pyflakes'`. + + +=============================================================================== pylint *ale-python-pylint* g:ale_python_pylint_change_directory *g:ale_python_pylint_change_directory* @@ -287,6 +308,8 @@ g:ale_python_pylint_executable *g:ale_python_pylint_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `pylint'`. + g:ale_python_pylint_options *g:ale_python_pylint_options* *b:ale_python_pylint_options* @@ -329,6 +352,8 @@ g:ale_python_pyls_executable *g:ale_python_pyls_executable* See |ale-integrations-local-executables| + Set this to `'pipenv'` to invoke `'pipenv` `run` `pyls'`. + g:ale_python_pyls_use_global *g:ale_python_pyls_use_global* *b:ale_python_pyls_use_global* diff --git a/doc/ale-rust.txt b/doc/ale-rust.txt index dad9ae66..f9afe17d 100644 --- a/doc/ale-rust.txt +++ b/doc/ale-rust.txt @@ -59,6 +59,26 @@ g:ale_rust_cargo_check_all_targets *g:ale_rust_cargo_check_all_targets* is used. See |g:ale_rust_cargo_use_check|, +g:ale_rust_cargo_check_tests *g:ale_rust_cargo_check_tests* + *b:ale_rust_cargo_check_tests* + Type: |Number| + Default: `0` + + When set to `1`, ALE will set the `--tests` option when `cargo check` + is used. This allows for linting of tests which are normally excluded. + See |g:ale_rust_cargo_use_check|, + + +g:ale_rust_cargo_check_examples *g:ale_rust_cargo_check_examples* + *b:ale_rust_cargo_check_examples* + Type: |Number| + Default: `0` + + When set to `1`, ALE will set the `--examples` option when `cargo check` + is used. This allows for linting of examples which are normally excluded. + See |g:ale_rust_cargo_use_check|, + + g:ale_rust_cargo_default_feature_behavior *g:ale_rust_cargo_default_feature_behavior* *b:ale_rust_cargo_default_feature_behavior* diff --git a/doc/ale-scala.txt b/doc/ale-scala.txt index 9c9472f6..b5474f3b 100644 --- a/doc/ale-scala.txt +++ b/doc/ale-scala.txt @@ -3,6 +3,31 @@ ALE Scala Integration *ale-scala-options* =============================================================================== +scalafmt *ale-scala-scalafmt* + +If Nailgun is used, override `g:ale_scala_scalafmt_executable` like so: > + let g:ale_scala_scalafmt_executable = 'ng' + + +g:ale_scala_scalafmt_executable *g:ale_scala_scalafmt_executable* + *b:ale_scala_scalafmt_executable* + Type: |String| + Default: `'scalafmt'` + + Override the invoked `scalafmt` binary. This is useful for running `scalafmt` + with Nailgun. + + +g:ale_scala_scalafmt_options *g:ale_scala_scalafmt_options* + *b:ale_scala_scalafmt_options* + Type: |String| + Default: `''` + + A string containing additional options to pass to `'scalafmt'`, or + `'ng scalafmt'` if Nailgun is used. + + +=============================================================================== scalastyle *ale-scala-scalastyle* `scalastyle` requires a configuration file for a project to run. When no diff --git a/doc/ale.txt b/doc/ale.txt index 2b4452d9..4fe3a23a 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -35,6 +35,8 @@ CONTENTS *ale-contents* foodcritic..........................|ale-chef-foodcritic| clojure...............................|ale-clojure-options| joker...............................|ale-clojure-joker| + cloudformation........................|ale-cloudformation-options| + cfn-python-lint.....................|ale-cloudformation-cfn-python-lint| cmake.................................|ale-cmake-options| cmakelint...........................|ale-cmake-cmakelint| cpp...................................|ale-cpp-options| @@ -44,6 +46,7 @@ CONTENTS *ale-contents* clangtidy...........................|ale-cpp-clangtidy| cppcheck............................|ale-cpp-cppcheck| cpplint.............................|ale-cpp-cpplint| + cquery..............................|ale-cpp-cquery| flawfinder..........................|ale-cpp-flawfinder| gcc.................................|ale-cpp-gcc| c#....................................|ale-cs-options| @@ -190,6 +193,7 @@ CONTENTS *ale-contents* mypy................................|ale-python-mypy| prospector..........................|ale-python-prospector| pycodestyle.........................|ale-python-pycodestyle| + pyflakes............................|ale-python-pyflakes| pylint..............................|ale-python-pylint| pyls................................|ale-python-pyls| yapf................................|ale-python-yapf| @@ -218,6 +222,7 @@ CONTENTS *ale-contents* sass..................................|ale-sass-options| stylelint...........................|ale-sass-stylelint| scala.................................|ale-scala-options| + scalafmt............................|ale-scala-scalafmt| scalastyle..........................|ale-scala-scalastyle| scss..................................|ale-scss-options| prettier............................|ale-scss-prettier| @@ -317,11 +322,12 @@ Notes: * Bash: `shell` (-n flag), `shellcheck`, `shfmt` * Bourne Shell: `shell` (-n flag), `shellcheck`, `shfmt` * C: `cppcheck`, `cpplint`!!, `clang`, `clangtidy`!!, `clang-format`, `flawfinder`, `gcc` -* C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `cppcheck`, `cpplint`!!, `flawfinder`, `gcc` +* C++ (filetype cpp): `clang`, `clangcheck`!!, `clangtidy`!!, `clang-format`, `cppcheck`, `cpplint`!!, `cquery`, `flawfinder`, `gcc` * CUDA: `nvcc`!! * C#: `mcs`, `mcsc`!! * Chef: `foodcritic` * Clojure: `joker` +* CloudFormation: `cfn-python-lint` * CMake: `cmakelint` * CoffeeScript: `coffee`, `coffeelint` * Crystal: `crystal`!! @@ -388,7 +394,7 @@ Notes: * Rust: `cargo`!!, `rls`, `rustc` (see |ale-integration-rust|), `rustfmt` * SASS: `sass-lint`, `stylelint` * SCSS: `prettier`, `sass-lint`, `scss-lint`, `stylelint` -* Scala: `fsc`, `scalac`, `scalastyle` +* Scala: `fsc`, `scalac`, `scalafmt`, `scalastyle` * Slim: `slim-lint` * SML: `smlnj` * Solidity: `solhint`, `solium` @@ -2329,6 +2335,10 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* An optional `completion_filter` callback may be defined for filtering completion results. + An optional `initialization_options` or + `initialization_options_callback` may be defined to + pass initialization options to the LSP. + `project_root_callback` A |String| or |Funcref| for a callback function accepting a buffer number. A |String| should be returned representing the path to the project for the @@ -2370,6 +2380,17 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* setting can make it easier to guess the linter name by offering a few alternatives. + `initialization_options` A |Dictionary| of initialization options for LSPs. + This will be fed (as JSON) to the LSP in the + initialize command. + + `initialization_options_callback` + A |String| or |Funcref| for a callback function + accepting a buffer number. A |Dictionary| should be + returned for initialization options to pass the LSP. + This can be used in place of `initialization_options` + when more complicated processing is needed. + Only one of `command`, `command_callback`, or `command_chain` should be specified. `command_callback` is generally recommended when a command string needs to be generated dynamically, or any global options are used. diff --git a/test/command_callback/scala_paths/dummy.scala b/test/command_callback/scala_paths/dummy.scala new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/scala_paths/dummy.scala diff --git a/test/command_callback/test_cargo_command_callbacks.vader b/test/command_callback/test_cargo_command_callbacks.vader index 9c06f27d..f674645f 100644 --- a/test/command_callback/test_cargo_command_callbacks.vader +++ b/test/command_callback/test_cargo_command_callbacks.vader @@ -1,11 +1,15 @@ Before: Save g:ale_rust_cargo_use_check Save g:ale_rust_cargo_check_all_targets + Save g:ale_rust_cargo_check_tests + Save g:ale_rust_cargo_check_examples Save g:ale_rust_cargo_default_feature_behavior Save g:ale_rust_cargo_include_features unlet! g:ale_rust_cargo_use_check - unlet! g:ale_cargo_check_all_targets + unlet! g:ale_rust_cargo_check_all_targets + unlet! g:ale_rust_cargo_check_tests + unlet! g:ale_rust_cargo_check_examples unlet! g:ale_rust_cargo_default_feature_behavior unlet! g:ale_rust_cargo_include_features @@ -119,6 +123,38 @@ Execute(--all-targets should be used when g:ale_rust_cargo_check_all_targets is AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) +Execute(--tests should be used when g:ale_rust_cargo_check_tests is set to 1): + let g:ale_rust_cargo_check_tests = 1 + + AssertEqual + \ 'cargo check --tests' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo check --tests' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + +Execute(--examples should be used when g:ale_rust_cargo_check_examples is set to 1): + let g:ale_rust_cargo_check_examples = 1 + + AssertEqual + \ 'cargo check --examples' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), [ + \ 'cargo 0.22.0 (3423351a5 2017-10-06)', + \ ]) + + " We should cache the version check + AssertEqual + \ 'cargo check --examples' . g:suffix, + \ ale_linters#rust#cargo#GetCommand(bufnr(''), []) + + AssertEqual '', ale_linters#rust#cargo#VersionCheck(bufnr('')) + Execute(--no-default-features should be used when g:ale_rust_cargo_default_feature_behavior is none): let g:ale_rust_cargo_default_feature_behavior = 'none' diff --git a/test/command_callback/test_cpp_cquery_command_callbacks.vader b/test/command_callback/test_cpp_cquery_command_callbacks.vader new file mode 100644 index 00000000..89a3e225 --- /dev/null +++ b/test/command_callback/test_cpp_cquery_command_callbacks.vader @@ -0,0 +1,48 @@ +" Author: Ben Falconer <ben@falconers.me.uk> +" Description: A language server for C++ + +Before: + Save g:ale_cpp_cquery_executable + Save g:ale_cpp_cquery_cache_directory + + unlet! g:ale_cpp_cquery_executable + unlet! b:ale_cpp_cquery_executable + unlet! g:ale_cpp_cquery_cache_directory + unlet! b:ale_cpp_cquery_cache_directory + + runtime ale_linters/cpp/cquery.vim + +After: + Restore + unlet! b:ale_cpp_cquery_executable + unlet! b:ale_cpp_cquery_cache_directory + call ale#linter#Reset() + +Execute(The executable should be configurable): + AssertEqual 'cquery', ale_linters#cpp#cquery#GetExecutable(bufnr('')) + + let b:ale_cpp_cquery_executable = 'foobar' + + AssertEqual 'foobar', ale_linters#cpp#cquery#GetExecutable(bufnr('')) + +Execute(The executable should be used in the command): + AssertEqual + \ ale#Escape('cquery'), + \ ale_linters#cpp#cquery#GetCommand(bufnr('')) + + let b:ale_cpp_cquery_executable = 'foobar' + + AssertEqual + \ ale#Escape('foobar'), + \ ale_linters#cpp#cquery#GetCommand(bufnr('')) + +Execute(The cache directory should be configurable): + AssertEqual + \ {'cacheDirectory': expand('$HOME/.cache/cquery')}, + \ ale_linters#cpp#cquery#GetInitializationOptions(bufnr('')) + + let b:ale_cpp_cquery_cache_directory = '/foo/bar' + + AssertEqual + \ {'cacheDirectory': '/foo/bar'}, + \ ale_linters#cpp#cquery#GetInitializationOptions(bufnr('')) diff --git a/test/command_callback/test_flake8_command_callback.vader b/test/command_callback/test_flake8_command_callback.vader index ecd06332..1cc50836 100644 --- a/test/command_callback/test_flake8_command_callback.vader +++ b/test/command_callback/test_flake8_command_callback.vader @@ -174,3 +174,11 @@ Execute(Using `python -m flake8` should be supported for running flake8): \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('python') . ' -m flake8 --some-option --format=default -', \ ale_linters#python#flake8#GetCommand(bufnr(''), ['2.9.9']) + +Execute(Setting executable to 'pipenv' appends 'run flake8'): + let g:ale_python_flake8_executable = 'path/to/pipenv' + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('path/to/pipenv') . ' run flake8 --format=default --stdin-display-name %s -', + \ ale_linters#python#flake8#GetCommand(bufnr(''), ['3.0.0']) diff --git a/test/command_callback/test_lintr_command_callback.vader b/test/command_callback/test_lintr_command_callback.vader index e655328b..2f7dfb1d 100644 --- a/test/command_callback/test_lintr_command_callback.vader +++ b/test/command_callback/test_lintr_command_callback.vader @@ -16,7 +16,7 @@ After: Execute(The default lintr command should be correct): AssertEqual \ 'cd ' . ale#Escape(getcwd()) . ' && ' - \ . 'Rscript -e ' + \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint(cache = FALSE, commandArgs(TRUE), ' \ . 'with_defaults())') @@ -28,7 +28,7 @@ Execute(The lintr options should be configurable): AssertEqual \ 'cd ' . ale#Escape(getcwd()) . ' && ' - \ . 'Rscript -e ' + \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint(cache = FALSE, commandArgs(TRUE), ' \ . 'with_defaults(object_usage_linter = NULL))') @@ -40,7 +40,7 @@ Execute(If the lint_package flag is set, lintr::lint_package should be called): AssertEqual \ 'cd ' . ale#Escape(getcwd()) . ' && ' - \ . 'Rscript -e ' + \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint_package(cache = FALSE, ' \ . 'linters = with_defaults())') diff --git a/test/command_callback/test_mypy_command_callback.vader b/test/command_callback/test_mypy_command_callback.vader index 6a0add52..0fb4cc54 100644 --- a/test/command_callback/test_mypy_command_callback.vader +++ b/test/command_callback/test_mypy_command_callback.vader @@ -95,3 +95,12 @@ Execute(You should able able to use the global mypy instead): \ . ' --show-column-numbers ' \ . '--shadow-file %s %t %s', \ ale_linters#python#mypy#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run mypy'): + let g:ale_python_mypy_executable = 'path/to/pipenv' + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('path/to/pipenv') . ' run mypy' + \ . ' --show-column-numbers --shadow-file %s %t %s', + \ ale_linters#python#mypy#GetCommand(bufnr('')) diff --git a/test/command_callback/test_prospector_command_callback.vader b/test/command_callback/test_prospector_command_callback.vader new file mode 100644 index 00000000..04cd58ed --- /dev/null +++ b/test/command_callback/test_prospector_command_callback.vader @@ -0,0 +1,23 @@ +Before: + Save g:ale_python_mypy_executable + Save g:ale_python_mypy_options + + unlet! g:ale_python_mypy_executable + unlet! g:ale_python_mypy_options + + runtime ale_linters/python/prospector.vim + +After: + Restore + + unlet! b:executable + + call ale#linter#Reset() + +Execute(Setting executable to 'pipenv' appends 'run prospector'): + let g:ale_python_prospector_executable = 'path/to/pipenv' + + AssertEqual + \ ale#Escape('path/to/pipenv') . ' run prospector' + \ . ' --messages-only --absolute-paths --zero-exit --output-format json %s', + \ ale_linters#python#prospector#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pycodestyle_command_callback.vader b/test/command_callback/test_pycodestyle_command_callback.vader index 5b309e19..90b07a24 100644 --- a/test/command_callback/test_pycodestyle_command_callback.vader +++ b/test/command_callback/test_pycodestyle_command_callback.vader @@ -25,3 +25,10 @@ Execute(The pycodestyle executable should be configurable): AssertEqual ale#Escape('~/.local/bin/pycodestyle') . ' -', \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run pycodestyle'): + let g:ale_python_pycodestyle_executable = 'path/to/pipenv' + + AssertEqual + \ ale#Escape('path/to/pipenv') . ' run pycodestyle -', + \ ale_linters#python#pycodestyle#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pyflakes_command_callback.vader b/test/command_callback/test_pyflakes_command_callback.vader index e8486ca8..491432e9 100644 --- a/test/command_callback/test_pyflakes_command_callback.vader +++ b/test/command_callback/test_pyflakes_command_callback.vader @@ -47,3 +47,10 @@ Execute(You should be able to override the pyflakes virtualenv lookup): AssertEqual ale#Escape('pyflakes') . ' %t', \ ale_linters#python#pyflakes#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run pyflakes'): + let g:ale_python_pyflakes_executable = 'path/to/pipenv' + + AssertEqual + \ ale#Escape('path/to/pipenv') . ' run pyflakes %t', + \ ale_linters#python#pyflakes#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pylint_command_callback.vader b/test/command_callback/test_pylint_command_callback.vader index 1cdc34d4..f8cb5800 100644 --- a/test/command_callback/test_pylint_command_callback.vader +++ b/test/command_callback/test_pylint_command_callback.vader @@ -102,3 +102,12 @@ Execute(You should able able to use the global pylint instead): \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('pylint') . ' ' . b:command_tail, \ ale_linters#python#pylint#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run pylint'): + let g:ale_python_pylint_executable = 'path/to/pipenv' + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('path/to/pipenv') . ' run pylint' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s', + \ ale_linters#python#pylint#GetCommand(bufnr('')) diff --git a/test/command_callback/test_pyls_command_callback.vader b/test/command_callback/test_pyls_command_callback.vader index 06ea718f..4bef4742 100644 --- a/test/command_callback/test_pyls_command_callback.vader +++ b/test/command_callback/test_pyls_command_callback.vader @@ -47,3 +47,10 @@ Execute(You should be able to override the pyls virtualenv lookup): AssertEqual ale#Escape('pyls'), \ ale_linters#python#pyls#GetCommand(bufnr('')) + +Execute(Setting executable to 'pipenv' appends 'run pyls'): + let g:ale_python_pyls_executable = 'path/to/pipenv' + + AssertEqual + \ ale#Escape('path/to/pipenv') . ' run pyls', + \ ale_linters#python#pyls#GetCommand(bufnr('')) diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader index 734b330c..8ba2ad38 100644 --- a/test/completion/test_lsp_completion_messages.vader +++ b/test/completion/test_lsp_completion_messages.vader @@ -15,12 +15,18 @@ Before: let g:message_list = [] let g:Callback = '' - function! ale#linter#StartLSP(buffer, linter, callback) abort + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback + let l:conn = ale#lsp#NewConnection({}) + let l:conn.id = 347 + let l:conn.open_documents = {a:buffer : -1} + return { + \ 'buffer': a:buffer, \ 'connection_id': 347, \ 'project_root': '/foo/bar', + \ 'language_id': 'python', \} endfunction @@ -43,6 +49,7 @@ After: unlet! b:ale_linters unlet! b:ale_tsserver_completion_names + call ale#lsp#RemoveConnectionWithID(347) call ale#test#RestoreDirectory() call ale#linter#Reset() diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader index 23bbd14c..736353e3 100644 --- a/test/completion/test_lsp_completion_parsing.vader +++ b/test/completion/test_lsp_completion_parsing.vader @@ -429,3 +429,23 @@ Execute(Should handle Python completion results correctly): \ ] \ } \ }) + +Execute(Should handle missing detail keys): + AssertEqual + \ [ + \ {'word': 'x', 'menu': '', 'info': 'y', 'kind': 'f', 'icase': 1}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'jsonrpc': '2.0', + \ 'id': 6, + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'label': 'x', + \ 'kind': 3, + \ 'documentation': 'y', + \ }, + \ ] + \ } + \ }) diff --git a/test/fixers/test_qmlfmt_fixer_callback.vader b/test/fixers/test_qmlfmt_fixer_callback.vader new file mode 100644 index 00000000..e216f2e1 --- /dev/null +++ b/test/fixers/test_qmlfmt_fixer_callback.vader @@ -0,0 +1,12 @@ +Before: + Save g:ale_qml_qmlfmt_executable + +After: + Restore + +Execute(The qmlfmt fixer should use the options you set): + let g:ale_qml_qmlfmt_executable = 'foo-exe' + + AssertEqual + \ {'command': ale#Escape('foo-exe')}, + \ ale#fixers#qmlfmt#Fix(bufnr('')) diff --git a/test/fixers/test_scalafmt_fixer_callback.vader b/test/fixers/test_scalafmt_fixer_callback.vader new file mode 100644 index 00000000..d82fda43 --- /dev/null +++ b/test/fixers/test_scalafmt_fixer_callback.vader @@ -0,0 +1,69 @@ +Before: + Save g:ale_scala_scalafmt_executable + Save g:ale_scala_scalafmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_scala_scalafmt_executable = 'xxxinvalid' + let g:ale_scala_scalafmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The scalafmt callback should return the correct default values): + call ale#test#SetFilename('scala_paths/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_scala_scalafmt_executable) + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should use ng with scalafmt automatically): + let g:ale_scala_scalafmt_executable = 'ng' + call ale#test#SetFilename('scala_paths/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('ng') + \ . ' scalafmt' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should include custom scalafmt options): + let g:ale_scala_scalafmt_options = '--diff' + call ale#test#SetFilename('scala_paths/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape(g:ale_scala_scalafmt_executable) + \ . ' --diff' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) + +Execute(The scalafmt callback should include custom scalafmt options and use ng with scalafmt): + let g:ale_scala_scalafmt_options = '--diff' + let g:ale_scala_scalafmt_executable = 'ng' + call ale#test#SetFilename('scala_paths/dummy.scala') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('ng') + \ . ' scalafmt' + \ . ' --diff' + \ . ' %t', + \ }, + \ ale#fixers#scalafmt#Fix(bufnr('')) diff --git a/test/handler/test_cfn_python_lint_handler.vader b/test/handler/test_cfn_python_lint_handler.vader new file mode 100644 index 00000000..a053e84e --- /dev/null +++ b/test/handler/test_cfn_python_lint_handler.vader @@ -0,0 +1,33 @@ +Before: + runtime! ale_linters/cloudformation/cfn_python_lint.vim + call ale#test#SetFilename('sample.template.yaml') + +After: + call ale#linter#Reset() + +Execute(The cfn_python_lint handler should parse items correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 96, + \ 'col': 7, + \ 'end_lnum': 96, + \ 'end_col': 15, + \ 'text': 'Property Resources/Sample/Properties/FromPort should be of type Integer', + \ 'code': 'E3012', + \ 'type': 'E', + \ }, + \ { + \ 'lnum': 97, + \ 'col': 7, + \ 'end_lnum': 97, + \ 'end_col': 15, + \ 'text': 'AllowedPattern and/or AllowedValues for Parameter should be specified at Parameters/SampleIpAddress. Example for AllowedPattern "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$"', + \ 'code': 'W2509', + \ 'type': 'W', + \ }, + \ ], + \ ale_linters#cloudformation#cfn_python_lint#Handle(bufnr(''), [ + \ fnamemodify(tempname(), ':h') . '/sample.template.yaml:96:7:96:15: [E3012] Property Resources/Sample/Properties/FromPort should be of type Integer', + \ fnamemodify(tempname(), ':h') . '/sample.template.yaml:97:7:97:15: [W2509] AllowedPattern and/or AllowedValues for Parameter should be specified at Parameters/SampleIpAddress. Example for AllowedPattern "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$"', + \ ]) diff --git a/test/handler/test_gitlint_handler.vader b/test/handler/test_gitlint_handler.vader index 73ee988f..60d632a0 100644 --- a/test/handler/test_gitlint_handler.vader +++ b/test/handler/test_gitlint_handler.vader @@ -1,8 +1,16 @@ Before: - runtime ale_linters/gitcommit/gitlint.vim + Save g:ale_warn_about_trailing_whitespace + + let g:ale_warn_about_trailing_whitespace = 1 + + runtime ale_linters/gitcommit/gitlint.vim After: - call ale#linter#Reset() + Restore + + unlet! b:ale_warn_about_trailing_whitespace + + call ale#linter#Reset() Execute(The gitlint handler should handle basic warnings and syntax errors): AssertEqual @@ -39,3 +47,24 @@ Execute(The gitlint handler should handle basic warnings and syntax errors): \ '8: T1 Title exceeds max length (92>72): "some very long commit subject line where the author can''t wait to explain what he just fixed"' \ ]) +Execute(Disabling trailing whitespace warnings should work): + AssertEqual + \ [ + \ { + \ 'lnum': 8, + \ 'type': 'E', + \ 'text': 'Trailing whitespace', + \ 'code': 'T2', + \ }, + \ ], + \ ale_linters#gitcommit#gitlint#Handle(bufnr(''), [ + \ '8: T2 Trailing whitespace', + \]) + + let b:ale_warn_about_trailing_whitespace = 0 + + AssertEqual + \ [], + \ ale_linters#gitcommit#gitlint#Handle(bufnr(''), [ + \ '8: T2 Trailing whitespace', + \ ]) diff --git a/test/handler/test_puppet_handler.vader b/test/handler/test_puppet_handler.vader index 0d274fd2..e73c9dc7 100644 --- a/test/handler/test_puppet_handler.vader +++ b/test/handler/test_puppet_handler.vader @@ -37,9 +37,15 @@ Execute(The puppet handler should parse lines and column correctly): \ 'lnum': 54, \ 'col': 9, \ 'text': "Syntax error at ':'" - \ } + \ }, + \ { + \ 'lnum': 45, + \ 'col': 12, + \ 'text': "Syntax error at 'parameter1'" + \ }, \ ], \ ale_linters#puppet#puppet#Handle(255, [ \ "Error: Could not parse for environment production: Syntax error at ':' at /root/puppetcode/modules/nginx/manifests/init.pp:43:12", \ "Error: Could not parse for environment production: Syntax error at ':' at C:/puppet/modules/nginx/manifests/init.pp:54:9", + \ "Error: Could not parse for environment production: Syntax error at 'parameter1' (file: /tmp/modules/mariadb/manifests/slave.pp, line: 45, column: 12)", \ ]) diff --git a/test/lsp/test_did_save_event.vader b/test/lsp/test_did_save_event.vader index 042a3ce2..97774372 100644 --- a/test/lsp/test_did_save_event.vader +++ b/test/lsp/test_did_save_event.vader @@ -34,12 +34,18 @@ Before: \ }) let g:ale_linters = {'foobar': ['dummy_linter']} - function! ale#linter#StartLSP(buffer, linter, callback) abort + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback + let l:conn = ale#lsp#NewConnection({}) + let l:conn.id = 347 + let l:conn.open_documents = {a:buffer : -1} + return { + \ 'buffer': a:buffer, \ 'connection_id': 347, \ 'project_root': '/foo/bar', + \ 'language_id': 'foobar', \} endfunction @@ -59,6 +65,7 @@ After: delfunction LanguageCallback delfunction ProjectRootCallback + call ale#lsp#RemoveConnectionWithID(347) call ale#test#RestoreDirectory() call ale#linter#Reset() diff --git a/test/lsp/test_lsp_client_messages.vader b/test/lsp/test_lsp_client_messages.vader index 89a29c8f..dc28c2e9 100644 --- a/test/lsp/test_lsp_client_messages.vader +++ b/test/lsp/test_lsp_client_messages.vader @@ -16,9 +16,11 @@ Execute(ale#lsp#message#Initialize() should return correct messages): \ 'processId': getpid(), \ 'rootPath': '/foo/bar', \ 'capabilities': {}, + \ 'initializationOptions': {'foo': 'bar'}, + \ 'rootUri': 'file:///foo/bar', \ } \ ], - \ ale#lsp#message#Initialize('/foo/bar') + \ ale#lsp#message#Initialize('/foo/bar', {'foo': 'bar'}) Execute(ale#lsp#message#Initialized() should return correct messages): AssertEqual [1, 'initialized'], ale#lsp#message#Initialized() diff --git a/test/test_engine_lsp_response_handling.vader b/test/test_engine_lsp_response_handling.vader index 3d317e9c..18bad0a1 100644 --- a/test/test_engine_lsp_response_handling.vader +++ b/test/test_engine_lsp_response_handling.vader @@ -11,7 +11,7 @@ After: call ale#test#RestoreDirectory() call ale#linter#Reset() - call ale#engine#ClearLSPData() + call ale#lsp_linter#ClearLSPData() Given foobar(An empty file): Execute(tsserver syntax error responses should be handled correctly): @@ -21,7 +21,7 @@ Execute(tsserver syntax error responses should be handled correctly): " When we get syntax errors and no semantic errors, we should keep the " syntax errors. - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -43,7 +43,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ ], \ }, \}) - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag', @@ -71,7 +71,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ getloclist(0) " After we get empty syntax errors, we should clear them. - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -94,7 +94,7 @@ Execute(tsserver semantic error responses should be handled correctly): " When we get syntax errors and no semantic errors, we should keep the " syntax errors. - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'syntaxDiag', @@ -104,7 +104,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ ], \ }, \}) - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag', @@ -144,7 +144,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ getloclist(0) " After we get empty syntax errors, we should clear them. - call ale#engine#HandleLSPResponse(1, { + call ale#lsp_linter#HandleLSPResponse(1, { \ 'seq': 0, \ 'type': 'event', \ 'event': 'semanticDiag', @@ -161,8 +161,8 @@ Execute(tsserver semantic error responses should be handled correctly): \ getloclist(0) Execute(LSP errors should be logged in the history): - call ale#engine#SetLSPLinterMap({'347': 'foobar'}) - call ale#engine#HandleLSPResponse(347, { + call ale#lsp_linter#SetLSPLinterMap({'347': 'foobar'}) + call ale#lsp_linter#HandleLSPResponse(347, { \ 'jsonrpc': '2.0', \ 'error': { \ 'code': -32602, diff --git a/test/test_find_references.vader b/test/test_find_references.vader index 6ab8e8fb..c2290ca3 100644 --- a/test/test_find_references.vader +++ b/test/test_find_references.vader @@ -14,12 +14,18 @@ Before: runtime autoload/ale/util.vim runtime autoload/ale/preview.vim - function! ale#linter#StartLSP(buffer, linter, callback) abort + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback + let l:conn = ale#lsp#NewConnection({}) + let l:conn.id = 347 + let l:conn.open_documents = {a:buffer : -1} + return { + \ 'buffer': a:buffer, \ 'connection_id': 347, \ 'project_root': '/foo/bar', + \ 'language_id': 'python', \} endfunction @@ -39,6 +45,7 @@ Before: endfunction After: + call ale#lsp#RemoveConnectionWithID(347) call ale#references#SetMap({}) call ale#test#RestoreDirectory() call ale#linter#Reset() diff --git a/test/test_go_to_definition.vader b/test/test_go_to_definition.vader index 5bd75675..3a1d7458 100644 --- a/test/test_go_to_definition.vader +++ b/test/test_go_to_definition.vader @@ -11,12 +11,18 @@ Before: runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim - function! ale#linter#StartLSP(buffer, linter, callback) abort + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback + let l:conn = ale#lsp#NewConnection({}) + let l:conn.id = 347 + let l:conn.open_documents = {a:buffer : -1} + return { + \ 'buffer': a:buffer, \ 'connection_id': 347, \ 'project_root': '/foo/bar', + \ 'language_id': 'python', \} endfunction @@ -31,6 +37,7 @@ Before: endfunction After: + call ale#lsp#RemoveConnectionWithID(347) call ale#definition#SetMap({}) call ale#test#RestoreDirectory() call ale#linter#Reset() @@ -56,6 +63,19 @@ Execute(Failed definition responses should be handled correctly): \) AssertEqual {}, ale#definition#GetMap() +Execute(Failed definition responses with no files should be handled correctly): + call ale#definition#SetMap({3: {'open_in_tab': 0}}) + call ale#definition#HandleTSServerResponse( + \ 1, + \ { + \ 'command': 'definition', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [], + \ } + \) + AssertEqual {}, ale#definition#GetMap() + Given typescript(Some typescript file): foo somelongerline diff --git a/test/test_hover.vader b/test/test_hover.vader index 18dcebaf..15f164f0 100644 --- a/test/test_hover.vader +++ b/test/test_hover.vader @@ -11,7 +11,7 @@ Before: runtime autoload/ale/lsp.vim runtime autoload/ale/util.vim - function! ale#linter#StartLSP(buffer, linter, callback) abort + function! ale#lsp_linter#StartLSP(buffer, linter, callback) abort let g:Callback = a:callback return { diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader index 653587b6..48a4a394 100644 --- a/test/test_linter_defintion_processing.vader +++ b/test/test_linter_defintion_processing.vader @@ -465,3 +465,28 @@ Execute(PreProcess should complain about address_callback for non-LSP linters): AssertThrows call ale#linter#PreProcess(g:linter) AssertEqual '`address_callback` cannot be used when lsp != ''socket''', g:vader_exception + +Execute(PreProcess should complain about using initialization_options and initialization_options_callback together): + let g:linter = { + \ 'name': 'x', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'initialization_options': 'x', + \ 'initialization_options_callback': 'x', + \} + + AssertThrows call ale#linter#PreProcess(g:linter) + AssertEqual 'Only one of `initialization_options` or `initialization_options_callback` should be set', g:vader_exception + +Execute (PreProcess should throw when initialization_options_callback is not a callback): + AssertThrows call ale#linter#PreProcess({ + \ 'name': 'foo', + \ 'lsp': 'socket', + \ 'address_callback': 'X', + \ 'language': 'x', + \ 'project_root_callback': 'x', + \ 'initialization_options_callback': {}, + \}) + AssertEqual '`initialization_options_callback` must be a callback if defined', g:vader_exception diff --git a/test/test_pattern_options.vader b/test/test_pattern_options.vader index 0e26eaaa..f439afbd 100644 --- a/test/test_pattern_options.vader +++ b/test/test_pattern_options.vader @@ -90,3 +90,14 @@ Execute(Patterns should be applied after the Dictionary changes): call ale#pattern_options#SetOptions(bufnr('')) AssertEqual 666, b:some_option + +Execute(SetOptions should tolerate settings being unset): + " This might happen if ALE is loaded in a weird way, so tolerate it. + unlet! g:ale_pattern_options + unlet! g:ale_pattern_options_enabled + + call ale#pattern_options#SetOptions(bufnr('')) + + let g:ale_pattern_options_enabled = 1 + + call ale#pattern_options#SetOptions(bufnr('')) |