diff options
311 files changed, 8275 insertions, 2737 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index aca83191..e6f2a1ee 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -19,6 +19,9 @@ init: # Stop git from changing newlines - git config --global core.autocrlf input +# NOTE: If you change the Vim or Vader versions here, please also update the +# instructions for running tests on Windows in ale-development.txt + install: # Download and unpack Vim - ps: >- diff --git a/.github/stale.yml b/.github/stale.yml new file mode 100644 index 00000000..3a5a354b --- /dev/null +++ b/.github/stale.yml @@ -0,0 +1,17 @@ +--- +# This configuration closes stale PRs after 28 + 7 days. +# That's 4 weeks until stale bot complains, and a week until it closes a PR. +# Issues in ALE are never, ever stale. They are either resolved or not. +only: pulls +daysUntilStale: 28 +daysUntilClose: 7 +exemptLabels: [] +staleLabel: stale +markComment: > + This pull request has been automatically marked as stale because it has not + been updated recently. Make sure to write tests and document your changes. + See `:help ale-dev` for information on writing tests. + + If your pull request is good to merge, bother w0rp or another maintainer + again, and get them to merge it. +closeComment: false @@ -1,11 +1,12 @@ !.editorconfig *.obj +*.pyc # Ignore all hidden files everywhere. # Use `git add -f` to add hidden files. .* -__pycache__ -*.pyc /doc/tags /init.vim /test/ale-info-test-file +/vader_output +__pycache__ tags @@ -1,4 +1,4 @@ -Copyright (c) 2016-2019, w0rp <devw0rp@gmail.com> +Copyright (c) 2016-2020, w0rp <devw0rp@gmail.com> All rights reserved. Redistribution and use in source and binary forms, with or without @@ -61,23 +61,25 @@ other content at [w0rp.com](https://w0rp.com). 4. [Contributing](#contributing) 5. [FAQ](#faq) 1. [How do I disable particular linters?](#faq-disable-linters) - 2. [How can I keep the sign gutter open?](#faq-keep-signs) - 3. [How can I change the signs ALE uses?](#faq-change-signs) - 4. [How can I change or disable the highlights ALE uses?](#faq-change-highlights) - 5. [How can I show errors or warnings in my statusline?](#faq-statusline) - 6. [How can I show errors or warnings in my lightline?](#faq-lightline) - 7. [How can I change the format for echo messages?](#faq-echo-format) - 8. [How can I execute some code when ALE starts or stops linting?](#faq-autocmd) - 9. [How can I navigate between errors quickly?](#faq-navigation) - 10. [How can I run linters only when I save files?](#faq-lint-on-save) - 11. [How can I use the quickfix list instead of the loclist?](#faq-quickfix) - 12. [How can I check JSX files with both stylelint and eslint?](#faq-jsx-stylelint-eslint) - 13. [How can I check Vue files with ESLint?](#faq-vue-eslint) - 14. [Will this plugin eat all of my laptop battery power?](#faq-my-battery-is-sad) - 15. [How can I configure my C or C++ project?](#faq-c-configuration) - 16. [How can I configure ALE differently for different buffers?](#faq-buffer-configuration) - 17. [How can I configure the height of the list in which ALE displays errors?](#faq-list-window-height) - 18. [How can I see what ALE has configured for the current file?](#faq-get-info) + 2. [How can I see what ALE has configured for the current file?](#faq-get-info) + 3. [How can I use ALE and coc.nvim together?](#faq-coc-nvim) + 4. [How can I keep the sign gutter open?](#faq-keep-signs) + 5. [How can I change the signs ALE uses?](#faq-change-signs) + 6. [How can I change or disable the highlights ALE uses?](#faq-change-highlights) + 7. [How can I show errors or warnings in my statusline?](#faq-statusline) + 8. [How can I show errors or warnings in my lightline?](#faq-lightline) + 9. [How can I change the format for echo messages?](#faq-echo-format) + 10. [How can I execute some code when ALE starts or stops linting?](#faq-autocmd) + 11. [How can I navigate between errors quickly?](#faq-navigation) + 12. [How can I run linters only when I save files?](#faq-lint-on-save) + 13. [How can I use the quickfix list instead of the loclist?](#faq-quickfix) + 14. [How can I check JSX files with both stylelint and eslint?](#faq-jsx-stylelint-eslint) + 15. [How can I check Vue files with ESLint?](#faq-vue-eslint) + 16. [Will this plugin eat all of my laptop battery power?](#faq-my-battery-is-sad) + 17. [How can I configure my C or C++ project?](#faq-c-configuration) + 18. [How can I configure ALE differently for different buffers?](#faq-buffer-configuration) + 19. [How can I configure the height of the list in which ALE displays errors?](#faq-list-window-height) + 20. [How can I run linters or fixers via Docker or a VM?](#faq-vm) <a name="supported-languages"></a> @@ -102,7 +104,7 @@ programs for checking the syntax and semantics of your programs. By default, linters will be re-run in the background to check your syntax when you open new buffers or as you make edits to your files. -The behaviour of linting can be configured with a variety of options, +The behavior of linting can be configured with a variety of options, documented in [the Vim help file](doc/ale.txt). For more information on the options ALE offers, consult `:help ale-options` for global options and `:help ale-integration-options` for options specified to particular linters. @@ -193,12 +195,11 @@ completion manually with `<C-x><C-o>`. set omnifunc=ale#completion#OmniFunc ``` -When working with TypeScript files, ALE supports automatic imports from -external modules. This behavior is disabled by default and can be enabled by -setting: +ALE supports automatic imports from external modules. This behavior is disabled +by default and can be enabled by setting: ```vim -let g:ale_completion_tsserver_autoimport = 1 +let g:ale_completion_autoimport = 1 ``` See `:help ale-completion` for more information. @@ -231,6 +232,9 @@ ALE supports "hover" information for printing brief information about symbols at the cursor taken from Language Server Protocol linters and `tsserver` with the `ALEHover` command. +Truncated information will be displayed when the cursor rests on a symbol by +default, as long as there are no problems on the same line. + The information can be displayed in a `balloon` tooltip in Vim or GVim by hovering your mouse over symbols. Mouse hovering is enabled by default in GVim, and needs to be configured for Vim 8.1+ in terminals. @@ -413,9 +417,56 @@ This plugin will look for linters in the [`ale_linters`](ale_linters) directory. Each directory within corresponds to a particular filetype in Vim, and each file in each directory corresponds to the name of a particular linter. +<a name="faq-get-info"></a> + +### 5.ii. How can I see what ALE has configured for the current file? + +Run the following to see what is currently configured: + +```vim +:ALEInfo +``` + +<a name="faq-coc-nvim"></a> + +### 5.iii. How can I use ALE and coc.nvim together? + +[coc.nvim](https://github.com/neoclide/coc.nvim) is a popular Vim plugin written +in TypeScript and dependent on the [npm](https://www.npmjs.com/) ecosystem for +providing full IDE features to Vim. Both ALE and coc.nvim implement +[Language Server Protocol](https://microsoft.github.io/language-server-protocol/) +(LSP) clients for supporting diagnostics (linting with a live server), and other +features like auto-completion, and others listed above. + +ALE is primarily focused on integrating with external programs through virtually +any means, provided the plugin remains almost entirely written in Vim script. +coc.nvim is primarily focused on bringing IDE features to Vim. If you want to +run external programs on your files to check for errors, and also use the most +advanced IDE features, you might want to use both plugins at the same time. + +The easiest way to get both plugins to work together is to configure coc.nvim to +send diagnostics to ALE, so ALE controls how all problems are presented to you, +and to disable all LSP features in ALE, so ALE doesn't try to provide LSP +features already provided by coc.nvim, such as auto-completion. + +1. Open your coc.nvim configuration file with `:CocConfig` and add + `"diagnostic.displayByAle": true` to your settings. +2. Add `let g:ale_disable_lsp = 1` to your vimrc file, before plugins are + loaded. + +You can also use `b:ale_disable_lsp` in your ftplugin files to enable or disable +LSP features in ALE for different filetypes. After you configure coc.nvim and +ALE this way, you can further configure how problems appear to you by using all +of the settings mentioned in ALE's help file, including how often diagnostics +are requested. See `:help ale-lint`. + +The integration between ALE and coc.nvim works using an API ALE offers for +letting any other plugin integrate with ALE. If you are interested in writing a +similar integration, see `:help ale-lint-other-sources`. + <a name="faq-keep-signs"></a> -### 5.ii. How can I keep the sign gutter open? +### 5.iv. How can I keep the sign gutter open? You can keep the sign gutter open at all times by setting the `g:ale_sign_column_always` to 1 @@ -426,7 +477,7 @@ let g:ale_sign_column_always = 1 <a name="faq-change-signs"></a> -### 5.iii. How can I change the signs ALE uses? +### 5.v. How can I change the signs ALE uses? Use these options to specify what text should be used for signs: @@ -446,7 +497,7 @@ highlight clear ALEWarningSign <a name="faq-change-highlights"></a> -### 5.iv. How can I change or disable the highlights ALE uses? +### 5.vi. How can I change or disable the highlights ALE uses? ALE's highlights problems with highlight groups which link to `SpellBad`, `SpellCap`, `error`, and `todo` groups by default. The characters that are @@ -472,7 +523,7 @@ See `:help ale-highlights` for more information. <a name="faq-statusline"></a> -### 5.v. How can I show errors or warnings in my statusline? +### 5.vii. How can I show errors or warnings in my statusline? [vim-airline](https://github.com/vim-airline/vim-airline) integrates with ALE for displaying error information in the status bar. If you want to see the @@ -521,7 +572,7 @@ for more information. <a name="faq-lightline"></a> -### 5.vi. How can I show errors or warnings in my lightline? +### 5.viii. How can I show errors or warnings in my lightline? [lightline](https://github.com/itchyny/lightline.vim) does not have built-in support for ALE, nevertheless there is a plugin that adds this functionality: [maximbaz/lightline-ale](https://github.com/maximbaz/lightline-ale). @@ -530,7 +581,7 @@ For more information, check out the sources of that plugin, `:help ale#statuslin <a name="faq-echo-format"></a> -### 5.vii. How can I change the format for echo messages? +### 5.ix. How can I change the format for echo messages? There are 3 global options that allow customizing the echoed message. @@ -559,7 +610,7 @@ See `:help g:ale_echo_msg_format` for more information. <a name="faq-autocmd"></a> -### 5.viii. How can I execute some code when ALE starts or stops linting? +### 5.x. How can I execute some code when ALE starts or stops linting? ALE runs its own [autocmd](http://vimdoc.sourceforge.net/htmldoc/autocmd.html) events when a lint or fix cycle are started and stopped. There is also an event @@ -582,7 +633,7 @@ augroup END <a name="faq-navigation"></a> -### 5.ix. How can I navigate between errors quickly? +### 5.xi. How can I navigate between errors quickly? ALE offers some commands with `<Plug>` keybinds for moving between warnings and errors quickly. You can map the keys Ctrl+j and Ctrl+k to moving between errors @@ -598,7 +649,7 @@ For more information, consult the online documentation with <a name="faq-lint-on-save"></a> -### 5.x. How can I run linters only when I save files? +### 5.xii. How can I run linters only when I save files? ALE offers an option `g:ale_lint_on_save` for enabling running the linters when files are saved. This option is enabled by default. If you only @@ -619,7 +670,7 @@ files, you can set `g:ale_lint_on_save` to `0`. <a name="faq-quickfix"></a> -### 5.xi. How can I use the quickfix list instead of the loclist? +### 5.xiii. How can I use the quickfix list instead of the loclist? The quickfix list can be enabled by turning the `g:ale_set_quickfix` option on. If you wish to also disable the loclist, you can disable @@ -649,7 +700,7 @@ instead of the default horizontally. <a name="faq-jsx-stylelint-eslint"></a> -### 5.xii. How can I check JSX files with both stylelint and eslint? +### 5.xiv. How can I check JSX files with both stylelint and eslint? If you configure ALE options correctly in your vimrc file, and install the right tools, you can check JSX files with stylelint and eslint. @@ -691,7 +742,7 @@ no linter will be run twice for the same file. <a name="faq-vue-eslint"></a> -### 5.xiii. How can I check Vue files with ESLint? +### 5.xv. How can I check Vue files with ESLint? To check Vue files with ESLint, your ESLint project configuration file must be configured to use the [Vue plugin](https://github.com/vuejs/eslint-plugin-vue). @@ -722,7 +773,7 @@ let g:ale_linters = {'vue': ['eslint', 'vls']} <a name="faq-my-battery-is-sad"></a> -### 5.xiv. Will this plugin eat all of my laptop battery power? +### 5.xvi. Will this plugin eat all of my laptop battery power? ALE takes advantage of the power of various tools to check your code. This of course means that CPU time will be used to continuously check your code. If you @@ -735,7 +786,7 @@ while you type. ALE uses a timeout which is cancelled and reset every time you type, and this delay can be increased so linters are run less often. See `:help g:ale_lint_delay` for more information. -If you don't wish to run linters while you type, you can disable that behaviour. +If you don't wish to run linters while you type, you can disable that behavior. Set `g:ale_lint_on_text_changed` to `never`. You won't get as frequent error checking, but ALE shouldn't block your ability to edit a document after you save a file, so the asynchronous nature of the plugin will still be an advantage. @@ -746,7 +797,7 @@ including the option `g:ale_lint_on_enter`, and you can run ALE manually with <a name="faq-c-configuration"></a> -### 5.xv. How can I configure my C or C++ project? +### 5.xvii. How can I configure my C or C++ project? The structure of C and C++ projects varies wildly from project to project, with many different build tools being used for building them, and many different @@ -766,13 +817,24 @@ setting. Consult the documentation for that setting for more information. `b:ale_linters` can be used to select which tools you want to run, say if you want to use only `gcc` for one project, and only `clang` for another. +ALE will attempt to parse `compile_commands.json` files to discover compiler +flags to use when linting code. See `:help g:ale_c_parse_compile_commands` for +more information. See Clang's documentation for +[compile_commands.json files](https://clang.llvm.org/docs/JSONCompilationDatabase.html). +You should strongly consider generating them in your builds, which is easy to do +with CMake. + +You can also configure ALE to automatically run `make -n` to run dry runs on +`Makefile`s to discover compiler flags. This can execute arbitrary code, so the +option is disabled by default. See `:help g:ale_c_parse_makefile`. + You may also configure buffer-local settings for linters with project-specific vimrc files. [local_vimrc](https://github.com/LucHermitte/local_vimrc) can be used for executing local vimrc files which can be shared in your project. <a name="faq-buffer-configuration"></a> -### 5.xvi. How can I configure ALE differently for different buffers? +### 5.xviii. How can I configure ALE differently for different buffers? ALE offers various ways to configure which linters or fixers are run, and other settings. For the majority of ALE's settings, they can either be @@ -808,7 +870,7 @@ Buffer-local variables for settings always override the global settings. <a name="faq-list-window-height"></a> -### 5.xvii. How can I configure the height of the list in which ALE displays errors? +### 5.xix. How can I configure the height of the list in which ALE displays errors? To set a default height for the error list, use the `g:ale_list_window_size` variable. @@ -817,12 +879,13 @@ To set a default height for the error list, use the `g:ale_list_window_size` var let g:ale_list_window_size = 5 ``` -<a name="faq-get-info"></a> - -### 5.xviii. How can I see what ALE has configured for the current file? +<a name="faq-vm"></a> -Run the following to see what is currently configured: +### 5.xx. How can I run linters or fixers via Docker or a VM? -```vim -:ALEInfo -``` +ALE supports running linters or fixers via Docker, virtual machines, or in +combination with any remote machine with a different file system, so long as the +tools are well-integrated with ALE, and ALE is properly configured to run the +correct commands and map filename paths between different file systems. See +`:help ale-lint-other-machines` for the full documentation on how to configure +ALE to support this. diff --git a/ale_linters/ada/gcc.vim b/ale_linters/ada/gcc.vim index 87496b81..5afc9ae3 100644 --- a/ale_linters/ada/gcc.vim +++ b/ale_linters/ada/gcc.vim @@ -18,7 +18,7 @@ function! ale_linters#ada#gcc#GetCommand(buffer) abort " -gnatc: Check syntax and semantics only (no code generation attempted) return '%e -x ada -c -gnatc' \ . ' -o ' . ale#Escape(l:out_file) - \ . ' -I ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) + \ . ' -I %s:h' \ . ale#Pad(ale#Var(a:buffer, 'ada_gcc_options')) \ . ' %t' endfunction diff --git a/ale_linters/asciidoc/languagetool.vim b/ale_linters/asciidoc/languagetool.vim new file mode 100644 index 00000000..8e8de7f3 --- /dev/null +++ b/ale_linters/asciidoc/languagetool.vim @@ -0,0 +1,5 @@ +" Author: Horacio Sanson (hsanson [ät] gmail.com) +" Description: languagetool for asciidoc files, copied from markdown. + + +call ale#handlers#languagetool#DefineLinter('asciidoc') diff --git a/ale_linters/asm/gcc.vim b/ale_linters/asm/gcc.vim index eecab6ef..cda38923 100644 --- a/ale_linters/asm/gcc.vim +++ b/ale_linters/asm/gcc.vim @@ -9,7 +9,7 @@ function! ale_linters#asm#gcc#GetCommand(buffer) abort " -fsyntax-only doesn't catch everything. return '%e -x assembler' \ . ' -o ' . g:ale#util#nul_file - \ . '-iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) + \ . '-iquote %s:h' \ . ' ' . ale#Var(a:buffer, 'asm_gcc_options') . ' -' endfunction diff --git a/ale_linters/bats/shellcheck.vim b/ale_linters/bats/shellcheck.vim new file mode 100644 index 00000000..5c2a0ea9 --- /dev/null +++ b/ale_linters/bats/shellcheck.vim @@ -0,0 +1,4 @@ +" Author: Ian2020 <https://github.com/Ian2020> +" Description: shellcheck linter for bats scripts. + +call ale#handlers#shellcheck#DefineLinter('bats') diff --git a/ale_linters/c/cc.vim b/ale_linters/c/cc.vim new file mode 100644 index 00000000..5655fbf7 --- /dev/null +++ b/ale_linters/c/cc.vim @@ -0,0 +1,53 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: A C compiler linter for C files with gcc/clang, etc. + +call ale#Set('c_cc_executable', '<auto>') +call ale#Set('c_cc_options', '-std=c11 -Wall') + +function! ale_linters#c#cc#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'c_cc_executable') + + " Default to either clang or gcc. + if l:executable is# '<auto>' + if ale#engine#IsExecutable(a:buffer, 'clang') + let l:executable = 'clang' + else + let l:executable = 'gcc' + endif + endif + + return l:executable +endfunction + +function! ale_linters#c#cc#GetCommand(buffer, output) abort + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) + let l:ale_flags = ale#Var(a:buffer, 'c_cc_options') + + if l:cflags =~# '-std=' + let l:ale_flags = substitute( + \ l:ale_flags, + \ '-std=\(c\|gnu\)[0-9]\{2\}', + \ '', + \ 'g') + endif + + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + " + " `-o /dev/null` or `-o null` is needed to catch all errors, + " -fsyntax-only doesn't catch everything. + return '%e -S -x c' + \ . ' -o ' . g:ale#util#nul_file + \ . ' -iquote %s:h' + \ . ale#Pad(l:cflags) + \ . ale#Pad(l:ale_flags) . ' -' +endfunction + +call ale#linter#Define('c', { +\ 'name': 'cc', +\ 'aliases': ['gcc', 'clang'], +\ 'output_stream': 'stderr', +\ 'executable': function('ale_linters#c#cc#GetExecutable'), +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#c#cc#GetCommand'))}, +\ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', +\}) diff --git a/ale_linters/c/ccls.vim b/ale_linters/c/ccls.vim index 9e3dafe9..9f105712 100644 --- a/ale_linters/c/ccls.vim +++ b/ale_linters/c/ccls.vim @@ -3,6 +3,7 @@ call ale#Set('c_ccls_executable', 'ccls') call ale#Set('c_ccls_init_options', {}) +call ale#Set('c_build_dir', '') call ale#linter#Define('c', { \ 'name': 'ccls', @@ -10,5 +11,5 @@ call ale#linter#Define('c', { \ 'executable': {b -> ale#Var(b, 'c_ccls_executable')}, \ 'command': '%e', \ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), -\ 'initialization_options': {b -> ale#Var(b, 'c_ccls_init_options')}, +\ 'initialization_options': {b -> ale#handlers#ccls#GetInitOpts(b, 'c_ccls_init_options')}, \}) diff --git a/ale_linters/c/clang.vim b/ale_linters/c/clang.vim deleted file mode 100644 index 681101fc..00000000 --- a/ale_linters/c/clang.vim +++ /dev/null @@ -1,24 +0,0 @@ -" Author: Masahiro H https://github.com/mshr-h -" Description: clang linter for c files - -call ale#Set('c_clang_executable', 'clang') -call ale#Set('c_clang_options', '-std=c11 -Wall') - -function! ale_linters#c#clang#GetCommand(buffer, output) abort - let l:cflags = ale#c#GetCFlags(a:buffer, a:output) - - " -iquote with the directory the file is in makes #include work for - " headers in the same directory. - return '%e -S -x c -fsyntax-only' - \ . ' -iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) - \ . ale#Pad(l:cflags) - \ . ale#Pad(ale#Var(a:buffer, 'c_clang_options')) . ' -' -endfunction - -call ale#linter#Define('c', { -\ 'name': 'clang', -\ 'output_stream': 'stderr', -\ 'executable': {b -> ale#Var(b, 'c_clang_executable')}, -\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#c#clang#GetCommand'))}, -\ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', -\}) diff --git a/ale_linters/c/cppcheck.vim b/ale_linters/c/cppcheck.vim index 309b2851..b671fc8b 100644 --- a/ale_linters/c/cppcheck.vim +++ b/ale_linters/c/cppcheck.vim @@ -10,9 +10,11 @@ function! ale_linters#c#cppcheck#GetCommand(buffer) abort let l:buffer_path_include = empty(l:compile_commands_option) \ ? ale#handlers#cppcheck#GetBufferPathIncludeOptions(a:buffer) \ : '' + let l:template = ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' return l:cd_command \ . '%e -q --language=c' + \ . l:template \ . ale#Pad(l:compile_commands_option) \ . ale#Pad(ale#Var(a:buffer, 'c_cppcheck_options')) \ . l:buffer_path_include diff --git a/ale_linters/c/gcc.vim b/ale_linters/c/gcc.vim deleted file mode 100644 index 1df1018e..00000000 --- a/ale_linters/c/gcc.vim +++ /dev/null @@ -1,28 +0,0 @@ -" Author: w0rp <devw0rp@gmail.com> -" Description: gcc linter for c files - -call ale#Set('c_gcc_executable', 'gcc') -call ale#Set('c_gcc_options', '-std=c11 -Wall') - -function! ale_linters#c#gcc#GetCommand(buffer, output) abort - let l:cflags = ale#c#GetCFlags(a:buffer, a:output) - - " -iquote with the directory the file is in makes #include work for - " headers in the same directory. - " - " `-o /dev/null` or `-o null` is needed to catch all errors, - " -fsyntax-only doesn't catch everything. - return '%e -S -x c' - \ . ' -o ' . g:ale#util#nul_file - \ . ' -iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) - \ . ale#Pad(l:cflags) - \ . ale#Pad(ale#Var(a:buffer, 'c_gcc_options')) . ' -' -endfunction - -call ale#linter#Define('c', { -\ 'name': 'gcc', -\ 'output_stream': 'stderr', -\ 'executable': {b -> ale#Var(b, 'c_gcc_executable')}, -\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#c#gcc#GetCommand'))}, -\ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', -\}) diff --git a/ale_linters/clojure/clj_kondo.vim b/ale_linters/clojure/clj_kondo.vim index 5dd11c12..eb60ce77 100644 --- a/ale_linters/clojure/clj_kondo.vim +++ b/ale_linters/clojure/clj_kondo.vim @@ -29,6 +29,6 @@ call ale#linter#Define('clojure', { \ 'name': 'clj-kondo', \ 'output_stream': 'stdout', \ 'executable': 'clj-kondo', -\ 'command': 'clj-kondo --lint %t', +\ 'command': 'clj-kondo --cache --lint %t', \ 'callback': 'ale_linters#clojure#clj_kondo#HandleCljKondoFormat', \}) diff --git a/ale_linters/cpp/cc.vim b/ale_linters/cpp/cc.vim new file mode 100644 index 00000000..ffb8f068 --- /dev/null +++ b/ale_linters/cpp/cc.vim @@ -0,0 +1,53 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: A C++ compiler linter for C++ files with gcc/clang, etc. + +call ale#Set('cpp_cc_executable', '<auto>') +call ale#Set('cpp_cc_options', '-std=c++14 -Wall') + +function! ale_linters#cpp#cc#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'cpp_cc_executable') + + " Default to either clang++ or gcc. + if l:executable is# '<auto>' + if ale#engine#IsExecutable(a:buffer, 'clang++') + let l:executable = 'clang++' + else + let l:executable = 'gcc' + endif + endif + + return l:executable +endfunction + +function! ale_linters#cpp#cc#GetCommand(buffer, output) abort + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) + let l:ale_flags = ale#Var(a:buffer, 'cpp_cc_options') + + if l:cflags =~# '-std=' + let l:ale_flags = substitute( + \ l:ale_flags, + \ '-std=\(c\|gnu\)++[0-9]\{2\}', + \ '', + \ 'g') + endif + + " -iquote with the directory the file is in makes #include work for + " headers in the same directory. + " + " `-o /dev/null` or `-o null` is needed to catch all errors, + " -fsyntax-only doesn't catch everything. + return '%e -S -x c++' + \ . ' -o ' . g:ale#util#nul_file + \ . ' -iquote %s:h' + \ . ale#Pad(l:cflags) + \ . ale#Pad(l:ale_flags) . ' -' +endfunction + +call ale#linter#Define('cpp', { +\ 'name': 'cc', +\ 'aliases': ['gcc', 'clang', 'g++', 'clang++'], +\ 'output_stream': 'stderr', +\ 'executable': function('ale_linters#cpp#cc#GetExecutable'), +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#cpp#cc#GetCommand'))}, +\ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', +\}) diff --git a/ale_linters/cpp/ccls.vim b/ale_linters/cpp/ccls.vim index b265ff70..38f8df9c 100644 --- a/ale_linters/cpp/ccls.vim +++ b/ale_linters/cpp/ccls.vim @@ -3,6 +3,7 @@ call ale#Set('cpp_ccls_executable', 'ccls') call ale#Set('cpp_ccls_init_options', {}) +call ale#Set('c_build_dir', '') call ale#linter#Define('cpp', { \ 'name': 'ccls', @@ -10,5 +11,5 @@ call ale#linter#Define('cpp', { \ 'executable': {b -> ale#Var(b, 'cpp_ccls_executable')}, \ 'command': '%e', \ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), -\ 'initialization_options': {b -> ale#Var(b, 'cpp_ccls_init_options')}, +\ 'initialization_options': {b -> ale#handlers#ccls#GetInitOpts(b, 'cpp_ccls_init_options')}, \}) diff --git a/ale_linters/cpp/clang.vim b/ale_linters/cpp/clang.vim deleted file mode 100644 index e48291eb..00000000 --- a/ale_linters/cpp/clang.vim +++ /dev/null @@ -1,24 +0,0 @@ -" Author: Tomota Nakamura <https://github.com/tomotanakamura> -" Description: clang linter for cpp files - -call ale#Set('cpp_clang_executable', 'clang++') -call ale#Set('cpp_clang_options', '-std=c++14 -Wall') - -function! ale_linters#cpp#clang#GetCommand(buffer, output) abort - let l:cflags = ale#c#GetCFlags(a:buffer, a:output) - - " -iquote with the directory the file is in makes #include work for - " headers in the same directory. - return '%e -S -x c++ -fsyntax-only' - \ . ' -iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) - \ . ale#Pad(l:cflags) - \ . ale#Pad(ale#Var(a:buffer, 'cpp_clang_options')) . ' -' -endfunction - -call ale#linter#Define('cpp', { -\ 'name': 'clang', -\ 'output_stream': 'stderr', -\ 'executable': {b -> ale#Var(b, 'cpp_clang_executable')}, -\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#cpp#clang#GetCommand'))}, -\ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', -\}) diff --git a/ale_linters/cpp/clangtidy.vim b/ale_linters/cpp/clangtidy.vim index 191b7b07..5e062d86 100644 --- a/ale_linters/cpp/clangtidy.vim +++ b/ale_linters/cpp/clangtidy.vim @@ -25,6 +25,11 @@ function! ale_linters#cpp#clangtidy#GetCommand(buffer, output) abort let l:options .= !empty(l:options) ? ale#Pad(l:cflags) : l:cflags endif + " Tell clang-tidy a .h header with a C++ filetype in Vim is a C++ file. + if expand('#' . a:buffer) =~# '\.h$' + let l:options .= !empty(l:options) ? ' -x c++' : '-x c++' + endif + " Get the options to pass directly to clang-tidy let l:extra_options = ale#Var(a:buffer, 'cpp_clangtidy_extra_options') diff --git a/ale_linters/cpp/cppcheck.vim b/ale_linters/cpp/cppcheck.vim index 7cd80dbc..2c832246 100644 --- a/ale_linters/cpp/cppcheck.vim +++ b/ale_linters/cpp/cppcheck.vim @@ -10,9 +10,11 @@ function! ale_linters#cpp#cppcheck#GetCommand(buffer) abort let l:buffer_path_include = empty(l:compile_commands_option) \ ? ale#handlers#cppcheck#GetBufferPathIncludeOptions(a:buffer) \ : '' + let l:template = ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' return l:cd_command \ . '%e -q --language=c++' + \ . l:template \ . ale#Pad(l:compile_commands_option) \ . ale#Pad(ale#Var(a:buffer, 'cpp_cppcheck_options')) \ . l:buffer_path_include diff --git a/ale_linters/cpp/gcc.vim b/ale_linters/cpp/gcc.vim deleted file mode 100644 index 108d6d70..00000000 --- a/ale_linters/cpp/gcc.vim +++ /dev/null @@ -1,29 +0,0 @@ -" Author: geam <mdelage@student.42.fr> -" Description: gcc linter for cpp files -" -call ale#Set('cpp_gcc_executable', 'gcc') -call ale#Set('cpp_gcc_options', '-std=c++14 -Wall') - -function! ale_linters#cpp#gcc#GetCommand(buffer, output) abort - let l:cflags = ale#c#GetCFlags(a:buffer, a:output) - - " -iquote with the directory the file is in makes #include work for - " headers in the same directory. - " - " `-o /dev/null` or `-o null` is needed to catch all errors, - " -fsyntax-only doesn't catch everything. - return '%e -S -x c++' - \ . ' -o ' . g:ale#util#nul_file - \ . ' -iquote ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) - \ . ale#Pad(l:cflags) - \ . ale#Pad(ale#Var(a:buffer, 'cpp_gcc_options')) . ' -' -endfunction - -call ale#linter#Define('cpp', { -\ 'name': 'gcc', -\ 'aliases': ['g++'], -\ 'output_stream': 'stderr', -\ 'executable': {b -> ale#Var(b, 'cpp_gcc_executable')}, -\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#cpp#gcc#GetCommand'))}, -\ 'callback': 'ale#handlers#gcc#HandleGCCFormatWithIncludes', -\}) diff --git a/ale_linters/cuda/nvcc.vim b/ale_linters/cuda/nvcc.vim index f3af07b6..2734f6ec 100644 --- a/ale_linters/cuda/nvcc.vim +++ b/ale_linters/cuda/nvcc.vim @@ -5,9 +5,6 @@ call ale#Set('cuda_nvcc_executable', 'nvcc') call ale#Set('cuda_nvcc_options', '-std=c++11') function! ale_linters#cuda#nvcc#GetCommand(buffer) abort - " Unused: use ale#util#nul_file - " let l:output_file = ale#util#Tempname() . '.ii' - " call ale#command#ManageFile(a:buffer, l:output_file) return '%e -cuda' \ . ale#Pad(ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer))) \ . ale#Pad(ale#Var(a:buffer, 'cuda_nvcc_options')) diff --git a/ale_linters/dockerfile/dockerfile_lint.vim b/ale_linters/dockerfile/dockerfile_lint.vim index 95768b12..0c0ad533 100644 --- a/ale_linters/dockerfile/dockerfile_lint.vim +++ b/ale_linters/dockerfile/dockerfile_lint.vim @@ -32,14 +32,29 @@ function! ale_linters#dockerfile#dockerfile_lint#Handle(buffer, lines) abort let l:line = get(l:object, 'line', -1) let l:message = l:object['message'] + let l:link = get(l:object, 'reference_url', '') + + if type(l:link) == v:t_list + " Somehow, reference_url is returned as two-part list. + " Anchor markers in that list are sometimes duplicated. + " See https://github.com/projectatomic/dockerfile_lint/issues/134 + let l:link = join(l:link, '') + let l:link = substitute(l:link, '##', '#', '') + endif + + let l:detail = l:message + if get(l:object, 'description', 'None') isnot# 'None' - let l:message = l:message . '. ' . l:object['description'] + let l:detail .= "\n\n" . l:object['description'] endif + let l:detail .= "\n\n" . l:link + call add(l:messages, { \ 'lnum': l:line, \ 'text': l:message, \ 'type': ale_linters#dockerfile#dockerfile_lint#GetType(l:type), + \ 'detail': l:detail, \}) endfor endfor diff --git a/ale_linters/elixir/credo.vim b/ale_linters/elixir/credo.vim index 317ecab3..7c298502 100644 --- a/ale_linters/elixir/credo.vim +++ b/ale_linters/elixir/credo.vim @@ -46,7 +46,7 @@ function! ale_linters#elixir#credo#GetMode() abort endfunction function! ale_linters#elixir#credo#GetCommand(buffer) abort - let l:project_root = ale#handlers#elixir#FindMixProjectRoot(a:buffer) + let l:project_root = ale#handlers#elixir#FindMixUmbrellaRoot(a:buffer) let l:mode = ale_linters#elixir#credo#GetMode() return ale#path#CdString(l:project_root) diff --git a/ale_linters/eruby/ruumba.vim b/ale_linters/eruby/ruumba.vim index 2e84acf7..f415f1ab 100644 --- a/ale_linters/eruby/ruumba.vim +++ b/ale_linters/eruby/ruumba.vim @@ -11,7 +11,7 @@ function! ale_linters#eruby#ruumba#GetCommand(buffer) abort return ale#ruby#EscapeExecutable(l:executable, 'ruumba') \ . ' --format json --force-exclusion ' \ . ale#Var(a:buffer, 'eruby_ruumba_options') - \ . ' --stdin ' . ale#Escape(expand('#' . a:buffer . ':p')) + \ . ' --stdin %s' endfunction function! ale_linters#eruby#ruumba#Handle(buffer, lines) abort diff --git a/ale_linters/go/gofmt.vim b/ale_linters/go/gofmt.vim index a233b422..b313f9ca 100644 --- a/ale_linters/go/gofmt.vim +++ b/ale_linters/go/gofmt.vim @@ -6,7 +6,6 @@ function! ale_linters#go#gofmt#GetCommand(buffer) abort \ . '%e -e %t' endfunction - call ale#linter#Define('go', { \ 'name': 'gofmt', \ 'output_stream': 'stderr', diff --git a/ale_linters/go/revive.vim b/ale_linters/go/revive.vim new file mode 100644 index 00000000..b14b5ab9 --- /dev/null +++ b/ale_linters/go/revive.vim @@ -0,0 +1,21 @@ +" Author: Penghui Liao <liaoishere@gmail.com> +" Description: Adds support for revive + +call ale#Set('go_revive_executable', 'revive') +call ale#Set('go_revive_options', '') + +function! ale_linters#go#revive#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'go_revive_options') + + return ale#go#EnvString(a:buffer) . '%e' + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t' +endfunction + +call ale#linter#Define('go', { +\ 'name': 'revive', +\ 'output_stream': 'both', +\ 'executable': {b -> ale#Var(b, 'go_revive_executable')}, +\ 'command': function('ale_linters#go#revive#GetCommand'), +\ 'callback': 'ale#handlers#unix#HandleAsWarning', +\}) diff --git a/ale_linters/handlebars/embertemplatelint.vim b/ale_linters/handlebars/embertemplatelint.vim index 74bd6a99..bd4d1d31 100644 --- a/ale_linters/handlebars/embertemplatelint.vim +++ b/ale_linters/handlebars/embertemplatelint.vim @@ -4,6 +4,28 @@ call ale#Set('handlebars_embertemplatelint_executable', 'ember-template-lint') call ale#Set('handlebars_embertemplatelint_use_global', get(g:, 'ale_use_global_executables', 0)) +function! ale_linters#handlebars#embertemplatelint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'handlebars_embertemplatelint', [ + \ 'node_modules/.bin/ember-template-lint', + \]) +endfunction + +function! ale_linters#handlebars#embertemplatelint#GetCommand(buffer, version) abort + " Reading from stdin was introduced in ember-template-lint@1.6.0 + return ale#semver#GTE(a:version, [1, 6, 0]) + \ ? '%e --json --filename %s' + \ : '%e --json %t' +endfunction + +function! ale_linters#handlebars#embertemplatelint#GetCommandWithVersionCheck(buffer) abort + return ale#semver#RunWithVersionCheck( + \ a:buffer, + \ ale_linters#handlebars#embertemplatelint#GetExecutable(a:buffer), + \ '%e --version', + \ function('ale_linters#handlebars#embertemplatelint#GetCommand'), + \) +endfunction + function! ale_linters#handlebars#embertemplatelint#Handle(buffer, lines) abort let l:output = [] let l:json = ale#util#FuzzyJSONDecode(a:lines, {}) @@ -30,10 +52,9 @@ function! ale_linters#handlebars#embertemplatelint#Handle(buffer, lines) abort endfunction call ale#linter#Define('handlebars', { -\ 'name': 'ember-template-lint', -\ 'executable': {b -> ale#node#FindExecutable(b, 'handlebars_embertemplatelint', [ -\ 'node_modules/.bin/ember-template-lint', -\ ])}, -\ 'command': '%e --json %t', +\ 'name': 'embertemplatelint', +\ 'aliases': ['ember-template-lint'], +\ 'executable': function('ale_linters#handlebars#embertemplatelint#GetExecutable'), +\ 'command': function('ale_linters#handlebars#embertemplatelint#GetCommandWithVersionCheck'), \ 'callback': 'ale_linters#handlebars#embertemplatelint#Handle', \}) diff --git a/ale_linters/java/checkstyle.vim b/ale_linters/java/checkstyle.vim index 7901ff7e..ec7339d1 100644 --- a/ale_linters/java/checkstyle.vim +++ b/ale_linters/java/checkstyle.vim @@ -52,7 +52,7 @@ endfunction function! ale_linters#java#checkstyle#GetCommand(buffer) abort let l:options = ale#Var(a:buffer, 'java_checkstyle_options') let l:config_option = ale#Var(a:buffer, 'java_checkstyle_config') - let l:config = l:options !~# '\v(^| )-c' && !empty(l:config_option) + let l:config = l:options !~# '\v(^| )-c ' && !empty(l:config_option) \ ? s:GetConfig(a:buffer, l:config_option) \ : '' diff --git a/ale_linters/java/eclipselsp.vim b/ale_linters/java/eclipselsp.vim index 2648893b..8bc09039 100644 --- a/ale_linters/java/eclipselsp.vim +++ b/ale_linters/java/eclipselsp.vim @@ -7,6 +7,7 @@ call ale#Set('java_eclipselsp_path', ale#path#Simplify($HOME . '/eclipse.jdt.ls' call ale#Set('java_eclipselsp_config_path', '') call ale#Set('java_eclipselsp_workspace_path', '') call ale#Set('java_eclipselsp_executable', 'java') +call ale#Set('java_eclipselsp_javaagent', '') function! ale_linters#java#eclipselsp#Executable(buffer) abort return ale#Var(a:buffer, 'java_eclipselsp_executable') @@ -19,25 +20,39 @@ endfunction function! ale_linters#java#eclipselsp#JarPath(buffer) abort let l:path = ale_linters#java#eclipselsp#TargetPath(a:buffer) + if has('win32') + let l:platform = 'win32' + elseif has('macunix') + let l:platform = 'macosx' + else + let l:platform = 'linux' + endif + " Search jar file within repository path when manually built using mvn - let l:repo_path = l:path . '/org.eclipse.jdt.ls.product/target/repository' - let l:files = globpath(l:repo_path, '**/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1) + let l:files = globpath(l:path, '**/'.l:platform.'/**/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1) - if len(l:files) == 1 + if len(l:files) >= 1 return l:files[0] endif " Search jar file within VSCode extensions folder. - let l:files = globpath(l:path, '**/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1) + let l:files = globpath(l:path, '**/'.l:platform.'/plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1) + + if len(l:files) >= 1 + return l:files[0] + endif + + " Search jar file within unzipped tar.gz file + let l:files = globpath(l:path, 'plugins/org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1) - if len(l:files) == 1 + if len(l:files) >= 1 return l:files[0] endif " Search jar file within system package path let l:files = globpath('/usr/share/java/jdtls/plugins', 'org.eclipse.equinox.launcher_\d\.\d\.\d\d\d\.*\.jar', 1, 1) - if len(l:files) == 1 + if len(l:files) >= 1 return l:files[0] endif @@ -100,12 +115,30 @@ function! ale_linters#java#eclipselsp#WorkspacePath(buffer) abort return ale#path#Dirname(ale#java#FindProjectRoot(a:buffer)) endfunction +function! ale_linters#java#eclipselsp#Javaagent(buffer) abort + let l:rets = [] + let l:raw = ale#Var(a:buffer, 'java_eclipselsp_javaagent') + + if empty(l:raw) + return '' + endif + + let l:jars = split(l:raw) + + for l:jar in l:jars + call add(l:rets, ale#Escape('-javaagent:' . l:jar)) + endfor + + return join(l:rets, ' ') +endfunction + function! ale_linters#java#eclipselsp#Command(buffer, version) abort let l:path = ale#Var(a:buffer, 'java_eclipselsp_path') let l:executable = ale_linters#java#eclipselsp#Executable(a:buffer) let l:cmd = [ ale#Escape(l:executable), + \ ale_linters#java#eclipselsp#Javaagent(a:buffer), \ '-Declipse.application=org.eclipse.jdt.ls.core.id1', \ '-Dosgi.bundles.defaultStartLevel=4', \ '-Declipse.product=org.eclipse.jdt.ls.core.product', @@ -147,7 +180,8 @@ function! ale_linters#java#eclipselsp#RunWithVersionCheck(buffer) abort return ale#command#Run( \ a:buffer, \ l:command, - \ function('ale_linters#java#eclipselsp#CommandWithVersion') + \ function('ale_linters#java#eclipselsp#CommandWithVersion'), + \ { 'output_stream': 'both' } \) endfunction diff --git a/ale_linters/java/javac.vim b/ale_linters/java/javac.vim index 8bb52c0b..f866eb09 100644 --- a/ale_linters/java/javac.vim +++ b/ale_linters/java/javac.vim @@ -6,6 +6,7 @@ let s:classpath_sep = has('unix') ? ':' : ';' call ale#Set('java_javac_executable', 'javac') call ale#Set('java_javac_options', '') call ale#Set('java_javac_classpath', '') +call ale#Set('java_javac_sourcepath', '') function! ale_linters#java#javac#RunWithImportPaths(buffer) abort let l:command = '' @@ -40,10 +41,15 @@ endfunction function! s:BuildClassPathOption(buffer, import_paths) abort " Filter out lines like [INFO], etc. let l:class_paths = filter(a:import_paths[:], 'v:val !~# ''[''') - call extend( - \ l:class_paths, - \ split(ale#Var(a:buffer, 'java_javac_classpath'), s:classpath_sep), - \) + let l:cls_path = ale#Var(a:buffer, 'java_javac_classpath') + + if !empty(l:cls_path) && type(l:cls_path) is v:t_string + call extend(l:class_paths, split(l:cls_path, s:classpath_sep)) + endif + + if !empty(l:cls_path) && type(l:cls_path) is v:t_list + call extend(l:class_paths, l:cls_path) + endif return !empty(l:class_paths) \ ? '-cp ' . ale#Escape(join(l:class_paths, s:classpath_sep)) @@ -79,6 +85,27 @@ function! ale_linters#java#javac#GetCommand(buffer, import_paths, meta) abort endif endif + let l:source_paths = [] + let l:source_path = ale#Var(a:buffer, 'java_javac_sourcepath') + + if !empty(l:source_path) && type(l:source_path) is v:t_string + let l:source_paths = split(l:source_path, s:classpath_sep) + endif + + if !empty(l:source_path) && type(l:source_path) is v:t_list + let l:source_paths = l:source_path + endif + + if !empty(l:source_paths) + for l:path in l:source_paths + let l:sp_path = ale#path#FindNearestDirectory(a:buffer, l:path) + + if !empty(l:sp_path) + call add(l:sp_dirs, l:sp_path) + endif + endfor + endif + if !empty(l:sp_dirs) let l:sp_option = '-sourcepath ' \ . ale#Escape(join(l:sp_dirs, s:classpath_sep)) diff --git a/ale_linters/kotlin/kotlinc.vim b/ale_linters/kotlin/kotlinc.vim index 3c6854fa..66c075be 100644 --- a/ale_linters/kotlin/kotlinc.vim +++ b/ale_linters/kotlin/kotlinc.vim @@ -174,6 +174,7 @@ endfunction call ale#linter#Define('kotlin', { \ 'name': 'kotlinc', \ 'executable': 'kotlinc', +\ 'output_stream': 'stderr', \ 'command': function('ale_linters#kotlin#kotlinc#RunWithImportPaths'), \ 'callback': 'ale_linters#kotlin#kotlinc#Handle', \ 'lint_file': 1, diff --git a/ale_linters/kotlin/ktlint.vim b/ale_linters/kotlin/ktlint.vim index f0384005..0bb64b19 100644 --- a/ale_linters/kotlin/ktlint.vim +++ b/ale_linters/kotlin/ktlint.vim @@ -6,5 +6,5 @@ call ale#linter#Define('kotlin', { \ 'executable': 'ktlint', \ 'command': function('ale#handlers#ktlint#GetCommand'), \ 'callback': 'ale#handlers#ktlint#Handle', -\ 'lint_file': 1 +\ 'output_stream': 'stderr' \}) diff --git a/ale_linters/markdown/markdownlint.vim b/ale_linters/markdown/markdownlint.vim index e935cbfe..7a293938 100644 --- a/ale_linters/markdown/markdownlint.vim +++ b/ale_linters/markdown/markdownlint.vim @@ -1,11 +1,22 @@ " Author: Ty-Lucas Kelley <tylucaskelley@gmail.com> " Description: Adds support for markdownlint +call ale#Set('markdown_markdownlint_options', '') + +function! ale_linters#markdown#markdownlint#GetCommand(buffer) abort + let l:executable = 'markdownlint' + + let l:options = ale#Var(a:buffer, 'markdown_markdownlint_options') + + return ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') . ' %s' +endfunction + call ale#linter#Define('markdown', { \ 'name': 'markdownlint', \ 'executable': 'markdownlint', \ 'lint_file': 1, \ 'output_stream': 'both', -\ 'command': 'markdownlint %s', +\ 'command': function('ale_linters#markdown#markdownlint#GetCommand'), \ 'callback': 'ale#handlers#markdownlint#Handle' \}) diff --git a/ale_linters/nasm/nasm.vim b/ale_linters/nasm/nasm.vim index 347abc1b..c4f53629 100644 --- a/ale_linters/nasm/nasm.vim +++ b/ale_linters/nasm/nasm.vim @@ -7,10 +7,9 @@ call ale#Set('nasm_nasm_options', '') function! ale_linters#nasm#nasm#GetCommand(buffer) abort " Note that NASM requires a trailing slash for the -I option. let l:separator = has('win32') ? '\' : '/' - let l:path = fnamemodify(bufname(a:buffer), ':p:h') . l:separator let l:output_null = has('win32') ? 'NUL' : '/dev/null' - return '%e -X gnu -I ' . ale#Escape(l:path) + return '%e -X gnu -I %s:h' . l:separator \ . ale#Pad(ale#Var(a:buffer, 'nasm_nasm_options')) \ . ' %s' \ . ' -o ' . l:output_null diff --git a/ale_linters/objc/ccls.vim b/ale_linters/objc/ccls.vim index 51ecf056..7aef5325 100644 --- a/ale_linters/objc/ccls.vim +++ b/ale_linters/objc/ccls.vim @@ -3,6 +3,7 @@ call ale#Set('objc_ccls_executable', 'ccls') call ale#Set('objc_ccls_init_options', {}) +call ale#Set('c_build_dir', '') call ale#linter#Define('objc', { \ 'name': 'ccls', @@ -10,5 +11,5 @@ call ale#linter#Define('objc', { \ 'executable': {b -> ale#Var(b, 'objc_ccls_executable')}, \ 'command': '%e', \ 'project_root': function('ale#handlers#ccls#GetProjectRoot'), -\ 'initialization_options': {b -> ale#Var(b, 'objc_ccls_init_options')}, +\ 'initialization_options': {b -> ale#handlers#ccls#GetInitOpts(b, 'objc_ccls_init_options')}, \}) diff --git a/ale_linters/objc/clang.vim b/ale_linters/objc/clang.vim index 7873dccd..cafb97db 100644 --- a/ale_linters/objc/clang.vim +++ b/ale_linters/objc/clang.vim @@ -10,7 +10,7 @@ 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')) + \ . '-iquote %s:h' \ . ' ' . ale#Var(a:buffer, 'objc_clang_options') . ' -' endfunction diff --git a/ale_linters/objcpp/clang.vim b/ale_linters/objcpp/clang.vim index 4dbe55b3..35a40c6f 100644 --- a/ale_linters/objcpp/clang.vim +++ b/ale_linters/objcpp/clang.vim @@ -10,7 +10,7 @@ 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')) + \ . '-iquote %s:h' \ . ' ' . ale#Var(a:buffer, 'objcpp_clang_options') . ' -' endfunction diff --git a/ale_linters/ocaml/ols.vim b/ale_linters/ocaml/ols.vim index d8208c52..ec71bdb4 100644 --- a/ale_linters/ocaml/ols.vim +++ b/ale_linters/ocaml/ols.vim @@ -9,6 +9,6 @@ call ale#linter#Define('ocaml', { \ 'lsp': 'stdio', \ 'executable': function('ale#handlers#ols#GetExecutable'), \ 'command': function('ale#handlers#ols#GetCommand'), -\ 'language_callback': 'ale#handlers#ols#GetLanguage', +\ 'language': function('ale#handlers#ols#GetLanguage'), \ 'project_root': function('ale#handlers#ols#GetProjectRoot'), \}) diff --git a/ale_linters/php/psalm.vim b/ale_linters/php/psalm.vim index ab4dbbc9..286c8a96 100644 --- a/ale_linters/php/psalm.vim +++ b/ale_linters/php/psalm.vim @@ -1,9 +1,9 @@ " Author: Matt Brown <https://github.com/muglug> " Description: plugin for Psalm, static analyzer for PHP -call ale#Set('psalm_langserver_executable', 'psalm') -call ale#Set('psalm_langserver_options', '') -call ale#Set('psalm_langserver_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('php_psalm_executable', 'psalm') +call ale#Set('php_psalm_options', '') +call ale#Set('php_psalm_use_global', get(g:, 'ale_use_global_executables', 0)) function! ale_linters#php#psalm#GetProjectRoot(buffer) abort let l:git_path = ale#path#FindNearestDirectory(a:buffer, '.git') @@ -12,13 +12,13 @@ function! ale_linters#php#psalm#GetProjectRoot(buffer) abort endfunction function! ale_linters#php#psalm#GetCommand(buffer) abort - return '%e --language-server' . ale#Pad(ale#Var(a:buffer, 'psalm_langserver_options')) + return '%e --language-server' . ale#Pad(ale#Var(a:buffer, 'php_psalm_options')) endfunction call ale#linter#Define('php', { \ 'name': 'psalm', \ 'lsp': 'stdio', -\ 'executable': {b -> ale#node#FindExecutable(b, 'psalm_langserver', [ +\ 'executable': {b -> ale#node#FindExecutable(b, 'php_psalm', [ \ 'vendor/bin/psalm', \ ])}, \ 'command': function('ale_linters#php#psalm#GetCommand'), diff --git a/ale_linters/puppet/puppet.vim b/ale_linters/puppet/puppet.vim index ae648615..59228dc8 100644 --- a/ale_linters/puppet/puppet.vim +++ b/ale_linters/puppet/puppet.vim @@ -8,13 +8,15 @@ function! ale_linters#puppet#puppet#Handle(buffer, lines) abort " 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: .*: \(.\+\) \((file:\|at\) .\+\.pp\(, line: \|:\)\(\d\+\)\(, column: \|:\)\=\(\d*\)' + " Error: Illegal attempt to assign to 'a Name'. Not an assignable reference (file: /tmp/modules/waffles/manifests/syrup.pp, line: 5, column: 11) + " Error: Could not parse for environment production: Syntax error at end of input (file: /tmp/modules/bob/manifests/init.pp) + 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[4] + 0, - \ 'col': l:match[6] + 0, + \ 'lnum': l:match[5] + 0, + \ 'col': l:match[7] + 0, \ 'text': l:match[1], \}) endfor diff --git a/ale_linters/pyrex/cython.vim b/ale_linters/pyrex/cython.vim index 84382ba1..247c3060 100644 --- a/ale_linters/pyrex/cython.vim +++ b/ale_linters/pyrex/cython.vim @@ -6,9 +6,7 @@ call ale#Set('pyrex_cython_executable', 'cython') call ale#Set('pyrex_cython_options', '--warning-extra') function! ale_linters#pyrex#cython#GetCommand(buffer) abort - let l:local_dir = ale#Escape(fnamemodify(bufname(a:buffer), ':p:h')) - - return '%e --working ' . l:local_dir . ' --include-dir ' . l:local_dir + return '%e --working %s:h --include-dir %s:h' \ . ale#Pad(ale#Var(a:buffer, 'pyrex_cython_options')) \ . ' --output-file ' . g:ale#util#nul_file . ' %t' endfunction diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim index e2e7b743..fc4ab692 100644 --- a/ale_linters/python/flake8.vim +++ b/ale_linters/python/flake8.vim @@ -4,7 +4,7 @@ call ale#Set('python_flake8_executable', 'flake8') call ale#Set('python_flake8_options', '') call ale#Set('python_flake8_use_global', get(g:, 'ale_use_global_executables', 0)) -call ale#Set('python_flake8_change_directory', 1) +call ale#Set('python_flake8_change_directory', 'project') call ale#Set('python_flake8_auto_pipenv', 0) function! s:UsingModule(buffer) abort @@ -38,10 +38,30 @@ function! ale_linters#python#flake8#RunWithVersionCheck(buffer) abort \) endfunction +function! ale_linters#python#flake8#GetCdString(buffer) abort + let l:change_directory = ale#Var(a:buffer, 'python_flake8_change_directory') + let l:cd_string = '' + + if l:change_directory is# 'project' + let l:project_root = ale#python#FindProjectRootIni(a:buffer) + + if !empty(l:project_root) + let l:cd_string = ale#path#CdString(l:project_root) + endif + endif + + if (l:change_directory is# 'project' && empty(l:cd_string)) + \|| l:change_directory is# 1 + \|| l:change_directory is# 'file' + let l:cd_string = ale#path#BufferCdString(a:buffer) + endif + + return l:cd_string +endfunction + function! ale_linters#python#flake8#GetCommand(buffer, version) abort - let l:cd_string = ale#Var(a:buffer, 'python_flake8_change_directory') - \ ? ale#path#BufferCdString(a:buffer) - \ : '' + let l:cd_string = ale_linters#python#flake8#GetCdString(a:buffer) + let l:executable = ale_linters#python#flake8#GetExecutable(a:buffer) let l:exec_args = l:executable =~? 'pipenv$' diff --git a/ale_linters/python/pydocstyle.vim b/ale_linters/python/pydocstyle.vim index 3901db4d..69ae3807 100644 --- a/ale_linters/python/pydocstyle.vim +++ b/ale_linters/python/pydocstyle.vim @@ -16,17 +16,15 @@ function! ale_linters#python#pydocstyle#GetExecutable(buffer) abort endfunction function! ale_linters#python#pydocstyle#GetCommand(buffer) abort - let l:dir = fnamemodify(bufname(a:buffer), ':p:h') let l:executable = ale_linters#python#pydocstyle#GetExecutable(a:buffer) - let l:exec_args = l:executable =~? 'pipenv$' \ ? ' run pydocstyle' \ : '' - return ale#path#CdString(l:dir) + return ale#path#BufferCdString(a:buffer) \ . ale#Escape(l:executable) . l:exec_args - \ . ' ' . ale#Var(a:buffer, 'python_pydocstyle_options') - \ . ' ' . ale#Escape(fnamemodify(bufname(a:buffer), ':p:t')) + \ . ale#Pad(ale#Var(a:buffer, 'python_pydocstyle_options')) + \ . ' %s:t' endfunction function! ale_linters#python#pydocstyle#Handle(buffer, lines) abort diff --git a/ale_linters/python/pyright.vim b/ale_linters/python/pyright.vim new file mode 100644 index 00000000..422ecd61 --- /dev/null +++ b/ale_linters/python/pyright.vim @@ -0,0 +1,43 @@ +call ale#Set('python_pyright_executable', 'pyright-langserver') +call ale#Set('python_pyright_config', {}) + +function! ale_linters#python#pyright#GetConfig(buffer) abort + let l:config = deepcopy(ale#Var(a:buffer, 'python_pyright_config')) + + if !has_key(l:config, 'python') + let l:config.python = {} + endif + + if type(l:config.python) is v:t_dict + " Automatically detect the virtualenv path and use it. + if !has_key(l:config.python, 'venvPath') + let l:venv = ale#python#FindVirtualenv(a:buffer) + + if !empty(l:venv) + let l:config.python.venvPath = l:venv + endif + endif + + " Automatically use the version of Python in virtualenv. + if type(get(l:config.python, 'venvPath')) is v:t_string + \&& !empty(l:config.python.venvPath) + \&& !has_key(l:config.python, 'pythonPath') + let l:config.python.pythonPath = ale#path#Simplify( + \ l:config.python.venvPath + \ . (has('win32') ? '/Scripts/python' : '/bin/python') + \) + endif + endif + + return l:config +endfunction + +call ale#linter#Define('python', { +\ 'name': 'pyright', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'python_pyright_executable')}, +\ 'command': '%e --stdio', +\ 'project_root': function('ale#python#FindProjectRoot'), +\ 'completion_filter': 'ale#completion#python#CompletionItemFilter', +\ 'lsp_config': function('ale_linters#python#pyright#GetConfig'), +\}) diff --git a/ale_linters/reason/ols.vim b/ale_linters/reason/ols.vim index 66137e1b..9fbd9b4f 100644 --- a/ale_linters/reason/ols.vim +++ b/ale_linters/reason/ols.vim @@ -9,6 +9,6 @@ call ale#linter#Define('reason', { \ 'lsp': 'stdio', \ 'executable': function('ale#handlers#ols#GetExecutable'), \ 'command': function('ale#handlers#ols#GetCommand'), -\ 'language_callback': 'ale#handlers#ols#GetLanguage', +\ 'language': function('ale#handlers#ols#GetLanguage'), \ 'project_root': function('ale#handlers#ols#GetProjectRoot'), \}) diff --git a/ale_linters/ruby/rubocop.vim b/ale_linters/ruby/rubocop.vim index 410ed0ea..483806a6 100644 --- a/ale_linters/ruby/rubocop.vim +++ b/ale_linters/ruby/rubocop.vim @@ -10,7 +10,7 @@ function! ale_linters#ruby#rubocop#GetCommand(buffer) abort return ale#ruby#EscapeExecutable(l:executable, 'rubocop') \ . ' --format json --force-exclusion ' \ . ale#Var(a:buffer, 'ruby_rubocop_options') - \ . ' --stdin ' . ale#Escape(expand('#' . a:buffer . ':p')) + \ . ' --stdin %s' endfunction function! ale_linters#ruby#rubocop#GetType(severity) abort diff --git a/ale_linters/ruby/standardrb.vim b/ale_linters/ruby/standardrb.vim index f751e803..6ccfd2d6 100644 --- a/ale_linters/ruby/standardrb.vim +++ b/ale_linters/ruby/standardrb.vim @@ -11,7 +11,7 @@ function! ale_linters#ruby#standardrb#GetCommand(buffer) abort return ale#ruby#EscapeExecutable(l:executable, 'standardrb') \ . ' --format json --force-exclusion ' \ . ale#Var(a:buffer, 'ruby_standardrb_options') - \ . ' --stdin ' . ale#Escape(expand('#' . a:buffer . ':p')) + \ . ' --stdin %s' endfunction " standardrb is based on RuboCop so the callback is the same diff --git a/ale_linters/rust/analyzer.vim b/ale_linters/rust/analyzer.vim new file mode 100644 index 00000000..3666ec03 --- /dev/null +++ b/ale_linters/rust/analyzer.vim @@ -0,0 +1,24 @@ +" Author: Jon Gjengset <jon@thesquareplanet.com> +" Description: The next generation language server for Rust + +call ale#Set('rust_analyzer_executable', 'rust-analyzer') +call ale#Set('rust_analyzer_config', {}) + +function! ale_linters#rust#analyzer#GetCommand(buffer) abort + return '%e' +endfunction + +function! ale_linters#rust#analyzer#GetProjectRoot(buffer) abort + let l:cargo_file = ale#path#FindNearestFile(a:buffer, 'Cargo.toml') + + return !empty(l:cargo_file) ? fnamemodify(l:cargo_file, ':h') : '' +endfunction + +call ale#linter#Define('rust', { +\ 'name': 'analyzer', +\ 'lsp': 'stdio', +\ 'lsp_config': {b -> ale#Var(b, 'rust_analyzer_config')}, +\ 'executable': {b -> ale#Var(b, 'rust_analyzer_executable')}, +\ 'command': function('ale_linters#rust#analyzer#GetCommand'), +\ 'project_root': function('ale_linters#rust#analyzer#GetProjectRoot'), +\}) diff --git a/ale_linters/rust/cargo.vim b/ale_linters/rust/cargo.vim index 99178585..3407abed 100644 --- a/ale_linters/rust/cargo.vim +++ b/ale_linters/rust/cargo.vim @@ -11,6 +11,7 @@ call ale#Set('rust_cargo_default_feature_behavior', 'default') call ale#Set('rust_cargo_include_features', '') call ale#Set('rust_cargo_use_clippy', 0) call ale#Set('rust_cargo_clippy_options', '') +call ale#Set('rust_cargo_target_dir', '') function! ale_linters#rust#cargo#GetCargoExecutable(bufnr) abort if ale#path#FindNearestFile(a:bufnr, 'Cargo.toml') isnot# '' @@ -31,6 +32,9 @@ function! ale_linters#rust#cargo#GetCommand(buffer, version) abort \ && ale#semver#GTE(a:version, [0, 22, 0]) let l:use_tests = ale#Var(a:buffer, 'rust_cargo_check_tests') \ && ale#semver#GTE(a:version, [0, 22, 0]) + let l:target_dir = ale#Var(a:buffer, 'rust_cargo_target_dir') + let l:use_target_dir = !empty(l:target_dir) + \ && ale#semver#GTE(a:version, [0, 17, 0]) let l:include_features = ale#Var(a:buffer, 'rust_cargo_include_features') @@ -82,6 +86,7 @@ function! ale_linters#rust#cargo#GetCommand(buffer, version) abort \ . (l:use_all_targets ? ' --all-targets' : '') \ . (l:use_examples ? ' --examples' : '') \ . (l:use_tests ? ' --tests' : '') + \ . (l:use_target_dir ? (' --target-dir ' . ale#Escape(l:target_dir)) : '') \ . ' --frozen --message-format=json -q' \ . l:default_feature \ . l:include_features diff --git a/ale_linters/scala/metals.vim b/ale_linters/scala/metals.vim index f78c7119..da9e855d 100644 --- a/ale_linters/scala/metals.vim +++ b/ale_linters/scala/metals.vim @@ -32,6 +32,8 @@ function! ale_linters#scala#metals#GetProjectRoot(buffer) abort \) endif endfor + + return '' endfunction function! ale_linters#scala#metals#GetCommand(buffer) abort diff --git a/ale_linters/sh/bashate.vim b/ale_linters/sh/bashate.vim new file mode 100644 index 00000000..3cd84245 --- /dev/null +++ b/ale_linters/sh/bashate.vim @@ -0,0 +1,43 @@ +" Author: hsanson <hsanson@gmail.com> +" Description: Lints sh files using bashate +" URL: https://github.com/openstack/bashate + +call ale#Set('sh_bashate_executable', 'bashate') +call ale#Set('sh_bashate_options', '') + +function! ale_linters#sh#bashate#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'sh_bashate_executable') +endfunction + +function! ale_linters#sh#bashate#GetCommand(buffer) abort + let l:options = ale#Var(a:buffer, 'sh_bashate_options') + let l:executable = ale_linters#sh#bashate#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' ' . l:options . ' ' . '%t' +endfunction + +function! ale_linters#sh#bashate#Handle(buffer, lines) abort + " Matches patterns line the following: + " + " /path/to/script/file:694:1: E003 Indent not multiple of 4 + let l:pattern = ':\(\d\+\):\(\d\+\): \(.*\)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': str2nr(l:match[1]), + \ 'col': str2nr(l:match[2]), + \ 'text': l:match[3], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('sh', { +\ 'name': 'bashate', +\ 'output_stream': 'stdout', +\ 'executable': function('ale_linters#sh#bashate#GetExecutable'), +\ 'command': function('ale_linters#sh#bashate#GetCommand'), +\ 'callback': 'ale_linters#sh#bashate#Handle', +\}) diff --git a/ale_linters/sh/shell.vim b/ale_linters/sh/shell.vim index 171fe64e..73ab3608 100644 --- a/ale_linters/sh/shell.vim +++ b/ale_linters/sh/shell.vim @@ -1,5 +1,5 @@ " Author: w0rp <devw0rp@gmail.com> -" Description: Lints sh files using bash -n +" Description: Lints shell files by invoking the shell with -n " Backwards compatibility if exists('g:ale_linters_sh_shell_default_shell') diff --git a/ale_linters/sh/shellcheck.vim b/ale_linters/sh/shellcheck.vim index 1d8b6096..d9945126 100644 --- a/ale_linters/sh/shellcheck.vim +++ b/ale_linters/sh/shellcheck.vim @@ -1,107 +1,4 @@ " Author: w0rp <devw0rp@gmail.com> -" Description: This file adds support for using the shellcheck linter with -" shell scripts. +" Description: shellcheck linter for shell scripts. -" This global variable can be set with a string of comma-separated error -" codes to exclude from shellcheck. For example: -" -" let g:ale_sh_shellcheck_exclusions = 'SC2002,SC2004' -call ale#Set('sh_shellcheck_exclusions', get(g:, 'ale_linters_sh_shellcheck_exclusions', '')) -call ale#Set('sh_shellcheck_executable', 'shellcheck') -call ale#Set('sh_shellcheck_dialect', 'auto') -call ale#Set('sh_shellcheck_options', '') -call ale#Set('sh_shellcheck_change_directory', 1) - -function! ale_linters#sh#shellcheck#GetDialectArgument(buffer) abort - let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) - - if !empty(l:shell_type) - " Use the dash dialect for /bin/ash, etc. - if l:shell_type is# 'ash' - return 'dash' - endif - - return l:shell_type - endif - - " If there's no hashbang, try using Vim's buffer variables. - if getbufvar(a:buffer, 'is_bash', 0) - return 'bash' - elseif getbufvar(a:buffer, 'is_sh', 0) - return 'sh' - elseif getbufvar(a:buffer, 'is_kornshell', 0) - return 'ksh' - endif - - return '' -endfunction - -function! ale_linters#sh#shellcheck#GetCommand(buffer, version) abort - let l:options = ale#Var(a:buffer, 'sh_shellcheck_options') - let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions') - let l:dialect = ale#Var(a:buffer, 'sh_shellcheck_dialect') - let l:external_option = ale#semver#GTE(a:version, [0, 4, 0]) ? ' -x' : '' - let l:cd_string = ale#Var(a:buffer, 'sh_shellcheck_change_directory') - \ ? ale#path#BufferCdString(a:buffer) - \ : '' - - if l:dialect is# 'auto' - let l:dialect = ale_linters#sh#shellcheck#GetDialectArgument(a:buffer) - endif - - return l:cd_string - \ . '%e' - \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '') - \ . (!empty(l:options) ? ' ' . l:options : '') - \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '') - \ . l:external_option - \ . ' -f gcc -' -endfunction - -function! ale_linters#sh#shellcheck#Handle(buffer, lines) abort - let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+) \[([^\]]+)\]$' - let l:output = [] - - for l:match in ale#util#GetMatches(a:lines, l:pattern) - if l:match[4] is# 'error' - let l:type = 'E' - elseif l:match[4] is# 'note' - let l:type = 'I' - else - let l:type = 'W' - endif - - let l:item = { - \ 'lnum': str2nr(l:match[2]), - \ 'type': l:type, - \ 'text': l:match[5], - \ 'code': l:match[6], - \} - - if !empty(l:match[3]) - let l:item.col = str2nr(l:match[3]) - endif - - " If the filename is something like <stdin>, <nofile> or -, then - " this is an error for the file we checked. - if l:match[1] isnot# '-' && l:match[1][0] isnot# '<' - let l:item['filename'] = l:match[1] - endif - - call add(l:output, l:item) - endfor - - return l:output -endfunction - -call ale#linter#Define('sh', { -\ 'name': 'shellcheck', -\ 'executable': {buffer -> ale#Var(buffer, 'sh_shellcheck_executable')}, -\ 'command': {buffer -> ale#semver#RunWithVersionCheck( -\ buffer, -\ ale#Var(buffer, 'sh_shellcheck_executable'), -\ '%e --version', -\ function('ale_linters#sh#shellcheck#GetCommand'), -\ )}, -\ 'callback': 'ale_linters#sh#shellcheck#Handle', -\}) +call ale#handlers#shellcheck#DefineLinter('sh') diff --git a/ale_linters/sql/sqllint.vim b/ale_linters/sql/sqllint.vim new file mode 100644 index 00000000..78396fe9 --- /dev/null +++ b/ale_linters/sql/sqllint.vim @@ -0,0 +1,33 @@ +" ale_linters/sql/sqllint.vim +" Author: Joe Reynolds <joereynolds952@gmail.co> +" Description: sql-lint for SQL files. +" sql-lint can be found at +" https://www.npmjs.com/package/sql-lint +" https://github.com/joereynolds/sql-lint + +function! ale_linters#sql#sqllint#Handle(buffer, lines) abort + " Matches patterns like the following: + " + " stdin:1 [ER_NO_DB_ERROR] No database selected + let l:pattern = '\v^[^:]+:(\d+) (.*)' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:match[3][0], + \ 'text': l:match[0], + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('sql', { +\ 'name': 'sqllint', +\ 'aliases': ['sql-lint'], +\ 'executable': 'sql-lint', +\ 'command': 'sql-lint', +\ 'callback': 'ale_linters#sql#sqllint#Handle', +\}) diff --git a/ale_linters/swift/swiftformat.vim b/ale_linters/swift/swiftformat.vim new file mode 100644 index 00000000..2504511a --- /dev/null +++ b/ale_linters/swift/swiftformat.vim @@ -0,0 +1,62 @@ +" Author: Klaas Pieter Annema <https://github.com/klaaspieter> +" Description: Support for swift-format https://github.com/apple/swift-format + +let s:default_executable = 'swift-format' +call ale#Set('swift_swiftformat_executable', s:default_executable) + +function! ale_linters#swift#swiftformat#UseSwift(buffer) abort + let l:swift_config = ale#path#FindNearestFile(a:buffer, 'Package.swift') + let l:executable = ale#Var(a:buffer, 'swift_swiftformat_executable') + + return !empty(l:swift_config) && l:executable is# s:default_executable +endfunction + +function! ale_linters#swift#swiftformat#GetExecutable(buffer) abort + if ale_linters#swift#swiftformat#UseSwift(a:buffer) + return 'swift' + endif + + return ale#Var(a:buffer, 'swift_swiftformat_executable') +endfunction + +function! ale_linters#swift#swiftformat#GetCommand(buffer) abort + let l:executable = ale_linters#swift#swiftformat#GetExecutable(a:buffer) + let l:args = '--mode lint %t' + + if ale_linters#swift#swiftformat#UseSwift(a:buffer) + let l:args = 'run swift-format' . ' ' . l:args + endif + + return ale#Escape(l:executable) . ' ' . l:args +endfunction + +function! ale_linters#swift#swiftformat#Handle(buffer, lines) abort + " Matches lines of the following pattern: + " + " Sources/main.swift:4:21: warning: [DoNotUseSemicolons]: remove ';' and move the next statement to the new line + " Sources/main.swift:3:12: warning: [Spacing]: remove 1 space + let l:pattern = '\v^.*:(\d+):(\d+): (\S+) \[(\S+)\]: (.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'type': l:match[3] is# 'error' ? 'E' : 'W', + \ 'code': l:match[4], + \ 'text': l:match[5], + \}) + endfor + + return l:output +endfunction + + +call ale#linter#Define('swift', { +\ 'name': 'swift-format', +\ 'executable': function('ale_linters#swift#swiftformat#GetExecutable'), +\ 'command': function('ale_linters#swift#swiftformat#GetCommand'), +\ 'output_stream': 'stderr', +\ 'language': 'swift', +\ 'callback': 'ale_linters#swift#swiftformat#Handle' +\}) diff --git a/ale_linters/terraform/terraform_lsp.vim b/ale_linters/terraform/terraform_lsp.vim new file mode 100644 index 00000000..e2408c15 --- /dev/null +++ b/ale_linters/terraform/terraform_lsp.vim @@ -0,0 +1,25 @@ +" Author: OJFord <dev@ojford.com> +" Description: terraform-lsp integration for ALE (cf. https://github.com/juliosueiras/terraform-lsp) + +call ale#Set('terraform_langserver_executable', 'terraform-lsp') +call ale#Set('terraform_langserver_options', '') + +function! ale_linters#terraform#terraform_lsp#GetCommand(buffer) abort + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'terraform_langserver_options')) +endfunction + +function! ale_linters#terraform#terraform_lsp#GetProjectRoot(buffer) abort + let l:tf_dir = ale#path#FindNearestDirectory(a:buffer, '.terraform') + + return !empty(l:tf_dir) ? fnamemodify(l:tf_dir, ':h:h') : '' +endfunction + +call ale#linter#Define('terraform', { +\ 'name': 'terraform_lsp', +\ 'lsp': 'stdio', +\ 'executable': {b -> ale#Var(b, 'terraform_langserver_executable')}, +\ 'command': function('ale_linters#terraform#terraform_lsp#GetCommand'), +\ 'project_root': function('ale_linters#terraform#terraform_lsp#GetProjectRoot'), +\ 'language': 'terraform', +\}) diff --git a/ale_linters/verilog/hdl_checker.vim b/ale_linters/verilog/hdl_checker.vim new file mode 100644 index 00000000..b05d8565 --- /dev/null +++ b/ale_linters/verilog/hdl_checker.vim @@ -0,0 +1,5 @@ +" Author: suoto <andre820@gmail.com> +" Description: Adds support for HDL Code Checker, which wraps vcom/vlog, ghdl +" or xvhdl. More info on https://github.com/suoto/hdl_checker + +call ale#handlers#hdl_checker#DefineLinter('verilog') diff --git a/ale_linters/verilog/verilator.vim b/ale_linters/verilog/verilator.vim index 64bb6e41..029dd4c9 100644 --- a/ale_linters/verilog/verilator.vim +++ b/ale_linters/verilog/verilator.vim @@ -28,21 +28,30 @@ function! ale_linters#verilog#verilator#Handle(buffer, lines) abort " %Warning-UNDRIVEN: test.v:3: Signal is not driven: clk " %Warning-UNUSED: test.v:4: Signal is not used: dout " %Warning-BLKSEQ: test.v:10: Blocking assignments (=) in sequential (flop or latch) block; suggest delayed assignments (<=). - let l:pattern = '^%\(Warning\|Error\)[^:]*:\([^:]\+\):\(\d\+\): \(.\+\)$' + " Since version 4.032 (04/2020) verilator linter messages also contain the column number, + " and look like: + " %Error: /tmp/test.sv:3:1: syntax error, unexpected endmodule, expecting ';' + " + " to stay compatible with old versions of the tool, the column number is + " optional in the researched pattern + let l:pattern = '^%\(Warning\|Error\)[^:]*:\([^:]\+\):\(\d\+\):\(\d\+\)\?:\? \(.\+\)$' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) - let l:line = l:match[3] + 0 - let l:type = l:match[1] is# 'Error' ? 'E' : 'W' - let l:text = l:match[4] + let l:item = { + \ 'lnum': str2nr(l:match[3]), + \ 'text': l:match[5], + \ 'type': l:match[1] is# 'Error' ? 'E' : 'W', + \} + + if !empty(l:match[4]) + let l:item.col = str2nr(l:match[4]) + endif + let l:file = l:match[2] if l:file =~# '_verilator_linted.v' - call add(l:output, { - \ 'lnum': l:line, - \ 'text': l:text, - \ 'type': l:type, - \}) + call add(l:output, l:item) endif endfor diff --git a/ale_linters/verilog/vlog.vim b/ale_linters/verilog/vlog.vim index 951e2037..45e1977c 100644 --- a/ale_linters/verilog/vlog.vim +++ b/ale_linters/verilog/vlog.vim @@ -13,14 +13,15 @@ function! ale_linters#verilog#vlog#Handle(buffer, lines) abort "Matches patterns like the following: "** Warning: add.v(7): (vlog-2623) Undefined variable: C. "** Error: file.v(1): (vlog-13294) Identifier must be declared with a port mode: C. - let l:pattern = '^**\s\(\w*\):[a-zA-Z0-9\-\.\_\/ ]\+(\(\d\+\)):\s\+\(.*\)' + let l:pattern = '^**\s\(\w*\): \([a-zA-Z0-9\-\.\_\/ ]\+\)(\(\d\+\)):\s\+\(.*\)' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'lnum': l:match[2] + 0, + \ 'lnum': l:match[3] + 0, \ 'type': l:match[1] is? 'Error' ? 'E' : 'W', - \ 'text': l:match[3], + \ 'text': l:match[4], + \ 'filename': l:match[2], \}) endfor @@ -28,13 +29,14 @@ function! ale_linters#verilog#vlog#Handle(buffer, lines) abort "** Warning: (vlog-2623) add.v(7): Undefined variable: C. "** Error: (vlog-13294) file.v(1): Identifier must be declared with a port mode: C. " let l:pattern = '^**\s\(\w*\):[a-zA-Z0-9\-\.\_\/ ]\+(\(\d\+\)):\s\+\(.*\)' - let l:pattern = '^**\s\(\w*\):\s\([^)]*)\)[a-zA-Z0-9\-\.\_\/ ]\+(\(\d\+\)):\s\+\(.*\)' + let l:pattern = '^**\s\(\w*\):\s\([^)]*)\) \([a-zA-Z0-9\-\.\_\/ ]\+\)(\(\d\+\)):\s\+\(.*\)' for l:match in ale#util#GetMatches(a:lines, l:pattern) call add(l:output, { - \ 'lnum': l:match[3] + 0, + \ 'lnum': l:match[4] + 0, \ 'type': l:match[1] is? 'Error' ? 'E' : 'W', - \ 'text': l:match[2] . ' ' . l:match[4], + \ 'text': l:match[2] . ' ' . l:match[5], + \ 'filename': l:match[3], \}) endfor diff --git a/ale_linters/vhdl/hdl_checker.vim b/ale_linters/vhdl/hdl_checker.vim new file mode 100644 index 00000000..c9d306b3 --- /dev/null +++ b/ale_linters/vhdl/hdl_checker.vim @@ -0,0 +1,5 @@ +" Author: suoto <andre820@gmail.com> +" Description: Adds support for HDL Code Checker, which wraps vcom/vlog, ghdl +" or xvhdl. More info on https://github.com/suoto/hdl_checker + +call ale#handlers#hdl_checker#DefineLinter('vhdl') diff --git a/ale_linters/vim/vimls.vim b/ale_linters/vim/vimls.vim new file mode 100644 index 00000000..26014d66 --- /dev/null +++ b/ale_linters/vim/vimls.vim @@ -0,0 +1,61 @@ +" Author: Jeffrey Lau - https://github.com/zoonfafer +" Description: Vim Language Server integration for ALE + +call ale#Set('vim_vimls_executable', 'vim-language-server') +call ale#Set('vim_vimls_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('vim_vimls_config', {}) + +function! ale_linters#vim#vimls#GetProjectRoot(buffer) abort + let l:trigger_file_candidates = [ + \ '.vimrc', + \ 'init.vim', + \] + + for l:candidate in l:trigger_file_candidates + let l:trigger_file = fnamemodify(bufname(a:buffer), ':t') + + if l:trigger_file is# l:candidate + return fnamemodify( + \ bufname(a:buffer), + \ ':h', + \) + endif + endfor + + let l:trigger_dir_candidates = [ + \ 'autoload', + \ 'plugin', + \ '.git', + \] + + let l:path_upwards = ale#path#Upwards(fnamemodify(bufname(a:buffer), ':p:h')) + + for l:path in l:path_upwards + for l:candidate in l:trigger_dir_candidates + let l:trigger_dir = ale#path#Simplify( + \ l:path . '/' . l:candidate, + \) + + if isdirectory(l:trigger_dir) + return fnamemodify( + \ l:trigger_dir, + \ ':p:h:h', + \) + endif + endfor + endfor + + return '' +endfunction + +call ale#linter#Define('vim', { +\ 'name': 'vimls', +\ 'lsp': 'stdio', +\ 'lsp_config': {b -> ale#Var(b, 'vim_vimls_config')}, +\ 'executable': {b -> ale#node#FindExecutable(b, 'vim_vimls', [ +\ 'node_modules/.bin/vim-language-server', +\ ])}, +\ 'command': '%e --stdio', +\ 'language': 'vim', +\ 'project_root': function('ale_linters#vim#vimls#GetProjectRoot'), +\}) diff --git a/ale_linters/vim/vint.vim b/ale_linters/vim/vint.vim index 6b996fa4..f7054ffb 100644 --- a/ale_linters/vim/vint.vim +++ b/ale_linters/vim/vint.vim @@ -5,7 +5,7 @@ call ale#Set('vim_vint_show_style_issues', 1) call ale#Set('vim_vint_executable', 'vint') let s:enable_neovim = has('nvim') ? ' --enable-neovim' : '' -let s:format = '-f "{file_path}:{line_number}:{column_number}: {severity}: {description} (see {reference})"' +let s:format = '-f "{file_path}:{line_number}:{column_number}: {severity}: {policy_name} - {description} (see {reference})"' function! ale_linters#vim#vint#GetCommand(buffer, version) abort let l:can_use_no_color_flag = empty(a:version) diff --git a/ale_linters/zig/zls.vim b/ale_linters/zig/zls.vim new file mode 100644 index 00000000..1390f6b1 --- /dev/null +++ b/ale_linters/zig/zls.vim @@ -0,0 +1,20 @@ +" Author: CherryMan <skipper308@hotmail.ca> +" Description: A language server for Zig + +call ale#Set('zig_zls_executable', 'zls') +call ale#Set('zig_zls_config', {}) + +function! ale_linters#zig#zls#GetProjectRoot(buffer) abort + let l:build_rs = ale#path#FindNearestFile(a:buffer, 'build.zig') + + return !empty(l:build_rs) ? fnamemodify(l:build_rs, ':h') : '' +endfunction + +call ale#linter#Define('zig', { +\ 'name': 'zls', +\ 'lsp': 'stdio', +\ 'lsp_config': {b -> ale#Var(b, 'zig_zls_config')}, +\ 'executable': {b -> ale#Var(b, 'zig_zls_executable')}, +\ 'command': '%e', +\ 'project_root': function('ale_linters#zig#zls#GetProjectRoot'), +\}) diff --git a/autoload/ale.vim b/autoload/ale.vim index ee1a0d54..84003993 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -100,13 +100,7 @@ function! s:Lint(buffer, should_lint_file, timer_id) abort " Use the filetype from the buffer let l:filetype = getbufvar(a:buffer, '&filetype') let l:linters = ale#linter#Get(l:filetype) - - " Apply ignore lists for linters only if needed. - let l:ignore_config = ale#Var(a:buffer, 'linters_ignore') - let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') - let l:linters = !empty(l:ignore_config) || l:disable_lsp - \ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config, l:disable_lsp) - \ : l:linters + let l:linters = ale#linter#RemoveIgnored(a:buffer, l:filetype, l:linters) " Tell other sources that they can start checking the buffer now. let g:ale_want_results_buffer = a:buffer @@ -163,7 +157,7 @@ function! ale#Queue(delay, ...) abort endif endfunction -let s:current_ale_version = [2, 6, 0] +let s:current_ale_version = [2, 7, 0] " A function used to check for ALE features in files outside of the project. function! ale#Has(feature) abort @@ -258,11 +252,33 @@ function! ale#GetLocItemMessage(item, format_string) abort " Replace special markers with certain information. " \=l:variable is used to avoid escaping issues. + let l:msg = substitute(l:msg, '\v\%([^\%]*)code([^\%]*)\%', l:code_repl, 'g') let l:msg = substitute(l:msg, '\V%severity%', '\=l:severity', 'g') let l:msg = substitute(l:msg, '\V%linter%', '\=l:linter_name', 'g') - let l:msg = substitute(l:msg, '\v\%([^\%]*)code([^\%]*)\%', l:code_repl, 'g') " Replace %s with the text. let l:msg = substitute(l:msg, '\V%s', '\=a:item.text', 'g') + " Windows may insert carriage return line endings (^M), strip these characters. + let l:msg = substitute(l:msg, '\r', '', 'g') return l:msg endfunction + +" Given a buffer and a linter or fixer name, return an Array of two-item +" Arrays describing how to map filenames to and from the local to foreign file +" systems. +function! ale#GetFilenameMappings(buffer, name) abort + let l:linter_mappings = ale#Var(a:buffer, 'filename_mappings') + + if type(l:linter_mappings) is v:t_list + return l:linter_mappings + endif + + let l:name = a:name + + if !has_key(l:linter_mappings, l:name) + " Use * as a default setting for all tools. + let l:name = '*' + endif + + return get(l:linter_mappings, l:name, []) +endfunction diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim index 291edcee..934fcaa8 100644 --- a/autoload/ale/assert.vim +++ b/autoload/ale/assert.vim @@ -130,7 +130,7 @@ endfunction function! ale#assert#LSPLanguage(expected_language) abort let l:buffer = bufnr('') let l:linter = s:GetLinter() - let l:language = ale#util#GetFunction(l:linter.language_callback)(l:buffer) + let l:language = ale#linter#GetLanguage(l:buffer, l:linter) AssertEqual a:expected_language, l:language endfunction diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim index 9b428700..cff53125 100644 --- a/autoload/ale/c.vim +++ b/autoload/ale/c.vim @@ -2,20 +2,28 @@ " Description: Functions for integrating with C-family linters. call ale#Set('c_parse_makefile', 0) -call ale#Set('c_parse_compile_commands', 0) +call ale#Set('c_always_make', has('unix') && !has('macunix')) +call ale#Set('c_parse_compile_commands', 1) + let s:sep = has('win32') ? '\' : '/' " Set just so tests can override it. let g:__ale_c_project_filenames = ['.git/HEAD', 'configure', 'Makefile', 'CMakeLists.txt'] -function! ale#c#GetBuildDirectory(buffer) abort - " Don't include build directory for header files, as compile_commands.json - " files don't consider headers to be translation units, and provide no - " commands for compiling header files. - if expand('#' . a:buffer) =~# '\v\.(h|hpp)$' - return '' - endif +let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [ +\ 'build', +\ 'bin', +\]) + +function! s:CanParseMakefile(buffer) abort + " Something somewhere seems to delete this setting in tests, so ensure we + " always have a default value. + call ale#Set('c_parse_makefile', 0) + return ale#Var(a:buffer, 'c_parse_makefile') +endfunction + +function! ale#c#GetBuildDirectory(buffer) abort let l:build_dir = ale#Var(a:buffer, 'c_build_dir') " c_build_dir has the priority if defined @@ -68,14 +76,73 @@ function! ale#c#ShellSplit(line) abort return l:args endfunction -function! ale#c#ParseCFlags(path_prefix, cflag_line) abort - let l:cflags_list = [] +" Takes the path prefix and a list of cflags and expands @file arguments to +" the contents of the file. +" +" @file arguments are command line arguments recognised by gcc and clang. For +" instance, if @./path/to/file was given to gcc, it would load .path/to/file +" and use the contents of that file as arguments. +function! ale#c#ExpandAtArgs(path_prefix, raw_split_lines) abort + let l:out_lines = [] + + for l:option in a:raw_split_lines + if stridx(l:option, '@') == 0 + " This is an argument specifying a location of a file containing other arguments + let l:path = join(split(l:option, '\zs')[1:], '') + + " Make path absolute + if !ale#path#IsAbsolute(l:path) + let l:rel_path = substitute(l:path, '"', '', 'g') + let l:rel_path = substitute(l:rel_path, '''', '', 'g') + let l:path = ale#path#GetAbsPath(a:path_prefix, l:rel_path) + endif + + " Read the file and add all the arguments + try + let l:additional_args = readfile(l:path) + catch + continue " All we can really do is skip this argument + endtry + + let l:file_lines = [] + + for l:line in l:additional_args + let l:file_lines += ale#c#ShellSplit(l:line) + endfor + + " @file arguments can include other @file arguments, so we must + " recurse. + let l:out_lines += ale#c#ExpandAtArgs(a:path_prefix, l:file_lines) + else + " This is not an @file argument, so don't touch it. + let l:out_lines += [l:option] + endif + endfor - let l:split_lines = ale#c#ShellSplit(a:cflag_line) + return l:out_lines +endfunction + +" Quote C/C++ a compiler argument, if needed. +" +" Quoting arguments might cause issues with some systems/compilers, so we only +" quote them if we need to. +function! ale#c#QuoteArg(arg) abort + if a:arg !~# '\v[#$&*()\\|[\]{};''"<>/?! ^%]' + return a:arg + endif + + return ale#Escape(a:arg) +endfunction + +function! ale#c#ParseCFlags(path_prefix, should_quote, raw_arguments) abort + " Expand @file arguments now before parsing + let l:arguments = ale#c#ExpandAtArgs(a:path_prefix, a:raw_arguments) + " A list of [already_quoted, argument] + let l:items = [] let l:option_index = 0 - while l:option_index < len(l:split_lines) - let l:option = l:split_lines[l:option_index] + while l:option_index < len(l:arguments) + let l:option = l:arguments[l:option_index] let l:option_index = l:option_index + 1 " Include options, that may need relative path fix @@ -83,56 +150,67 @@ function! ale#c#ParseCFlags(path_prefix, cflag_line) abort \ || stridx(l:option, '-iquote') == 0 \ || stridx(l:option, '-isystem') == 0 \ || stridx(l:option, '-idirafter') == 0 + \ || stridx(l:option, '-iframework') == 0 + \ || stridx(l:option, '-include') == 0 if stridx(l:option, '-I') == 0 && l:option isnot# '-I' let l:arg = join(split(l:option, '\zs')[2:], '') let l:option = '-I' else - let l:arg = l:split_lines[l:option_index] + let l:arg = l:arguments[l:option_index] let l:option_index = l:option_index + 1 endif " Fix relative paths if needed - if stridx(l:arg, s:sep) != 0 && stridx(l:arg, '/') != 0 + if !ale#path#IsAbsolute(l:arg) let l:rel_path = substitute(l:arg, '"', '', 'g') let l:rel_path = substitute(l:rel_path, '''', '', 'g') - let l:arg = ale#Escape(a:path_prefix . s:sep . l:rel_path) + let l:arg = ale#path#GetAbsPath(a:path_prefix, l:rel_path) endif - call add(l:cflags_list, l:option) - call add(l:cflags_list, l:arg) + call add(l:items, [1, l:option]) + call add(l:items, [1, ale#Escape(l:arg)]) " Options with arg that can be grouped with the option or separate elseif stridx(l:option, '-D') == 0 || stridx(l:option, '-B') == 0 - call add(l:cflags_list, l:option) - if l:option is# '-D' || l:option is# '-B' - call add(l:cflags_list, l:split_lines[l:option_index]) + call add(l:items, [1, l:option]) + call add(l:items, [0, l:arguments[l:option_index]]) let l:option_index = l:option_index + 1 + else + call add(l:items, [0, l:option]) endif " Options that have an argument (always separate) elseif l:option is# '-iprefix' || stridx(l:option, '-iwithprefix') == 0 \ || l:option is# '-isysroot' || l:option is# '-imultilib' - call add(l:cflags_list, l:option) - call add(l:cflags_list, l:split_lines[l:option_index]) + call add(l:items, [0, l:option]) + call add(l:items, [0, l:arguments[l:option_index]]) let l:option_index = l:option_index + 1 " Options without argument elseif (stridx(l:option, '-W') == 0 && stridx(l:option, '-Wa,') != 0 && stridx(l:option, '-Wl,') != 0 && stridx(l:option, '-Wp,') != 0) \ || l:option is# '-w' || stridx(l:option, '-pedantic') == 0 \ || l:option is# '-ansi' || stridx(l:option, '-std=') == 0 - \ || (stridx(l:option, '-f') == 0 && stridx(l:option, '-fdump') != 0 && stridx(l:option, '-fdiagnostics') != 0 && stridx(l:option, '-fno-show-column') != 0) + \ || stridx(l:option, '-f') == 0 && l:option !~# '\v^-f(dump|diagnostics|no-show-column|stack-usage)' \ || stridx(l:option, '-O') == 0 \ || l:option is# '-C' || l:option is# '-CC' || l:option is# '-trigraphs' \ || stridx(l:option, '-nostdinc') == 0 || stridx(l:option, '-iplugindir=') == 0 \ || stridx(l:option, '--sysroot=') == 0 || l:option is# '--no-sysroot-suffix' \ || stridx(l:option, '-m') == 0 - call add(l:cflags_list, l:option) + call add(l:items, [0, l:option]) endif endwhile - return join(l:cflags_list, ' ') + if a:should_quote + " Quote C arguments that haven't already been quoted above. + " If and only if we've been asked to quote them. + call map(l:items, 'v:val[0] ? v:val[1] : ale#c#QuoteArg(v:val[1])') + else + call map(l:items, 'v:val[1]') + endif + + return join(l:items, ' ') endfunction function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort - if !g:ale_c_parse_makefile + if !s:CanParseMakefile(a:buffer) return v:null endif @@ -150,7 +228,7 @@ function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile') let l:makefile_dir = fnamemodify(l:makefile_path, ':p:h') - return ale#c#ParseCFlags(l:makefile_dir, l:cflag_line) + return ale#c#ParseCFlags(l:makefile_dir, 0, ale#c#ShellSplit(l:cflag_line)) endfunction " Given a buffer number, find the project directory containing @@ -218,6 +296,10 @@ if !exists('s:compile_commands_cache') let s:compile_commands_cache = {} endif +function! ale#c#ResetCompileCommandsCache() abort + let s:compile_commands_cache = {} +endfunction + function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort let l:empty = [{}, {}] @@ -248,9 +330,20 @@ function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort let l:dir_lookup = {} for l:entry in (type(l:raw_data) is v:t_list ? l:raw_data : []) + let l:filename = ale#path#GetAbsPath(l:entry.directory, l:entry.file) + + " Store a key for lookups by the absolute path to the filename. + let l:file_lookup[l:filename] = get(l:file_lookup, l:filename, []) + [l:entry] + + " Store a key for fuzzy lookups by the absolute path to the directory. + let l:dirname = fnamemodify(l:filename, ':h') + let l:dir_lookup[l:dirname] = get(l:dir_lookup, l:dirname, []) + [l:entry] + + " Store a key for fuzzy lookups by just the basename of the file. let l:basename = tolower(fnamemodify(l:entry.file, ':t')) let l:file_lookup[l:basename] = get(l:file_lookup, l:basename, []) + [l:entry] + " Store a key for fuzzy lookups by just the basename of the directory. let l:dirbasename = tolower(fnamemodify(l:entry.directory, ':p:h:t')) let l:dir_lookup[l:dirbasename] = get(l:dir_lookup, l:dirbasename, []) + [l:entry] endfor @@ -265,28 +358,80 @@ function! s:GetLookupFromCompileCommandsFile(compile_commands_file) abort return l:empty endfunction -function! ale#c#GetCompileCommand(json_item) abort - if has_key(a:json_item, 'command') - return a:json_item.command - elseif has_key(a:json_item, 'arguments') - return join(a:json_item.arguments, ' ') +" Get [should_quote, arguments] from either 'command' or 'arguments' +" 'arguments' should be quoted later, the split 'command' strings should not. +function! s:GetArguments(json_item) abort + if has_key(a:json_item, 'arguments') + return [1, a:json_item.arguments] + elseif has_key(a:json_item, 'command') + return [0, ale#c#ShellSplit(a:json_item.command)] endif - return '' + return [0, []] endfunction function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort + let l:buffer_filename = ale#path#Simplify(expand('#' . a:buffer . ':p')) + let l:basename = tolower(fnamemodify(l:buffer_filename, ':t')) + " Look for any file in the same directory if we can't find an exact match. + let l:dir = fnamemodify(l:buffer_filename, ':h') + " Search for an exact file match first. - let l:basename = tolower(expand('#' . a:buffer . ':t')) - let l:file_list = get(a:file_lookup, l:basename, []) + let l:file_list = get(a:file_lookup, l:buffer_filename, []) + + " We may have to look for /foo/bar instead of C:\foo\bar + if empty(l:file_list) && has('win32') + let l:file_list = get( + \ a:file_lookup, + \ ale#path#RemoveDriveLetter(l:buffer_filename), + \ [] + \) + endif + + " Try the absolute path to the directory second. + let l:dir_list = get(a:dir_lookup, l:dir, []) + + if empty(l:dir_list) && has('win32') + let l:dir_list = get( + \ a:dir_lookup, + \ ale#path#RemoveDriveLetter(l:dir), + \ [] + \) + endif + + if empty(l:file_list) && empty(l:dir_list) + " If we can't find matches with the path to the file, try a + " case-insensitive match for any similarly-named file. + let l:file_list = get(a:file_lookup, l:basename, []) + + " If we can't find matches with the path to the directory, try a + " case-insensitive match for anything in similarly-named directory. + let l:dir_list = get(a:dir_lookup, tolower(fnamemodify(l:dir, ':t')), []) + endif + " A source file matching the header filename. let l:source_file = '' if empty(l:file_list) && l:basename =~? '\.h$\|\.hpp$' for l:suffix in ['.c', '.cpp'] - let l:key = fnamemodify(l:basename, ':r') . l:suffix + " Try to find a source file by an absolute path first. + let l:key = fnamemodify(l:buffer_filename, ':r') . l:suffix let l:file_list = get(a:file_lookup, l:key, []) + if empty(l:file_list) && has('win32') + let l:file_list = get( + \ a:file_lookup, + \ ale#path#RemoveDriveLetter(l:key), + \ [] + \) + endif + + if empty(l:file_list) + " Look fuzzy matches on the basename second. + let l:key = fnamemodify(l:basename, ':r') . l:suffix + let l:file_list = get(a:file_lookup, l:key, []) + endif + if !empty(l:file_list) let l:source_file = l:key break @@ -295,28 +440,31 @@ function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort endif for l:item in l:file_list + let l:filename = ale#path#GetAbsPath(l:item.directory, l:item.file) + " Load the flags for this file, or for a source file matching the " header file. if ( - \ bufnr(l:item.file) is a:buffer + \ bufnr(l:filename) is a:buffer \ || ( \ !empty(l:source_file) - \ && l:item.file[-len(l:source_file):] is? l:source_file + \ && l:filename[-len(l:source_file):] is? l:source_file \ ) \) - return ale#c#ParseCFlags(l:item.directory, ale#c#GetCompileCommand(l:item)) + let [l:should_quote, l:args] = s:GetArguments(l:item) + + return ale#c#ParseCFlags(l:item.directory, l:should_quote, l:args) endif endfor - " Look for any file in the same directory if we can't find an exact match. - let l:dir = ale#path#Simplify(expand('#' . a:buffer . ':p:h')) + for l:item in l:dir_list + let l:filename = ale#path#GetAbsPath(l:item.directory, l:item.file) - let l:dirbasename = tolower(expand('#' . a:buffer . ':p:h:t')) - let l:dir_list = get(a:dir_lookup, l:dirbasename, []) + if ale#path#RemoveDriveLetter(fnamemodify(l:filename, ':h')) + \ is? ale#path#RemoveDriveLetter(l:dir) + let [l:should_quote, l:args] = s:GetArguments(l:item) - for l:item in l:dir_list - if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir - return ale#c#ParseCFlags(l:item.directory, ale#c#GetCompileCommand(l:item)) + return ale#c#ParseCFlags(l:item.directory, l:should_quote, l:args) endif endfor @@ -334,10 +482,6 @@ endfunction function! ale#c#GetCFlags(buffer, output) abort let l:cflags = v:null - if ale#Var(a:buffer, 'c_parse_makefile') && !empty(a:output) - let l:cflags = ale#c#ParseCFlagsFromMakeOutput(a:buffer, a:output) - endif - if ale#Var(a:buffer, 'c_parse_compile_commands') let [l:root, l:json_file] = ale#c#FindCompileCommands(a:buffer) @@ -346,6 +490,10 @@ function! ale#c#GetCFlags(buffer, output) abort endif endif + if s:CanParseMakefile(a:buffer) && !empty(a:output) && !empty(l:cflags) + let l:cflags = ale#c#ParseCFlagsFromMakeOutput(a:buffer, a:output) + endif + if l:cflags is v:null let l:cflags = ale#c#IncludeOptions(ale#c#FindLocalHeaderPaths(a:buffer)) endif @@ -354,11 +502,14 @@ function! ale#c#GetCFlags(buffer, output) abort endfunction function! ale#c#GetMakeCommand(buffer) abort - if ale#Var(a:buffer, 'c_parse_makefile') - let l:makefile_path = ale#path#FindNearestFile(a:buffer, 'Makefile') + if s:CanParseMakefile(a:buffer) + let l:path = ale#path#FindNearestFile(a:buffer, 'Makefile') - if !empty(l:makefile_path) - return 'cd '. fnamemodify(l:makefile_path, ':p:h') . ' && make -n' + if !empty(l:path) + let l:always_make = ale#Var(a:buffer, 'c_always_make') + + return ale#path#CdString(fnamemodify(l:path, ':h')) + \ . 'make -n' . (l:always_make ? ' --always-make' : '') endif endif @@ -427,8 +578,3 @@ function! ale#c#IncludeOptions(include_paths) abort return join(l:option_list) endfunction - -let g:ale_c_build_dir_names = get(g:, 'ale_c_build_dir_names', [ -\ 'build', -\ 'bin', -\]) diff --git a/autoload/ale/code_action.vim b/autoload/ale/code_action.vim index 0af1bb70..8c7263f3 100644 --- a/autoload/ale/code_action.vim +++ b/autoload/ale/code_action.vim @@ -1,7 +1,7 @@ " Author: Jerko Steiner <jerko.steiner@gmail.com> " Description: Code action support for LSP / tsserver -function! ale#code_action#HandleCodeAction(code_action) abort +function! ale#code_action#HandleCodeAction(code_action, should_save) abort let l:current_buffer = bufnr('') let l:changes = a:code_action.changes @@ -17,11 +17,50 @@ function! ale#code_action#HandleCodeAction(code_action) abort for l:file_code_edit in l:changes call ale#code_action#ApplyChanges( - \ l:file_code_edit.fileName, l:file_code_edit.textChanges) + \ l:file_code_edit.fileName, + \ l:file_code_edit.textChanges, + \ a:should_save, + \ ) endfor endfunction -function! ale#code_action#ApplyChanges(filename, changes) abort +function! s:ChangeCmp(left, right) abort + if a:left.start.line < a:right.start.line + return -1 + endif + + if a:left.start.line > a:right.start.line + return 1 + endif + + if a:left.start.offset < a:right.start.offset + return -1 + endif + + if a:left.start.offset > a:right.start.offset + return 1 + endif + + if a:left.end.line < a:right.end.line + return -1 + endif + + if a:left.end.line > a:right.end.line + return 1 + endif + + if a:left.end.offset < a:right.end.offset + return -1 + endif + + if a:left.end.offset > a:right.end.offset + return 1 + endif + + return 0 +endfunction + +function! ale#code_action#ApplyChanges(filename, changes, should_save) abort let l:current_buffer = bufnr('') " The buffer is used to determine the fileformat, if available. let l:buffer = bufnr(a:filename) @@ -45,7 +84,8 @@ function! ale#code_action#ApplyChanges(filename, changes) abort let l:column_offset = 0 let l:last_end_line = 0 - for l:code_edit in a:changes + " Changes have to be sorted so we apply them from top-to-bottom. + for l:code_edit in sort(copy(a:changes), function('s:ChangeCmp')) if l:code_edit.start.line isnot l:last_end_line let l:column_offset = 0 endif @@ -106,10 +146,17 @@ function! ale#code_action#ApplyChanges(filename, changes) abort call remove(l:lines, -1) endif - call ale#util#Writefile(l:buffer, l:lines, a:filename) + if a:should_save + call ale#util#Writefile(l:buffer, l:lines, a:filename) + else + call ale#util#SetBufferContents(l:buffer, l:lines) + endif if l:is_current_buffer - call ale#util#Execute(':e!') + if a:should_save + call ale#util#Execute(':e!') + endif + call setpos('.', [0, l:pos[0], l:pos[1], 0]) endif endfunction diff --git a/autoload/ale/command.vim b/autoload/ale/command.vim index 1bbc4f4c..8f497169 100644 --- a/autoload/ale/command.vim +++ b/autoload/ale/command.vim @@ -133,11 +133,36 @@ function! ale#command#EscapeCommandPart(command_part) abort return substitute(a:command_part, '%', '%%', 'g') endfunction +" Format a filename, converting it with filename mappings, if non-empty, +" and escaping it for putting into a command string. +" +" The filename can be modified. +function! s:FormatFilename(filename, mappings, modifiers) abort + let l:filename = a:filename + + if !empty(a:mappings) + let l:filename = ale#filename_mapping#Map(l:filename, a:mappings) + endif + + if !empty(a:modifiers) + let l:filename = fnamemodify(l:filename, a:modifiers) + endif + + return ale#Escape(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, executable, command, pipe_file_if_needed, input) abort +function! ale#command#FormatCommand( +\ buffer, +\ executable, +\ command, +\ pipe_file_if_needed, +\ input, +\ mappings, +\) abort let l:temporary_file = '' let l:command = a:command @@ -154,14 +179,24 @@ function! ale#command#FormatCommand(buffer, executable, command, pipe_file_if_ne " 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') + let l:command = substitute( + \ l:command, + \ '\v\%s(%(:h|:t|:r|:e)*)', + \ '\=s:FormatFilename(l:filename, a:mappings, submatch(1))', + \ 'g' + \) endif if a:input isnot v:false && 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') + let l:command = substitute( + \ l:command, + \ '\v\%t(%(:h|:t|:r|:e)*)', + \ '\=s:FormatFilename(l:temporary_file, a:mappings, submatch(1))', + \ 'g' + \) endif " Finish formatting so %% becomes %. @@ -265,6 +300,7 @@ function! ale#command#Run(buffer, command, Callback, ...) abort \ a:command, \ get(l:options, 'read_buffer', 0), \ get(l:options, 'input', v:null), + \ get(l:options, 'filename_mappings', []), \) let l:command = ale#job#PrepareCommand(a:buffer, l:command) let l:job_options = { diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index 80684a30..ecd93600 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -5,7 +5,7 @@ scriptencoding utf-8 " The omnicompletion menu is shown through a special Plug mapping which is " only valid in Insert mode. This way, feedkeys() won't send these keys if you " quit Insert mode quickly enough. -inoremap <silent> <Plug>(ale_show_completion_menu) <C-x><C-o> +inoremap <silent> <Plug>(ale_show_completion_menu) <C-x><C-o><C-p> " If we hit the key sequence in normal mode, then we won't show the menu, so " we should restore the old settings right away. nnoremap <silent> <Plug>(ale_show_completion_menu) :call ale#completion#RestoreCompletionOptions()<CR> @@ -16,7 +16,8 @@ onoremap <silent> <Plug>(ale_show_completion_menu) <Nop> let g:ale_completion_delay = get(g:, 'ale_completion_delay', 100) let g:ale_completion_excluded_words = get(g:, 'ale_completion_excluded_words', []) let g:ale_completion_max_suggestions = get(g:, 'ale_completion_max_suggestions', 50) -let g:ale_completion_tsserver_autoimport = get(g:, 'ale_completion_tsserver_autoimport', 0) +let g:ale_completion_autoimport = get(g:, 'ale_completion_autoimport', 0) +let g:ale_completion_tsserver_remove_warnings = get(g:, 'ale_completion_tsserver_remove_warnings', 0) let s:timer_id = -1 let s:last_done_pos = [] @@ -187,7 +188,13 @@ function! ale#completion#GetTriggerCharacter(filetype, prefix) abort return '' endfunction -function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort +function! ale#completion#Filter( +\ buffer, +\ filetype, +\ suggestions, +\ prefix, +\ exact_prefix_match, +\) abort let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words') if empty(a:prefix) @@ -214,10 +221,17 @@ function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort " Dictionaries is accepted here. let l:word = type(l:item) is v:t_string ? l:item : l:item.word - " Add suggestions if the suggestion starts with a - " case-insensitive match for the prefix. - if l:word[: len(a:prefix) - 1] is? a:prefix - call add(l:filtered_suggestions, l:item) + if a:exact_prefix_match + " Add suggestions if the word is an exact match. + if l:word is# a:prefix + call add(l:filtered_suggestions, l:item) + endif + else + " Add suggestions if the suggestion starts with a + " case-insensitive match for the prefix. + if l:word[: len(a:prefix) - 1] is? a:prefix + call add(l:filtered_suggestions, l:item) + endif endif endfor endif @@ -240,21 +254,17 @@ function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort return l:filtered_suggestions endfunction -function! s:ReplaceCompletionOptions() abort - let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - - if l:source is# 'ale-automatic' || l:source is# 'ale-manual' - " Remember the old omnifunc value, if there is one. - " If we don't store an old one, we'll just never reset the option. - " This will stop some random exceptions from appearing. - if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc) - let b:ale_old_omnifunc = &l:omnifunc - endif - - let &l:omnifunc = 'ale#completion#AutomaticOmniFunc' +function! s:ReplaceCompletionOptions(source) abort + " Remember the old omnifunc value, if there is one. + " If we don't store an old one, we'll just never reset the option. + " This will stop some random exceptions from appearing. + if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc) + let b:ale_old_omnifunc = &l:omnifunc endif - if l:source is# 'ale-automatic' + let &l:omnifunc = 'ale#completion#AutomaticOmniFunc' + + if a:source is# 'ale-automatic' if !exists('b:ale_old_completeopt') let b:ale_old_completeopt = &l:completeopt endif @@ -317,41 +327,70 @@ function! ale#completion#AutomaticOmniFunc(findstart, base) abort else let l:result = ale#completion#GetCompletionResult() - call s:ReplaceCompletionOptions() + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if l:source is# 'ale-automatic' || l:source is# 'ale-manual' + call s:ReplaceCompletionOptions(l:source) + endif return l:result isnot v:null ? l:result : [] endif endfunction +function! s:OpenCompletionMenu(...) abort + if !&l:paste + call ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)") + endif +endfunction + function! ale#completion#Show(result) abort - if ale#util#Mode() isnot# 'i' + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if ale#util#Mode() isnot# 'i' && l:source isnot# 'ale-import' return endif - " Set the list in the buffer, temporarily replace omnifunc with our - " function, and then start omni-completion. + " Set the list in the buffer. let b:ale_completion_result = a:result " Don't try to open the completion menu if there's nothing to show. if empty(b:ale_completion_result) + if l:source is# 'ale-import' + " If we ran completion from :ALEImport, + " tell the user that nothing is going to happen. + call s:message('No possible imports found.') + endif + return endif " Replace completion options shortly before opening the menu. - call s:ReplaceCompletionOptions() - - let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - if l:source is# 'ale-automatic' || l:source is# 'ale-manual' - call timer_start( - \ 0, - \ {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")} - \) + call s:ReplaceCompletionOptions(l:source) + + call timer_start(0, function('s:OpenCompletionMenu')) endif if l:source is# 'ale-callback' call b:CompleteCallback(b:ale_completion_result) endif + + if l:source is# 'ale-import' + call ale#completion#HandleUserData(b:ale_completion_result[0]) + + let l:text_changed = '' . g:ale_lint_on_text_changed + + " Check the buffer again right away, if linting is enabled. + if g:ale_enabled + \&& ( + \ l:text_changed is# '1' + \ || l:text_changed is# 'always' + \ || l:text_changed is# 'normal' + \ || l:text_changed is# 'insert' + \) + call ale#Queue(0, '') + endif + endif endfunction function! ale#completion#GetAllTriggers() abort @@ -382,14 +421,18 @@ endfunction function! s:CompletionStillValid(request_id) abort let [l:line, l:column] = getpos('.')[1:2] - return ale#util#Mode() is# 'i' - \&& has_key(b:, 'ale_completion_info') + return has_key(b:, 'ale_completion_info') + \&& ( + \ ale#util#Mode() is# 'i' + \ || b:ale_completion_info.source is# 'ale-import' + \) \&& b:ale_completion_info.request_id == a:request_id \&& b:ale_completion_info.line == l:line \&& ( \ b:ale_completion_info.column == l:column \ || b:ale_completion_info.source is# 'ale-omnifunc' \ || b:ale_completion_info.source is# 'ale-callback' + \ || b:ale_completion_info.source is# 'ale-import' \) endfunction @@ -397,10 +440,14 @@ function! ale#completion#ParseTSServerCompletions(response) abort let l:names = [] for l:suggestion in a:response.body - call add(l:names, { - \ 'word': l:suggestion.name, - \ 'source': get(l:suggestion, 'source', ''), - \}) + let l:kind = get(l:suggestion, 'kind', '') + + if g:ale_completion_tsserver_remove_warnings == 0 || l:kind isnot# 'warning' + call add(l:names, { + \ 'word': l:suggestion.name, + \ 'source': get(l:suggestion, 'source', ''), + \}) + endif endfor return l:names @@ -410,15 +457,26 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort let l:buffer = bufnr('') let l:results = [] let l:names_with_details = [] + let l:info = get(b:, 'ale_completion_info', {}) for l:suggestion in a:response.body let l:displayParts = [] + let l:local_name = v:null for l:action in get(l:suggestion, 'codeActions', []) call add(l:displayParts, l:action.description . ' ') endfor for l:part in l:suggestion.displayParts + " Stop on stop on line breaks for the menu. + if get(l:part, 'kind') is# 'lineBreak' + break + endif + + if get(l:part, 'kind') is# 'localName' + let l:local_name = l:part.text + endif + call add(l:displayParts, l:part.text) endfor @@ -431,21 +489,35 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort " See :help complete-items let l:result = { - \ 'word': l:suggestion.name, + \ 'word': ( + \ l:suggestion.name is# 'default' + \ && l:suggestion.kind is# 'alias' + \ && !empty(l:local_name) + \ ? l:local_name + \ : l:suggestion.name + \ ), \ 'kind': ale#completion#GetCompletionSymbols(l:suggestion.kind), \ 'icase': 1, \ 'menu': join(l:displayParts, ''), - \ 'dup': g:ale_completion_tsserver_autoimport, + \ 'dup': get(l:info, 'additional_edits_only', 0) + \ || g:ale_completion_autoimport, \ 'info': join(l:documentationParts, ''), \} + " This flag is used to tell if this completion came from ALE or not. + let l:user_data = {'_ale_completion_item': 1} if has_key(l:suggestion, 'codeActions') - let l:result.user_data = json_encode({ - \ 'codeActions': l:suggestion.codeActions, - \ }) + let l:user_data.code_actions = l:suggestion.codeActions endif - call add(l:results, l:result) + let l:result.user_data = json_encode(l:user_data) + + " Include this item if we'll accept any items, + " or if we only want items with additional edits, and this has them. + if !get(l:info, 'additional_edits_only', 0) + \|| has_key(l:user_data, 'code_actions') + call add(l:results, l:result) + endif endfor let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', []) @@ -464,6 +536,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort \ 'icase': 1, \ 'menu': '', \ 'info': '', + \ 'user_data': json_encode({'_ale_completion_item': 1}), \}) endfor endif @@ -517,23 +590,80 @@ function! ale#completion#ParseLSPCompletions(response) abort continue endif + " Don't use LSP items with additional text edits when autoimport for + " completions is turned off. + if !empty(get(l:item, 'additionalTextEdits')) + \&& !( + \ get(l:info, 'additional_edits_only', 0) + \ || g:ale_completion_autoimport + \) + continue + endif + let l:doc = get(l:item, 'documentation', '') if type(l:doc) is v:t_dict && has_key(l:doc, 'value') let l:doc = l:doc.value endif - call add(l:results, { + let l:result = { \ 'word': l:word, \ 'kind': ale#completion#GetCompletionSymbols(get(l:item, 'kind', '')), \ 'icase': 1, \ 'menu': get(l:item, 'detail', ''), \ 'info': (type(l:doc) is v:t_string ? l:doc : ''), - \}) + \} + " This flag is used to tell if this completion came from ALE or not. + let l:user_data = {'_ale_completion_item': 1} + + if has_key(l:item, 'additionalTextEdits') + let l:text_changes = [] + + for l:edit in l:item.additionalTextEdits + call add(l:text_changes, { + \ 'start': { + \ 'line': l:edit.range.start.line + 1, + \ 'offset': l:edit.range.start.character + 1, + \ }, + \ 'end': { + \ 'line': l:edit.range.end.line + 1, + \ 'offset': l:edit.range.end.character + 1, + \ }, + \ 'newText': l:edit.newText, + \}) + endfor + + if !empty(l:text_changes) + let l:user_data.code_actions = [{ + \ 'description': 'completion', + \ 'changes': [ + \ { + \ 'fileName': expand('#' . l:buffer . ':p'), + \ 'textChanges': l:text_changes, + \ }, + \ ], + \}] + endif + endif + + let l:result.user_data = json_encode(l:user_data) + + " Include this item if we'll accept any items, + " or if we only want items with additional edits, and this has them. + if !get(l:info, 'additional_edits_only', 0) + \|| has_key(l:user_data, 'code_actions') + call add(l:results, l:result) + endif endfor if has_key(l:info, 'prefix') - let l:results = ale#completion#Filter(l:buffer, &filetype, l:results, l:info.prefix) + let l:results = ale#completion#Filter( + \ l:buffer, + \ &filetype, + \ l:results, + \ l:info.prefix, + \ get(l:info, 'additional_edits_only', 0), + \) endif return l:results[: g:ale_completion_max_suggestions - 1] @@ -557,13 +687,18 @@ function! ale#completion#HandleTSServerResponse(conn_id, response) abort \ &filetype, \ ale#completion#ParseTSServerCompletions(a:response), \ b:ale_completion_info.prefix, + \ get(b:ale_completion_info, 'additional_edits_only', 0), \)[: g:ale_completion_max_suggestions - 1] " We need to remember some names for tsserver, as it doesn't send " details back for everything we send. call setbufvar(l:buffer, 'ale_tsserver_completion_names', l:names) - if !empty(l:names) + if empty(l:names) + " Response with no results now and skip making a redundant request + " for nothing. + call ale#completion#Show([]) + else let l:identifiers = [] for l:name in l:names @@ -628,12 +763,17 @@ function! s:OnReady(linter, lsp_details) abort call ale#lsp#RegisterCallback(l:id, l:Callback) if a:linter.lsp is# 'tsserver' + if get(g:, 'ale_completion_tsserver_autoimport') is 1 + execute 'echom `g:ale_completion_tsserver_autoimport` is deprecated. Use `g:ale_completion_autoimport` instead.''' + endif + let l:message = ale#lsp#tsserver_message#Completions( \ l:buffer, \ b:ale_completion_info.line, \ b:ale_completion_info.column, \ b:ale_completion_info.prefix, - \ g:ale_completion_tsserver_autoimport, + \ get(b:ale_completion_info, 'additional_edits_only', 0) + \ || g:ale_completion_autoimport, \) else " Send a message saying the buffer has changed first, otherwise @@ -692,9 +832,19 @@ function! ale#completion#GetCompletions(...) abort let b:CompleteCallback = l:CompleteCallback endif - let [l:line, l:column] = getpos('.')[1:2] + if has_key(l:options, 'line') && has_key(l:options, 'column') + " Use a provided line and column, if given. + let l:line = l:options.line + let l:column = l:options.column + else + let [l:line, l:column] = getpos('.')[1:2] + endif - let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) + if has_key(l:options, 'prefix') + let l:prefix = l:options.prefix + else + let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) + endif if l:source is# 'ale-automatic' && empty(l:prefix) return 0 @@ -713,6 +863,11 @@ function! ale#completion#GetCompletions(...) abort \} unlet! b:ale_completion_result + if has_key(l:options, 'additional_edits_only') + let b:ale_completion_info.additional_edits_only = + \ l:options.additional_edits_only + endif + let l:buffer = bufnr('') let l:Callback = function('s:OnReady') @@ -729,6 +884,37 @@ function! ale#completion#GetCompletions(...) abort return l:started endfunction +function! s:message(message) abort + call ale#util#Execute('echom ' . string(a:message)) +endfunction + +" This function implements the :ALEImport command. +function! ale#completion#Import() abort + let l:word = expand('<cword>') + + if empty(l:word) + call s:message('Nothing to complete at cursor!') + + return + endif + + let [l:line, l:column] = getpos('.')[1:2] + let l:column = searchpos('\V' . escape(l:word, '/\'), 'bn', l:line)[1] + + if l:column isnot 0 + let l:started = ale#completion#GetCompletions('ale-import', { + \ 'line': l:line, + \ 'column': l:column, + \ 'prefix': l:word, + \ 'additional_edits_only': 1, + \}) + + if !l:started + call s:message('No completion providers are available.') + endif + endif +endfunction + function! ale#completion#OmniFunc(findstart, base) abort if a:findstart let l:started = ale#completion#GetCompletions('ale-omnifunc') @@ -802,29 +988,29 @@ function! ale#completion#Queue() abort endfunction function! ale#completion#HandleUserData(completed_item) abort - let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - - if l:source isnot# 'ale-automatic' - \&& l:source isnot# 'ale-manual' - \&& l:source isnot# 'ale-callback' - return - endif - let l:user_data_json = get(a:completed_item, 'user_data', '') + let l:user_data = !empty(l:user_data_json) + \ ? json_decode(l:user_data_json) + \ : v:null - if empty(l:user_data_json) + if type(l:user_data) isnot v:t_dict + \|| get(l:user_data, '_ale_completion_item', 0) isnot 1 return endif - let l:user_data = json_decode(l:user_data_json) + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - if type(l:user_data) isnot v:t_dict - return + if l:source is# 'ale-automatic' + \|| l:source is# 'ale-manual' + \|| l:source is# 'ale-callback' + \|| l:source is# 'ale-import' + \|| l:source is# 'ale-omnifunc' + for l:code_action in get(l:user_data, 'code_actions', []) + call ale#code_action#HandleCodeAction(l:code_action, v:false) + endfor endif - for l:code_action in get(l:user_data, 'codeActions', []) - call ale#code_action#HandleCodeAction(l:code_action) - endfor + silent doautocmd <nomodeline> User ALECompletePost endfunction function! ale#completion#Done() abort @@ -836,6 +1022,8 @@ function! ale#completion#Done() abort endfunction augroup ALECompletionActions + autocmd! + autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item) augroup END diff --git a/autoload/ale/cursor.vim b/autoload/ale/cursor.vim index 8c331c5c..9ca6fb15 100644 --- a/autoload/ale/cursor.vim +++ b/autoload/ale/cursor.vim @@ -39,6 +39,8 @@ function! ale#cursor#TruncatedEcho(original_message) abort endif exec 'echomsg l:message' + catch /E481/ + " Do nothing if running from a visual selection. endtry " Reset the cursor position if we moved off the end of the line. diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim index 4e134f8c..5e6d5906 100644 --- a/autoload/ale/debugging.vim +++ b/autoload/ale/debugging.vim @@ -8,6 +8,7 @@ let s:global_variable_list = [ \ 'ale_completion_delay', \ 'ale_completion_enabled', \ 'ale_completion_max_suggestions', +\ 'ale_disable_lsp', \ 'ale_echo_cursor', \ 'ale_echo_msg_error_str', \ 'ale_echo_msg_format', @@ -28,6 +29,7 @@ let s:global_variable_list = [ \ 'ale_linter_aliases', \ 'ale_linters', \ 'ale_linters_explicit', +\ 'ale_linters_ignore', \ 'ale_list_vertical', \ 'ale_list_window_size', \ 'ale_loclist_msg_format', @@ -196,6 +198,7 @@ function! s:EchoLSPErrorMessages(all_linter_names) abort endfunction function! ale#debugging#Info() abort + let l:buffer = bufnr('') let l:filetype = &filetype " We get the list of enabled linters for free by the above function. @@ -222,10 +225,20 @@ function! ale#debugging#Info() abort let l:fixers = uniq(sort(l:fixers[0] + l:fixers[1])) let l:fixers_string = join(map(copy(l:fixers), '"\n " . v:val'), '') + let l:non_ignored_names = map( + \ copy(ale#linter#RemoveIgnored(l:buffer, l:filetype, l:enabled_linters)), + \ 'v:val[''name'']', + \) + let l:ignored_names = filter( + \ copy(l:enabled_names), + \ 'index(l:non_ignored_names, v:val) < 0' + \) + call s:Echo(' Current Filetype: ' . l:filetype) call s:Echo('Available Linters: ' . string(l:all_names)) call s:EchoLinterAliases(l:all_linters) call s:Echo(' Enabled Linters: ' . string(l:enabled_names)) + call s:Echo(' Ignored Linters: ' . string(l:ignored_names)) call s:Echo(' Suggested Fixers: ' . l:fixers_string) call s:Echo(' Linter Variables:') call s:Echo('') diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim index 3915cac1..0c1fb7cf 100644 --- a/autoload/ale/definition.vim +++ b/autoload/ale/definition.vim @@ -5,6 +5,7 @@ let s:go_to_definition_map = {} " Enable automatic updates of the tagstack let g:ale_update_tagstack = get(g:, 'ale_update_tagstack', 1) +let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer') " Used to get the definition map in tests. function! ale#definition#GetMap() abort @@ -154,3 +155,33 @@ function! ale#definition#GoToType(options) abort endif endfor endfunction + +function! ale#definition#GoToCommandHandler(command, ...) abort + let l:options = {} + + if len(a:000) > 0 + for l:option in a:000 + if l:option is? '-tab' + let l:options.open_in = 'tab' + elseif l:option is? '-split' + let l:options.open_in = 'split' + elseif l:option is? '-vsplit' + let l:options.open_in = 'vsplit' + endif + endfor + endif + + if !has_key(l:options, 'open_in') + let l:default_navigation = ale#Var(bufnr(''), 'default_navigation') + + if index(['tab', 'split', 'vsplit'], l:default_navigation) >= 0 + let l:options.open_in = l:default_navigation + endif + endif + + if a:command is# 'type' + call ale#definition#GoToType(l:options) + else + call ale#definition#GoTo(l:options) + endif +endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 491d3c2e..63195d0f 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -4,6 +4,7 @@ " Remapping of linter problems. let g:ale_type_map = get(g:, 'ale_type_map', {}) +let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {}) if !has_key(s:, 'executable_cache_map') let s:executable_cache_map = {} @@ -104,42 +105,6 @@ function! ale#engine#IsCheckingBuffer(buffer) abort \ || !empty(get(l:info, 'active_other_sources_list', [])) endfunction -" Register a temporary file to be managed with the ALE engine for -" a current job run. -function! ale#engine#ManageFile(buffer, filename) abort - if !get(g:, 'ale_ignore_2_4_warnings') - execute 'echom ''ale#engine#ManageFile is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' - endif - - call ale#command#ManageFile(a:buffer, a:filename) -endfunction - -" Same as the above, but manage an entire directory. -function! ale#engine#ManageDirectory(buffer, directory) abort - if !get(g:, 'ale_ignore_2_4_warnings') - execute 'echom ''ale#engine#ManageDirectory is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' - endif - - call ale#command#ManageDirectory(a:buffer, a:directory) -endfunction - -function! ale#engine#CreateFile(buffer) abort - if !get(g:, 'ale_ignore_2_4_warnings') - execute 'echom ''ale#engine#CreateFile is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' - endif - - return ale#command#CreateFile(a:buffer) -endfunction - -" Create a new temporary directory and manage it in one go. -function! ale#engine#CreateDirectory(buffer) abort - if !get(g:, 'ale_ignore_2_4_warnings') - execute 'echom ''ale#engine#CreateDirectory is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' - endif - - return ale#command#CreateDirectory(a:buffer) -endfunction - function! ale#engine#HandleLoclist(linter_name, buffer, loclist, from_other_source) abort let l:info = get(g:ale_buffer_info, a:buffer, {}) @@ -192,7 +157,6 @@ function! s:HandleExit(job_info, buffer, output, data) abort let l:linter = a:job_info.linter let l:executable = a:job_info.executable - let l:next_chain_index = a:job_info.next_chain_index " Remove this job from the list. call ale#engine#MarkLinterInactive(l:buffer_info, l:linter.name) @@ -207,20 +171,6 @@ function! s:HandleExit(job_info, buffer, output, data) abort call remove(a:output, -1) endif - if l:next_chain_index < len(get(l:linter, 'command_chain', [])) - let [l:command, l:options] = ale#engine#ProcessChain( - \ a:buffer, - \ l:executable, - \ l:linter, - \ l:next_chain_index, - \ a:output, - \) - - call s:RunJob(l:command, l:options) - - return - endif - try let l:loclist = ale#util#GetFunction(l:linter.callback)(a:buffer, a:output) " Handle the function being unknown, or being deleted. @@ -307,6 +257,13 @@ function! s:RemapItemTypes(type_map, loclist) abort endfunction function! ale#engine#FixLocList(buffer, linter_name, from_other_source, loclist) abort + let l:mappings = ale#GetFilenameMappings(a:buffer, a:linter_name) + + if !empty(l:mappings) + " We need to apply reverse filename mapping here. + let l:mappings = ale#filename_mapping#Invert(l:mappings) + endif + let l:bufnr_map = {} let l:new_loclist = [] @@ -347,13 +304,19 @@ function! ale#engine#FixLocList(buffer, linter_name, from_other_source, loclist) let l:item.code = l:old_item.code endif - if has_key(l:old_item, 'filename') - \&& !ale#path#IsTempName(l:old_item.filename) + let l:old_name = get(l:old_item, 'filename', '') + + " Map parsed from output to local filesystem files. + if !empty(l:old_name) && !empty(l:mappings) + let l:old_name = ale#filename_mapping#Map(l:old_name, l:mappings) + endif + + if !empty(l:old_name) && !ale#path#IsTempName(l:old_name) " Use the filename given. " Temporary files are assumed to be for this buffer, " and the filename is not included then, because it looks bad " in the loclist window. - let l:filename = l:old_item.filename + let l:filename = l:old_name let l:item.filename = l:filename if has_key(l:old_item, 'bufnr') @@ -454,20 +417,19 @@ function! s:RunJob(command, options) abort let l:buffer = a:options.buffer let l:linter = a:options.linter let l:output_stream = a:options.output_stream - let l:next_chain_index = a:options.next_chain_index - let l:read_buffer = a:options.read_buffer + let l:read_buffer = a:options.read_buffer && !a:options.lint_file let l:info = g:ale_buffer_info[l:buffer] let l:Callback = function('s:HandleExit', [{ \ 'linter': l:linter, \ 'executable': l:executable, - \ 'next_chain_index': l:next_chain_index, \}]) let l:result = ale#command#Run(l:buffer, l:command, l:Callback, { \ 'output_stream': l:output_stream, \ 'executable': l:executable, \ 'read_buffer': l:read_buffer, - \ 'log_output': l:next_chain_index >= len(get(l:linter, 'command_chain', [])), + \ 'log_output': 1, + \ 'filename_mappings': ale#GetFilenameMappings(l:buffer, l:linter.name), \}) " Only proceed if the job is being run. @@ -482,68 +444,6 @@ function! s:RunJob(command, options) abort return 1 endfunction -" Determine which commands to run for a link in a command chain, or -" just a regular command. -function! ale#engine#ProcessChain(buffer, executable, linter, chain_index, input) abort - let l:output_stream = get(a:linter, 'output_stream', 'stdout') - let l:read_buffer = a:linter.read_buffer - let l:chain_index = a:chain_index - let l:input = a:input - - while l:chain_index < len(a:linter.command_chain) - " Run a chain of commands, one asynchronous command after the other, - " so that many programs can be run in a sequence. - let l:chain_item = a:linter.command_chain[l:chain_index] - - if l:chain_index == 0 - " The first callback in the chain takes only a buffer number. - let l:command = ale#util#GetFunction(l:chain_item.callback)( - \ a:buffer - \) - else - " The second callback in the chain takes some input too. - let l:command = ale#util#GetFunction(l:chain_item.callback)( - \ a:buffer, - \ l:input - \) - endif - - " If we have a command to run, execute that. - if !empty(l:command) - " The chain item can override the output_stream option. - if has_key(l:chain_item, 'output_stream') - let l:output_stream = l:chain_item.output_stream - endif - - " The chain item can override the read_buffer option. - if has_key(l:chain_item, 'read_buffer') - let l:read_buffer = l:chain_item.read_buffer - elseif l:chain_index != len(a:linter.command_chain) - 1 - " Don't read the buffer for commands besides the last one - " in the chain by default. - let l:read_buffer = 0 - endif - - break - endif - - " Command chain items can return an empty string to indicate that - " a command should be skipped, so we should try the next item - " with no input. - let l:input = [] - let l:chain_index += 1 - endwhile - - return [l:command, { - \ 'executable': a:executable, - \ 'buffer': a:buffer, - \ 'linter': a:linter, - \ 'output_stream': l:output_stream, - \ 'next_chain_index': l:chain_index + 1, - \ 'read_buffer': l:read_buffer, - \}] -endfunction - function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort let l:info = get(g:ale_buffer_info, a:buffer, {}) call ale#command#StopJobs(a:buffer, 'linter') @@ -558,6 +458,10 @@ function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort endif endfunction +function! ale#engine#Stop(buffer) abort + call s:StopCurrentJobs(a:buffer, 1) +endfunction + function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort " Figure out which linters are still enabled, and remove " problems for linters which are no longer enabled. @@ -608,10 +512,15 @@ function! s:AddProblemsFromOtherBuffers(buffer, linters) abort endif endfunction -function! s:RunIfExecutable(buffer, linter, executable) abort +function! s:RunIfExecutable(buffer, linter, lint_file, executable) abort if ale#command#IsDeferred(a:executable) let a:executable.result_callback = { - \ executable -> s:RunIfExecutable(a:buffer, a:linter, executable) + \ executable -> s:RunIfExecutable( + \ a:buffer, + \ a:linter, + \ a:lint_file, + \ executable + \ ) \} return 1 @@ -619,29 +528,17 @@ function! s:RunIfExecutable(buffer, linter, executable) abort if ale#engine#IsExecutable(a:buffer, a:executable) " Use different job types for file or linter jobs. - let l:job_type = a:linter.lint_file ? 'file_linter' : 'linter' + let l:job_type = a:lint_file ? 'file_linter' : 'linter' call setbufvar(a:buffer, 'ale_job_type', l:job_type) - if has_key(a:linter, 'command_chain') - let [l:command, l:options] = ale#engine#ProcessChain( - \ a:buffer, - \ a:executable, - \ a:linter, - \ 0, - \ [] - \) - - return s:RunJob(l:command, l:options) - endif - let l:command = ale#linter#GetCommand(a:buffer, a:linter) let l:options = { \ 'executable': a:executable, \ 'buffer': a:buffer, \ 'linter': a:linter, \ 'output_stream': get(a:linter, 'output_stream', 'stdout'), - \ 'next_chain_index': 1, \ 'read_buffer': a:linter.read_buffer, + \ 'lint_file': a:lint_file, \} return s:RunJob(l:command, l:options) @@ -653,33 +550,62 @@ endfunction " Run a linter for a buffer. " " Returns 1 if the linter was successfully run. -function! s:RunLinter(buffer, linter) abort +function! s:RunLinter(buffer, linter, lint_file) abort if !empty(a:linter.lsp) return ale#lsp_linter#CheckWithLSP(a:buffer, a:linter) else let l:executable = ale#linter#GetExecutable(a:buffer, a:linter) - return s:RunIfExecutable(a:buffer, a:linter, l:executable) + return s:RunIfExecutable(a:buffer, a:linter, a:lint_file, l:executable) endif return 0 endfunction -function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort - " Initialise the buffer information if needed. - let l:new_buffer = ale#engine#InitBufferInfo(a:buffer) - call s:StopCurrentJobs(a:buffer, a:should_lint_file) - call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters) +function! s:GetLintFileValues(slots, Callback) abort + let l:deferred_list = [] + let l:new_slots = [] - " We can only clear the results if we aren't checking the buffer. - let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer) + for [l:lint_file, l:linter] in a:slots + while ale#command#IsDeferred(l:lint_file) && has_key(l:lint_file, 'value') + " If we've already computed the return value, use it. + let l:lint_file = l:lint_file.value + endwhile - silent doautocmd <nomodeline> User ALELintPre + if ale#command#IsDeferred(l:lint_file) + " If we are going to return the result later, wait for it. + call add(l:deferred_list, l:lint_file) + else + " If we have the value now, coerce it to 0 or 1. + let l:lint_file = l:lint_file is 1 + endif - for l:linter in a:linters + call add(l:new_slots, [l:lint_file, l:linter]) + endfor + + if !empty(l:deferred_list) + for l:deferred in l:deferred_list + let l:deferred.result_callback = + \ {-> s:GetLintFileValues(l:new_slots, a:Callback)} + endfor + else + call a:Callback(l:new_slots) + endif +endfunction + +function! s:RunLinters( +\ buffer, +\ slots, +\ should_lint_file, +\ new_buffer, +\ can_clear_results +\) abort + let l:can_clear_results = a:can_clear_results + + for [l:lint_file, l:linter] in a:slots " Only run lint_file linters if we should. - if !l:linter.lint_file || a:should_lint_file - if s:RunLinter(a:buffer, l:linter) + if !l:lint_file || a:should_lint_file + if s:RunLinter(a:buffer, l:linter, l:lint_file) " If a single linter ran, we shouldn't clear everything. let l:can_clear_results = 0 endif @@ -694,11 +620,49 @@ function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort " disabled, or ALE itself is disabled. if l:can_clear_results call ale#engine#SetResults(a:buffer, []) - elseif l:new_buffer - call s:AddProblemsFromOtherBuffers(a:buffer, a:linters) + elseif a:new_buffer + call s:AddProblemsFromOtherBuffers( + \ a:buffer, + \ map(copy(a:slots), 'v:val[1]') + \) endif endfunction +function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort + " Initialise the buffer information if needed. + let l:new_buffer = ale#engine#InitBufferInfo(a:buffer) + call s:StopCurrentJobs(a:buffer, a:should_lint_file) + call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters) + + " We can only clear the results if we aren't checking the buffer. + let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer) + + silent doautocmd <nomodeline> User ALELintPre + + " Handle `lint_file` callbacks first. + let l:linter_slots = [] + + for l:linter in a:linters + let l:LintFile = l:linter.lint_file + + if type(l:LintFile) is v:t_func + let l:LintFile = l:LintFile(a:buffer) + endif + + call add(l:linter_slots, [l:LintFile, l:linter]) + endfor + + call s:GetLintFileValues(l:linter_slots, { + \ new_slots -> s:RunLinters( + \ a:buffer, + \ new_slots, + \ a:should_lint_file, + \ l:new_buffer, + \ l:can_clear_results, + \ ) + \}) +endfunction + " Clean up a buffer. " " This function will stop all current jobs for the buffer, diff --git a/autoload/ale/events.vim b/autoload/ale/events.vim index da554ef9..3568c117 100644 --- a/autoload/ale/events.vim +++ b/autoload/ale/events.vim @@ -105,11 +105,11 @@ function! ale#events#Init() abort if g:ale_enabled if l:text_changed is? 'always' || l:text_changed is# '1' - autocmd TextChanged,TextChangedI * call ale#Queue(g:ale_lint_delay) + autocmd TextChanged,TextChangedI * call ale#Queue(ale#Var(str2nr(expand('<abuf>')), 'lint_delay')) elseif l:text_changed is? 'normal' - autocmd TextChanged * call ale#Queue(g:ale_lint_delay) + autocmd TextChanged * call ale#Queue(ale#Var(str2nr(expand('<abuf>')), 'lint_delay')) elseif l:text_changed is? 'insert' - autocmd TextChangedI * call ale#Queue(g:ale_lint_delay) + autocmd TextChangedI * call ale#Queue(ale#Var(str2nr(expand('<abuf>')), 'lint_delay')) endif if g:ale_lint_on_enter @@ -147,6 +147,10 @@ function! ale#events#Init() abort autocmd InsertLeave * if exists('*ale#engine#Cleanup') | call ale#virtualtext#ShowCursorWarning() | endif endif + if g:ale_hover_cursor + autocmd CursorHold * if exists('*ale#lsp#Send') | call ale#hover#ShowTruncatedMessageAtCursor() | endif + endif + if g:ale_close_preview_on_insert autocmd InsertEnter * if exists('*ale#preview#CloseIfTypeMatches') | call ale#preview#CloseIfTypeMatches('ale-preview') | endif endif diff --git a/autoload/ale/filename_mapping.vim b/autoload/ale/filename_mapping.vim new file mode 100644 index 00000000..76d47acc --- /dev/null +++ b/autoload/ale/filename_mapping.vim @@ -0,0 +1,22 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: Logic for handling mappings between files + +" Invert filesystem mappings so they can be mapped in reverse. +function! ale#filename_mapping#Invert(filename_mappings) abort + return map(copy(a:filename_mappings), '[v:val[1], v:val[0]]') +endfunction + +" Given a filename and some filename_mappings, map a filename. +function! ale#filename_mapping#Map(filename, filename_mappings) abort + let l:simplified_filename = ale#path#Simplify(a:filename) + + for [l:mapping_from, l:mapping_to] in a:filename_mappings + let l:mapping_from = ale#path#Simplify(l:mapping_from) + + if l:simplified_filename[:len(l:mapping_from) - 1] is# l:mapping_from + return l:mapping_to . l:simplified_filename[len(l:mapping_from):] + endif + endfor + + return a:filename +endfunction diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim index dad9e2bc..c3338fc5 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -1,57 +1,43 @@ -call ale#Set('fix_on_save_ignore', {}) +" Author: w0rp <devw0rp@gmail.com> +" Description: Functions for fixing code with programs, or other means. + +let g:ale_fix_on_save_ignore = get(g:, 'ale_fix_on_save_ignore', {}) +let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {}) " Apply fixes queued up for buffers which may be hidden. " Vim doesn't let you modify hidden buffers. function! ale#fix#ApplyQueuedFixes(buffer) abort let l:data = get(g:ale_fix_buffer_data, a:buffer, {'done': 0}) - let l:has_bufline_api = exists('*deletebufline') && exists('*setbufline') - if !l:data.done || (!l:has_bufline_api && a:buffer isnot bufnr('')) + if !l:data.done || (!ale#util#HasBuflineApi() && a:buffer isnot bufnr('')) return endif call remove(g:ale_fix_buffer_data, a:buffer) - if l:data.changes_made - " If the file is in DOS mode, we have to remove carriage returns from - " the ends of lines before calling setline(), or we will see them - " twice. - let l:new_lines = getbufvar(a:buffer, '&fileformat') is# 'dos' - \ ? map(copy(l:data.output), 'substitute(v:val, ''\r\+$'', '''', '''')') - \ : l:data.output - let l:first_line_to_remove = len(l:new_lines) + 1 - - " Use a Vim API for setting lines in other buffers, if available. - if l:has_bufline_api - call setbufline(a:buffer, 1, l:new_lines) - call deletebufline(a:buffer, l:first_line_to_remove, '$') - " Fall back on setting lines the old way, for the current buffer. - else - let l:old_line_length = len(l:data.lines_before) - - if l:old_line_length >= l:first_line_to_remove - let l:save = winsaveview() - silent execute - \ l:first_line_to_remove . ',' . l:old_line_length . 'd_' - call winrestview(l:save) - endif - - call setline(1, l:new_lines) - endif - - if l:data.should_save - if a:buffer is bufnr('') - if empty(&buftype) - noautocmd :w! + try + if l:data.changes_made + let l:new_lines = ale#util#SetBufferContents(a:buffer, l:data.output) + + if l:data.should_save + if a:buffer is bufnr('') + if empty(&buftype) + noautocmd :w! + else + set nomodified + endif else - set nomodified + call writefile(l:new_lines, expand('#' . a:buffer . ':p')) " no-custom-checks + call setbufvar(a:buffer, '&modified', 0) endif - else - call writefile(l:new_lines, expand('#' . a:buffer . ':p')) " no-custom-checks - call setbufvar(a:buffer, '&modified', 0) endif endif - endif + catch /E21/ + " If we cannot modify the buffer now, try again later. + let g:ale_fix_buffer_data[a:buffer] = l:data + + return + endtry if l:data.should_save let l:should_lint = ale#Var(a:buffer, 'fix_on_save') @@ -89,7 +75,10 @@ function! ale#fix#ApplyFixes(buffer, output) abort if l:data.lines_before != l:lines call remove(g:ale_fix_buffer_data, a:buffer) - execute 'echoerr ''The file was changed before fixing finished''' + + if !l:data.ignore_file_changed_errors + execute 'echoerr ''The file was changed before fixing finished''' + endif return endif @@ -115,7 +104,6 @@ function! s:HandleExit(job_info, buffer, job_output, data) abort let l:output = a:job_output endif - let l:ChainCallback = get(a:job_info, 'chain_with', v:null) let l:ProcessWith = get(a:job_info, 'process_with', v:null) " Post-process the output with a function if we have one. @@ -127,27 +115,17 @@ function! s:HandleExit(job_info, buffer, job_output, data) abort " otherwise skip this job and use the input from before. " " We'll use the input from before for chained commands. - if l:ChainCallback is v:null && !empty(split(join(l:output))) + if !empty(split(join(l:output))) let l:input = l:output else let l:input = a:job_info.input endif - if l:ChainCallback isnot v:null && !get(g:, 'ale_ignore_2_4_warnings') - execute 'echom ''chain_with is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' - endif - - let l:next_index = l:ChainCallback is v:null - \ ? a:job_info.callback_index + 1 - \ : a:job_info.callback_index - call s:RunFixer({ \ 'buffer': a:buffer, \ 'input': l:input, - \ 'output': l:output, \ 'callback_list': a:job_info.callback_list, - \ 'callback_index': l:next_index, - \ 'chain_callback': l:ChainCallback, + \ 'callback_index': a:job_info.callback_index + 1, \}) endfunction @@ -160,6 +138,7 @@ function! s:RunJob(result, options) abort let l:buffer = a:options.buffer let l:input = a:options.input + let l:fixer_name = a:options.fixer_name if a:result is 0 || type(a:result) is v:t_list if type(a:result) is v:t_list @@ -177,26 +156,21 @@ function! s:RunJob(result, options) abort endif let l:command = get(a:result, 'command', '') - let l:ChainWith = get(a:result, 'chain_with', v:null) if empty(l:command) - " If the command is empty, skip to the next item, or call the - " chain_with function. + " If the command is empty, skip to the next item. call s:RunFixer({ \ 'buffer': l:buffer, \ 'input': l:input, - \ 'callback_index': a:options.callback_index + (l:ChainWith is v:null), + \ 'callback_index': a:options.callback_index, \ 'callback_list': a:options.callback_list, - \ 'chain_callback': l:ChainWith, - \ 'output': [], \}) return endif let l:read_temporary_file = get(a:result, 'read_temporary_file', 0) - " Default to piping the buffer for the last fixer in the chain. - let l:read_buffer = get(a:result, 'read_buffer', l:ChainWith is v:null) + let l:read_buffer = get(a:result, 'read_buffer', 1) let l:output_stream = get(a:result, 'output_stream', 'stdout') if l:read_temporary_file @@ -205,7 +179,6 @@ function! s:RunJob(result, options) abort let l:Callback = function('s:HandleExit', [{ \ 'input': l:input, - \ 'chain_with': l:ChainWith, \ 'callback_index': a:options.callback_index, \ 'callback_list': a:options.callback_list, \ 'process_with': get(a:result, 'process_with', v:null), @@ -217,6 +190,7 @@ function! s:RunJob(result, options) abort \ 'read_buffer': l:read_buffer, \ 'input': l:input, \ 'log_output': 0, + \ 'filename_mappings': ale#GetFilenameMappings(l:buffer, l:fixer_name), \}) if empty(l:run_result) @@ -240,32 +214,22 @@ function! s:RunFixer(options) abort return endif - let l:ChainCallback = get(a:options, 'chain_callback', v:null) - - let l:Function = l:ChainCallback isnot v:null - \ ? ale#util#GetFunction(l:ChainCallback) - \ : a:options.callback_list[l:index] + let [l:fixer_name, l:Function] = a:options.callback_list[l:index] " Record new jobs started as fixer jobs. call setbufvar(l:buffer, 'ale_job_type', 'fixer') - if l:ChainCallback isnot v:null - " Chained commands accept (buffer, output, [input]) - let l:result = ale#util#FunctionArgCount(l:Function) == 2 - \ ? call(l:Function, [l:buffer, a:options.output]) - \ : call(l:Function, [l:buffer, a:options.output, copy(l:input)]) - else - " Regular fixer commands accept (buffer, [input]) - let l:result = ale#util#FunctionArgCount(l:Function) == 1 - \ ? call(l:Function, [l:buffer]) - \ : call(l:Function, [l:buffer, copy(l:input)]) - endif + " Regular fixer commands accept (buffer, [input]) + let l:result = ale#util#FunctionArgCount(l:Function) == 1 + \ ? call(l:Function, [l:buffer]) + \ : call(l:Function, [l:buffer, copy(l:input)]) call s:RunJob(l:result, { \ 'buffer': l:buffer, \ 'input': l:input, \ 'callback_list': a:options.callback_list, \ 'callback_index': l:index, + \ 'fixer_name': l:fixer_name, \}) endfunction @@ -333,16 +297,24 @@ function! s:GetCallbacks(buffer, fixing_flag, fixers) abort " Variables with capital characters are needed, or Vim will complain about " funcref variables. for l:Item in l:callback_list + " Try to capture the names of registered fixer names, so we can use + " them for filename mapping or other purposes later. + let l:fixer_name = v:null + if type(l:Item) is v:t_string let l:Func = ale#fix#registry#GetFunc(l:Item) if !empty(l:Func) + let l:fixer_name = l:Item let l:Item = l:Func endif endif try - call add(l:corrected_list, ale#util#GetFunction(l:Item)) + call add(l:corrected_list, [ + \ l:fixer_name, + \ ale#util#GetFunction(l:Item) + \]) catch /E475/ " Rethrow exceptions for failing to get a function so we can print " a friendly message about it. @@ -360,6 +332,7 @@ function! ale#fix#InitBufferData(buffer, fixing_flag) abort \ 'lines_before': getbufline(a:buffer, 1, '$'), \ 'done': 0, \ 'should_save': a:fixing_flag is# 'save_file', + \ 'ignore_file_changed_errors': a:fixing_flag is# '!', \ 'temporary_directory_list': [], \} endfunction @@ -368,19 +341,23 @@ endfunction " " Returns 0 if no fixes can be applied, and 1 if fixing can be done. function! ale#fix#Fix(buffer, fixing_flag, ...) abort - if a:fixing_flag isnot# '' && a:fixing_flag isnot# 'save_file' - throw "fixing_flag must be either '' or 'save_file'" + if a:fixing_flag isnot# '' + \&& a:fixing_flag isnot# '!' + \&& a:fixing_flag isnot# 'save_file' + throw "fixing_flag must be '', '!', or 'save_file'" endif try let l:callback_list = s:GetCallbacks(a:buffer, a:fixing_flag, a:000) catch /E700\|BADNAME/ - let l:function_name = join(split(split(v:exception, ':')[3])) - let l:echo_message = printf( - \ 'There is no fixer named `%s`. Check :ALEFixSuggest', - \ l:function_name, - \) - execute 'echom l:echo_message' + if a:fixing_flag isnot# '!' + let l:function_name = join(split(split(v:exception, ':')[3])) + let l:echo_message = printf( + \ 'There is no fixer named `%s`. Check :ALEFixSuggest', + \ l:function_name, + \) + execute 'echom l:echo_message' + endif return 0 endtry @@ -414,3 +391,4 @@ endfunction augroup ALEBufferFixGroup autocmd! autocmd BufEnter * call ale#fix#ApplyQueuedFixes(str2nr(expand('<abuf>'))) +augroup END diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim index 1b3ca1a8..d71668f2 100644 --- a/autoload/ale/fix/registry.vim +++ b/autoload/ale/fix/registry.vim @@ -160,6 +160,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['php'], \ 'description': 'Fix PHP files with php-cs-fixer.', \ }, +\ 'astyle': { +\ 'function': 'ale#fixers#astyle#Fix', +\ 'suggested_filetypes': ['c', 'cpp'], +\ 'description': 'Fix C/C++ with astyle.', +\ }, \ 'clangtidy': { \ 'function': 'ale#fixers#clangtidy#Fix', \ 'suggested_filetypes': ['c', 'cpp', 'objc'], @@ -360,11 +365,21 @@ let s:default_registry = { \ 'suggested_filetypes': ['nix'], \ 'description': 'A formatter for Nix code', \ }, +\ 'remark-lint': { +\ 'function': 'ale#fixers#remark_lint#Fix', +\ 'suggested_filetypes': ['markdown'], +\ 'description': 'Fix markdown files with remark-lint', +\ }, \ 'html-beautify': { \ 'function': 'ale#fixers#html_beautify#Fix', \ 'suggested_filetypes': ['html', 'htmldjango'], \ 'description': 'Fix HTML files with html-beautify.', \ }, +\ 'dhall': { +\ 'function': 'ale#fixers#dhall#Fix', +\ 'suggested_filetypes': ['dhall'], +\ 'description': 'Fix Dhall files with dhall-format.', +\ }, \} " Reset the function registry to the default entries. diff --git a/autoload/ale/fixers/astyle.vim b/autoload/ale/fixers/astyle.vim new file mode 100644 index 00000000..3a5a70a1 --- /dev/null +++ b/autoload/ale/fixers/astyle.vim @@ -0,0 +1,59 @@ +" Author: James Kim <jhlink@users.noreply.github.com> +" Description: Fix C/C++ files with astyle. + +function! s:set_variables() abort + for l:ft in ['c', 'cpp'] + call ale#Set(l:ft . '_astyle_executable', 'astyle') + call ale#Set(l:ft . '_astyle_project_options', '') + endfor +endfunction + +call s:set_variables() + + +function! ale#fixers#astyle#Var(buffer, name) abort + let l:ft = getbufvar(str2nr(a:buffer), '&filetype') + let l:ft = l:ft =~# 'cpp' ? 'cpp' : 'c' + + return ale#Var(a:buffer, l:ft . '_astyle_' . a:name) +endfunction + +" Try to find a project options file. +function! ale#fixers#astyle#FindProjectOptions(buffer) abort + let l:proj_options = ale#fixers#astyle#Var(a:buffer, 'project_options') + + " If user has set project options variable then use it and skip any searching. + " This would allow users to use project files named differently than .astylerc. + if !empty(l:proj_options) + return l:proj_options + endif + + " Try to find nearest .astylerc file. + let l:proj_options = fnamemodify(ale#path#FindNearestFile(a:buffer, '.astylerc'), ':t') + + if !empty(l:proj_options) + return l:proj_options + endif + + " Try to find nearest _astylerc file. + let l:proj_options = fnamemodify(ale#path#FindNearestFile(a:buffer, '_astylerc'), ':t') + + if !empty(l:proj_options) + return l:proj_options + endif + + " If no project options file is found return an empty string. + return '' +endfunction + +function! ale#fixers#astyle#Fix(buffer) abort + let l:executable = ale#fixers#astyle#Var(a:buffer, 'executable') + let l:proj_options = ale#fixers#astyle#FindProjectOptions(a:buffer) + let l:command = ' --stdin=' . ale#Escape(expand('#' . a:buffer)) + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:proj_options) ? '' : ' --project=' . l:proj_options) + \ . l:command + \} +endfunction diff --git a/autoload/ale/fixers/dhall.vim b/autoload/ale/fixers/dhall.vim new file mode 100644 index 00000000..18f6006c --- /dev/null +++ b/autoload/ale/fixers/dhall.vim @@ -0,0 +1,23 @@ +" Author: Pat Brisbin <pbrisbin@gmail.com> +" Description: Integration of dhall-format with ALE. + +call ale#Set('dhall_format_executable', 'dhall') + +function! ale#fixers#dhall#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'dhall_format_executable') + + " Dhall is written in Haskell and commonly installed with Stack + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'dhall') +endfunction + +function! ale#fixers#dhall#Fix(buffer) abort + let l:executable = ale#fixers#dhall#GetExecutable(a:buffer) + + return { + \ 'command': l:executable + \ . ' format' + \ . ' --inplace' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/eslint.vim b/autoload/ale/fixers/eslint.vim index 62e692b1..f725875c 100644 --- a/autoload/ale/fixers/eslint.vim +++ b/autoload/ale/fixers/eslint.vim @@ -53,7 +53,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort " Use --fix-to-stdout with eslint_d if l:executable =~# 'eslint_d$' && ale#semver#GTE(a:version, [3, 19, 0]) return { - \ 'command': ale#node#Executable(a:buffer, l:executable) + \ 'command': ale#handlers#eslint#GetCdString(a:buffer) + \ . ale#node#Executable(a:buffer, l:executable) \ . ale#Pad(l:options) \ . ' --stdin-filename %s --stdin --fix-to-stdout', \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', @@ -63,7 +64,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort " 4.9.0 is the first version with --fix-dry-run if ale#semver#GTE(a:version, [4, 9, 0]) return { - \ 'command': ale#node#Executable(a:buffer, l:executable) + \ 'command': ale#handlers#eslint#GetCdString(a:buffer) + \ . ale#node#Executable(a:buffer, l:executable) \ . ale#Pad(l:options) \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', @@ -71,7 +73,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort endif return { - \ 'command': ale#node#Executable(a:buffer, l:executable) + \ 'command': ale#handlers#eslint#GetCdString(a:buffer) + \ . ale#node#Executable(a:buffer, l:executable) \ . ale#Pad(l:options) \ . (!empty(l:config) ? ' -c ' . ale#Escape(l:config) : '') \ . ' --fix %t', diff --git a/autoload/ale/fixers/ktlint.vim b/autoload/ale/fixers/ktlint.vim index cb975d6c..64d1340d 100644 --- a/autoload/ale/fixers/ktlint.vim +++ b/autoload/ale/fixers/ktlint.vim @@ -3,7 +3,6 @@ function! ale#fixers#ktlint#Fix(buffer) abort return { - \ 'command': ale#handlers#ktlint#GetCommand(a:buffer) . ' --format', - \ 'read_temporary_file': 1, + \ 'command': ale#handlers#ktlint#GetCommand(a:buffer) . ' --format' \} endfunction diff --git a/autoload/ale/fixers/latexindent.vim b/autoload/ale/fixers/latexindent.vim index b0a0884a..54f1231e 100644 --- a/autoload/ale/fixers/latexindent.vim +++ b/autoload/ale/fixers/latexindent.vim @@ -10,9 +10,7 @@ function! ale#fixers#latexindent#Fix(buffer) abort return { \ 'command': ale#Escape(l:executable) - \ . ' -l -w' + \ . ' -l' \ . (empty(l:options) ? '' : ' ' . l:options) - \ . ' %t', - \ 'read_temporary_file': 1, \} endfunction diff --git a/autoload/ale/fixers/ocamlformat.vim b/autoload/ale/fixers/ocamlformat.vim index 9b7c3e12..b12d2eb9 100644 --- a/autoload/ale/fixers/ocamlformat.vim +++ b/autoload/ale/fixers/ocamlformat.vim @@ -5,14 +5,13 @@ call ale#Set('ocaml_ocamlformat_executable', 'ocamlformat') call ale#Set('ocaml_ocamlformat_options', '') function! ale#fixers#ocamlformat#Fix(buffer) abort - let l:filename = expand('#' . a:buffer . ':p') let l:executable = ale#Var(a:buffer, 'ocaml_ocamlformat_executable') let l:options = ale#Var(a:buffer, 'ocaml_ocamlformat_options') return { \ 'command': ale#Escape(l:executable) \ . (empty(l:options) ? '' : ' ' . l:options) - \ . ' --name=' . ale#Escape(l:filename) + \ . ' --name=%s' \ . ' -' \} endfunction diff --git a/autoload/ale/fixers/prettier.vim b/autoload/ale/fixers/prettier.vim index 23120777..e0f4972e 100644 --- a/autoload/ale/fixers/prettier.vim +++ b/autoload/ale/fixers/prettier.vim @@ -34,6 +34,21 @@ function! ale#fixers#prettier#ProcessPrettierDOutput(buffer, output) abort return a:output endfunction +function! ale#fixers#prettier#GetProjectRoot(buffer) abort + let l:config = ale#path#FindNearestFile(a:buffer, '.prettierignore') + + if !empty(l:config) + return fnamemodify(l:config, ':h') + endif + + " Fall back to the directory of the buffer + return fnamemodify(bufname(a:buffer), ':p:h') +endfunction + +function! ale#fixers#prettier#CdProjectRoot(buffer) abort + return ale#path#CdString(ale#fixers#prettier#GetProjectRoot(a:buffer)) +endfunction + function! ale#fixers#prettier#ApplyFixForVersion(buffer, version) abort let l:executable = ale#fixers#prettier#GetExecutable(a:buffer) let l:options = ale#Var(a:buffer, 'javascript_prettier_options') @@ -97,7 +112,7 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version) abort " 1.4.0 is the first version with --stdin-filepath if ale#semver#GTE(a:version, [1, 4, 0]) return { - \ 'command': ale#path#BufferCdString(a:buffer) + \ 'command': ale#fixers#prettier#CdProjectRoot(a:buffer) \ . ale#Escape(l:executable) \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --stdin-filepath %s --stdin', diff --git a/autoload/ale/fixers/prettier_standard.vim b/autoload/ale/fixers/prettier_standard.vim index b6e0a6f9..9d982ff6 100644 --- a/autoload/ale/fixers/prettier_standard.vim +++ b/autoload/ale/fixers/prettier_standard.vim @@ -17,8 +17,8 @@ function! ale#fixers#prettier_standard#Fix(buffer) abort return { \ 'command': ale#Escape(ale#fixers#prettier_standard#GetExecutable(a:buffer)) - \ . ' %t' + \ . ' --stdin' + \ . ' --stdin-filepath=%s' \ . ' ' . l:options, - \ 'read_temporary_file': 1, \} endfunction diff --git a/autoload/ale/fixers/remark_lint.vim b/autoload/ale/fixers/remark_lint.vim new file mode 100644 index 00000000..3ce442f3 --- /dev/null +++ b/autoload/ale/fixers/remark_lint.vim @@ -0,0 +1,24 @@ +" Author: blyoa <blyoa110@gmail.com> +" Description: Fixing files with remark-lint. + +call ale#Set('markdown_remark_lint_executable', 'remark') +call ale#Set('markdown_remark_lint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('markdown_remark_lint_options', '') + +function! ale#fixers#remark_lint#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'markdown_remark_lint', [ + \ 'node_modules/remark-cli/cli.js', + \ 'node_modules/.bin/remark', + \]) +endfunction + +function! ale#fixers#remark_lint#Fix(buffer) abort + let l:executable = ale#fixers#remark_lint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'markdown_remark_lint_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : ''), + \} +endfunction + diff --git a/autoload/ale/fixers/rubocop.vim b/autoload/ale/fixers/rubocop.vim index 0c7441e4..cdfb014a 100644 --- a/autoload/ale/fixers/rubocop.vim +++ b/autoload/ale/fixers/rubocop.vim @@ -1,20 +1,40 @@ call ale#Set('ruby_rubocop_options', '') +call ale#Set('ruby_rubocop_auto_correct_all', 0) call ale#Set('ruby_rubocop_executable', 'rubocop') +" Rubocop fixer outputs diagnostics first and then the fixed +" output. These are delimited by a "=======" string that we +" look for to remove everything before it. +function! ale#fixers#rubocop#PostProcess(buffer, output) abort + let l:line = 0 + + for l:output in a:output + let l:line = l:line + 1 + + if l:output =~# "^=\\+$" + break + endif + endfor + + return a:output[l:line :] +endfunction + function! ale#fixers#rubocop#GetCommand(buffer) abort let l:executable = ale#Var(a:buffer, 'ruby_rubocop_executable') let l:config = ale#path#FindNearestFile(a:buffer, '.rubocop.yml') let l:options = ale#Var(a:buffer, 'ruby_rubocop_options') + let l:auto_correct_all = ale#Var(a:buffer, 'ruby_rubocop_auto_correct_all') return ale#ruby#EscapeExecutable(l:executable, 'rubocop') \ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '') \ . (!empty(l:options) ? ' ' . l:options : '') - \ . ' --auto-correct --force-exclusion %t' + \ . (l:auto_correct_all ? ' --auto-correct-all' : ' --auto-correct') + \ . ' --force-exclusion --stdin %s' endfunction function! ale#fixers#rubocop#Fix(buffer) abort return { \ 'command': ale#fixers#rubocop#GetCommand(a:buffer), - \ 'read_temporary_file': 1, + \ 'process_with': 'ale#fixers#rubocop#PostProcess' \} endfunction diff --git a/autoload/ale/fixers/standard.vim b/autoload/ale/fixers/standard.vim index cffa9f9d..46decebf 100644 --- a/autoload/ale/fixers/standard.vim +++ b/autoload/ale/fixers/standard.vim @@ -27,7 +27,7 @@ function! ale#fixers#standard#Fix(buffer) abort return { \ 'command': ale#node#Executable(a:buffer, l:executable) \ . (!empty(l:options) ? ' ' . l:options : '') - \ . ' --fix %t', + \ . ' --fix --stdin < %s > %t', \ 'read_temporary_file': 1, \} endfunction diff --git a/autoload/ale/fixers/tslint.vim b/autoload/ale/fixers/tslint.vim index b352af3a..15768fd5 100644 --- a/autoload/ale/fixers/tslint.vim +++ b/autoload/ale/fixers/tslint.vim @@ -16,7 +16,7 @@ function! ale#fixers#tslint#Fix(buffer) abort return { \ 'command': ale#node#Executable(a:buffer, l:executable) \ . l:tslint_config_option - \ . ' --fix %t', + \ . ' --outputAbsolutePaths --fix %t', \ 'read_temporary_file': 1, \} endfunction diff --git a/autoload/ale/handlers/ccls.vim b/autoload/ale/handlers/ccls.vim index 1e2aa318..290f5852 100644 --- a/autoload/ale/handlers/ccls.vim +++ b/autoload/ale/handlers/ccls.vim @@ -17,3 +17,10 @@ function! ale#handlers#ccls#GetProjectRoot(buffer) abort " Fall back on default project root detection. return ale#c#FindProjectRoot(a:buffer) endfunction + +function! ale#handlers#ccls#GetInitOpts(buffer, init_options_var) abort + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + let l:init_options = empty(l:build_dir) ? {} : {'compilationDatabaseDirectory': l:build_dir} + + return extend(l:init_options, ale#Var(a:buffer, a:init_options_var)) +endfunction diff --git a/autoload/ale/handlers/cppcheck.vim b/autoload/ale/handlers/cppcheck.vim index 6d8fa15d..7f68ba67 100644 --- a/autoload/ale/handlers/cppcheck.vim +++ b/autoload/ale/handlers/cppcheck.vim @@ -44,16 +44,21 @@ endfunction function! ale#handlers#cppcheck#HandleCppCheckFormat(buffer, lines) abort " Look for lines like the following. " - " [test.cpp:5]: (error) Array 'a[10]' accessed at index 10, which is out of bounds - let l:pattern = '\v^\[(.+):(\d+)\]: \(([a-z]+)\) (.+)$' + "test.cpp:974:6: error: Array 'n[3]' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\ + " n[3]=3; + " ^ + let l:pattern = '\v^(\f+):(\d+):(\d+): (\w+): (.*) \[(\w+)\]\' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) if ale#path#IsBufferPath(a:buffer, l:match[1]) call add(l:output, { - \ 'lnum': str2nr(l:match[2]), - \ 'type': l:match[3] is# 'error' ? 'E' : 'W', - \ 'text': l:match[4], + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \ 'sub_type': l:match[4] is# 'style' ? 'style' : '', + \ 'text': l:match[5], + \ 'code': l:match[6] \}) endif endfor diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim index 156b939f..e37d6902 100644 --- a/autoload/ale/handlers/eslint.vim +++ b/autoload/ale/handlers/eslint.vim @@ -1,6 +1,11 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Functions for working with eslint, for checking or fixing files. +let s:executables = [ +\ 'node_modules/.bin/eslint_d', +\ 'node_modules/eslint/bin/eslint.js', +\ 'node_modules/.bin/eslint', +\] let s:sep = has('win32') ? '\' : '/' call ale#Set('javascript_eslint_options', '') @@ -30,29 +35,39 @@ function! ale#handlers#eslint#FindConfig(buffer) abort endfunction function! ale#handlers#eslint#GetExecutable(buffer) abort - return ale#node#FindExecutable(a:buffer, 'javascript_eslint', [ - \ 'node_modules/.bin/eslint_d', - \ 'node_modules/eslint/bin/eslint.js', - \ 'node_modules/.bin/eslint', - \]) + return ale#node#FindExecutable(a:buffer, 'javascript_eslint', s:executables) endfunction -function! ale#handlers#eslint#GetCommand(buffer) abort - let l:executable = ale#handlers#eslint#GetExecutable(a:buffer) - - let l:options = ale#Var(a:buffer, 'javascript_eslint_options') - +" Given a buffer, return a command prefix string which changes directory +" as necessary for running ESLint. +function! ale#handlers#eslint#GetCdString(buffer) abort " ESLint 6 loads plugins/configs/parsers from the project root " By default, the project root is simply the CWD of the running process. " https://github.com/eslint/rfcs/blob/master/designs/2018-simplified-package-loading/README.md " https://github.com/dense-analysis/ale/issues/2787 - " Identify project root from presence of node_modules dir. + " + " If eslint is installed in a directory which contains the buffer, assume + " it is the ESLint project root. Otherwise, use nearest node_modules. " Note: If node_modules not present yet, can't load local deps anyway. - let l:modules_dir = ale#path#FindNearestDirectory(a:buffer, 'node_modules') - let l:project_dir = !empty(l:modules_dir) ? fnamemodify(l:modules_dir, ':h:h') : '' - let l:cd_command = !empty(l:project_dir) ? ale#path#CdString(l:project_dir) : '' + let l:executable = ale#node#FindNearestExecutable(a:buffer, s:executables) + + if !empty(l:executable) + let l:nmi = strridx(l:executable, 'node_modules') + let l:project_dir = l:executable[0:l:nmi - 2] + else + let l:modules_dir = ale#path#FindNearestDirectory(a:buffer, 'node_modules') + let l:project_dir = !empty(l:modules_dir) ? fnamemodify(l:modules_dir, ':h:h') : '' + endif + + return !empty(l:project_dir) ? ale#path#CdString(l:project_dir) : '' +endfunction + +function! ale#handlers#eslint#GetCommand(buffer) abort + let l:executable = ale#handlers#eslint#GetExecutable(a:buffer) + + let l:options = ale#Var(a:buffer, 'javascript_eslint_options') - return l:cd_command + return ale#handlers#eslint#GetCdString(a:buffer) \ . ale#node#Executable(a:buffer, l:executable) \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' -f json --stdin --stdin-filename %s' diff --git a/autoload/ale/handlers/gcc.vim b/autoload/ale/handlers/gcc.vim index ec16b977..0b37c98a 100644 --- a/autoload/ale/handlers/gcc.vim +++ b/autoload/ale/handlers/gcc.vim @@ -10,7 +10,7 @@ let s:pragma_error = '#pragma once in main file' " <stdin>:8:5: warning: conversion lacks type at end of format [-Wformat=] " <stdin>:10:27: error: invalid operands to binary - (have ‘int’ and ‘char *’) " -:189:7: note: $/${} is unnecessary on arithmetic variables. [SC2004] -let s:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+)$' +let s:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+)?:?(\d+)?:? ([^:]+): (.+)$' let s:inline_pattern = '\v inlined from .* at \<stdin\>:(\d+):(\d+):$' function! s:IsHeaderFile(filename) abort @@ -117,6 +117,23 @@ function! ale#handlers#gcc#HandleGCCFormat(buffer, lines) abort if !empty(l:output) if !has_key(l:output[-1], 'detail') let l:output[-1].detail = l:output[-1].text + + " handle macro expansion errors/notes + if l:match[5] =~? '^in expansion of macro ‘\w*\w’$' + " if the macro expansion is in the file we're in, add + " the lnum and col keys to the previous error + if l:match[1] is# '<stdin>' + \ && !has_key(l:output[-1], 'col') + let l:output[-1].lnum = str2nr(l:match[2]) + let l:output[-1].col = str2nr(l:match[3]) + else + " the error is not in the current file, and since + " macro expansion errors don't show the full path to + " the error from the current file, we have to just + " give out a generic error message + let l:output[-1].text = 'Error found in macro expansion. See :ALEDetail' + endif + endif endif let l:output[-1].detail = l:output[-1].detail . "\n" diff --git a/autoload/ale/handlers/go.vim b/autoload/ale/handlers/go.vim index f17cd862..c969669d 100644 --- a/autoload/ale/handlers/go.vim +++ b/autoload/ale/handlers/go.vim @@ -6,9 +6,12 @@ " " Author: Ben Paxton <ben@gn32.uk> " Description: moved to generic Golang file from govet +" +" Author: mostfunkyduck <mostfunkyduck@protonmail.com> +" Description: updated to work with go 1.14 function! ale#handlers#go#Handler(buffer, lines) abort - let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? ?(.+)$' + let l:pattern = '\v^%(vet: )?([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?:? ?(.+)$' let l:output = [] let l:dir = expand('#' . a:buffer . ':p:h') diff --git a/autoload/ale/handlers/hdl_checker.vim b/autoload/ale/handlers/hdl_checker.vim new file mode 100644 index 00000000..36dbd259 --- /dev/null +++ b/autoload/ale/handlers/hdl_checker.vim @@ -0,0 +1,71 @@ +" Author: suoto <andre820@gmail.com> +" Description: Adds support for HDL Code Checker, which wraps vcom/vlog, ghdl +" or xvhdl. More info on https://github.com/suoto/hdl_checker + +call ale#Set('hdl_checker_executable', 'hdl_checker') +call ale#Set('hdl_checker_config_file', has('unix') ? '.hdl_checker.config' : '_hdl_checker.config') +call ale#Set('hdl_checker_options', '') + +" Use this as a function so we can mock it on testing. Need to do this because +" test files are inside /testplugin (which refers to the ale repo), which will +" always have a .git folder +function! ale#handlers#hdl_checker#IsDotGit(path) abort + return ! empty(a:path) && isdirectory(a:path) +endfunction + +" Sould return (in order of preference) +" 1. Nearest config file +" 2. Nearest .git directory +" 3. The current path +function! ale#handlers#hdl_checker#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestFile( + \ a:buffer, + \ ale#Var(a:buffer, 'hdl_checker_config_file')) + + if !empty(l:project_root) + return fnamemodify(l:project_root, ':h') + endif + + " Search for .git to use as root + let l:project_root = ale#path#FindNearestDirectory(a:buffer, '.git') + + if ale#handlers#hdl_checker#IsDotGit(l:project_root) + return fnamemodify(l:project_root, ':h:h') + endif +endfunction + +function! ale#handlers#hdl_checker#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'hdl_checker_executable') +endfunction + +function! ale#handlers#hdl_checker#GetCommand(buffer) abort + let l:command = ale#Escape(ale#handlers#hdl_checker#GetExecutable(a:buffer)) . ' --lsp' + + " Add extra parameters only if config has been set + let l:options = ale#Var(a:buffer, 'hdl_checker_options') + + if ! empty(l:options) + let l:command = l:command . ' ' . l:options + endif + + return l:command +endfunction + +" To allow testing +function! ale#handlers#hdl_checker#GetInitOptions(buffer) abort + return {'project_file': ale#Var(a:buffer, 'hdl_checker_config_file')} +endfunction + +" Define the hdl_checker linter for a given filetype. +function! ale#handlers#hdl_checker#DefineLinter(filetype) abort + call ale#linter#Define(a:filetype, { + \ 'name': 'hdl-checker', + \ 'lsp': 'stdio', + \ 'language': a:filetype, + \ 'executable': function('ale#handlers#hdl_checker#GetExecutable'), + \ 'command': function('ale#handlers#hdl_checker#GetCommand'), + \ 'project_root': function('ale#handlers#hdl_checker#GetProjectRoot'), + \ 'initialization_options': function('ale#handlers#hdl_checker#GetInitOptions'), + \ }) +endfunction + diff --git a/autoload/ale/handlers/ktlint.vim b/autoload/ale/handlers/ktlint.vim index ad999485..77e7ab66 100644 --- a/autoload/ale/handlers/ktlint.vim +++ b/autoload/ale/handlers/ktlint.vim @@ -13,7 +13,7 @@ function! ale#handlers#ktlint#GetCommand(buffer) abort return ale#Escape(l:executable) \ . (empty(l:options) ? '' : ' ' . l:options) \ . (empty(l:rulesets) ? '' : ' ' . l:rulesets) - \ . ' %t' + \ . ' --stdin' endfunction function! ale#handlers#ktlint#GetRulesets(buffer) abort diff --git a/autoload/ale/handlers/markdownlint.vim b/autoload/ale/handlers/markdownlint.vim index daaa1d66..6c273bd0 100644 --- a/autoload/ale/handlers/markdownlint.vim +++ b/autoload/ale/handlers/markdownlint.vim @@ -2,15 +2,22 @@ " Description: Adds support for markdownlint function! ale#handlers#markdownlint#Handle(buffer, lines) abort - let l:pattern=': \(\d*\): \(MD\d\{3}\)\(\/\)\([A-Za-z0-9-]\+\)\(.*\)$' + let l:pattern=': \?\(\d\+\)\(:\(\d\+\)\?\)\? \(MD\d\{3}/[A-Za-z0-9-/]\+\) \(.*\)$' let l:output=[] for l:match in ale#util#GetMatches(a:lines, l:pattern) - call add(l:output, { + let l:result = ({ \ 'lnum': l:match[1] + 0, - \ 'text': '(' . l:match[2] . l:match[3] . l:match[4] . ')' . l:match[5], + \ 'code': l:match[4], + \ 'text': l:match[5], \ 'type': 'W', \}) + + if len(l:match[3]) > 0 + let l:result.col = (l:match[3] + 0) + endif + + call add(l:output, l:result) endfor return l:output diff --git a/autoload/ale/handlers/sh.vim b/autoload/ale/handlers/sh.vim index 75eaf71f..1e50cb89 100644 --- a/autoload/ale/handlers/sh.vim +++ b/autoload/ale/handlers/sh.vim @@ -4,17 +4,24 @@ function! ale#handlers#sh#GetShellType(buffer) abort let l:bang_line = get(getbufline(a:buffer, 1), 0, '') + let l:command = '' + " Take the shell executable from the hashbang, if we can. if l:bang_line[:1] is# '#!' " Remove options like -e, etc. let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g') + endif - for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh'] - if l:command =~# l:possible_shell . '\s*$' - return l:possible_shell - endif - endfor + " If we couldn't find a hashbang, try the filetype + if l:command is# '' + let l:command = &filetype endif + for l:possible_shell in ['bash', 'dash', 'ash', 'tcsh', 'csh', 'zsh', 'ksh', 'sh'] + if l:command =~# l:possible_shell . '\s*$' + return l:possible_shell + endif + endfor + return '' endfunction diff --git a/autoload/ale/handlers/shellcheck.vim b/autoload/ale/handlers/shellcheck.vim new file mode 100644 index 00000000..b16280f0 --- /dev/null +++ b/autoload/ale/handlers/shellcheck.vim @@ -0,0 +1,107 @@ +" Author: w0rp <devw0rp@gmail.com> +" Description: This file adds support for using the shellcheck linter + +function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort + let l:shell_type = ale#handlers#sh#GetShellType(a:buffer) + + if !empty(l:shell_type) + " Use the dash dialect for /bin/ash, etc. + if l:shell_type is# 'ash' + return 'dash' + endif + + return l:shell_type + endif + + " If there's no hashbang, try using Vim's buffer variables. + if getbufvar(a:buffer, 'is_bash', 0) + return 'bash' + elseif getbufvar(a:buffer, 'is_sh', 0) + return 'sh' + elseif getbufvar(a:buffer, 'is_kornshell', 0) + return 'ksh' + endif + + return '' +endfunction + +function! ale#handlers#shellcheck#GetCommand(buffer, version) abort + let l:options = ale#Var(a:buffer, 'sh_shellcheck_options') + let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions') + let l:dialect = ale#Var(a:buffer, 'sh_shellcheck_dialect') + let l:external_option = ale#semver#GTE(a:version, [0, 4, 0]) ? ' -x' : '' + let l:cd_string = ale#Var(a:buffer, 'sh_shellcheck_change_directory') + \ ? ale#path#BufferCdString(a:buffer) + \ : '' + + if l:dialect is# 'auto' + let l:dialect = ale#handlers#shellcheck#GetDialectArgument(a:buffer) + endif + + return l:cd_string + \ . '%e' + \ . (!empty(l:dialect) ? ' -s ' . l:dialect : '') + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '') + \ . l:external_option + \ . ' -f gcc -' +endfunction + +function! ale#handlers#shellcheck#Handle(buffer, lines) abort + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):(\d+)?:? ([^:]+): (.+) \[([^\]]+)\]$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + if l:match[4] is# 'error' + let l:type = 'E' + elseif l:match[4] is# 'note' + let l:type = 'I' + else + let l:type = 'W' + endif + + let l:item = { + \ 'lnum': str2nr(l:match[2]), + \ 'type': l:type, + \ 'text': l:match[5], + \ 'code': l:match[6], + \} + + if !empty(l:match[3]) + let l:item.col = str2nr(l:match[3]) + endif + + " If the filename is something like <stdin>, <nofile> or -, then + " this is an error for the file we checked. + if l:match[1] isnot# '-' && l:match[1][0] isnot# '<' + let l:item['filename'] = l:match[1] + endif + + call add(l:output, l:item) + endfor + + return l:output +endfunction + +function! ale#handlers#shellcheck#DefineLinter(filetype) abort + " This global variable can be set with a string of comma-separated error + " codes to exclude from shellcheck. For example: + " let g:ale_sh_shellcheck_exclusions = 'SC2002,SC2004' + call ale#Set('sh_shellcheck_exclusions', get(g:, 'ale_linters_sh_shellcheck_exclusions', '')) + call ale#Set('sh_shellcheck_executable', 'shellcheck') + call ale#Set('sh_shellcheck_dialect', 'auto') + call ale#Set('sh_shellcheck_options', '') + call ale#Set('sh_shellcheck_change_directory', 1) + + call ale#linter#Define(a:filetype, { + \ 'name': 'shellcheck', + \ 'executable': {buffer -> ale#Var(buffer, 'sh_shellcheck_executable')}, + \ 'command': {buffer -> ale#semver#RunWithVersionCheck( + \ buffer, + \ ale#Var(buffer, 'sh_shellcheck_executable'), + \ '%e --version', + \ function('ale#handlers#shellcheck#GetCommand'), + \ )}, + \ 'callback': 'ale#handlers#shellcheck#Handle', + \}) +endfunction diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim index 8fdd288c..38b4b866 100644 --- a/autoload/ale/hover.vim +++ b/autoload/ale/hover.vim @@ -42,6 +42,8 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort \&& exists('*balloon_show') \&& ale#Var(l:options.buffer, 'set_balloons') call balloon_show(a:response.body.displayString) + elseif get(l:options, 'truncated_echo', 0) + call ale#cursor#TruncatedEcho(split(a:response.body.displayString, "\n")[0]) elseif g:ale_hover_to_preview call ale#preview#Show(split(a:response.body.displayString, "\n"), { \ 'filetype': 'ale-preview.message', @@ -54,6 +56,137 @@ function! ale#hover#HandleTSServerResponse(conn_id, response) abort endif endfunction +" Convert a language name to another one. +" The language name could be an empty string or v:null +function! s:ConvertLanguageName(language) abort + return a:language +endfunction + +function! ale#hover#ParseLSPResult(contents) abort + let l:includes = {} + let l:highlights = [] + let l:lines = [] + let l:list = type(a:contents) is v:t_list ? a:contents : [a:contents] + let l:region_index = 0 + + for l:item in l:list + if !empty(l:lines) + call add(l:lines, '') + endif + + if type(l:item) is v:t_dict && has_key(l:item, 'kind') + if l:item.kind is# 'markdown' + " Handle markdown values as we handle strings below. + let l:item = get(l:item, 'value', '') + elseif l:item.kind is# 'plaintext' + " We shouldn't try to parse plaintext as markdown. + " Pass the lines on and skip parsing them. + call extend(l:lines, split(get(l:item, 'value', ''), "\n")) + + continue + endif + endif + + let l:marked_list = [] + + " If the item is a string, then we should parse it as Markdown text. + if type(l:item) is v:t_string + let l:fence_language = v:null + let l:fence_lines = [] + + for l:line in split(l:item, "\n") + if l:fence_language is v:null + " Look for the start of a code fence. (```python, etc.) + let l:match = matchlist(l:line, '^```\(.*\)$') + + if !empty(l:match) + let l:fence_language = l:match[1] + + if !empty(l:marked_list) + call add(l:fence_lines, '') + endif + else + if !empty(l:marked_list) + \&& l:marked_list[-1][0] isnot v:null + call add(l:marked_list, [v:null, ['']]) + endif + + call add(l:marked_list, [v:null, [l:line]]) + endif + elseif l:line =~# '^```$' + " When we hit the end of a code fence, pass the fenced + " lines on to the next steps below. + call add(l:marked_list, [l:fence_language, l:fence_lines]) + let l:fence_language = v:null + let l:fence_lines = [] + else + " Gather lines inside of a code fence. + call add(l:fence_lines, l:line) + endif + endfor + " If the result from the LSP server is a {language: ..., value: ...} + " Dictionary, then that should be interpreted as if it was: + " + " ```${language} + " ${value} + " ``` + elseif type(l:item) is v:t_dict + \&& has_key(l:item, 'language') + \&& type(l:item.language) is v:t_string + \&& has_key(l:item, 'value') + \&& type(l:item.value) is v:t_string + call add( + \ l:marked_list, + \ [l:item.language, split(l:item.value, "\n")], + \) + endif + + for [l:language, l:marked_lines] in l:marked_list + if l:language is v:null + " NOTE: We could handle other Markdown formatting here. + call map( + \ l:marked_lines, + \ 'substitute(v:val, ''\\_'', ''_'', ''g'')', + \) + else + let l:language = s:ConvertLanguageName(l:language) + + if !empty(l:language) + let l:includes[l:language] = printf( + \ 'syntax/%s.vim', + \ l:language, + \) + + let l:start = len(l:lines) + 1 + let l:end = l:start + len(l:marked_lines) + let l:region_index += 1 + + call add(l:highlights, 'syntax region' + \ . ' ALE_hover_' . l:region_index + \ . ' start=/\%' . l:start . 'l/' + \ . ' end=/\%' . l:end . 'l/' + \ . ' contains=@ALE_hover_' . l:language + \) + endif + endif + + call extend(l:lines, l:marked_lines) + endfor + endfor + + let l:include_commands = [] + + for [l:language, l:lang_path] in sort(items(l:includes)) + call add(l:include_commands, 'unlet! b:current_syntax') + call add( + \ l:include_commands, + \ printf('syntax include @ALE_hover_%s %s', l:language, l:lang_path), + \) + endfor + + return [l:include_commands + l:highlights, l:lines] +endfunction + function! ale#hover#HandleLSPResponse(conn_id, response) abort if has_key(a:response, 'id') \&& has_key(s:hover_map, a:response.id) @@ -80,37 +213,25 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort return endif - let l:result = l:result.contents - - if type(l:result) is v:t_string - " The result can be just a string. - let l:result = [l:result] - endif - - if type(l:result) is v:t_dict - " If the result is an object, then it's markup content. - let l:result = [l:result.value] - endif + let [l:commands, l:lines] = ale#hover#ParseLSPResult(l:result.contents) - if type(l:result) is v:t_list - " Replace objects with text values. - call map(l:result, 'type(v:val) is v:t_string ? v:val : v:val.value') - let l:str = join(l:result, "\n") - let l:str = substitute(l:str, '^\s*\(.\{-}\)\s*$', '\1', '') - - if !empty(l:str) - if get(l:options, 'hover_from_balloonexpr', 0) - \&& exists('*balloon_show') - \&& ale#Var(l:options.buffer, 'set_balloons') - call balloon_show(l:str) - elseif g:ale_hover_to_preview - call ale#preview#Show(split(l:str, "\n"), { - \ 'filetype': 'ale-preview.message', - \ 'stay_here': 1, - \}) - else - call ale#util#ShowMessage(l:str) - endif + if !empty(l:lines) + if get(l:options, 'hover_from_balloonexpr', 0) + \&& exists('*balloon_show') + \&& ale#Var(l:options.buffer, 'set_balloons') + call balloon_show(join(l:lines, "\n")) + elseif get(l:options, 'truncated_echo', 0) + call ale#cursor#TruncatedEcho(l:lines[0]) + elseif g:ale_hover_to_preview + call ale#preview#Show(l:lines, { + \ 'filetype': 'ale-preview.message', + \ 'stay_here': 1, + \ 'commands': l:commands, + \}) + else + call ale#util#ShowMessage(join(l:lines, "\n"), { + \ 'commands': l:commands, + \}) endif endif endif @@ -143,7 +264,10 @@ function! s:OnReady(line, column, opt, linter, lsp_details) abort " hover position probably won't make sense. call ale#lsp#NotifyForChanges(l:id, l:buffer) - let l:column = min([a:column, len(getbufline(l:buffer, a:line)[0])]) + let l:column = max([ + \ min([a:column, len(getbufline(l:buffer, a:line)[0])]), + \ 1, + \]) let l:message = ale#lsp#message#Hover(l:buffer, a:line, l:column) endif @@ -156,6 +280,7 @@ function! s:OnReady(line, column, opt, linter, lsp_details) abort \ 'column': l:column, \ 'hover_from_balloonexpr': get(a:opt, 'called_from_balloonexpr', 0), \ 'show_documentation': get(a:opt, 'show_documentation', 0), + \ 'truncated_echo': get(a:opt, 'truncated_echo', 0), \} endfunction @@ -181,6 +306,8 @@ function! ale#hover#Show(buffer, line, col, opt) abort endfor endfunction +let s:last_pos = [0, 0, 0] + " This function implements the :ALEHover command. function! ale#hover#ShowAtCursor() abort let l:buffer = bufnr('') @@ -189,6 +316,25 @@ function! ale#hover#ShowAtCursor() abort call ale#hover#Show(l:buffer, l:pos[1], l:pos[2], {}) endfunction +function! ale#hover#ShowTruncatedMessageAtCursor() abort + let l:buffer = bufnr('') + let l:pos = getpos('.')[0:2] + + if l:pos != s:last_pos + let s:last_pos = l:pos + let [l:info, l:loc] = ale#util#FindItemAtCursor(l:buffer) + + if empty(l:loc) + call ale#hover#Show( + \ l:buffer, + \ l:pos[1], + \ l:pos[2], + \ {'truncated_echo': 1}, + \) + endif + endif +endfunction + " This function implements the :ALEDocumentation command. function! ale#hover#ShowDocumentationAtCursor() abort let l:buffer = bufnr('') diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index a85f06e2..645c25f9 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -14,6 +14,7 @@ let s:default_ale_linter_aliases = { \ 'csh': 'sh', \ 'javascriptreact': ['javascript', 'jsx'], \ 'plaintex': 'tex', +\ 'ps1': 'powershell', \ 'rmarkdown': 'r', \ 'rmd': 'r', \ 'systemverilog': 'verilog', @@ -31,7 +32,7 @@ let s:default_ale_linter_aliases = { " " No linters are used for plaintext files by default. " -" Only cargo is enabled for Rust by default. +" Only cargo and rls are enabled for Rust by default. " rpmlint is disabled by default because it can result in code execution. " hhast is disabled by default because it executes code in the project root. " @@ -44,8 +45,8 @@ let s:default_ale_linters = { \ 'help': [], \ 'perl': ['perlcritic'], \ 'perl6': [], -\ 'python': ['flake8', 'mypy', 'pylint'], -\ 'rust': ['cargo'], +\ 'python': ['flake8', 'mypy', 'pylint', 'pyright'], +\ 'rust': ['cargo', 'rls'], \ 'spec': [], \ 'text': [], \ 'vue': ['eslint', 'vls'], @@ -76,10 +77,6 @@ function! s:IsBoolean(value) abort return type(a:value) is v:t_number && (a:value == 0 || a:value == 1) endfunction -function! s:LanguageGetter(buffer) dict abort - return l:self.language -endfunction - function! ale#linter#PreProcess(filetype, linter) abort if type(a:linter) isnot v:t_dict throw 'The linter object must be a Dictionary' @@ -113,14 +110,7 @@ function! ale#linter#PreProcess(filetype, linter) abort if !l:needs_executable if has_key(a:linter, 'executable') - \|| has_key(a:linter, 'executable_callback') - throw '`executable` and `executable_callback` cannot be used when lsp == ''socket''' - endif - elseif has_key(a:linter, 'executable_callback') - let l:obj.executable_callback = a:linter.executable_callback - - if !s:IsCallback(l:obj.executable_callback) - throw '`executable_callback` must be a callback if defined' + throw '`executable` cannot be used when lsp == ''socket''' endif elseif has_key(a:linter, 'executable') let l:obj.executable = a:linter.executable @@ -130,54 +120,12 @@ function! ale#linter#PreProcess(filetype, linter) abort throw '`executable` must be a String or Function if defined' endif else - throw 'Either `executable` or `executable_callback` must be defined' + throw '`executable` must be defined' endif if !l:needs_command if has_key(a:linter, 'command') - \|| has_key(a:linter, 'command_callback') - \|| has_key(a:linter, 'command_chain') - throw '`command` and `command_callback` and `command_chain` cannot be used when lsp == ''socket''' - endif - elseif has_key(a:linter, 'command_chain') - let l:obj.command_chain = a:linter.command_chain - - if type(l:obj.command_chain) isnot v:t_list - throw '`command_chain` must be a List' - endif - - if empty(l:obj.command_chain) - throw '`command_chain` must contain at least one item' - endif - - let l:link_index = 0 - - for l:link in l:obj.command_chain - let l:err_prefix = 'The `command_chain` item ' . l:link_index . ' ' - - if !s:IsCallback(get(l:link, 'callback')) - throw l:err_prefix . 'must define a `callback` function' - endif - - if has_key(l:link, 'output_stream') - if type(l:link.output_stream) isnot v:t_string - \|| index(['stdout', 'stderr', 'both'], l:link.output_stream) < 0 - throw l:err_prefix . '`output_stream` flag must be ' - \ . "'stdout', 'stderr', or 'both'" - endif - endif - - if has_key(l:link, 'read_buffer') && !s:IsBoolean(l:link.read_buffer) - throw l:err_prefix . 'value for `read_buffer` must be `0` or `1`' - endif - - let l:link_index += 1 - endfor - elseif has_key(a:linter, 'command_callback') - let l:obj.command_callback = a:linter.command_callback - - if !s:IsCallback(l:obj.command_callback) - throw '`command_callback` must be a callback if defined' + throw '`command` cannot be used when lsp == ''socket''' endif elseif has_key(a:linter, 'command') let l:obj.command = a:linter.command @@ -187,22 +135,12 @@ function! ale#linter#PreProcess(filetype, linter) abort throw '`command` must be a String or Function if defined' endif else - throw 'Either `command`, `executable_callback`, `command_chain` ' - \ . 'must be defined' - endif - - if ( - \ has_key(a:linter, 'command') - \ + has_key(a:linter, 'command_chain') - \ + has_key(a:linter, 'command_callback') - \) > 1 - throw 'Only one of `command`, `command_callback`, or `command_chain` ' - \ . 'should be set' + throw '`command` must be defined' endif if !l:needs_address - if has_key(a:linter, 'address') || has_key(a:linter, 'address_callback') - throw '`address` or `address_callback` cannot be used when lsp != ''socket''' + if has_key(a:linter, 'address') + throw '`address` cannot be used when lsp != ''socket''' endif elseif has_key(a:linter, 'address') if type(a:linter.address) isnot v:t_string @@ -211,41 +149,17 @@ function! ale#linter#PreProcess(filetype, linter) abort endif let l:obj.address = a:linter.address - 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` or `address_callback` must be defined for getting the LSP address' + throw '`address` must be defined for getting the LSP address' endif if l:needs_lsp_details - if has_key(a:linter, 'language_callback') - if has_key(a:linter, 'language') - throw 'Only one of `language` or `language_callback` ' - \ . 'should be set' - endif - - let l:obj.language_callback = get(a:linter, 'language_callback') + " Default to using the filetype as the language. + let l:obj.language = get(a:linter, 'language', a:filetype) - if !s:IsCallback(l:obj.language_callback) - throw '`language_callback` must be a callback for LSP linters' - endif - else - " Default to using the filetype as the language. - let l:Language = get(a:linter, 'language', a:filetype) - - if type(l:Language) is v:t_string - " Make 'language_callback' return the 'language' value. - let l:obj.language = l:Language - let l:obj.language_callback = function('s:LanguageGetter') - elseif type(l:Language) is v:t_func - let l:obj.language_callback = l:Language - else - throw '`language` must be a String or Funcref' - endif + if type(l:obj.language) isnot v:t_string + \&& type(l:obj.language) isnot v:t_func + throw '`language` must be a String or Funcref if defined' endif if has_key(a:linter, 'project_root') @@ -253,16 +167,10 @@ function! ale#linter#PreProcess(filetype, linter) abort if type(l:obj.project_root) isnot v:t_string \&& type(l:obj.project_root) isnot v:t_func - throw '`project_root` must be a String or Function if defined' - endif - elseif has_key(a:linter, 'project_root_callback') - let l:obj.project_root_callback = a:linter.project_root_callback - - if !s:IsCallback(l:obj.project_root_callback) - throw '`project_root_callback` must be a callback if defined' + throw '`project_root` must be a String or Function' endif else - throw '`project_root` or `project_root_callback` must be defined for LSP linters' + throw '`project_root` must be defined for LSP linters' endif if has_key(a:linter, 'completion_filter') @@ -273,37 +181,16 @@ function! ale#linter#PreProcess(filetype, linter) abort 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') + if has_key(a:linter, 'initialization_options') let l:obj.initialization_options = a:linter.initialization_options if type(l:obj.initialization_options) isnot v:t_dict \&& type(l:obj.initialization_options) isnot v:t_func - throw '`initialization_options` must be a String or Function if defined' + throw '`initialization_options` must be a Dictionary or Function if defined' endif endif - if has_key(a:linter, 'lsp_config_callback') - if has_key(a:linter, 'lsp_config') - throw 'Only one of `lsp_config` or `lsp_config_callback` should be set' - endif - - let l:obj.lsp_config_callback = a:linter.lsp_config_callback - - if !s:IsCallback(l:obj.lsp_config_callback) - throw '`lsp_config_callback` must be a callback if defined' - endif - elseif has_key(a:linter, 'lsp_config') + if has_key(a:linter, 'lsp_config') if type(a:linter.lsp_config) isnot v:t_dict \&& type(a:linter.lsp_config) isnot v:t_func throw '`lsp_config` must be a Dictionary or Function if defined' @@ -324,21 +211,17 @@ function! ale#linter#PreProcess(filetype, linter) abort " file on disk. let l:obj.lint_file = get(a:linter, 'lint_file', 0) - if !s:IsBoolean(l:obj.lint_file) - throw '`lint_file` must be `0` or `1`' + if !s:IsBoolean(l:obj.lint_file) && type(l:obj.lint_file) isnot v:t_func + throw '`lint_file` must be `0`, `1`, or a Function' endif " An option indicating that the buffer should be read. - let l:obj.read_buffer = get(a:linter, 'read_buffer', !l:obj.lint_file) + let l:obj.read_buffer = get(a:linter, 'read_buffer', 1) if !s:IsBoolean(l:obj.read_buffer) throw '`read_buffer` must be `0` or `1`' endif - if l:obj.lint_file && l:obj.read_buffer - throw 'Only one of `lint_file` or `read_buffer` can be `1`' - endif - let l:obj.aliases = get(a:linter, 'aliases', []) if type(l:obj.aliases) isnot v:t_list @@ -346,14 +229,6 @@ function! ale#linter#PreProcess(filetype, linter) abort throw '`aliases` must be a List of String values' endif - for l:key in filter(keys(a:linter), 'v:val[-9:] is# ''_callback'' || v:val is# ''command_chain''') - if !get(g:, 'ale_ignore_2_4_warnings') - execute 'echom l:key . '' is deprecated. Use `let g:ale_ignore_2_4_warnings = 1` to disable this message.''' - endif - - break - endfor - return l:obj endfunction @@ -519,11 +394,19 @@ function! ale#linter#Get(original_filetypes) abort return reverse(l:combined_linters) endfunction +function! ale#linter#RemoveIgnored(buffer, filetype, linters) abort + " Apply ignore lists for linters only if needed. + let l:ignore_config = ale#Var(a:buffer, 'linters_ignore') + let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') + + return !empty(l:ignore_config) || l:disable_lsp + \ ? ale#engine#ignore#Exclude(a:filetype, a:linters, l:ignore_config, l:disable_lsp) + \ : a:linters +endfunction + " Given a buffer and linter, get the executable String for the linter. function! ale#linter#GetExecutable(buffer, linter) abort - let l:Executable = has_key(a:linter, 'executable_callback') - \ ? function(a:linter.executable_callback) - \ : a:linter.executable + let l:Executable = a:linter.executable return type(l:Executable) is v:t_func \ ? l:Executable(a:buffer) @@ -531,24 +414,21 @@ function! ale#linter#GetExecutable(buffer, linter) abort endfunction " Given a buffer and linter, get the command String for the linter. -" The command_chain key is not supported. function! ale#linter#GetCommand(buffer, linter) abort - let l:Command = has_key(a:linter, 'command_callback') - \ ? function(a:linter.command_callback) - \ : a:linter.command + let l:Command = a:linter.command - return type(l:Command) is v:t_func - \ ? l:Command(a:buffer) - \ : l:Command + return type(l:Command) is v:t_func ? l:Command(a:buffer) : l:Command endfunction " Given a buffer and linter, get the address for connecting to the server. function! ale#linter#GetAddress(buffer, linter) abort - let l:Address = has_key(a:linter, 'address_callback') - \ ? function(a:linter.address_callback) - \ : a:linter.address + let l:Address = a:linter.address + + return type(l:Address) is v:t_func ? l:Address(a:buffer) : l:Address +endfunction + +function! ale#linter#GetLanguage(buffer, linter) abort + let l:Language = a:linter.language - return type(l:Address) is v:t_func - \ ? l:Address(a:buffer) - \ : l:Address + return type(l:Language) is v:t_func ? l:Language(a:buffer) : l:Language endfunction diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 2509174e..7d99e9d2 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -64,6 +64,9 @@ endfunction " Used only in tests. function! ale#lsp#GetConnections() abort + " This command will throw from the sandbox. + let &l:equalprg=&l:equalprg + return s:connections endfunction @@ -196,14 +199,26 @@ function! s:UpdateCapabilities(conn, capabilities) abort let a:conn.capabilities.hover = 1 endif + if type(get(a:capabilities, 'hoverProvider')) is v:t_dict + let a:conn.capabilities.hover = 1 + endif + if get(a:capabilities, 'referencesProvider') is v:true let a:conn.capabilities.references = 1 endif + if type(get(a:capabilities, 'referencesProvider')) is v:t_dict + let a:conn.capabilities.references = 1 + endif + if get(a:capabilities, 'renameProvider') is v:true let a:conn.capabilities.rename = 1 endif + if type(get(a:capabilities, 'renameProvider')) is v:t_dict + let a:conn.capabilities.rename = 1 + endif + if !empty(get(a:capabilities, 'completionProvider')) let a:conn.capabilities.completion = 1 endif @@ -220,13 +235,25 @@ function! s:UpdateCapabilities(conn, capabilities) abort let a:conn.capabilities.definition = 1 endif + if type(get(a:capabilities, 'definitionProvider')) is v:t_dict + let a:conn.capabilities.definition = 1 + endif + if get(a:capabilities, 'typeDefinitionProvider') is v:true let a:conn.capabilities.typeDefinition = 1 endif + if type(get(a:capabilities, 'typeDefinitionProvider')) is v:t_dict + let a:conn.capabilities.typeDefinition = 1 + endif + if get(a:capabilities, 'workspaceSymbolProvider') is v:true let a:conn.capabilities.symbol_search = 1 endif + + if type(get(a:capabilities, 'workspaceSymbolProvider')) is v:t_dict + let a:conn.capabilities.symbol_search = 1 + endif endfunction " Update a connection's configuration dictionary and notify LSP servers @@ -425,6 +452,7 @@ function! ale#lsp#StartProgram(conn_id, executable, command) abort endif if l:started && !l:conn.is_tsserver + let l:conn.initialized = 0 call s:SendInitMessage(l:conn) endif diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index e4148ceb..dcd76e8f 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -34,7 +34,11 @@ 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 . '$') + let l:escaped_name = escape( + \ fnameescape(l:filename), + \ has('win32') ? '^' : '^,}]' + \) + let l:buffer = bufnr('^' . l:escaped_name . '$') let l:info = get(g:ale_buffer_info, l:buffer, {}) if empty(l:info) @@ -52,7 +56,11 @@ endfunction function! s:HandleTSServerDiagnostics(response, error_type) abort let l:linter_name = 'tsserver' - let l:buffer = bufnr('^' . a:response.body.file . '$') + let l:escaped_name = escape( + \ fnameescape(a:response.body.file), + \ has('win32') ? '^' : '^,}]' + \) + let l:buffer = bufnr('^' . l:escaped_name . '$') let l:info = get(g:ale_buffer_info, l:buffer, {}) if empty(l:info) @@ -227,7 +235,7 @@ function! ale#lsp_linter#OnInit(linter, details, Callback) abort let l:command = a:details.command let l:config = ale#lsp_linter#GetConfig(l:buffer, a:linter) - let l:language_id = ale#util#GetFunction(a:linter.language_callback)(l:buffer) + let l:language_id = ale#linter#GetLanguage(l:buffer, a:linter) call ale#lsp#UpdateConfig(l:conn_id, l:buffer, l:config) @@ -265,7 +273,14 @@ function! s:StartLSP(options, address, executable, command) abort call ale#lsp#MarkConnectionAsTsserver(l:conn_id) endif - let l:command = ale#command#FormatCommand(l:buffer, a:executable, a:command, 0, v:false)[1] + let l:command = ale#command#FormatCommand( + \ l:buffer, + \ a:executable, + \ a:command, + \ 0, + \ v:false, + \ [], + \)[1] let l:command = ale#job#PrepareCommand(l:buffer, l:command) let l:ready = ale#lsp#StartProgram(l:conn_id, a:executable, l:command) endif diff --git a/autoload/ale/node.vim b/autoload/ale/node.vim index 69060122..9b9b335a 100644 --- a/autoload/ale/node.vim +++ b/autoload/ale/node.vim @@ -12,6 +12,18 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort return ale#Var(a:buffer, a:base_var_name . '_executable') endif + let l:nearest = ale#node#FindNearestExecutable(a:buffer, a:path_list) + + if !empty(l:nearest) + return l:nearest + endif + + return ale#Var(a:buffer, a:base_var_name . '_executable') +endfunction + +" 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. +function! ale#node#FindNearestExecutable(buffer, path_list) abort for l:path in a:path_list let l:executable = ale#path#FindNearestFile(a:buffer, l:path) @@ -20,7 +32,7 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort endif endfor - return ale#Var(a:buffer, a:base_var_name . '_executable') + return '' endfunction " Create a executable string which executes a Node.js script command with a diff --git a/autoload/ale/organize_imports.vim b/autoload/ale/organize_imports.vim index bc9b829e..e89c832c 100644 --- a/autoload/ale/organize_imports.vim +++ b/autoload/ale/organize_imports.vim @@ -15,7 +15,7 @@ function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort call ale#code_action#HandleCodeAction({ \ 'description': 'Organize Imports', \ 'changes': l:file_code_edits, - \}) + \}, v:false) endfunction function! s:OnReady(linter, lsp_details) abort diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim index 30550503..fed95ccd 100644 --- a/autoload/ale/path.vim +++ b/autoload/ale/path.vim @@ -24,6 +24,14 @@ function! ale#path#Simplify(path) abort return substitute(simplify(l:win_path), '^\\\+', '\', 'g') " no-custom-checks endfunction +" Simplify a path without a Windows drive letter. +" This function can be used for checking if paths are equal. +function! ale#path#RemoveDriveLetter(path) abort + return has('win32') && a:path[1:2] is# ':\' + \ ? ale#path#Simplify(a:path[2:]) + \ : ale#path#Simplify(a:path) +endfunction + " Given a buffer and a filename, find the nearest file by searching upwards " through the paths relative to the given buffer. function! ale#path#FindNearestFile(buffer, filename) abort @@ -74,15 +82,19 @@ endfunction function! ale#path#CdString(directory) abort if has('win32') return 'cd /d ' . ale#Escape(a:directory) . ' && ' - else - return 'cd ' . ale#Escape(a:directory) . ' && ' endif + + return 'cd ' . ale#Escape(a:directory) . ' && ' endfunction " Output 'cd <buffer_filename_directory> && ' " This function can be used changing the directory for a linter command. function! ale#path#BufferCdString(buffer) abort - return ale#path#CdString(fnamemodify(bufname(a:buffer), ':p:h')) + if has('win32') + return 'cd /d %s:h && ' + endif + + return 'cd %s:h && ' endfunction " Return 1 if a path is an absolute path. @@ -95,7 +107,7 @@ function! ale#path#IsAbsolute(filename) abort return a:filename[:0] is# '/' || a:filename[1:2] is# ':\' endfunction -let s:temp_dir = ale#path#Simplify(fnamemodify(ale#util#Tempname(), ':h')) +let s:temp_dir = ale#path#Simplify(fnamemodify(ale#util#Tempname(), ':h:h')) " Given a filename, return 1 if the file represents some temporary file " created by Vim. diff --git a/autoload/ale/preview.vim b/autoload/ale/preview.vim index 6d58aca9..8b94aa7a 100644 --- a/autoload/ale/preview.vim +++ b/autoload/ale/preview.vim @@ -1,6 +1,22 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Preview windows for showing whatever information in. +if !has_key(s:, 'last__list') + let s:last_list = [] +endif + +if !has_key(s:, 'last_options') + let s:last_options = {} +endif + +function! ale#preview#SetLastSelection(item_list, options) abort + let s:last_list = a:item_list + let s:last_options = { + \ 'open_in': get(a:options, 'open_in', 'current-buffer'), + \ 'use_relative_paths': get(a:options, 'use_relative_paths', 0), + \} +endfunction + " Open a preview window and show some lines in it. " A second argument can be passed as a Dictionary with options. They are... " @@ -23,6 +39,10 @@ function! ale#preview#Show(lines, ...) abort setlocal readonly let &l:filetype = get(l:options, 'filetype', 'ale-preview') + for l:command in get(l:options, 'commands', []) + call execute(l:command) + endfor + if get(l:options, 'stay_here') wincmd p endif @@ -67,9 +87,19 @@ function! ale#preview#ShowSelection(item_list, ...) abort call ale#preview#Show(l:lines, {'filetype': 'ale-preview-selection'}) let b:ale_preview_item_list = a:item_list + let b:ale_preview_item_open_in = get(l:options, 'open_in', 'current-buffer') + + " Remember preview state, so we can repeat it later. + call ale#preview#SetLastSelection(a:item_list, l:options) endfunction -function! s:Open(open_in_tab) abort +function! ale#preview#RepeatSelection() abort + if !empty(s:last_list) + call ale#preview#ShowSelection(s:last_list, s:last_options) + endif +endfunction + +function! s:Open(open_in) abort let l:item_list = get(b:, 'ale_preview_item_list', []) let l:item = get(l:item_list, getpos('.')[1] - 1, {}) @@ -77,22 +107,20 @@ function! s:Open(open_in_tab) abort return endif - if !a:open_in_tab - :q! - endif + :q! call ale#util#Open( \ l:item.filename, \ l:item.line, \ l:item.column, - \ {'open_in_tab': a:open_in_tab}, + \ {'open_in': a:open_in}, \) endfunction -function! ale#preview#OpenSelectionInBuffer() abort - call s:Open(0) +function! ale#preview#OpenSelection() abort + call s:Open(b:ale_preview_item_open_in) endfunction function! ale#preview#OpenSelectionInTab() abort - call s:Open(1) + call s:Open('tab') endfunction diff --git a/autoload/ale/references.vim b/autoload/ale/references.vim index b9725e1e..38ff0d3d 100644 --- a/autoload/ale/references.vim +++ b/autoload/ale/references.vim @@ -1,3 +1,5 @@ +let g:ale_default_navigation = get(g:, 'ale_default_navigation', 'buffer') + let s:references_map = {} " Used to get the references map in tests. @@ -99,7 +101,8 @@ function! s:OnReady(line, column, options, linter, lsp_details) abort let l:request_id = ale#lsp#Send(l:id, l:message) let s:references_map[l:request_id] = { - \ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0 + \ 'use_relative_paths': has_key(a:options, 'use_relative_paths') ? a:options.use_relative_paths : 0, + \ 'open_in': get(a:options, 'open_in', 'current-buffer'), \} endfunction @@ -110,10 +113,24 @@ function! ale#references#Find(...) abort for l:option in a:000 if l:option is? '-relative' let l:options.use_relative_paths = 1 + elseif l:option is? '-tab' + let l:options.open_in = 'tab' + elseif l:option is? '-split' + let l:options.open_in = 'split' + elseif l:option is? '-vsplit' + let l:options.open_in = 'vsplit' endif endfor endif + if !has_key(l:options, 'open_in') + let l:default_navigation = ale#Var(bufnr(''), 'default_navigation') + + if index(['tab', 'split', 'vsplit'], l:default_navigation) >= 0 + let l:options.open_in = l:default_navigation + endif + endif + let l:buffer = bufnr('') let [l:line, l:column] = getpos('.')[1:2] let l:column = min([l:column, len(getline(l:line))]) diff --git a/autoload/ale/rename.vim b/autoload/ale/rename.vim index 02b7b579..64952e63 100644 --- a/autoload/ale/rename.vim +++ b/autoload/ale/rename.vim @@ -80,7 +80,32 @@ function! ale#rename#HandleTSServerResponse(conn_id, response) abort call ale#code_action#HandleCodeAction({ \ 'description': 'rename', \ 'changes': l:changes, - \}) + \}, v:true) +endfunction + +function! s:getChanges(workspace_edit) abort + let l:changes = {} + + if has_key(a:workspace_edit, 'changes') && !empty(a:workspace_edit.changes) + return a:workspace_edit.changes + elseif has_key(a:workspace_edit, 'documentChanges') + let l:document_changes = [] + + if type(a:workspace_edit.documentChanges) is v:t_dict + \ && has_key(a:workspace_edit.documentChanges, 'edits') + call add(l:document_changes, a:workspace_edit.documentChanges) + elseif type(a:workspace_edit.documentChanges) is v:t_list + let l:document_changes = a:workspace_edit.documentChanges + endif + + for l:text_document_edit in l:document_changes + let l:filename = l:text_document_edit.textDocument.uri + let l:edits = l:text_document_edit.edits + let l:changes[l:filename] = l:edits + endfor + endif + + return l:changes endfunction function! ale#rename#HandleLSPResponse(conn_id, response) abort @@ -94,9 +119,9 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort return endif - let l:workspace_edit = a:response.result + let l:changes_map = s:getChanges(a:response.result) - if !has_key(l:workspace_edit, 'changes') || empty(l:workspace_edit.changes) + if empty(l:changes_map) call s:message('No changes received from server') return @@ -104,8 +129,8 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort let l:changes = [] - for l:file_name in keys(l:workspace_edit.changes) - let l:text_edits = l:workspace_edit.changes[l:file_name] + for l:file_name in keys(l:changes_map) + let l:text_edits = l:changes_map[l:file_name] let l:text_changes = [] for l:edit in l:text_edits @@ -134,7 +159,7 @@ function! ale#rename#HandleLSPResponse(conn_id, response) abort call ale#code_action#HandleCodeAction({ \ 'description': 'rename', \ 'changes': l:changes, - \}) + \}, v:true) endif endfunction diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim index db0e1ab6..8109c60e 100644 --- a/autoload/ale/sign.vim +++ b/autoload/ale/sign.vim @@ -23,7 +23,7 @@ let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000) let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', 0) let g:ale_sign_highlight_linenrs = get(g:, 'ale_sign_highlight_linenrs', 0) -let s:supports_sign_groups = has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) +let s:supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614') if !hlexists('ALEErrorSign') highlight link ALEErrorSign error diff --git a/autoload/ale/test.vim b/autoload/ale/test.vim index 082d91ff..6fcbf35e 100644 --- a/autoload/ale/test.vim +++ b/autoload/ale/test.vim @@ -145,8 +145,8 @@ function! ale#test#WaitForJobs(deadline) abort " end, but before handlers are run. sleep 10ms - " We must check the buffer data again to see if new jobs started - " for command_chain linters. + " We must check the buffer data again to see if new jobs started for + " linters with chained commands. let l:has_new_jobs = 0 " Check again to see if any jobs are running. diff --git a/autoload/ale/uri.vim b/autoload/ale/uri.vim index 934637d9..e71c6340 100644 --- a/autoload/ale/uri.vim +++ b/autoload/ale/uri.vim @@ -1,9 +1,18 @@ -" This probably doesn't handle Unicode characters well. +function! s:EncodeChar(char) abort + let l:result = '' + + for l:index in range(strlen(a:char)) + let l:result .= printf('%%%02x', char2nr(a:char[l:index])) + endfor + + return l:result +endfunction + function! ale#uri#Encode(value) abort return substitute( \ a:value, \ '\([^a-zA-Z0-9\\/$\-_.!*''(),]\)', - \ '\=printf(''%%%02x'', char2nr(submatch(1)))', + \ '\=s:EncodeChar(submatch(1))', \ 'g' \) endfunction @@ -12,7 +21,7 @@ function! ale#uri#Decode(value) abort return substitute( \ a:value, \ '%\(\x\x\)', - \ '\=nr2char(''0x'' . submatch(1))', + \ '\=printf("%c", str2nr(submatch(1), 16))', \ 'g' \) endfunction diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index 8d166625..1f396377 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -16,7 +16,9 @@ endfunction " Vim 8 does not support echoing long messages from asynchronous callbacks, " but NeoVim does. Small messages can be echoed in Vim 8, and larger messages " have to be shown in preview windows. -function! ale#util#ShowMessage(string) abort +function! ale#util#ShowMessage(string, ...) abort + let l:options = get(a:000, 0, {}) + if !has('nvim') call ale#preview#CloseIfTypeMatches('ale-preview.message') endif @@ -25,10 +27,13 @@ function! ale#util#ShowMessage(string) abort if has('nvim') || (a:string !~? "\n" && len(a:string) < &columns) execute 'echo a:string' else - call ale#preview#Show(split(a:string, "\n"), { - \ 'filetype': 'ale-preview.message', - \ 'stay_here': 1, - \}) + call ale#preview#Show(split(a:string, "\n"), extend( + \ { + \ 'filetype': 'ale-preview.message', + \ 'stay_here': 1, + \ }, + \ l:options, + \)) endif endfunction @@ -91,17 +96,17 @@ endfunction " options['open_in'] can be: " current-buffer (default) " tab -" vertical-split -" horizontal-split +" split +" vsplit function! ale#util#Open(filename, line, column, options) abort let l:open_in = get(a:options, 'open_in', 'current-buffer') let l:args_to_open = '+' . a:line . ' ' . fnameescape(a:filename) if l:open_in is# 'tab' call ale#util#Execute('tabedit ' . l:args_to_open) - elseif l:open_in is# 'horizontal-split' + elseif l:open_in is# 'split' call ale#util#Execute('split ' . l:args_to_open) - elseif l:open_in is# 'vertical-split' + elseif l:open_in is# 'vsplit' call ale#util#Execute('vsplit ' . l:args_to_open) elseif bufnr(a:filename) isnot bufnr('') " Open another file only if we need to. @@ -418,7 +423,10 @@ function! ale#util#Writefile(buffer, lines, filename) abort \ ? map(copy(a:lines), 'substitute(v:val, ''\r*$'', ''\r'', '''')') \ : a:lines - call writefile(l:corrected_lines, a:filename, 'S') " no-custom-checks + " Set binary flag if buffer doesn't have eol and nofixeol to avoid appending newline + let l:flags = !getbufvar(a:buffer, '&eol') && exists('+fixeol') && !&fixeol ? 'bS' : 'S' + + call writefile(l:corrected_lines, a:filename, l:flags) " no-custom-checks endfunction if !exists('s:patial_timers') @@ -476,3 +484,44 @@ endfunction function! ale#util#Input(message, value) abort return input(a:message, a:value) endfunction + +function! ale#util#HasBuflineApi() abort + return exists('*deletebufline') && exists('*setbufline') +endfunction + +" Sets buffer contents to lines +function! ale#util#SetBufferContents(buffer, lines) abort + let l:has_bufline_api = ale#util#HasBuflineApi() + + if !l:has_bufline_api && a:buffer isnot bufnr('') + return + endif + + " If the file is in DOS mode, we have to remove carriage returns from + " the ends of lines before calling setline(), or we will see them + " twice. + let l:new_lines = getbufvar(a:buffer, '&fileformat') is# 'dos' + \ ? map(copy(a:lines), 'substitute(v:val, ''\r\+$'', '''', '''')') + \ : a:lines + let l:first_line_to_remove = len(l:new_lines) + 1 + + " Use a Vim API for setting lines in other buffers, if available. + if l:has_bufline_api + call setbufline(a:buffer, 1, l:new_lines) + call deletebufline(a:buffer, l:first_line_to_remove, '$') + " Fall back on setting lines the old way, for the current buffer. + else + let l:old_line_length = line('$') + + if l:old_line_length >= l:first_line_to_remove + let l:save = winsaveview() + silent execute + \ l:first_line_to_remove . ',' . l:old_line_length . 'd_' + call winrestview(l:save) + endif + + call setline(1, l:new_lines) + endif + + return l:new_lines +endfunction diff --git a/doc/ale-bats.txt b/doc/ale-bats.txt new file mode 100644 index 00000000..cf2199ec --- /dev/null +++ b/doc/ale-bats.txt @@ -0,0 +1,13 @@ +=============================================================================== +ALE Bats Integration *ale-bats-options* + + +=============================================================================== +shellcheck *ale-bats-shellcheck* + +The `shellcheck` linter for Bats uses the sh options for `shellcheck`; see: +|ale-sh-shellcheck|. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-c.txt b/doc/ale-c.txt index c9eb79db..b0d94b8e 100644 --- a/doc/ale-c.txt +++ b/doc/ale-c.txt @@ -1,22 +1,36 @@ =============================================================================== ALE C Integration *ale-c-options* +For basic checking of problems with C files, ALE offers the `cc` linter, which +runs either `clang`, or `gcc`. See |ale-c-cc|. + =============================================================================== Global Options +g:ale_c_always_make *g:ale_c_always_make* + *b:ale_c_always_make* + Type: |Number| + Default: `has('unix') && !has('macunix')` + + If set to `1`, use `--always-make` for `make`, which means that output will + always be parsed from `make` dry runs with GNU make. BSD `make` does not + support this option, so you probably want to turn this option off when using + a BSD variant. + + g:ale_c_build_dir_names *g:ale_c_build_dir_names* *b:ale_c_build_dir_names* Type: |List| Default: `['build', 'bin']` - A list of directory names to be used when searching upwards from cpp - files to discover compilation databases with. For directory named `'foo'`, - ALE will search for `'foo/compile_commands.json'` in all directories on and above - the directory containing the cpp file to find path to compilation database. - This feature is useful for the clang tools wrapped around LibTooling (namely - here, clang-tidy) + A list of directory names to be used when searching upwards from cpp files + to discover compilation databases with. For directory named `'foo'`, ALE + will search for `'foo/compile_commands.json'` in all directories on and + above the directory containing the cpp file to find path to compilation + database. This feature is useful for the clang tools wrapped around + LibTooling (namely here, clang-tidy) g:ale_c_build_dir *g:ale_c_build_dir* @@ -37,7 +51,7 @@ g:ale_c_build_dir *g:ale_c_build_dir* g:ale_c_parse_compile_commands *g:ale_c_parse_compile_commands* *b:ale_c_parse_compile_commands* Type: |Number| - Default: `0` + Default: `1` If set to `1`, ALE will parse `compile_commands.json` files to automatically determine flags for C or C++ compilers. ALE will first search for the @@ -45,9 +59,6 @@ g:ale_c_parse_compile_commands *g:ale_c_parse_compile_commands* `compile_commands.json` files in the directories for |g:ale_c_build_dir_names|. - If |g:ale_c_parse_makefile| or |b:ale_c_parse_makefile| is set to `1`, the - output of `make -n` will be preferred over `compile_commands.json` files. - g:ale_c_parse_makefile *g:ale_c_parse_makefile* *b:ale_c_parse_makefile* @@ -58,24 +69,102 @@ g:ale_c_parse_makefile *g:ale_c_parse_makefile* set for C or C++ compilers. This can make it easier to determine the correct build flags to use for different files. + NOTE: When using this option on BSD, you may need to set + |g:ale_c_always_make| to `0`, and `make -n` will not provide consistent + results if binaries have already been built, so use `make clean` when + editing your files. + + WARNING: Running `make -n` automatically can execute arbitrary code, even + though it's supposed to be a dry run, so enable this option with care. You + might prefer to use the buffer-local version of the option instead with + |g:ale_pattern_options|, or you own code for checking which project you're + in. + + You might want to disable this option if `make -n` takes too long to run for + projects you work on. + + If |g:ale_c_parse_compile_commands| or |b:ale_c_parse_compile_commands| is + set to `1`, flags taken from `compile_commands.json` will be preferred over + `make -n` output. + =============================================================================== -clang *ale-c-clang* +astyle *ale-c-astyle* -g:ale_c_clang_executable *g:ale_c_clang_executable* - *b:ale_c_clang_executable* +g:ale_c_astyle_executable *g:ale_c_astyle_executable* + *b:ale_c_astyle_executable* Type: |String| - Default: `'clang'` + Default: `'astyle'` + + This variable can be changed to use a different executable for astyle. + + +g:ale_c_astyle_project_options *g:ale_c_astyle_project_options* + *b:ale_c_astyle_project_options* + Type: |String| + Default: `''` + + This variable can be changed to use an option file for project level + configurations. Provide only the filename of the option file that should be + present at the project's root directory. + + For example, if .astylrc is specified, the file is searched in the parent + directories of the source file's directory. + + +=============================================================================== +cc *ale-c-cc* + *ale-c-gcc* + *ale-c-clang* + +g:ale_c_cc_executable *g:ale_c_cc_executable* + *b:ale_c_cc_executable* + Type: |String| + Default: `'<auto>'` + + This variable can be changed to use a different executable for a C compiler. - This variable can be changed to use a different executable for clang. + ALE will try to use `clang` if Clang is available, otherwise ALE will + default to checking C code with `gcc`. -g:ale_c_clang_options *g:ale_c_clang_options* - *b:ale_c_clang_options* +g:ale_c_cc_options *g:ale_c_cc_options* + *b:ale_c_cc_options* Type: |String| Default: `'-std=c11 -Wall'` - This variable can be changed to modify flags given to clang. + This variable can be change to modify flags given to the C compiler. + + +=============================================================================== +ccls *ale-c-ccls* + +g:ale_c_ccls_executable *g:ale_c_ccls_executable* + *b:ale_c_ccls_executable* + Type: |String| + Default: `'ccls'` + + This variable can be changed to use a different executable for ccls. + + +g:ale_c_ccls_init_options *g:ale_c_ccls_init_options* + *b:ale_c_ccls_init_options* + Type: |Dictionary| + Default: `{}` + + This variable can be changed to customize ccls initialization options. + Example: > + { + \ 'cacheDirectory': '/tmp/ccls', + \ 'cacheFormat': 'binary', + \ 'diagnostics': { + \ 'onOpen': 0, + \ 'opChange': 1000, + \ }, + \ } +< + Visit https://github.com/MaskRay/ccls/wiki/Initialization-options for all + available options and explanations. =============================================================================== @@ -261,25 +350,6 @@ g:ale_c_flawfinder_error_severity *g:ale_c_flawfinder_error_severity* =============================================================================== -gcc *ale-c-gcc* - -g:ale_c_gcc_executable *g:ale_c_gcc_executable* - *b:ale_c_gcc_executable* - Type: |String| - Default: `'gcc'` - - This variable can be changed to use a different executable for gcc. - - -g:ale_c_gcc_options *g:ale_c_gcc_options* - *b:ale_c_gcc_options* - Type: |String| - Default: `'-std=c11 -Wall'` - - This variable can be change to modify flags given to gcc. - - -=============================================================================== uncrustify *ale-c-uncrustify* g:ale_c_uncrustify_executable *g:ale_c_uncrustify_executable* @@ -299,35 +369,4 @@ g:ale_c_uncrustify_options *g:ale_c_uncrustify_options* =============================================================================== -ccls *ale-c-ccls* - -g:ale_c_ccls_executable *g:ale_c_ccls_executable* - *b:ale_c_ccls_executable* - Type: |String| - Default: `'ccls'` - - This variable can be changed to use a different executable for ccls. - - -g:ale_c_ccls_init_options *g:ale_c_ccls_init_options* - *b:ale_c_ccls_init_options* - Type: |Dictionary| - Default: `{}` - - This variable can be changed to customize ccls initialization options. - Example: > - { - \ 'cacheDirectory': '/tmp/ccls', - \ 'cacheFormat': 'binary', - \ 'diagnostics': { - \ 'onOpen': 0, - \ 'opChange': 1000, - \ }, - \ } -< - Visit https://github.com/MaskRay/ccls/wiki/Initialization-options for all - available options and explanations. - - -=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-cloudformation.txt b/doc/ale-cloudformation.txt index 59c6af06..9724403b 100644 --- a/doc/ale-cloudformation.txt +++ b/doc/ale-cloudformation.txt @@ -7,8 +7,40 @@ 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 +Website: https://github.com/awslabs/cfn-python-lint +Installation +------------------------------------------------------------------------------- + + +Install cfn-python-lint using either pip or brew: > + +`pip install cfn-lint`. If pip is not available, run +`python setup.py clean --all` then `python setup.py install`. + + Homebrew (macOS): + +`brew install cfn-lint` + +< +Configuration +------------------------------------------------------------------------------- + +To get cloudformation linter to work on only CloudFormation files we must set +the buffer |filetype| to yaml.cloudformation. +This causes ALE to lint the file with linters configured for cloudformation and +yaml files. + +Just put: + +> + + au BufRead,BufNewFile *.template.yaml set filetype=yaml.cloudformation + +< + +on `ftdetect/cloudformation.vim` + +This will get both cloudformation and yaml linters to work on any file with `.template.yaml` ext. =============================================================================== 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 ead3be28..17894e6e 100644 --- a/doc/ale-cpp.txt +++ b/doc/ale-cpp.txt @@ -1,12 +1,16 @@ =============================================================================== ALE C++ Integration *ale-cpp-options* +For basic checking of problems with C++ files, ALE offers the `cc` linter, +which runs either `clang++`, or `gcc`. See |ale-cpp-cc|. + =============================================================================== Global Options The following C options also apply to some C++ linters too. +* |g:ale_c_always_make| * |g:ale_c_build_dir_names| * |g:ale_c_build_dir| * |g:ale_c_parse_makefile| @@ -14,41 +18,82 @@ The following C options also apply to some C++ linters too. =============================================================================== -clang *ale-cpp-clang* +astyle *ale-cpp-astyle* -g:ale_cpp_clang_executable *g:ale_cpp_clang_executable* - *b:ale_cpp_clang_executable* +g:ale_cpp_astyle_executable *g:ale_cpp_astyle_executable* + *b:ale_cpp_astyle_executable* Type: |String| - Default: `'clang++'` + Default: `'astyle'` - This variable can be changed to use a different executable for clang. + This variable can be changed to use a different executable for astyle. -g:ale_cpp_clang_options *g:ale_cpp_clang_options* - *b:ale_cpp_clang_options* +g:ale_cpp_astyle_project_options *g:ale_cpp_astyle_project_options* + *b:ale_cpp_astyle_project_options* Type: |String| - Default: `'-std=c++14 -Wall'` + Default: `''` + + This variable can be changed to use an option file for project level + configurations. Provide only the filename of the option file that should be + present at the project's root directory. - This variable can be changed to modify flags given to clang. + For example, if .astylrc is specified, the file is searched in the parent + directories of the source file's directory. =============================================================================== -clangd *ale-cpp-clangd* +cc *ale-cpp-cc* + *ale-cpp-gcc* + *ale-cpp-clang* -g:ale_cpp_clangd_executable *g:ale_cpp_clangd_executable* - *b:ale_cpp_clangd_executable* +g:ale_cpp_cc_executable *g:ale_cpp_cc_executable* + *b:ale_cpp_cc_executable* Type: |String| - Default: `'clangd'` + Default: `'<auto>'` - This variable can be changed to use a different executable for clangd. + This variable can be changed to use a different executable for a C++ compiler. + ALE will try to use `clang++` if Clang is available, otherwise ALE will + default to checking C++ code with `gcc`. -g:ale_cpp_clangd_options *g:ale_cpp_clangd_options* - *b:ale_cpp_clangd_options* + +g:ale_cpp_cc_options *g:ale_cpp_cc_options* + *b:ale_cpp_cc_options* Type: |String| - Default: `''` + Default: `'-std=c++14 -Wall'` + + This variable can be change to modify flags given to the C++ compiler. + + +=============================================================================== +ccls *ale-cpp-ccls* + +g:ale_cpp_ccls_executable *g:ale_cpp_ccls_executable* + *b:ale_cpp_ccls_executable* + Type: |String| + Default: `'ccls'` + + This variable can be changed to use a different executable for ccls. - This variable can be changed to modify flags given to clangd. + +g:ale_cpp_ccls_init_options *g:ale_cpp_ccls_init_options* + *b:ale_cpp_ccls_init_options* + Type: |Dictionary| + Default: `{}` + + This variable can be changed to customize ccls initialization options. + Example: > + { + \ 'cacheDirectory': '/tmp/ccls', + \ 'cacheFormat': 'binary', + \ 'diagnostics': { + \ 'onOpen': 0, + \ 'opChange': 1000, + \ }, + \ } +< + Visit https://github.com/MaskRay/ccls/wiki/Initialization-options for all + available options and explanations. =============================================================================== @@ -83,6 +128,25 @@ g:ale_cpp_clangcheck_options *g:ale_cpp_clangcheck_options* =============================================================================== +clangd *ale-cpp-clangd* + +g:ale_cpp_clangd_executable *g:ale_cpp_clangd_executable* + *b:ale_cpp_clangd_executable* + Type: |String| + Default: `'clangd'` + + This variable can be changed to use a different executable for clangd. + + +g:ale_cpp_clangd_options *g:ale_cpp_clangd_options* + *b:ale_cpp_clangd_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to clangd. + + +=============================================================================== clang-format *ale-cpp-clangformat* See |ale-c-clangformat| for information about the available options. @@ -272,60 +336,10 @@ g:ale_cpp_flawfinder_options *g:ale-cpp-flawfinder* =============================================================================== -gcc *ale-cpp-gcc* - -g:ale_cpp_gcc_executable *g:ale_cpp_gcc_executable* - *b:ale_cpp_gcc_executable* - Type: |String| - Default: `'gcc'` - - This variable can be changed to use a different executable for gcc. - - -g:ale_cpp_gcc_options *g:ale_cpp_gcc_options* - *b:ale_cpp_gcc_options* - Type: |String| - Default: `'-std=c++14 -Wall'` - - This variable can be changed to modify flags given to gcc. - - -=============================================================================== uncrustify *ale-cpp-uncrustify* See |ale-c-uncrustify| for information about the available options. =============================================================================== -ccls *ale-cpp-ccls* - -g:ale_cpp_ccls_executable *g:ale_cpp_ccls_executable* - *b:ale_cpp_ccls_executable* - Type: |String| - Default: `'ccls'` - - This variable can be changed to use a different executable for ccls. - - -g:ale_cpp_ccls_init_options *g:ale_cpp_ccls_init_options* - *b:ale_cpp_ccls_init_options* - Type: |Dictionary| - Default: `{}` - - This variable can be changed to customize ccls initialization options. - Example: > - { - \ 'cacheDirectory': '/tmp/ccls', - \ 'cacheFormat': 'binary', - \ 'diagnostics': { - \ 'onOpen': 0, - \ 'opChange': 1000, - \ }, - \ } -< - Visit https://github.com/MaskRay/ccls/wiki/Initialization-options for all - available options and explanations. - - -=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-development.txt b/doc/ale-development.txt index faa570c1..afd9798f 100644 --- a/doc/ale-development.txt +++ b/doc/ale-development.txt @@ -13,6 +13,7 @@ CONTENTS *ale-development-contents* 4. Testing ALE..........................|ale-development-tests| 4.1. Writing Linter Tests.............|ale-development-linter-tests| 4.2. Writing Fixer Tests..............|ale-development-fixer-tests| + 4.3. Running Tests in a Windows VM....|ale-development-windows-tests| =============================================================================== 1. Introduction *ale-development-introduction* @@ -170,6 +171,11 @@ will run all of the tests in Vader, Vint checks, and several Bash scripts for finding extra issues. Run `./run-tests --help` to see all of the options the script supports. Note that the script supports selecting particular test files. +Once you get used to dealing with Vim and NeoVim compatibility issues, you +probably want to use `./run-tests --fast -q` for running tests with only the +fastest available Vim version, and with success messages from tests +suppressed. + Generally write tests for any changes you make. The following types of tests are recommended for the following types of code. @@ -354,4 +360,80 @@ given the above setup are as follows. =============================================================================== +4.3 Running Tests in a Windows VM *ale-development-windows-tests* + +Tests are run for ALE in a build of Vim 8 for Windows via AppVeyor. These +tests can frequently break due to minor differences in paths and how escaping +is done for commands on Windows. If you are a Linux or Mac user, running these +tests locally can be difficult. Here is a process that will make that easier. + +First, you want to install a Windows image with VirtualBox. Install VirtualBox +and grab a VirtualBox image for Windows such as from here: +https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/ + +NOTE: If you need to enter a password for the virtual machine at any point, +the password is "Passw0rd!" without the double quotes. + +NOTE: If your trial period for Windows runs out, run the commands like the +wallpaper tells you to. + +Your virtual machine will need to have PowerShell installed. Before you go any +further, confirm that PowerShell is installed in your Windows virtual machine. + +Consult the VirtualBox documentation on how to install "Guest Additions." +You probably want to install "Guest Additions" for most things to work +properly. + +After you've loaded your virtual machine image, go into "Settings" for your +virtual machine, and "Shared Folders." Add a shared folder with the name +"ale", and set the "Folder Path" to the path to your ALE repository, for +example: "/home/w0rp/ale" + +Find out which drive letter "ale" has been mounted as in Windows. We'll use +"E:" as the drive letter, for example. Open the command prompt as an +administrator by typing in `cmd` in the start menu, right clicking on the +command prompt application, and clicking "Run as administrator." Click "Yes" +when prompted to ask if you're sure you want to run the command prompt. You +should type in the following command to mount the "ale" directory for testing, +where "E:" is replaced with your drive letter. > + + mklink /D C:\testplugin E: +< +Close the administrator Command Prompt, and try running the command +`type C:\testplugin\LICENSE` in a new Command Prompt which you are NOT running +as administrator. You should see the license for ALE in your terminal. After +you have confirmed that you have mounted ALE on your machine, search in the +Start Menu for "power shell," run PowerShell as an administrator, and issue +the following commands to install the correct Vim and Vader versions for +running tests. > + + Add-Type -A System.IO.Compression.FileSystem + + Invoke-WebRequest ftp://ftp.vim.org/pub/vim/pc/vim80-586w32.zip -OutFile C:\vim.zip + [IO.Compression.ZipFile]::ExtractToDirectory('C:\vim.zip', 'C:\vim') + rm C:\vim.zip + + Invoke-WebRequest ftp://ftp.vim.org/pub/vim/pc/vim80-586rt.zip -OutFile C:\rt.zip + [IO.Compression.ZipFile]::ExtractToDirectory('C:\rt.zip', 'C:\vim') + rm C:\rt.zip + + Invoke-WebRequest https://github.com/junegunn/vader.vim/archive/c6243dd81c98350df4dec608fa972df98fa2a3af.zip -OutFile C:\vader.zip + [IO.Compression.ZipFile]::ExtractToDirectory('C:\vader.zip', 'C:\') + mv C:\vader.vim-c6243dd81c98350df4dec608fa972df98fa2a3af C:\vader + rm C:\vader.zip +< +After you have finished installing everything, you can run all of the tests +in Windows by opening a Command Prompt NOT as an administrator by navigating +to the directory where you've mounted the ALE code, which must be named +`C:\testplugin`, and by running the `run-tests.bat` batch file. > + + cd C:\testplugin + run-tests +< +It will probably take several minutes for all of the tests to run. Be patient. +You can run a specific test by passing the filename as an argument to the +batch file, for example: `run-tests test/test_c_flag_parsing.vader` . This will +give you results much more quickly. + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-elixir.txt b/doc/ale-elixir.txt index 5864f728..de9daacf 100644 --- a/doc/ale-elixir.txt +++ b/doc/ale-elixir.txt @@ -6,7 +6,7 @@ ALE Elixir Integration *ale-elixir-options* mix *ale-elixir-mix* -The `mix` linter is disabled by default, as it can bee too expensive to run. +The `mix` linter is disabled by default, as it can be too expensive to run. See `:help g:ale_linters` diff --git a/doc/ale-go.txt b/doc/ale-go.txt index be53783e..5c0791bc 100644 --- a/doc/ale-go.txt +++ b/doc/ale-go.txt @@ -220,6 +220,25 @@ g:ale_go_govet_options *g:ale_go_govet_options* =============================================================================== +revive *ale-go-revive* + +g:ale_go_revive_executable *g:ale_go_revive_executable* + *b:ale_go_revive_executable* + Type: |String| + Default: `'revive'` + + This variable can be set to change the revive executable path. + + +g:ale_go_revive_options *g:ale_go_revive_options* + *b:ale_go_revive_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the revive + + +=============================================================================== staticcheck *ale-go-staticcheck* g:ale_go_staticcheck_options *g:ale_go_staticcheck_options* diff --git a/doc/ale-handlebars.txt b/doc/ale-handlebars.txt index 5daec5b3..4a5a3870 100644 --- a/doc/ale-handlebars.txt +++ b/doc/ale-handlebars.txt @@ -14,7 +14,8 @@ ember-template-lint *ale-handlebars-embertemplatelint* g:ale_handlebars_embertemplatelint_executable *g:ale_handlebars_embertemplatelint_executable* - Type: |String| *b:ale_handlebars_embertemplatelint_executable* + *b:ale_handlebars_embertemplatelint_executable* + Type: |String| Default: `'ember-template-lint'` See |ale-integrations-local-executables| @@ -22,7 +23,8 @@ g:ale_handlebars_embertemplatelint_executable g:ale_handlebars_embertemplatelint_use_global *g:ale_handlebars_embertemplatelint_use_global* - Type: |Number| *b:ale_handlebars_embertemplatelint_use_global* + *b:ale_handlebars_embertemplatelint_use_global* + Type: |Number| Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale-java.txt b/doc/ale-java.txt index 32f0e6eb..0debc1af 100644 --- a/doc/ale-java.txt +++ b/doc/ale-java.txt @@ -46,7 +46,7 @@ javac *ale-java-javac* g:ale_java_javac_classpath *g:ale_java_javac_classpath* *b:ale_java_javac_classpath* - Type: |String| + Type: |String| or |List| Default: `''` This variable can be set to change the global classpath for Java. @@ -67,6 +67,30 @@ g:ale_java_javac_options *g:ale_java_javac_options* This variable can be set to pass additional options to javac. +g:ale_java_javac_sourcepath *g:ale_java_javac_sourcepath* + *b:ale_java_javac_sourcepath* + Type: |String| or |List| + Default: `''` + +This variable can set multiple source code paths, the source code path is a +relative path (relative to the project root directory). + +Example: + +String type: +Note that the unix system separator is a colon(`:`) window system +is a semicolon(`;`). +> + let g:ale_java_javac_sourcepath = 'build/gen/source/xx/main:build/gen/source' +< +List type: +> + let g:ale_java_javac_sourcepath = [ + \ 'build/generated/source/querydsl/main', + \ 'target/generated-sources/source/querydsl/main' + \ ] +< + =============================================================================== google-java-format *ale-java-google-java-format* @@ -107,18 +131,26 @@ javalsp *ale-java-javalsp* To enable Java LSP linter you need to download and build the vscode-javac language server from https://github.com/georgewfraser/java-language-server. -Simply download the source code and then build a distribution: - scripts/link_mac.sh +Before building the language server you need to install pre-requisites: npm, +maven, and protobuf. You also need to have Java 13 and JAVA_HOME properly +set. + +After downloading the source code and installing all pre-requisites you can +build the language server with the included build.sh script: + + scripts/build.sh -or +This will create launch scripts for Linux, Mac, and Windows in the dist folder +within the repo: - scripts/link_windows.sh + - lang_server_linux.sh + - lang_server_mac.sh + - lang_server_windows.sh -This generates a dist/mac or dist/windows directory that contains the -language server. To let ALE use this language server you need to set the +To let ALE use this language server you need to set the g:ale_java_javalsp_executable variable to the absolute path of the launcher -executable in this directory. +executable for your platform. g:ale_java_javalsp_executable *g:ale_java_javalsp_executable* *b:ale_java_javalsp_executable* @@ -128,7 +160,7 @@ g:ale_java_javalsp_executable *g:ale_java_javalsp_executable* This variable must be set to the absolute path of the language server launcher executable. For example: > - let g:ale_java_javalsp_executable=/java-language-server/dist/mac/bin/launcher + let g:ale_java_javalsp_executable=/java-language-server/dist/lang_server_linux.sh < g:ale_java_javalsp_config *g:ale_java_javalsp_config* @@ -140,7 +172,7 @@ The javalsp linter automatically detects external depenencies for Maven and Gradle projects. In case the javalsp fails to detect some of them, you can specify them setting a dictionary to |g:ale_java_javalsp_config| variable. > - let g:ale_java_javalsp_executable = + let g:ale_java_javalsp_config = \ { \ 'java': { \ 'externalDependencies': [ @@ -222,6 +254,17 @@ g:ale_java_eclipselsp_workspace_path *g:ale_java_eclipselsp_workspace_path* absolute path of the Eclipse workspace. If not set this value will be set to the parent folder of the project root. +g:ale_java_eclipselsp_javaagent *g:ale_java_eclipselsp_javaagent* + *b:ale_java_eclipselsp_javaagent* + + Type: |String| + Default: `''` + + A variable to add java agent for annotation processing such as Lombok. + If you have multiple java agent files, use space to separate them. For example: +> + let g:ale_java_eclipselsp_javaagent='/eclipse/lombok.jar /eclipse/jacoco.jar' +< =============================================================================== uncrustify *ale-java-uncrustify* diff --git a/doc/ale-javascript.txt b/doc/ale-javascript.txt index ea0a7089..13059eaa 100644 --- a/doc/ale-javascript.txt +++ b/doc/ale-javascript.txt @@ -138,7 +138,7 @@ g:ale_javascript_flow_use_respect_pragma By default, ALE will use the `--respect-pragma` option for `flow`, so only files with the `@flow` pragma are checked by ALE. This option can be set to - `0` to disable that behaviour, so all files can be checked by `flow`. + `0` to disable that behavior, so all files can be checked by `flow`. =============================================================================== diff --git a/doc/ale-markdown.txt b/doc/ale-markdown.txt index 4e27eb91..99848878 100644 --- a/doc/ale-markdown.txt +++ b/doc/ale-markdown.txt @@ -3,6 +3,17 @@ ALE Markdown Integration *ale-markdown-options* =============================================================================== +markdownlint *ale-markdown-markdownlint* + +g:ale_markdown_markdownlint_options *g:ale_markdown_markdownlint_options* + *b:ale_markdown_markdownlint_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to markdownlint. + + +=============================================================================== mdl *ale-markdown-mdl* g:ale_markdown_mdl_executable *g:ale_markdown_mdl_executable* diff --git a/doc/ale-php.txt b/doc/ale-php.txt index 645decd7..9fe868f8 100644 --- a/doc/ale-php.txt +++ b/doc/ale-php.txt @@ -189,42 +189,55 @@ g:ale_php_psalm_executable *g:ale_php_psalm_executable* This variable sets the executable used for psalm. -g:ale_psalm_langserver_options *g:ale_psalm_langserver_options* - *b:ale_psalm_langserver_options* + +g:ale_php_psalm_options *g:ale_php_psalm_options* + *b:ale_php_psalm_options* Type: |String| Default: `''` This variable can be set to pass additional options to psalm. + +g:ale_php_psalm_use_global *g:ale_php_psalm_use_global* + *b:ale_php_psalm_use_global* + Type: |Boolean| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + =============================================================================== -php-cs-fixer *ale-php-php-cs-fixer* +php-cs-fixer *ale-php-php-cs-fixer* -g:ale_php_cs_fixer_executable *g:ale_php_cs_fixer_executable* - *b:ale_php_cs_fixer_executable* +g:ale_php_cs_fixer_executable *g:ale_php_cs_fixer_executable* + *b:ale_php_cs_fixer_executable* Type: |String| Default: `'php-cs-fixer'` This variable sets executable used for php-cs-fixer. -g:ale_php_cs_fixer_use_global *g:ale_php_cs_fixer_use_global* - *b:ale_php_cs_fixer_use_global* - Type: |Boolean| - Default: `get(g:, 'ale_use_global_executables', 0)` - - This variable force globally installed fixer. -g:ale_php_cs_fixer_options *g:ale_php_cs_fixer_options* - *b:ale_php_cs_fixer_options* +g:ale_php_cs_fixer_options *g:ale_php_cs_fixer_options* + *b:ale_php_cs_fixer_options* Type: |String| Default: `''` This variable can be set to pass additional options to php-cs-fixer. + +g:ale_php_cs_fixer_use_global *g:ale_php_cs_fixer_use_global* + *b:ale_php_cs_fixer_use_global* + Type: |Boolean| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + =============================================================================== -php *ale-php-php* +php *ale-php-php* -g:ale_php_php_executable *g:ale_php_php_executable* - *b:ale_php_php_executable* +g:ale_php_php_executable *g:ale_php_php_executable* + *b:ale_php_php_executable* Type: |String| Default: `'php'` diff --git a/doc/ale-powershell.txt b/doc/ale-powershell.txt index c28ef9ea..485c9bd0 100644 --- a/doc/ale-powershell.txt +++ b/doc/ale-powershell.txt @@ -25,13 +25,6 @@ Installation Install PSScriptAnalyzer by any means, so long as it can be automatically imported in PowerShell. -Some PowerShell plugins set the filetype of files to `ps1`. To continue using -these plugins, use the ale_linter_aliases global to alias `ps1` to `powershell` - -> - " Allow ps1 filetype to work with powershell linters - let g:ale_linter_aliases = {'ps1': 'powershell'} -< g:ale_powershell_psscriptanalyzer_executable *g:ale_powershell_psscriptanalyzer_executable* diff --git a/doc/ale-python.txt b/doc/ale-python.txt index 93f1d668..6b1a6d33 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -169,13 +169,14 @@ flake8 *ale-python-flake8* g:ale_python_flake8_change_directory *g:ale_python_flake8_change_directory* *b:ale_python_flake8_change_directory* - Type: |Number| - Default: `1` + Type: |String| + Default: `project` - If set to `1`, ALE will switch to the directory the Python file being - checked with `flake8` is in before checking it. This helps `flake8` find - configuration files more easily. This option can be turned off if you want - to control the directory Python is executed from yourself. + If set to `project`, ALE will switch to the project root before checking file. + If set to `file`, ALE will switch to directory the Python file being + checked with `flake8` is in before checking it. + You can turn it off with `off` option if you want to control the directory + Python is executed from yourself. g:ale_python_flake8_executable *g:ale_python_flake8_executable* @@ -598,6 +599,7 @@ g:ale_python_pylint_use_msg_id *g:ale_python_pylint_use_msg_id* Use message for output (e.g. I0011) instead of symbolic name of the message (e.g. locally-disabled). + =============================================================================== pyls *ale-python-pyls* @@ -683,6 +685,65 @@ g:ale_python_pyre_auto_pipenv *g:ale_python_pyre_auto_pipenv* =============================================================================== +pyright *ale-python-pyright* + +The `pyrlight` linter requires a recent version of `pyright` which includes +the `pyright-langserver` executable. You can install `pyright` on your system +through `npm` with `sudo npm install -g pyright` or similar. + +Refer to their README for installation instructions: +https://github.com/Microsoft/pyright + +`pyright` needs to know the path to your Python executable and probably a +virtualenv to run. ALE will try to detect these automatically. +See |g:ale_python_pyright_config|. + + +g:ale_python_pyright_executable *g:ale_python_pyright_executable* + *b:ale_python_pyright_executable* + Type: |String| + Default: `'pyright-langserver'` + + The executable for running `pyright`, which is typically installed globally. + + +g:ale_python_pyright_config *g:ale_python_pyright_config* + *b:ale_python_pyright_config* + Type: |Dictionary| + Default: `{}` + + Settings for configuring the `pyright` language server. + + See pyright's documentation for a full list of options: + https://github.com/microsoft/pyright/blob/master/docs/settings.md + + ALE will automatically try to set defaults for `venvPath` and `pythonPath` + so your project can automatically be checked with the right libraries. + You can override these settings with whatever you want in your ftplugin + file like so: > + + let b:ale_python_pyright_config = { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ 'venvPath': '/other/dir', + \ }, + \} +< + If `venvPath` is set, but `pythonPath` is not, + ALE will use `venvPath . '/bin/python'` or similar as `pythonPath`. + + A commonly used setting for `pyright` is disabling language services + apart from type checking and "hover" (|ale-hover|), you can set this + setting like so, or use whatever other settings you want: > + + let b:ale_python_pyright_config = { + \ 'pyright': { + \ 'disableLanguageServices': v:true, + \ }, + \} +< + +=============================================================================== reorder-python-imports *ale-python-reorder_python_imports* g:ale_python_reorder_python_imports_executable diff --git a/doc/ale-ruby.txt b/doc/ale-ruby.txt index a27a20b2..8815404a 100644 --- a/doc/ale-ruby.txt +++ b/doc/ale-ruby.txt @@ -114,6 +114,14 @@ g:ale_ruby_rubocop_options *g:ale_ruby_rubocop_options* This variable can be changed to modify flags given to rubocop. +g:ale_ruby_rubocop_auto_correct_all *g:ale_ruby_rubocop_auto_correct_all* + *b:ale_ruby_rubocop_auto_correct_all* + Type: Number + Default: `0` + + This variable can be changed to make rubocop to correct all offenses (unsafe). + + =============================================================================== ruby *ale-ruby-ruby* @@ -172,8 +180,8 @@ g:ale_ruby_sorbet_options *g:ale_ruby_sorbet_options* =============================================================================== standardrb *ale-ruby-standardrb* -g:ale_ruby_standardrb_executable *g:ale_ruby_standardrb_executable* - *b:ale_ruby_standardrb_executable* +g:ale_ruby_standardrb_executable *g:ale_ruby_standardrb_executable* + *b:ale_ruby_standardrb_executable* Type: String Default: `'standardrb'` diff --git a/doc/ale-rust.txt b/doc/ale-rust.txt index 44a79b18..f4b4e7b7 100644 --- a/doc/ale-rust.txt +++ b/doc/ale-rust.txt @@ -9,7 +9,7 @@ Integration Information files for Rust distributed in Vim >=8.0.0501 or upstream: https://github.com/rust-lang/rust.vim - Note that there are three possible linters for Rust files: + Note that there are several possible linters and fixers for Rust files: 1. rustc -- The Rust compiler is used to check the currently edited file. So, if your project consists of multiple files, you will get some errors @@ -23,20 +23,45 @@ Integration Information over cargo. rls implements the Language Server Protocol for incremental compilation of Rust code, and can check Rust files while you type. `rls` requires Rust files to contained in Cargo projects. - 4. rustfmt -- If you have `rustfmt` installed, you can use it as a fixer to + 4. analyzer -- If you have rust-analyzer installed, you might prefer using + this linter over cargo and rls. rust-analyzer also implements the + Language Server Protocol for incremental compilation of Rust code, and is + the next iteration of rls. rust-analyzer, like rls, requires Rust files + to contained in Cargo projects. + 5. rustfmt -- If you have `rustfmt` installed, you can use it as a fixer to consistently reformat your Rust code. - Only cargo is enabled by default. To switch to using rustc instead of cargo, - configure |g:ale_linters| appropriately: > + Only cargo and rls are enabled by default. To switch to using rustc instead + of cargo, configure |g:ale_linters| appropriately: > " See the help text for the option for more information. - let g:ale_linters = {'rust': ['rustc']} + let g:ale_linters = {'rust': ['rustc', 'rls']} < Also note that rustc 1.12. or later is needed. =============================================================================== +analyzer *ale-rust-analyzer* + +g:ale_rust_analyzer_executable *g:ale_rust_analyzer_executable* + *b:ale_rust_analyzer_executable* + Type: |String| + Default: `'rust-analyzer'` + + This variable can be modified to change the executable path for + `rust-analyzer`. + + +g:ale_rust_analyzer_config *g:ale_rust_analyzer_config* + *b:ale_rust_analyzer_config* + Type: |Dictionary| + Default: `{}` + + Dictionary with configuration settings for rust-analyzer. + + +=============================================================================== cargo *ale-rust-cargo* g:ale_rust_cargo_use_check *g:ale_rust_cargo_use_check* @@ -150,6 +175,18 @@ g:ale_rust_cargo_clippy_options only `cargo clippy` supports (e.g. `--deny`). +g:ale_rust_cargo_target_dir + *g:ale_rust_cargo_target_dir* + *b:ale_rust_cargo_target_dir* + + Type: |String| + Default: `''` + + Use a custom target directory when running the commands for ALE. This can + help to avoid "waiting for file lock on build directory" messages when + running `cargo` commands manually while ALE is performing its checks. + + =============================================================================== rls *ale-rust-rls* @@ -216,23 +253,25 @@ g:ale_rust_ignore_error_codes *g:ale_rust_ignore_error_codes* > let g:ale_rust_ignore_error_codes = ['E0432', 'E0433'] + g:ale_rust_ignore_secondary_spans *g:ale_rust_ignore_secondary_spans* *b:ale_rust_ignore_secondary_spans* Type: Number Default: 0 - When set to 1, instructs the Rust error repporting to ignore secondary - spans. The problem with secondary spans is that they sometimes appear in - error messages before the main cause of the error, for example: > + When set to 1, instructs the Rust error reporting to ignore secondary spans. + The problem with secondary spans is that they sometimes appear in error + messages before the main cause of the error, for example: > 1 src/main.rs|98 col 5 error| this function takes 4 parameters but 5 - parameters were supplied: defined here + parameters were supplied: defined here 2 src/main.rs|430 col 32 error| this function takes 4 parameters but 5 - parameters were supplied: expected 4 parameters + parameters were supplied: expected 4 parameters < This is due to the sorting by line numbers. With this option set to 1, the 'defined here' span will not be presented. + =============================================================================== rustfmt *ale-rust-rustfmt* diff --git a/doc/ale-sh.txt b/doc/ale-sh.txt index 3eac9038..c06f737a 100644 --- a/doc/ale-sh.txt +++ b/doc/ale-sh.txt @@ -3,6 +3,29 @@ ALE Shell Integration *ale-sh-options* =============================================================================== +bashate *ale-sh-bashate* + +g:ale_sh_bashate_executable *g:ale_sh_bashate_executable* + *b:ale_sh_bashate_executable* + Type: |String| + Default: `'bashate'` + + This variable sets executable used for bashate. + + +g:ale_sh_bashate_options *g:ale_sh_bashate_options* + *b:ale_sh_bashate_options* + Type: |String| + Default: `''` + + With this variable we are able to pass extra arguments for bashate. For + example to ignore the indentation rule: + +> + let g:ale_sh_bashate_options = '-i E003' +< + +=============================================================================== sh-language-server *ale-sh-language-server* g:ale_sh_language_server_executable *g:ale_sh_language_server_executable* diff --git a/doc/ale-sql.txt b/doc/ale-sql.txt index 2807271b..398e24d3 100644 --- a/doc/ale-sql.txt +++ b/doc/ale-sql.txt @@ -3,7 +3,7 @@ ALE SQL Integration *ale-sql-options* =============================================================================== -pgformatter *ale-sql-pgformatter* +pgformatter *ale-sql-pgformatter* g:ale_sql_pgformatter_executable *g:ale_sql_pgformatter_executable* *b:ale_sql_pgformatter_executable* diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt index 29dabab7..c6bcf421 100644 --- a/doc/ale-supported-languages-and-tools.txt +++ b/doc/ale-supported-languages-and-tools.txt @@ -21,6 +21,7 @@ Notes: * `drafter` * AsciiDoc * `alex`!! + * `languagetool`!! * `proselint` * `redpen` * `textlint` @@ -31,10 +32,13 @@ Notes: * Awk * `gawk` * Bash + * `bashate` * `language-server` * `shell` (-n flag) * `shellcheck` * `shfmt` +* Bats + * `shellcheck` * BibTeX * `bibclean` * Bourne Shell @@ -42,8 +46,9 @@ Notes: * `shellcheck` * `shfmt` * C + * `astyle` * `ccls` - * `clang` + * `clang` (`cc`) * `clangd` * `clang-format` * `clangtidy`!! @@ -51,7 +56,7 @@ Notes: * `cpplint`!! * `cquery` * `flawfinder` - * `gcc` + * `gcc` (`cc`) * `uncrustify` * C# * `csc`!! @@ -59,8 +64,9 @@ Notes: * `mcsc`!! * `uncrustify` * C++ (filetype cpp) + * `astyle` * `ccls` - * `clang` + * `clang` (`cc`) * `clangcheck`!! * `clangd` * `clang-format` @@ -70,7 +76,7 @@ Notes: * `cpplint`!! * `cquery` * `flawfinder` - * `gcc` + * `gcc` (`cc`) * `uncrustify` * Chef * `cookstyle` @@ -113,6 +119,8 @@ Notes: * `dartanalyzer`!! * `dartfmt`!! * `language_server` +* Dhall + * `dhall-format` * Dockerfile * `dockerfile_lint` * `hadolint` @@ -162,6 +170,7 @@ Notes: * `gosimple`!! * `gotype`!! * `go vet`!! + * `revive`!! * `staticcheck`!! * GraphQL * `eslint` @@ -366,6 +375,7 @@ Notes: * `pylint`!! * `pyls` * `pyre` + * `pyright` * `reorder-python-imports` * `vulture`!! * `yapf` @@ -408,6 +418,7 @@ Notes: * Rust * `cargo`!! * `rls` + * `rust-analyzer` * `rustc` (see |ale-integration-rust|) * `rustfmt` * Sass @@ -438,6 +449,7 @@ Notes: * `sqlfmt` * `sqlformat` * `sqlint` + * `sql-lint` * Stylus * `stylelint` * SugarSS @@ -445,6 +457,7 @@ Notes: * Swift * `sourcekit-lsp` * `swiftformat` + * `swift-format` * `swiftlint` * Tcl * `nagelfar`!! @@ -476,6 +489,7 @@ Notes: * VALA * `uncrustify` * Verilog + * `hdl-checker` * `iverilog` * `verilator` * `vlog` @@ -485,6 +499,7 @@ Notes: * `vcom` * `xvhdl` * Vim + * `vimls` * `vint` * Vim help^ * `alex`!! @@ -505,3 +520,5 @@ Notes: * `yamllint` * YANG * `yang-lsp` +* Zig + * `zls` diff --git a/doc/ale-terraform.txt b/doc/ale-terraform.txt index 387fd732..f62db190 100644 --- a/doc/ale-terraform.txt +++ b/doc/ale-terraform.txt @@ -33,6 +33,25 @@ g:ale_terraform_terraform_executable *g:ale_terraform_terraform_executable* =============================================================================== +terraform-lsp *ale-terraform-terraform-lsp* + +g:ale_terraform_langserver_executable *g:ale_terraform_langserver_executable* + *b:ale_terraform_langserver_executable* + Type: |String| + Default: `'terraform-lsp'` + + This variable can be changed to use a different executable for terraform-lsp. + + +g:ale_terraform_langserver_options *g:ale_terraform_langserver_options* + *b:ale_terraform_langserver_options* + Type: |String| + Default: `''` + + This variable can be changed to pass custom CLI flags to terraform-lsp. + + +=============================================================================== tflint *ale-terraform-tflint* g:ale_terraform_tflint_executable *g:ale_terraform_tflint_executable* diff --git a/doc/ale-verilog.txt b/doc/ale-verilog.txt index 94b820b8..01af63c2 100644 --- a/doc/ale-verilog.txt +++ b/doc/ale-verilog.txt @@ -3,7 +3,10 @@ ALE Verilog/SystemVerilog Integration *ale-verilog-options* =============================================================================== -ALE can use four different linters for Verilog HDL: +ALE can use five different linters for Verilog HDL: + + HDL Checker + Using `hdl_checker --lsp` iverilog: Using `iverilog -t null -Wall` @@ -26,6 +29,9 @@ defining 'g:ale_linters' variable: \ let g:ale_linters = {'systemverilog' : ['verilator'],} < +=============================================================================== +General notes + Linters/compilers that utilize a "work" directory for analyzing designs- such as ModelSim and Vivado- can be passed the location of these directories as part of their respective option strings listed below. This is useful for @@ -40,6 +46,16 @@ changing. This can happen in the form of hangs or crashes. To help prevent this when using these linters, it may help to run linting less frequently; for example, only when a file is saved. +HDL Checker is an alternative for some of the issues described above. It wraps +around ghdl, Vivado and ModelSim/Questa and, when using the latter, it can +handle mixed language (VHDL, Verilog, SystemVerilog) designs. + +=============================================================================== +hdl-checker *ale-verilog-hdl-checker* + +See |ale-vhdl-hdl-checker| + + =============================================================================== iverilog *ale-verilog-iverilog* diff --git a/doc/ale-vhdl.txt b/doc/ale-vhdl.txt index 3fea947d..c2870240 100644 --- a/doc/ale-vhdl.txt +++ b/doc/ale-vhdl.txt @@ -3,10 +3,10 @@ ALE VHDL Integration *ale-vhdl-options* =============================================================================== -ALE can use three different linters for VHDL: +ALE can use four different linters for VHDL: - iverilog: - Using `iverilog -t null -Wall` + ghdl: + Using `ghdl --std=08` ModelSim/Questa Using `vcom -2008 -quiet -lint` @@ -14,8 +14,15 @@ ALE can use three different linters for VHDL: Vivado Using `xvhdl --2008` -Note all linters default to VHDL-2008 support. This, and other options, can be -changed with each linter's respective option variable. + HDL Checker + Using `hdl_checker --lsp` + +=============================================================================== +General notes + +ghdl, ModelSim/Questa and Vivado linters default to VHDL-2008 support. This, +and other options, can be changed with each linter's respective option +variable. Linters/compilers that utilize a "work" directory for analyzing designs- such as ModelSim and Vivado- can be passed the location of these directories as @@ -31,6 +38,10 @@ changing. This can happen in the form of hangs or crashes. To help prevent this when using these linters, it may help to run linting less frequently; for example, only when a file is saved. +HDL Checker is an alternative for some of the issues described above. It wraps +around ghdl, Vivado and ModelSim/Questa and, when using the latter, it can +handle mixed language (VHDL, Verilog, SystemVerilog) designs. + =============================================================================== ghdl *ale-vhdl-ghdl* @@ -51,6 +62,60 @@ g:ale_vhdl_ghdl_options *g:ale_vhdl_ghdl_options* =============================================================================== +hdl-checker *ale-vhdl-hdl-checker* + +HDL Checker is a wrapper for VHDL/Verilg/SystemVerilog tools that aims to +reduce the boilerplate code needed to set things up. It can automatically +infer libraries for VHDL sources, determine the compilation order and provide +some static checks. + +You can install it using pip: +> + $ pip install hdl-checker + +`hdl-checker` will be run from a detected project root, determined by the +following methods, in order: + +1. Find the first directory containing a configuration file (see + |g:ale_hdl_checker_config_file|) +2. If no configuration file can be found, find the first directory containing + a folder named `'.git' +3. If no such folder is found, use the directory of the current buffer + + +g:ale_hdl_checker_executable + *g:ale_hdl_checker_executable* + *b:ale_hdl_checker_executable* + Type: |String| + Default: `'hdl_checker'` + + This variable can be changed to the path to the 'hdl_checker' executable. + + +g:ale_hdl_checker_options *g:ale_hdl_checker_options* + *b:ale_hdl_checker_options* + Type: |String| + Default: `''` + + This variable can be changed to modify the flags/options passed to the + 'hdl_checker' server startup command. + + +g:ale_hdl_checker_config_file *g:ale_hdl_checker_config_file* + *b:ale_hdl_checker_config_file* + Type: |String| + Default: `'.hdl_checker.config'` (Unix), + `'_hdl_checker.config'` (Windows) + + This variable can be changed to modify the config file HDL Checker will try + to look for. It will also affect how the project's root directory is + determined (see |ale-vhdl-hdl-checker|). + + More info on the configuration file format can be found at: + https://github.com/suoto/hdl_checker/wiki/Setting-up-a-project + + +=============================================================================== vcom *ale-vhdl-vcom* g:ale_vhdl_vcom_executable *g:ale_vhdl_vcom_executable* diff --git a/doc/ale-vim.txt b/doc/ale-vim.txt index 772bad23..f85b43eb 100644 --- a/doc/ale-vim.txt +++ b/doc/ale-vim.txt @@ -3,6 +3,61 @@ ALE Vim Integration *ale-vim-options* =============================================================================== +vimls *ale-vim-vimls* + + The `vim-language-server` is the engine that powers VimL editor support + using the Language Server Protocol. See the installation instructions: + https://github.com/iamcco/vim-language-server#install + +g:ale_vim_vimls_executable *g:ale_vim_vimls_executable* + *b:ale_vim_vimls_executable* + Type: |String| + Default: `'vim-language-server'` + + This option can be set to change the executable path for vimls. + + +g:ale_vim_vimls_config *g:ale_vim_vimls_config* + *b:ale_vim_vimls_config* + Type: |Dictionary| + Default: `{}` + + Dictionary containing configuration settings that will be passed to the + language server. For example: > + { + \ 'vim': { + \ 'iskeyword': '@,48-57,_,192-255,-#', + \ 'vimruntime': '', + \ 'runtimepath': '', + \ 'diagnostic': { + \ 'enable': v:true + \ }, + \ 'indexes': { + \ 'runtimepath': v:true, + \ 'gap': 100, + \ 'count': 3, + \ 'projectRootPatterns' : ['.git', 'autoload', 'plugin'] + \ }, + \ 'suggest': { + \ 'fromVimruntime': v:true, + \ 'fromRuntimepath': v:false + \ }, + \ } + \} +< + Consult the vim-language-server documentation for more information about + settings. + + +g:ale_vim_vimls_use_global *g:ale_vim_vimls_use_global* + *b:ale_vim_vimls_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== vint *ale-vim-vint* g:ale_vim_vint_executable *g:ale_vim_vint_executable* diff --git a/doc/ale-zig.txt b/doc/ale-zig.txt new file mode 100644 index 00000000..70a53bbb --- /dev/null +++ b/doc/ale-zig.txt @@ -0,0 +1,33 @@ +=============================================================================== +ALE Zig Integration *ale-zig-options* + *ale-integration-zig* + +=============================================================================== +Integration Information + + Currently, the only supported linter for zig is zls. + +=============================================================================== +zls *ale-zig-zls* + +g:ale_zig_zls_executable *g:ale_zig_zls_executable* + *b:ale_zig_zls_executable* + Type: |String| + Default: `'zls'` + + This variable can be modified to change the executable path for `zls`. + + +g:ale_zig_zls_config *g:ale_zig_zls_config* + *b:ale_zig_zls_config* + Type: |Dictionary| + Default: `{}` + + WARNING: As of writing, zls does not support receiving configuration + from the client. This variable is a PLACEHOLDER until it does. + + Dictionary with configuration settings for zls. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale.txt b/doc/ale.txt index da4328d9..e6b91be8 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -9,8 +9,9 @@ CONTENTS *ale-contents* 1. Introduction.........................|ale-introduction| 2. Supported Languages & Tools..........|ale-support| 3. Linting..............................|ale-lint| - 3.1 Adding Language Servers...........|ale-lint-language-servers| - 3.2 Other Sources.....................|ale-lint-other-sources| + 3.1 Linting On Other Machines.........|ale-lint-other-machines| + 3.2 Adding Language Servers...........|ale-lint-language-servers| + 3.3 Other Sources.....................|ale-lint-other-sources| 4. Fixing Problems......................|ale-fix| 5. Language Server Protocol Support.....|ale-lsp| 5.1 Completion........................|ale-completion| @@ -127,7 +128,7 @@ their relevant options. * By showing balloons for your mouse cursor - |g:ale_set_balloons| Please consult the documentation for each option, which can reveal some other -ways of tweaking the behaviour of each way of displaying problems. You can +ways of tweaking the behavior of each way of displaying problems. You can disable or enable whichever options you prefer. Most settings can be configured for each buffer. (|b:| instead of |g:|), @@ -146,9 +147,65 @@ ALE offers several options for controlling which linters are run. * Disabling only a subset of linters. - |g:ale_linters_ignore| * Disabling LSP linters and `tsserver`. - |g:ale_disable_lsp| +You can stop ALE any currently running linters with the |ALELintStop| command. +Any existing problems will be kept. ------------------------------------------------------------------------------- -3.1 Adding Language Servers *ale-lint-language-servers* +3.1 Linting On Other Machines *ale-lint-other-machines* + +ALE offers support for running linters or fixers on files you are editing +locally on other machines, so long as the other machine has access the file +you are editing. This could be a linter or fixer run inside of a Docker image, +running in a virtual machine, running on a remote server, etc. + +In order to run tools on other machines, you will need to configure your tools +to run via scripts that execute commands on those machines, such as by setting +the ALE `_executable` options for those tools to a path for a script to run, +or by using |g:ale_command_wrapper| to specify a script to wrap all commands +that are run by ALE, before they are executed. For tools that ALE runs where +ALE looks for locally installed executables first, you may need to set the +`_use_global` options for those tools to `1`, or you can set +|g:ale_use_global_executables| to `1` before ALE is loaded to only use global +executables for all tools. + +In order for ALE to properly lint or fix files which are running on another +file system, you must provide ALE with |List|s of strings for mapping paths to +and from your local file system and the remote file system, such as the file +system of your Docker container. See |g:ale_filename_mappings| for all of the +different ways these filename mappings can be configured. + +For example, you might configure `pylint` to run via Docker by creating a +script like so. > + + #!/usr/bin/env bash + + exec docker run --rm -v "$(pwd):/data" cytopia/pylint "$@" +< +With the above script in mind, you might configure ALE to lint your Python +project with `pylint` by providing the path to the script to execute, and +mappings which describe how to between the two file systems in your +`python.vim` |ftplugin| file, like so: > + + if expand('%:p') =~# '^/home/w0rp/git/test-pylint/' + let b:ale_linters = ['pylint'] + let b:ale_python_pylint_use_global = 1 + " This is the path to the script above. + let b:ale_python_pylint_executable = '/home/w0rp/git/test-pylint/pylint.sh' + " /data matches the path in Docker. + let b:ale_filename_mappings = { + \ 'pylint': [ + \ ['/home/w0rp/git/test-pylint', '/data'], + \ ], + \} + endif +< + +You might consider using a Vim plugin for loading Vim configuration files +specific to each project, if you have a lot of projects to manage. + + +------------------------------------------------------------------------------- +3.2 Adding Language Servers *ale-lint-language-servers* ALE comes with many default configurations for language servers, so they can be detected and run automatically. ALE can connect to other language servers @@ -189,7 +246,7 @@ address to connect to instead. > ------------------------------------------------------------------------------- -3.2 Other Sources *ale-lint-other-sources* +3.3 Other Sources *ale-lint-other-sources* Problems for a buffer can be taken from other sources and rendered by ALE. This allows ALE to be used in combination with other plugins which also want @@ -246,7 +303,7 @@ A plugin might integrate its own checks with ALE like so: > function! WorkDone(buffer, results) abort " Send results to ALE after they have been collected. - call ale#other_source#ShowResults(buffer, 'some-name', a:results) + call ale#other_source#ShowResults(a:buffer, 'some-name', a:results) endfunction < @@ -287,6 +344,8 @@ are supported for running the commands. file will be created, containing the lines from the file after previous adjustment have been done. + See |ale-command-format-strings| for formatting options. + `read_temporary_file` When set to `1`, ALE will read the contents of the temporary file created for `%t`. This option can be used for commands which need to modify some file on disk in @@ -356,6 +415,10 @@ by default. Fixers can be disabled on save with |g:ale_fix_on_save_ignore|. They will still be run when you manually run |ALEFix|. +Fixers can be run on another machines, just like linters, such as fixers run +from a Docker container, running in a virtual machine, running a remote +server, etc. See |ale-lint-other-machines|. + =============================================================================== 5. Language Server Protocol Support *ale-lsp* @@ -402,12 +465,56 @@ is loaded. The delay for completion can be configured with |g:ale_completion_delay|. This setting should not be enabled if you wish to use ALE as a completion source for other plugins. +ALE automatic completion will not work when 'paste' is active. Only set +'paste' when you are copy and pasting text into your buffers. + +ALE automatic completion will interfere with default insert completion with +`CTRL-N` and so on (|compl-vim|). You can write your own keybinds and a +function in your |vimrc| file to force insert completion instead, like so: > + + function! SmartInsertCompletion() abort + " Use the default CTRL-N in completion menus + if pumvisible() + return "\<C-n>" + endif + + " Exit and re-enter insert mode, and use insert completion + return "\<C-c>a\<C-n>" + endfunction + + inoremap <silent> <C-n> <C-R>=SmartInsertCompletion()<CR> +< ALE provides an 'omnifunc' function |ale#completion#OmniFunc| for triggering completion manually with CTRL-X CTRL-O. |i_CTRL-X_CTRL-O| > " Use ALE's function for omnicompletion. set omnifunc=ale#completion#OmniFunc < + *ale-completion-fallback* + +You can write your own completion function and fallback on other methods of +completion by checking if there are no results that ALE can determine. For +example, for Python code, you could fall back on the `python3complete` +function. > + + function! TestCompletionFunc(findstart, base) abort + let l:result = ale#completion#OmniFunc(a:findstart, a:base) + + " Check if ALE couldn't find anything. + if (a:findstart && l:result is -3) + \|| (!a:findstart && empty(l:result)) + " Defer to another omnifunc if ALE couldn't find anything. + return python3complete#Complete(a:findstart, a:base) + endif + + return l:result + endfunction + + set omnifunc=TestCompletionFunc +< +See |complete-functions| for documentation on how to write completion +functions. + ALE will only suggest so many possible matches for completion. The maximum number of items can be controlled with |g:ale_completion_max_suggestions|. @@ -418,9 +525,21 @@ The |ALEComplete| command can be used to show completion suggestions manually, even when |g:ale_completion_enabled| is set to `0`. For manually requesting completion information with Deoplete, consult Deoplete's documentation. -When working with TypeScript files, ALE by can support automatic imports -from external modules. This behavior can be enabled by setting the -|g:ale_completion_tsserver_autoimport| variable to `1`. +ALE by can support automatic imports from external modules. This behavior can +be enabled by setting the |g:ale_completion_autoimport| variable to `1`. + +You can manually request imports for symbols at the cursor with the +|ALEImport| command. The word at the cursor must be an exact match for some +potential completion result which includes additional text to insert into the +current buffer, which ALE will assume is code for an import line. This command +can be useful when your code already contains something you need to import. + +You can execute other commands whenever ALE inserts some completion text with +the |ALECompletePost| event. + +When working with TypeScript files, ALE can remove warnings from your +completions by setting the |g:ale_completion_tsserver_remove_warnings| +variable to 1. *ale-completion-completeopt-bug* @@ -478,12 +597,9 @@ would like to use. An example here shows the available options for symbols > ALE supports jumping to the files and locations where symbols are defined through any enabled LSP linters. The locations ALE will jump to depend on the -information returned by LSP servers. The following commands are supported: - -|ALEGoToDefinition| - Open the definition of the symbol under the cursor. -|ALEGoToDefinitionInTab| - The same, but for opening the file in a new tab. -|ALEGoToDefinitionInSplit| - The same, but in a new split. -|ALEGoToDefinitionInVSplit| - The same, but in a new vertical split. +information returned by LSP servers. The |ALEGoToDefinition| command will jump +to the definition of symbols under the cursor. See the documentation for the +command for configuring how the location will be displayed. ALE will update Vim's |tagstack| automatically unless |g:ale_update_tagstack| is set to `0`. @@ -493,28 +609,17 @@ set to `0`. ALE supports jumping to the files and locations where symbols' types are defined through any enabled LSP linters. The locations ALE will jump to depend -on the information returned by LSP servers. The following commands are -supported: - -|ALEGoToTypeDefinition| - Open the definition of the symbol's type under - the cursor. -|ALEGoToTypeDefinitionInTab| - The same, but for opening the file in a new tab. -|ALEGoToTypeDefinitionInSplit| - The same, but in a new split. -|ALEGoToTypeDefinitionInVSplit| - The same, but in a new vertical split. - +on the information returned by LSP servers. The |ALEGoToTypeDefinition| +command will jump to the definition of symbols under the cursor. See the +documentation for the command for configuring how the location will be +displayed. ------------------------------------------------------------------------------- 5.4 Find References *ale-find-references* -ALE supports finding references for symbols though any enabled LSP linters. -ALE will display a preview window showing the places where a symbol is -referenced in a codebase when a command is run. The following commands are -supported: - -|ALEFindReferences| - Find references for the word under the cursor. - -Options: - `-relative` Show file paths in the results relative to the working dir +ALE supports finding references for symbols though any enabled LSP linters +with the |ALEFindReferences| command. See the documentation for the command +for a full list of options. ------------------------------------------------------------------------------- 5.5 Hovering *ale-hover* @@ -524,6 +629,10 @@ at the cursor taken from LSP linters. The following commands are supported: |ALEHover| - Print information about the symbol at the cursor. +Truncated information will be displayed when the cursor rests on a symbol by +default, as long as there are no problems on the same line. You can disable +this behavior by setting |g:ale_hover_cursor| to `0`. + If |g:ale_set_balloons| is set to `1` and your version of Vim supports the |balloon_show()| function, then "hover" information also show up when you move the mouse over a symbol in a buffer. Diagnostic information will take priority @@ -553,13 +662,9 @@ Documentation for symbols at the cursor can be retrieved using the ------------------------------------------------------------------------------- 5.6 Symbol Search *ale-symbol-search* -ALE supports searching for workspace symbols via LSP linters. The following -commands are supported: - -|ALESymbolSearch| - Search for symbols in the workspace. - -Options: - `-relative` Show file paths in the results relative to the working dir +ALE supports searching for workspace symbols via LSP linters with the +|ALESymbolSearch| command. See the documentation for the command +for a full list of options. =============================================================================== 6. Global Options *ale-options* @@ -666,7 +771,7 @@ g:ale_completion_delay *g:ale_completion_delay* g:ale_completion_enabled *g:ale_completion_enabled* -b:ale_completion_enabled *b:ale_completion_enabled* + *b:ale_completion_enabled* Type: |Number| Default: `0` @@ -679,6 +784,9 @@ b:ale_completion_enabled *b:ale_completion_enabled* This setting should not be enabled if you wish to use ALE as a completion source for other completion plugins. + ALE automatic completion will not work when 'paste' is active. Only set + 'paste' when you are copy and pasting text into your buffers. + A buffer-local version of this setting `b:ale_completion_enabled` can be set to `0` to disable ALE's automatic completion support for a single buffer. ALE's completion support must be enabled globally to be enabled locally. @@ -686,7 +794,18 @@ b:ale_completion_enabled *b:ale_completion_enabled* See |ale-completion| -g:ale_completion_tsserver_autoimport *g:ale_completion_tsserver_autoimport* + *g:ale_completion_tsserver_remove_warnings* +g:ale_completion_tsserver_remove_warnings + + Type: Number + Default: `0` + + When this option is set to `0`, ALE will return all completion items, + including those that are a warning. Warnings can be excluded from completed + items by setting it to `1`. + + +g:ale_completion_autoimport *g:ale_completion_autoimport* Type: Number Default: `0` @@ -793,6 +912,16 @@ g:ale_cursor_detail *g:ale_cursor_detail* loaded for messages to be displayed. See |ale-lint-settings-on-startup|. +g:ale_default_navigation *g:ale_default_navigation* + *b:ale_default_navigation* + + Type: |String| + Default: `'buffer'` + + The default method for navigating away from the current buffer to another + buffer, such as for |ALEFindReferences|, or |ALEGoToDefinition|. + + g:ale_disable_lsp *g:ale_disable_lsp* *b:ale_disable_lsp* @@ -845,7 +974,7 @@ g:ale_echo_msg_error_str *g:ale_echo_msg_error_str* g:ale_echo_msg_format *g:ale_echo_msg_format* -b:ale_echo_msg_format *b:ale_echo_msg_format* + *b:ale_echo_msg_format* Type: |String| Default: `'%code: %%s'` @@ -924,7 +1053,7 @@ g:ale_enabled *g:ale_enabled* g:ale_exclude_highlights *g:ale_exclude_highlights* -b:ale_exclude_highlights *b:ale_exclude_highlights* + *b:ale_exclude_highlights* Type: |List| Default: `[]` @@ -961,7 +1090,7 @@ g:ale_fixers *g:ale_fixers* < g:ale_fix_on_save *g:ale_fix_on_save* -b:ale_fix_on_save *b:ale_fix_on_save* + *b:ale_fix_on_save* Type: |Number| Default: `0` @@ -983,7 +1112,7 @@ b:ale_fix_on_save *b:ale_fix_on_save* g:ale_fix_on_save_ignore *g:ale_fix_on_save_ignore* -b:ale_fix_on_save_ignore *b:ale_fix_on_save_ignore* + *b:ale_fix_on_save_ignore* Type: |Dictionary| or |List| Default: `{}` @@ -1046,9 +1175,27 @@ g:ale_history_log_output *g:ale_history_log_output* if you want to save on some memory usage. +g:ale_hover_cursor *g:ale_hover_cursor* + + Type: |Number| + Default: `1` + + If set to `1`, ALE will show truncated information in the echo line about + the symbol at the cursor automatically when the |CursorHold| event is fired. + The delay before requesting hover information is based on 'updatetime', as + with all |CursorHold| events. + + If there's a problem on the line where the cursor is resting, ALE will not + show any hover information. + + See |ale-hover| for more information on hover information. + + This setting must be set to `1` before ALE is loaded for this behavior + to be enabled. See |ale-lint-settings-on-startup|. + + g:ale_hover_to_preview *g:ale_hover_to_preview* *b:ale_hover_to_preview* - Type: |Number| Default: `0` @@ -1083,7 +1230,7 @@ g:ale_list_window_size *g:ale_list_window_size* g:ale_lint_delay *g:ale_lint_delay* - + *b:ale_lint_delay* Type: |Number| Default: `200` @@ -1091,6 +1238,9 @@ g:ale_lint_delay *g:ale_lint_delay* be run after text is changed. This option is only meaningful with the |g:ale_lint_on_text_changed| variable set to `always`, `insert`, or `normal`. + A buffer-local option, `b:ale_lint_delay`, can be set to change the delay + for different buffers, such as in |ftplugin| files. + g:ale_lint_on_enter *g:ale_lint_on_enter* @@ -1203,6 +1353,7 @@ g:ale_linter_aliases *g:ale_linter_aliases* \ 'csh': 'sh', \ 'javascriptreact': ['javascript', 'jsx'], \ 'plaintex': 'tex', + \ 'ps1': 'powershell', \ 'rmarkdown': 'r', \ 'rmd': 'r', \ 'systemverilog': 'verilog', @@ -1247,6 +1398,90 @@ g:ale_linter_aliases *g:ale_linter_aliases* < No linters will be loaded when the buffer's filetype is empty. + +g:ale_filename_mappings *g:ale_filename_mappings* + *b:ale_filename_mappings* + + Type: |Dictionary| or |List| + Default: `{}` + + Either a |Dictionary| mapping a linter or fixer name, as displayed in + |:ALEInfo|, to a |List| of two-item |List|s for filename mappings, or just a + |List| of two-item |List|s. When given some paths to files, the value of + this setting will be used to convert filenames on a local file system to + filenames on some remote file system, such as paths in a Docker image, + virtual machine, or network drive. + + For example: > + + let g:ale_filename_mappings = { + \ 'pylint': [ + \ ['/home/john/proj', '/data'], + \ ], + \} +< + With the above configuration, a filename such as `/home/john/proj/foo.py` + will be provided to the linter/fixer as `/data/foo.py`, and paths parsed + from linter results such as `/data/foo.py` will be converted back to + `/home/john/proj/foo.py`. + + You can use `*` as to apply a |List| of filename mappings to all other + linters or fixers not otherwise matched. > + + " Use one List of paths for pylint. + " Use another List of paths for everything else. + let g:ale_filename_mappings = { + \ 'pylint': [ + \ ['/home/john/proj', '/data'], + \ ], + \ '*': [ + \ ['/home/john/proj', '/other-data'], + \ ], + \} +< + If you just want every single linter or fixer to use the same filename + mapping, you can just use a |List|. > + + " Same as above, but for ALL linters and fixers. + let g:ale_filename_mappings = [ + \ ['/home/john/proj', '/data'], + \] +< + You can provide many such filename paths for multiple projects. Paths are + matched by checking if the start of a file path matches the given strings, + in a case-sensitive manner. Earlier entries in the |List| will be tried + before later entries when mapping to a given file system. + + Buffer-local options can be set to the same values to override the global + options, such as in |ftplugin| files. + + NOTE: Only fixers registered with a short name can support filename mapping + by their fixer names. See |ale-fix|. Filename mappings set for all tools by + using only a |List| for the setting will also be applied to fixers not in + the registry. + + NOTE: In order for this filename mapping to work correctly, linters and + fixers must exclusively determine paths to files to lint or fix via ALE + command formatting as per |ale-command-format-strings|, and paths parsed + from linter files must be provided in `filename` keys if a linter returns + results for more than one file at a time, as per |ale-loclist-format|. If + you discover a linter or fixer which does not behave properly, please report + it as an issue. + + If you are running a linter or fixer through Docker or another remote file + system, you may have to mount your temporary directory, which you can + discover with the following command: > + + :echo fnamemodify(tempname(), ':h:h') +< + You should provide a mapping from this temporary directory to whatever you + mount this directory to in Docker, or whatever remote file system you are + working with. + + You can inspect the filename mappings ALE will use with the + |ale#GetFilenameMappings()| function. + + g:ale_linters *g:ale_linters* *b:ale_linters* Type: |Dictionary| @@ -1266,8 +1501,8 @@ g:ale_linters *g:ale_linters* \ 'help': [], \ 'perl': ['perlcritic'], \ 'perl6': [], - \ 'python': ['flake8', 'mypy', 'pylint'], - \ 'rust': ['cargo'], + \ 'python': ['flake8', 'mypy', 'pylint', 'pyright'], + \ 'rust': ['cargo', 'rls'], \ 'spec': [], \ 'text': [], \ 'vue': ['eslint', 'vls'], @@ -1359,7 +1594,7 @@ g:ale_list_vertical *g:ale_list_vertical* g:ale_loclist_msg_format *g:ale_loclist_msg_format* -b:ale_loclist_msg_format *b:ale_loclist_msg_format* + *b:ale_loclist_msg_format* Type: |String| Default: `g:ale_echo_msg_format` @@ -1411,7 +1646,7 @@ g:ale_lsp_show_message_severity *g:ale_lsp_show_message_severity* g:ale_lsp_root *g:ale_lsp_root* -b:ale_lsp_root *b:ale_lsp_root* + *b:ale_lsp_root* Type: |Dictionary| or |String| Default: {} @@ -1892,7 +2127,8 @@ g:ale_virtualtext_cursor *g:ale_virtualtext_cursor* g:ale_virtualtext_delay *g:ale_virtualtext_delay* -b:ale_virtualtext_delay *b:ale_virtualtext_delay* + *b:ale_virtualtext_delay* + Type: |Number| Default: `10` @@ -1911,7 +2147,7 @@ g:ale_virtualtext_prefix *g:ale_virtualtext_prefix* Prefix to be used with |g:ale_virtualtext_cursor|. g:ale_virtualenv_dir_names *g:ale_virtualenv_dir_names* -b:ale_virtualenv_dir_names *b:ale_virtualenv_dir_names* + *b:ale_virtualenv_dir_names* Type: |List| Default: `['.env', '.venv', 'env', 've-py3', 've', 'virtualenv', 'venv']` @@ -1925,7 +2161,7 @@ b:ale_virtualenv_dir_names *b:ale_virtualenv_dir_names* g:ale_warn_about_trailing_blank_lines *g:ale_warn_about_trailing_blank_lines* -b:ale_warn_about_trailing_blank_lines *b:ale_warn_about_trailing_blank_lines* + *b:ale_warn_about_trailing_blank_lines* Type: |Number| Default: `1` @@ -1937,7 +2173,7 @@ b:ale_warn_about_trailing_blank_lines *b:ale_warn_about_trailing_blank_lines* g:ale_warn_about_trailing_whitespace *g:ale_warn_about_trailing_whitespace* -b:ale_warn_about_trailing_whitespace *b:ale_warn_about_trailing_whitespace* + *b:ale_warn_about_trailing_whitespace* Type: |Number| Default: `1` @@ -2276,19 +2512,21 @@ documented in additional help files. gcc...................................|ale-asm-gcc| awk.....................................|ale-awk-options| gawk..................................|ale-awk-gawk| + bats....................................|ale-bats-options| + shellcheck............................|ale-bats-shellcheck| bib.....................................|ale-bib-options| bibclean..............................|ale-bib-bibclean| c.......................................|ale-c-options| - clang.................................|ale-c-clang| + astyle................................|ale-c-astyle| + cc....................................|ale-c-cc| + ccls..................................|ale-c-ccls| clangd................................|ale-c-clangd| clang-format..........................|ale-c-clangformat| clangtidy.............................|ale-c-clangtidy| cppcheck..............................|ale-c-cppcheck| cquery................................|ale-c-cquery| flawfinder............................|ale-c-flawfinder| - gcc...................................|ale-c-gcc| uncrustify............................|ale-c-uncrustify| - ccls..................................|ale-c-ccls| chef....................................|ale-chef-options| cookstyle.............................|ale-chef-cookstyle| foodcritic............................|ale-chef-foodcritic| @@ -2301,9 +2539,11 @@ documented in additional help files. cmakelint.............................|ale-cmake-cmakelint| cmake-format..........................|ale-cmake-cmakeformat| cpp.....................................|ale-cpp-options| - clang.................................|ale-cpp-clang| - clangd................................|ale-cpp-clangd| + astyle................................|ale-cpp-astyle| + cc....................................|ale-cpp-cc| + ccls..................................|ale-cpp-ccls| clangcheck............................|ale-cpp-clangcheck| + clangd................................|ale-cpp-clangd| clang-format..........................|ale-cpp-clangformat| clangtidy.............................|ale-cpp-clangtidy| clazy.................................|ale-cpp-clazy| @@ -2311,9 +2551,7 @@ documented in additional help files. cpplint...............................|ale-cpp-cpplint| cquery................................|ale-cpp-cquery| flawfinder............................|ale-cpp-flawfinder| - gcc...................................|ale-cpp-gcc| uncrustify............................|ale-cpp-uncrustify| - ccls..................................|ale-cpp-ccls| c#......................................|ale-cs-options| csc...................................|ale-cs-csc| mcs...................................|ale-cs-mcs| @@ -2374,6 +2612,7 @@ documented in additional help files. gometalinter..........................|ale-go-gometalinter| gopls.................................|ale-go-gopls| govet.................................|ale-go-govet| + revive................................|ale-go-revive| staticcheck...........................|ale-go-staticcheck| graphql.................................|ale-graphql-options| eslint................................|ale-graphql-eslint| @@ -2460,6 +2699,7 @@ documented in additional help files. luac..................................|ale-lua-luac| luacheck..............................|ale-lua-luacheck| markdown................................|ale-markdown-options| + markdownlint..........................|ale-markdown-markdownlint| mdl...................................|ale-markdown-mdl| prettier..............................|ale-markdown-prettier| remark-lint...........................|ale-markdown-remark-lint| @@ -2548,6 +2788,7 @@ documented in additional help files. pylint................................|ale-python-pylint| pyls..................................|ale-python-pyls| pyre..................................|ale-python-pyre| + pyright...............................|ale-python-pyright| reorder-python-imports................|ale-python-reorder_python_imports| vulture...............................|ale-python-vulture| yapf..................................|ale-python-yapf| @@ -2576,6 +2817,7 @@ documented in additional help files. sorbet................................|ale-ruby-sorbet| standardrb............................|ale-ruby-standardrb| rust....................................|ale-rust-options| + analyzer..............................|ale-rust-analyzer| cargo.................................|ale-rust-cargo| rls...................................|ale-rust-rls| rustc.................................|ale-rust-rustc| @@ -2593,6 +2835,7 @@ documented in additional help files. sasslint..............................|ale-scss-sasslint| stylelint.............................|ale-scss-stylelint| sh......................................|ale-sh-options| + bashate...............................|ale-sh-bashate| sh-language-server....................|ale-sh-language-server| shell.................................|ale-sh-shell| shellcheck............................|ale-sh-shellcheck| @@ -2620,6 +2863,7 @@ documented in additional help files. terraform...............................|ale-terraform-options| terraform-fmt-fixer...................|ale-terraform-fmt-fixer| terraform.............................|ale-terraform-terraform| + terraform-lsp.........................|ale-terraform-terraform-lsp| tflint................................|ale-terraform-tflint| tex.....................................|ale-tex-options| chktex................................|ale-tex-chktex| @@ -2642,15 +2886,18 @@ documented in additional help files. vala....................................|ale-vala-options| uncrustify............................|ale-vala-uncrustify| verilog/systemverilog...................|ale-verilog-options| + hdl-checker...........................|ale-verilog-hdl-checker| iverilog..............................|ale-verilog-iverilog| verilator.............................|ale-verilog-verilator| vlog..................................|ale-verilog-vlog| xvlog.................................|ale-verilog-xvlog| vhdl....................................|ale-vhdl-options| ghdl..................................|ale-vhdl-ghdl| + hdl-checker...........................|ale-vhdl-hdl-checker| vcom..................................|ale-vhdl-vcom| xvhdl.................................|ale-vhdl-xvhdl| vim.....................................|ale-vim-options| + vimls.................................|ale-vim-vimls| vint..................................|ale-vim-vint| vim help................................|ale-vim-help-options| write-good............................|ale-vim-help-write-good| @@ -2667,6 +2914,8 @@ documented in additional help files. yamllint..............................|ale-yaml-yamllint| yang....................................|ale-yang-options| yang-lsp..............................|ale-yang-lsp| + zig.....................................|ale-zig-options| + zls...................................|ale-zig-zls| =============================================================================== @@ -2704,15 +2953,38 @@ ALEFindReferences *ALEFindReferences* Enter key (`<CR>`) can be used to jump to a referencing location, or the `t` key can be used to jump to the location in a new tab. + The locations opened in different ways using the following variations. + + `:ALEFindReferences -tab` - Open the location in a new tab. + `:ALEFindReferences -split` - Open the location in a horizontal split. + `:ALEFindReferences -vsplit` - Open the location in a vertical split. + + The default method used for navigating to a new location can be changed + by modifying |g:ale_default_navigation|. + + You can add `-relative` to the command to view results with relatives paths, + instead of absolute paths. + + The selection can be opened again with the |ALERepeatSelection| command. + You can jump back to the position you were at before going to a reference of something with jump motions like CTRL-O. See |jump-motions|. A plug mapping `<Plug>(ale_find_references)` is defined for this command. + You can define additional plug mapping with any additional options you want + like so: > + + nnoremap <silent> <Plug>(my_mapping) :ALEFindReferences -relative<Return> +< ALEFix *ALEFix* Fix problems with the current buffer. See |ale-fix| for more information. + If the command is run with a bang (`:ALEFix!`), all warnings will be + suppressed, including warnings about no fixers being defined, and warnings + about not being able to apply fixes to a file because it has been changed. + A plug mapping `<Plug>(ale_fix)` is defined for this command. @@ -2723,12 +2995,21 @@ ALEFixSuggest *ALEFixSuggest* See |ale-fix| for more information. -ALEGoToDefinition *ALEGoToDefinition* +ALEGoToDefinition `<options>` *ALEGoToDefinition* Jump to the definition of a symbol under the cursor using the enabled LSP linters for the buffer. ALE will jump to a definition if an LSP server provides a location to jump to. Otherwise, ALE will do nothing. + The locations opened in different ways using the following variations. + + `:ALEGoToDefinition -tab` - Open the location in a new tab. + `:ALEGoToDefinition -split` - Open the location in a horizontal split. + `:ALEGoToDefinition -vsplit` - Open the location in a vertical split. + + The default method used for navigating to a new location can be changed + by modifying |g:ale_default_navigation|. + You can jump back to the position you were at before going to the definition of something with jump motions like CTRL-O. See |jump-motions|. @@ -2736,31 +3017,13 @@ ALEGoToDefinition *ALEGoToDefinition* command. Otherwise, Vim will refuse to leave the buffer you're jumping from unless you have saved your edits. - A plug mapping `<Plug>(ale_go_to_definition)` is defined for this command. - + The following Plug mappings are defined for this command, which correspond + to the following commands. -ALEGoToDefinitionInTab *ALEGoToDefinitionInTab* - - The same as |ALEGoToDefinition|, but opens results in a new tab. - - A plug mapping `<Plug>(ale_go_to_definition_in_tab)` is defined for this - command. - - -ALEGoToDefinitionInSplit *ALEGoToDefinitionInSplit* - - The same as |ALEGoToDefinition|, but opens results in a new split. - - A plug mapping `<Plug>(ale_go_to_definition_in_split)` is defined for this - command. - - -ALEGoToDefinitionInVSplit *ALEGoToDefinitionInVSplit* - - The same as |ALEGoToDefinition|, but opens results in a new vertical split. - - A plug mapping `<Plug>(ale_go_to_definition_in_vsplit)` is defined for this - command. + `<Plug>(ale_go_to_definition)` - `:ALEGoToDefinition` + `<Plug>(ale_go_to_definition_in_tab)` - `:ALEGoToDefinition -tab` + `<Plug>(ale_go_to_definition_in_split)` - `:ALEGoToDefinition -split` + `<Plug>(ale_go_to_definition_in_vsplit)` - `:ALEGoToDefinition -vsplit` ALEGoToTypeDefinition *ALEGoToTypeDefinition* @@ -2770,36 +3033,25 @@ ALEGoToTypeDefinition *ALEGoToTypeDefinition* definition if an LSP server provides a location to jump to. Otherwise, ALE will do nothing. - You can jump back to the position you were at before going to the definition - of something with jump motions like CTRL-O. See |jump-motions|. - - A plug mapping `<Plug>(ale_go_to_type_definition)` is defined for this - command. - - -ALEGoToTypeDefinitionInTab *ALEGoToTypeDefinitionInTab* - - The same as |ALEGoToTypeDefinition|, but opens results in a new tab. - - A plug mapping `<Plug>(ale_go_to_type_definition_in_tab)` is defined for - this command. - - -ALEGoToTypeDefinitionInSplit *ALEGoToTypeDefinitionInSplit* - - The same as |ALEGoToTypeDefinition|, but opens results in a new split. + The locations opened in different ways using the following variations. - A plug mapping `<Plug>(ale_go_to_type_definition_in_split)` is defined for - this command. + `:ALEGoToTypeDefinition -tab` - Open the location in a new tab. + `:ALEGoToTypeDefinition -split` - Open the location in a horizontal split. + `:ALEGoToTypeDefinition -vsplit` - Open the location in a vertical split. + The default method used for navigating to a new location can be changed + by modifying |g:ale_default_navigation|. -ALEGoToTypeDefinitionInVSplit *ALEGoToTypeDefinitionInVSplit* + You can jump back to the position you were at before going to the definition + of something with jump motions like CTRL-O. See |jump-motions|. - The same as |ALEGoToTypeDefinition|, but opens results in a new vertical - split. + The following Plug mappings are defined for this command, which correspond + to the following commands. - A plug mapping `<Plug>(ale_go_to_type_definition_in_vsplit)` is defined for - this command. + `<Plug>(ale_go_to_type_definition)` - `:ALEGoToTypeDefinition` + `<Plug>(ale_go_to_type_definition_in_tab)` - `:ALEGoToTypeDefinition -tab` + `<Plug>(ale_go_to_type_definition_in_split)` - `:ALEGoToTypeDefinition -split` + `<Plug>(ale_go_to_type_definition_in_vsplit)` - `:ALEGoToTypeDefinition -vsplit` ALEHover *ALEHover* @@ -2815,6 +3067,23 @@ ALEHover *ALEHover* A plug mapping `<Plug>(ale_hover)` is defined for this command. +ALEImport *ALEImport* + + Try to import a symbol using `tsserver` or a Language Server. + + ALE will look for completions for the word at the cursor which contain + additional text edits that possible insert lines to import the symbol. The + first match with additional text edits will be used, and may add other code + to the current buffer other than import lines. + + If linting is enabled, and |g:ale_lint_on_text_changed| is set to ever check + buffers when text is changed, the buffer will be checked again after changes + are made. + + A Plug mapping `<Plug>(ale_import)` is defined for this command. This + mapping should only be bound for normal mode. + + ALEOrganizeImports *ALEOrganizeImports* Organize imports using tsserver. Currently not implemented for LSPs. @@ -2822,9 +3091,15 @@ ALEOrganizeImports *ALEOrganizeImports* ALERename *ALERename* - Rename a symbol using TypeScript server or Language Server. + Rename a symbol using `tsserver` or a Language Server. + + The symbol where the cursor is resting will be the symbol renamed, and a + prompt will open to request a new name. + - The user will be prompted for a new name. +ALERepeatSelection *ALERepeatSelection* + + Repeat the last selection displayed in the preview window. ALESymbolSearch `<query>` *ALESymbolSearch* @@ -2834,18 +3109,28 @@ ALESymbolSearch `<query>` *ALESymbolSearch* The arguments provided to this command will be used as a search query for finding symbols in the workspace, such as functions, types, etc. + You can add `-relative` to the command to view results with relatives paths, + instead of absolute paths. + *:ALELint* ALELint *ALELint* Run ALE once for the current buffer. This command can be used to run ALE manually, instead of automatically, if desired. - This command will also run linters where `lint_file` is set to `1`, or in - other words linters which check the file instead of the Vim buffer. + This command will also run linters where `lint_file` is evaluates to `1`, + meaning linters which check the file instead of the Vim buffer. A plug mapping `<Plug>(ale_lint)` is defined for this command. +ALELintStop *ALELintStop* + + Stop any currently running jobs for checking the current buffer. + + Any problems from previous linter results will continue to be shown. + + ALEPrevious *ALEPrevious* ALEPreviousWrap *ALEPreviousWrap* ALENext *ALENext* @@ -2863,7 +3148,7 @@ ALELast *ALELast* the last or first warning or error in the file, respectively. `ALEPrevious` and `ALENext` take optional flags arguments to custom their - behaviour : + behavior : `-wrap` enable wrapping around the file `-error`, `-warning` and `-info` enable jumping to errors, warnings or infos respectively, ignoring anything else. They are mutually exclusive and if @@ -3023,6 +3308,15 @@ ale#Env(variable_name, value) *ale#Env()* 'set VAR="some value" && command' # On Windows +ale#GetFilenameMappings(buffer, name) *ale#GetFilenameMappings()* + + Given a `buffer` and the `name` of either a linter for fixer, return a + |List| of two-item |List|s that describe mapping to and from the local and + foreign file systems for running a particular linter or fixer. + + See |g:ale_filename_mappings| for details on filename mapping. + + ale#Has(feature) *ale#Has()* Return `1` if ALE supports a given feature, like |has()| for Vim features. @@ -3045,9 +3339,9 @@ ale#Queue(delay, [linting_flag, buffer_number]) *ale#Queue()* The linters will always be run in the background. Calling this function again from the same buffer - An optional `linting_flag` argument can be given. If `linting_flag` - is `'lint_file'`, then linters where the `lint_file` option is set to `1` will be - run. Linters with `lint_file` set to `1` are not run by default. + An optional `linting_flag` argument can be given. If `linting_flag` is + `'lint_file'`, then linters where the `lint_file` option evaluates to `1` + will be run. Otherwise, those linters will not be run. An optional `buffer_number` argument can be given for specifying the buffer to check. The active buffer (`bufnr('')`) will be checked by default. @@ -3135,26 +3429,38 @@ ale#command#Run(buffer, command, callback, [options]) *ale#command#Run()* 'command': {b -> ale#command#Run(b, 'foo', function('s:GetCommand'))} < - The following `options` can be provided. - `output_stream` - Either `'stdout'`, `'stderr'`, `'both'`, or `'none`' for - selecting which output streams to read lines from. + `output_stream` - Either `'stdout'`, `'stderr'`, `'both'`, or + `'none`' for selecting which output streams to read + lines from. + + The default is `'stdout'` + + `executable` - An executable for formatting into `%e` in the + command. If this option is not provided, formatting + commands with `%e` will not work. + + `read_buffer` - If set to `1`, the buffer will be piped into the + command. + + The default is `0`. - The default is `'stdout'` + `input` - When creating temporary files with `%t` or piping + text into a command `input` can be set to a |List| of + text to use instead of the buffer's text. - `executable` - An executable for formatting into `%e` in the command. - If this option is not provided, formatting commands with - `%e` will not work. + `filename_mappings` - A |List| of two-item |List|s describing filename + mappings to apply for formatted filenames in the + command string, as per |g:ale_filename_mappings|. - `read_buffer` - If set to `1`, the buffer will be piped into the - command. + If the call to this function is being used for a + linter or fixer, the mappings should be provided with + this option, and can be retrieved easily with + |ale#GetFilenameMappings()|. - The default is `0`. + The default is `[]`. - `input` - When creating temporary files with `%t` or piping text - into a command `input` can be set to a |List| of text to - use instead of the buffer's text. ale#command#EscapeCommandPart(command_part) *ale#command#EscapeCommandPart()* @@ -3369,24 +3675,30 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* if a command manually reads from a temporary file instead, etc. + This option behaves as if it was set to `0` when the + `lint_file` option evaluates to `1`. + *ale-lint-file* - `lint_file` A |Number| (`0` or `1`) indicating whether a command - should read the file instead of the Vim buffer. This - option can be used for linters which must check the - file on disk, and which cannot check a Vim buffer - instead. - - Linters set with this option will not be run as a - user types, per |g:ale_lint_on_text_changed|. Linters - will instead be run only when events occur against - the file on disk, including |g:ale_lint_on_enter| - and |g:ale_lint_on_save|. Linters with this option - set to `1` will also be run when linters are run - manually, per |ALELintPost-autocmd|. - - When this option is set to `1`, `read_buffer` will - be set automatically to `0`. The two options cannot - be used together. + `lint_file` A |Number| (`0` or `1`), or a |Funcref| for a function + accepting a buffer number for computing either `0` or + `1`, indicating whether a command should read the file + instead of the Vim buffer. This option can be used + for linters which must check the file on disk, and + which cannot check a Vim buffer instead. + + The result can be computed with |ale#command#Run()|. + + Linters where the eventual value of this option + evaluates to `1` will not be run as a user types, per + |g:ale_lint_on_text_changed|. Linters will instead be + run only when events occur against the file on disk, + including |g:ale_lint_on_enter| and + |g:ale_lint_on_save|. Linters where this option + evaluates to `1` will also be run when the |ALELint| + command is run. + + When this option is evaluates to `1`, ALE will behave + as if `read_buffer` was set to `0`. *ale-lsp-linters* `lsp` A |String| for defining LSP (Language Server Protocol) @@ -3506,7 +3818,7 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* contents of the buffer being checked. All occurrences of `%t` in command strings will reference the one temporary file. The temporary file will be created inside a temporary directory, and the entire temporary directory - will be automatically deleted, following the behaviour of + will be automatically deleted, following the behavior of |ale#command#ManageDirectory|. This option can be used for some linters which do not support reading from stdin. @@ -3525,13 +3837,22 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* command, so literal character sequences `%s` and `%t` can be escaped by using `%%s` and `%%t` instead, etc. + Some |filename-modifiers| can be applied to `%s` and `%t`. Only `:h`, `:t`, + `:r`, and `:e` may be applied, other modifiers will be ignored. Filename + modifiers can be applied to the format markers by placing them after them. + + For example: > + 'command': '%s:h %s:e %s:h:t', +< + Given a path `/foo/baz/bar.txt`, the above command string will generate + something akin to `'/foo/baz' 'txt' 'baz'` + If a callback for a command generates part of a command string which might possibly contain `%%`, `%s`, `%t`, or `%e`, where the special formatting behavior is not desired, the |ale#command#EscapeCommandPart()| function can be used to replace those characters to avoid formatting issues. *ale-linter-loading-behavior* - *ale-linter-loading-behaviour* Linters for ALE will be loaded by searching |runtimepath| in the following format: > @@ -3675,6 +3996,23 @@ g:ale_want_results_buffer *g:ale_want_results_buffer* figure out which buffer other sources should lint. +ALECompletePost *ALECompletePost-autocmd* + *ALECompletePost* + + This |User| autocmd is triggered after ALE inserts an item on + |CompleteDone|. This event can be used to run commands after a buffer + is changed by ALE as the result of completion. For example, |ALEFix| can + be configured to run automatically when completion is done: > + + augroup FixAfterComplete + autocmd! + " Run ALEFix when completion items are added. + autocmd User ALECompletePost ALEFix! + " If ALE starts fixing a file, stop linters running for now. + autocmd User ALEFixPre ALELintStop + augroup END +< + ALELintPre *ALELintPre-autocmd* *ALELintPre* ALELintPost *ALELintPost-autocmd* diff --git a/ftplugin/ale-preview-selection.vim b/ftplugin/ale-preview-selection.vim index d77b4f98..7ec84068 100644 --- a/ftplugin/ale-preview-selection.vim +++ b/ftplugin/ale-preview-selection.vim @@ -12,5 +12,5 @@ noremap <buffer> A <NOP> noremap <buffer> o <NOP> noremap <buffer> O <NOP> " Keybinds for opening selection items. -noremap <buffer> <CR> :call ale#preview#OpenSelectionInBuffer()<CR> +noremap <buffer> <CR> :call ale#preview#OpenSelection()<CR> noremap <buffer> t :call ale#preview#OpenSelectionInTab()<CR> diff --git a/plugin/ale.vim b/plugin/ale.vim index 19c86ea6..18d867ee 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -97,6 +97,10 @@ let g:ale_fix_on_save = get(g:, 'ale_fix_on_save', 0) " should be used instead. let g:ale_enabled = get(g:, 'ale_enabled', 1) +" A Dictionary mapping linter or fixer names to Arrays of two-item Arrays +" mapping filename paths from one system to another. +let g:ale_filename_mappings = get(g:, 'ale_filename_mappings', {}) + " These flags dictates if ale uses the quickfix or the loclist (loclist is the " default, quickfix overrides loclist). let g:ale_set_loclist = get(g:, 'ale_set_loclist', 1) @@ -121,6 +125,9 @@ let g:ale_cursor_detail = get(g:, 'ale_cursor_detail', 0) " This flag can be set to 1 to enable virtual text when the cursor moves. let g:ale_virtualtext_cursor = get(g:, 'ale_virtualtext_cursor', 0) +" This flag can be set to 1 to enable LSP hover messages at the cursor. +let g:ale_hover_cursor = get(g:, 'ale_hover_cursor', 1) + " This flag can be set to 1 to automatically close the preview window upon " entering Insert Mode. let g:ale_close_preview_on_insert = get(g:, 'ale_close_preview_on_insert', 0) @@ -188,6 +195,8 @@ command! -bar ALEStopAllLSPs :call ale#lsp#reset#StopAllLSPs() " A command for linting manually. command! -bar ALELint :call ale#Queue(0, 'lint_file') +" Stop current jobs when linting. +command! -bar ALELintStop :call ale#engine#Stop(bufnr('')) " Define a command to get information about current filetype. command! -bar ALEInfo :call ale#debugging#Info() @@ -197,21 +206,18 @@ command! -bar ALEInfoToClipboard :call ale#debugging#InfoToClipboard() command! -bar -nargs=1 ALEInfoToFile :call ale#debugging#InfoToFile(<f-args>) " Fix problems in files. -command! -bar -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEFix :call ale#fix#Fix(bufnr(''), '', <f-args>) +command! -bar -bang -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEFix :call ale#fix#Fix(bufnr(''), '<bang>', <f-args>) " Suggest registered functions to use for fixing problems. command! -bar ALEFixSuggest :call ale#fix#registry#Suggest(&filetype) " Go to definition for tsserver and LSP -command! -bar ALEGoToDefinition :call ale#definition#GoTo({}) -command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in': 'tab'}) -command! -bar ALEGoToDefinitionInSplit :call ale#definition#GoTo({'open_in': 'horizontal-split'}) -command! -bar ALEGoToDefinitionInVSplit :call ale#definition#GoTo({'open_in': 'vertical-split'}) +command! -bar -nargs=* ALEGoToDefinition :call ale#definition#GoToCommandHandler('', <f-args>) " Go to type definition for tsserver and LSP -command! -bar ALEGoToTypeDefinition :call ale#definition#GoToType({}) -command! -bar ALEGoToTypeDefinitionInTab :call ale#definition#GoToType({'open_in': 'tab'}) -command! -bar ALEGoToTypeDefinitionInSplit :call ale#definition#GoToType({'open_in': 'horizontal-split'}) -command! -bar ALEGoToTypeDefinitionInVSplit :call ale#definition#GoToType({'open_in': 'vertical-split'}) +command! -bar -nargs=* ALEGoToTypeDefinition :call ale#definition#GoToCommandHandler('type', <f-args>) + +" Repeat a previous selection in the preview window +command! -bar ALERepeatSelection :call ale#preview#RepeatSelection() " Find references for tsserver and LSP command! -bar -nargs=* ALEFindReferences :call ale#references#Find(<f-args>) @@ -225,8 +231,12 @@ command! -bar ALEDocumentation :call ale#hover#ShowDocumentationAtCursor() " Search for appearances of a symbol, such as a type name or function name. command! -nargs=1 ALESymbolSearch :call ale#symbol#Search(<q-args>) +" Complete text with tsserver and LSP command! -bar ALEComplete :call ale#completion#GetCompletions('ale-manual') +" Try to find completions for the current symbol that add additional text. +command! -bar ALEImport :call ale#completion#Import() + " Rename symbols using tsserver and LSP command! -bar ALERename :call ale#rename#Execute() @@ -260,18 +270,20 @@ nnoremap <silent> <Plug>(ale_lint) :ALELint<Return> nnoremap <silent> <Plug>(ale_detail) :ALEDetail<Return> nnoremap <silent> <Plug>(ale_fix) :ALEFix<Return> nnoremap <silent> <Plug>(ale_go_to_definition) :ALEGoToDefinition<Return> -nnoremap <silent> <Plug>(ale_go_to_definition_in_tab) :ALEGoToDefinitionInTab<Return> -nnoremap <silent> <Plug>(ale_go_to_definition_in_split) :ALEGoToDefinitionInSplit<Return> -nnoremap <silent> <Plug>(ale_go_to_definition_in_vsplit) :ALEGoToDefinitionInVSplit<Return> +nnoremap <silent> <Plug>(ale_go_to_definition_in_tab) :ALEGoToDefinition -tab<Return> +nnoremap <silent> <Plug>(ale_go_to_definition_in_split) :ALEGoToDefinition -split<Return> +nnoremap <silent> <Plug>(ale_go_to_definition_in_vsplit) :ALEGoToDefinition -vsplit<Return> nnoremap <silent> <Plug>(ale_go_to_type_definition) :ALEGoToTypeDefinition<Return> -nnoremap <silent> <Plug>(ale_go_to_type_definition_in_tab) :ALEGoToTypeDefinitionInTab<Return> -nnoremap <silent> <Plug>(ale_go_to_type_definition_in_split) :ALEGoToTypeDefinitionInSplit<Return> -nnoremap <silent> <Plug>(ale_go_to_type_definition_in_vsplit) :ALEGoToTypeDefinitionInVSplit<Return> +nnoremap <silent> <Plug>(ale_go_to_type_definition_in_tab) :ALEGoToTypeDefinition -tab<Return> +nnoremap <silent> <Plug>(ale_go_to_type_definition_in_split) :ALEGoToTypeDefinition -split<Return> +nnoremap <silent> <Plug>(ale_go_to_type_definition_in_vsplit) :ALEGoToTypeDefinitionIn -vsplit<Return> nnoremap <silent> <Plug>(ale_find_references) :ALEFindReferences<Return> nnoremap <silent> <Plug>(ale_hover) :ALEHover<Return> nnoremap <silent> <Plug>(ale_documentation) :ALEDocumentation<Return> inoremap <silent> <Plug>(ale_complete) <C-\><C-O>:ALEComplete<Return> +nnoremap <silent> <Plug>(ale_import) :ALEImport<Return> nnoremap <silent> <Plug>(ale_rename) :ALERename<Return> +nnoremap <silent> <Plug>(ale_repeat_selection) :ALERepeatSelection<Return> " Set up autocmd groups now. call ale#events#Init() diff --git a/rplugin/python3/deoplete/sources/ale.py b/rplugin/python3/deoplete/sources/ale.py index ae1f4039..82d9bbf2 100644 --- a/rplugin/python3/deoplete/sources/ale.py +++ b/rplugin/python3/deoplete/sources/ale.py @@ -49,12 +49,13 @@ class Source(Base): if event == 'Async': result = self.vim.call('ale#completion#GetCompletionResult') + return result or [] if context.get('is_refresh'): self.vim.command( - "call ale#completion#GetCompletions('ale-callback', " + \ - "{'callback': {completions -> deoplete#auto_complete() }})" + "call ale#completion#GetCompletions('ale-callback', " + + "{'callback': {completions -> deoplete#auto_complete() }})" ) return [] @@ -83,6 +83,13 @@ while [ $# -ne 0 ]; do run_neovim_03_tests=0 shift ;; + --fast) + run_vim_80_tests=0 + run_vim_81_tests=0 + run_neovim_02_tests=0 + run_neovim_03_tests=1 + shift + ;; --help) echo 'Usage: ./run-tests [OPTION]... [FILE]...' echo @@ -99,6 +106,7 @@ while [ $# -ne 0 ]; do echo ' --vim-80-only Run tests only for Vim 8.0' echo ' --vim-81-only Run tests only for Vim 8.1' echo ' --linters-only Run only Vint and custom checks' + echo ' --fast Run only the fastest Vim and custom checks' echo ' --help Show this help text' echo ' -- Stop parsing options after this' exit 0 diff --git a/supported-tools.md b/supported-tools.md index 0abc6b75..66e46348 100644 --- a/supported-tools.md +++ b/supported-tools.md @@ -16,7 +16,7 @@ formatting. | Key | Definition | | ------------- | -------------------------------- | -| :floppy_disk: | Only checked when saved to disk | +| :floppy_disk: | May only run on files on disk | | :warning: | Disabled by default | --- @@ -30,6 +30,7 @@ formatting. * [drafter](https://github.com/apiaryio/drafter) * AsciiDoc * [alex](https://github.com/wooorm/alex) :floppy_disk: + * [languagetool](https://languagetool.org/) :floppy_disk: * [proselint](http://proselint.com/) * [redpen](http://redpen.cc/) * [textlint](https://textlint.github.io/) @@ -40,10 +41,13 @@ formatting. * Awk * [gawk](https://www.gnu.org/software/gawk/) * Bash + * [bashate](https://github.com/openstack/bashate) * [language-server](https://github.com/mads-hartmann/bash-language-server) * 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) +* Bats + * [shellcheck](https://www.shellcheck.net/) * BibTeX * [bibclean](http://ftp.math.utah.edu/pub/bibclean/) * Bourne Shell @@ -51,6 +55,7 @@ formatting. * [shellcheck](https://www.shellcheck.net/) * [shfmt](https://github.com/mvdan/sh) * C + * [astyle](http://astyle.sourceforge.net/) * [ccls](https://github.com/MaskRay/ccls) * [clang](http://clang.llvm.org/) * [clangd](https://clang.llvm.org/extra/clangd.html) @@ -68,6 +73,7 @@ formatting. * [mcsc](http://www.mono-project.com/docs/about-mono/languages/csharp/) :floppy_disk: see:`help ale-cs-mcsc` for details and configuration * [uncrustify](https://github.com/uncrustify/uncrustify) * C++ (filetype cpp) + * [astyle](http://astyle.sourceforge.net/) * [ccls](https://github.com/MaskRay/ccls) * [clang](http://clang.llvm.org/) * [clangcheck](http://clang.llvm.org/docs/ClangCheck.html) :floppy_disk: @@ -122,6 +128,8 @@ formatting. * [dartanalyzer](https://github.com/dart-lang/sdk/tree/master/pkg/analyzer_cli) :floppy_disk: * [dartfmt](https://github.com/dart-lang/sdk/tree/master/utils/dartfmt) * [language_server](https://github.com/natebosch/dart_language_server) +* Dhall + * [dhall-format](https://github.com/dhall-lang/dhall-lang) * Dockerfile * [dockerfile_lint](https://github.com/projectatomic/dockerfile_lint) * [hadolint](https://github.com/hadolint/hadolint) @@ -171,6 +179,7 @@ formatting. * [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple) :warning: :floppy_disk: * [gotype](https://godoc.org/golang.org/x/tools/cmd/gotype) :warning: :floppy_disk: * [go vet](https://golang.org/cmd/vet/) :floppy_disk: + * [revive](https://github.com/mgechev/revive) :warning: :floppy_disk: * [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) :warning: :floppy_disk: * GraphQL * [eslint](http://eslint.org/) @@ -375,6 +384,7 @@ formatting. * [pylint](https://www.pylint.org/) :floppy_disk: * [pyls](https://github.com/palantir/python-language-server) :warning: * [pyre](https://github.com/facebook/pyre-check) :warning: + * [pyright](https://github.com/microsoft/pyright) * [reorder-python-imports](https://github.com/asottile/reorder_python_imports) * [vulture](https://github.com/jendrikseipp/vulture) :warning: :floppy_disk: * [yapf](https://github.com/google/yapf) @@ -417,6 +427,7 @@ formatting. * Rust * [cargo](https://github.com/rust-lang/cargo) :floppy_disk: (see `:help ale-integration-rust` for configuration instructions) * [rls](https://github.com/rust-lang-nursery/rls) :warning: + * [rust-analyzer](https://github.com/rust-analyzer/rust-analyzer) :warning: * [rustc](https://www.rust-lang.org/) :warning: * [rustfmt](https://github.com/rust-lang-nursery/rustfmt) * Sass @@ -447,6 +458,7 @@ formatting. * [sqlfmt](https://github.com/jackc/sqlfmt) * [sqlformat](https://github.com/andialbrecht/sqlparse) * [sqlint](https://github.com/purcell/sqlint) + * [sql-lint](https://github.com/joereynolds/sql-lint) * Stylus * [stylelint](https://github.com/stylelint/stylelint) * SugarSS @@ -454,6 +466,7 @@ formatting. * Swift * [sourcekit-lsp](https://github.com/apple/sourcekit-lsp) * [swiftformat](https://github.com/nicklockwood/SwiftFormat) + * [swift-format](https://github.com/apple/swift-format) * [swiftlint](https://github.com/realm/SwiftLint) * Tcl * [nagelfar](http://nagelfar.sourceforge.net) :floppy_disk: @@ -485,6 +498,7 @@ formatting. * VALA * [uncrustify](https://github.com/uncrustify/uncrustify) * Verilog + * [hdl-checker](https://pypi.org/project/hdl-checker) * [iverilog](https://github.com/steveicarus/iverilog) * [verilator](http://www.veripool.org/projects/verilator/wiki/Intro) * [vlog](https://www.mentor.com/products/fv/questa/) @@ -494,6 +508,7 @@ formatting. * [vcom](https://www.mentor.com/products/fv/questa/) * [xvhdl](https://www.xilinx.com/products/design-tools/vivado.html) * Vim + * [vimls](https://github.com/iamcco/vim-language-server) * [vint](https://github.com/Kuniwak/vint) * Vim help * [alex](https://github.com/wooorm/alex) :warning: :floppy_disk: @@ -514,3 +529,5 @@ formatting. * [yamllint](https://yamllint.readthedocs.io/) * YANG * [yang-lsp](https://github.com/theia-ide/yang-lsp) +* Zig + * [zls](https://github.com/zigtools/zls) diff --git a/test/command_callback/ccls_paths/with_build_dir/unusual_build_dir_name/compile_commands.json b/test/command_callback/ccls_paths/with_build_dir/unusual_build_dir_name/compile_commands.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/ccls_paths/with_build_dir/unusual_build_dir_name/compile_commands.json diff --git a/test/command_callback/hdl_server/foo.vhd b/test/command_callback/hdl_server/foo.vhd new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/hdl_server/foo.vhd diff --git a/test/command_callback/hdl_server/with_config_file/.hdl_checker.config b/test/command_callback/hdl_server/with_config_file/.hdl_checker.config new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/hdl_server/with_config_file/.hdl_checker.config diff --git a/test/command_callback/hdl_server/with_config_file/_hdl_checker.config b/test/command_callback/hdl_server/with_config_file/_hdl_checker.config new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/hdl_server/with_config_file/_hdl_checker.config diff --git a/test/command_callback/hdl_server/with_config_file/foo.vhd b/test/command_callback/hdl_server/with_config_file/foo.vhd new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/hdl_server/with_config_file/foo.vhd diff --git a/test/command_callback/hdl_server/with_git/files/foo.vhd b/test/command_callback/hdl_server/with_git/files/foo.vhd new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/test/command_callback/hdl_server/with_git/files/foo.vhd @@ -0,0 +1 @@ + diff --git a/test/command_callback/java_paths/build/gen/main/java/com/something/dummy b/test/command_callback/java_paths/build/gen/main/java/com/something/dummy new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/java_paths/build/gen/main/java/com/something/dummy diff --git a/test/command_callback/java_paths/build/gen2/main/java/com/something/dummy b/test/command_callback/java_paths/build/gen2/main/java/com/something/dummy new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/java_paths/build/gen2/main/java/com/something/dummy diff --git a/test/command_callback/test_ada_gcc_command_callbacks.vader b/test/command_callback/test_ada_gcc_command_callbacks.vader index de6e355e..906b31a4 100644 --- a/test/command_callback/test_ada_gcc_command_callbacks.vader +++ b/test/command_callback/test_ada_gcc_command_callbacks.vader @@ -18,11 +18,10 @@ After: call ale#assert#TearDownLinterTest() Execute(The executable should be configurable): - AssertLinter 'gcc', \ ale#Escape('gcc') . ' -x ada -c -gnatc' \ . ' -o ' . b:out_file - \ . ' -I ' . ale#Escape(getcwd()) + \ . ' -I %s:h' \ . ' -gnatwa -gnatq %t' let b:ale_ada_gcc_executable = 'foo' @@ -30,15 +29,14 @@ Execute(The executable should be configurable): AssertLinter 'foo', \ ale#Escape('foo') . ' -x ada -c -gnatc' \ . ' -o ' . b:out_file - \ . ' -I ' . ale#Escape(getcwd()) + \ . ' -I %s:h' \ . ' -gnatwa -gnatq %t' Execute(The options should be configurable): - let g:ale_ada_gcc_options = '--foo --bar' AssertLinter 'gcc', \ ale#Escape('gcc') . ' -x ada -c -gnatc' \ . ' -o ' . b:out_file - \ . ' -I ' . ale#Escape(getcwd()) + \ . ' -I %s:h' \ . ' --foo --bar %t' diff --git a/test/command_callback/test_asm_gcc_command_callbacks.vader b/test/command_callback/test_asm_gcc_command_callbacks.vader index 42606ec0..5976b5f2 100644 --- a/test/command_callback/test_asm_gcc_command_callbacks.vader +++ b/test/command_callback/test_asm_gcc_command_callbacks.vader @@ -3,7 +3,7 @@ Before: call ale#test#SetFilename('test.cpp') let b:command_tail = ' -x assembler' \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') - \ . '-iquote ' . ale#Escape(g:dir) + \ . '-iquote %s:h' \ . ' -Wall -' After: diff --git a/test/command_callback/test_bashate_command_callback.vader b/test/command_callback/test_bashate_command_callback.vader new file mode 100644 index 00000000..714cf690 --- /dev/null +++ b/test/command_callback/test_bashate_command_callback.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('sh', 'bashate') + call ale#test#SetFilename('test.sh') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default bashate command should be correct): + AssertLinter 'bashate', ale#Escape('bashate') . ' %t' + +Execute(The bashate command should accept options): + let b:ale_sh_bashate_options = '-i E310 --max-line-length 100' + + AssertLinter 'bashate', + \ ale#Escape('bashate') . ' -i E310 --max-line-length 100 %t' diff --git a/test/command_callback/test_c_cc_command_callbacks.vader b/test/command_callback/test_c_cc_command_callbacks.vader new file mode 100644 index 00000000..c8c2de7d --- /dev/null +++ b/test/command_callback/test_c_cc_command_callbacks.vader @@ -0,0 +1,55 @@ +Before: + Save g:ale_c_parse_makefile + Save g:ale_history_enabled + + let g:ale_c_parse_makefile = 0 + let g:ale_history_enabled = 0 + + let g:get_cflags_return_value = '' + let g:executable_map = {} + + runtime autoload/ale/c.vim + runtime autoload/ale/engine.vim + + function! ale#engine#IsExecutable(buffer, executable) abort + return has_key(g:executable_map, a:executable) + endfunction + + function! ale#c#GetCFlags(buffer, output) abort + return g:get_cflags_return_value + endfunction + + call ale#assert#SetUpLinterTest('c', 'cc') + + let b:command_tail = ' -S -x c' + \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -std=c11 -Wall -' + +After: + unlet! g:get_cflags_return_value + unlet! g:executable_map + unlet! b:command_tail + + runtime autoload/ale/c.vim + runtime autoload/ale/engine.vim + + call ale#assert#TearDownLinterTest() + +Execute(clang should be used instead of gcc, if available): + let g:executable_map = {'clang': 1} + + AssertLinter 'clang', [ale#Escape('clang') . b:command_tail] + +Execute(The executable should be configurable): + AssertLinter 'gcc', [ale#Escape('gcc') . b:command_tail] + + let b:ale_c_cc_executable = 'foobar' + + AssertLinter 'foobar', [ale#Escape('foobar') . b:command_tail] + +Execute(The -std flag should be replaced by parsed C flags): + let b:command_tail = substitute(b:command_tail, 'c11', 'c99 ', '') + let g:get_cflags_return_value = '-std=c99' + + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail diff --git a/test/command_callback/test_c_ccls_command_callbacks.vader b/test/command_callback/test_c_ccls_command_callbacks.vader index 43fdb366..04643d02 100644 --- a/test/command_callback/test_c_ccls_command_callbacks.vader +++ b/test/command_callback/test_c_ccls_command_callbacks.vader @@ -4,6 +4,10 @@ Before: call ale#assert#SetUpLinterTest('c', 'ccls') + Save b:ale_c_build_dir_names + Save b:ale_c_ccls_executable + Save b:ale_c_ccls_init_options + After: call ale#assert#TearDownLinterTest() @@ -47,3 +51,19 @@ Execute(The initialization options should be configurable): let b:ale_c_ccls_init_options = { 'cacheDirectory': '/tmp/ccls' } AssertLSPOptions { 'cacheDirectory': '/tmp/ccls' } + +Execute(The compile command database should be detected correctly): + call ale#test#SetFilename('ccls_paths/with_ccls/dummy.c') + + AssertLSPOptions {} + + call ale#test#SetFilename('ccls_paths/with_compile_commands_json/dummy.c') + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/ccls_paths/with_compile_commands_json') } + + call ale#test#SetFilename('ccls_paths/with_build_dir/dummy.c') + let b:ale_c_build_dir_names = ['unusual_build_dir_name'] + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/ccls_paths/with_build_dir/unusual_build_dir_name') } diff --git a/test/command_callback/test_c_clang_command_callbacks.vader b/test/command_callback/test_c_clang_command_callbacks.vader deleted file mode 100644 index b8c02e4d..00000000 --- a/test/command_callback/test_c_clang_command_callbacks.vader +++ /dev/null @@ -1,20 +0,0 @@ -Before: - Save g:ale_c_parse_makefile - let g:ale_c_parse_makefile = 0 - - call ale#assert#SetUpLinterTest('c', 'clang') - let b:command_tail = ' -S -x c -fsyntax-only -iquote' - \ . ' ' . ale#Escape(getcwd()) - \ . ' -std=c11 -Wall -' - -After: - unlet! b:command_tail - - call ale#assert#TearDownLinterTest() - -Execute(The executable should be configurable): - AssertLinter 'clang', [ale#Escape('clang') . b:command_tail] - - let b:ale_c_clang_executable = 'foobar' - - AssertLinter 'foobar', [ale#Escape('foobar') . b:command_tail] diff --git a/test/command_callback/test_c_clang_tidy_command_callback.vader b/test/command_callback/test_c_clang_tidy_command_callback.vader index 5ebbbd45..c4433550 100644 --- a/test/command_callback/test_c_clang_tidy_command_callback.vader +++ b/test/command_callback/test_c_clang_tidy_command_callback.vader @@ -57,7 +57,7 @@ Execute(The build directory setting should override the options): \ . ' -checks=' . ale#Escape('*') . ' %s' \ . ' -p ' . ale#Escape('/foo/bar') -Execute(The build directory should be ignored for header files): +Execute(The build directory should be used for header files): call ale#test#SetFilename('test.h') let b:ale_c_clangtidy_checks = ['*'] @@ -66,12 +66,8 @@ Execute(The build directory should be ignored for header files): AssertLinter 'clang-tidy', \ ale#Escape('clang-tidy') - \ . ' -checks=' . ale#Escape('*') . ' %s -- -Wall' - - call ale#test#SetFilename('test.h') - - AssertLinter 'clang-tidy', - \ ale#Escape('clang-tidy') . ' -checks=' . ale#Escape('*') . ' %s -- -Wall' + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') Execute(The executable should be configurable): let b:ale_c_clangtidy_checks = ['*'] diff --git a/test/command_callback/test_c_cppcheck_command_callbacks.vader b/test/command_callback/test_c_cppcheck_command_callbacks.vader index 3d487a31..b6ea3179 100644 --- a/test/command_callback/test_c_cppcheck_command_callbacks.vader +++ b/test/command_callback/test_c_cppcheck_command_callbacks.vader @@ -1,7 +1,6 @@ Before: call ale#assert#SetUpLinterTest('c', 'cppcheck') - - let b:command_tail = ' -q --language=c --enable=style -I' . ale#Escape(ale#path#Simplify(g:dir)) .' %t' + let b:command_tail = ' -q --language=c --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}'' --enable=style -I' . ale#Escape(ale#path#Simplify(g:dir)) .' %t' After: " Remove a test file we might open for some tests. @@ -10,9 +9,8 @@ After: set buftype=nofile endif - call ale#assert#TearDownLinterTest() - unlet! b:command_tail + call ale#assert#TearDownLinterTest() Execute(The executable should be configurable): AssertLinter 'cppcheck', ale#Escape('cppcheck') . b:command_tail @@ -28,6 +26,7 @@ Execute(cppcheck for C should detect compile_commands.json files): \ ale#path#CdString(ale#path#Simplify(g:dir . '/cppcheck_paths/one')) \ . ale#Escape('cppcheck') \ . ' -q --language=c' + \ . ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' \ . ' --project=' . ale#Escape('compile_commands.json') \ . ' --enable=style %t' @@ -38,6 +37,7 @@ Execute(cppcheck for C should detect compile_commands.json files in build direct \ ale#path#CdString(ale#path#Simplify(g:dir . '/cppcheck_paths/with_build_dir')) \ . ale#Escape('cppcheck') \ . ' -q --language=c' + \ . ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' \ . ' --project=' . ale#Escape(ale#path#Simplify('build/compile_commands.json')) \ . ' --enable=style %t' @@ -47,6 +47,7 @@ Execute(cppcheck for C should include file dir if compile_commands.json file is AssertLinter 'cppcheck', \ ale#Escape('cppcheck') \ . ' -q --language=c' + \ . ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' \ . ' --enable=style' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/cppcheck_paths')) \ . ' %t' @@ -61,6 +62,7 @@ Execute(cppcheck for C should ignore compile_commands.json file if buffer is mod \ ale#path#CdString(ale#path#Simplify(g:dir . '/cppcheck_paths/one')) \ . ale#Escape('cppcheck') \ . ' -q --language=c' + \ . ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' \ . ' --enable=style' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/cppcheck_paths/one')) \ . ' %t' diff --git a/test/command_callback/test_c_gcc_command_callbacks.vader b/test/command_callback/test_c_gcc_command_callbacks.vader deleted file mode 100644 index 2dbb8b7c..00000000 --- a/test/command_callback/test_c_gcc_command_callbacks.vader +++ /dev/null @@ -1,22 +0,0 @@ -Before: - Save g:ale_c_parse_makefile - let g:ale_c_parse_makefile = 0 - - call ale#assert#SetUpLinterTest('c', 'gcc') - - let b:command_tail = ' -S -x c' - \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(getcwd()) - \ . ' -std=c11 -Wall -' - -After: - call ale#assert#TearDownLinterTest() - - unlet! b:command_tail - -Execute(The executable should be configurable): - AssertLinter 'gcc', [ale#Escape('gcc') . b:command_tail] - - let b:ale_c_gcc_executable = 'foobar' - - AssertLinter 'foobar', [ale#Escape('foobar') . b:command_tail] diff --git a/test/command_callback/test_c_import_paths.vader b/test/command_callback/test_c_import_paths.vader index e6102998..3c2bd79b 100644 --- a/test/command_callback/test_c_import_paths.vader +++ b/test/command_callback/test_c_import_paths.vader @@ -7,6 +7,7 @@ Before: Save g:__ale_c_project_filenames let g:original_project_filenames = g:__ale_c_project_filenames + let g:executable_map = {} " Remove the .git/HEAD dir for C import paths for these tests. " The tests run inside of a git repo. @@ -18,106 +19,67 @@ Before: let g:ale_c_parse_compile_commands = 0 let g:ale_c_parse_makefile = 0 + runtime autoload/ale/engine.vim + + function! ale#engine#IsExecutable(buffer, executable) abort + return has_key(g:executable_map, a:executable) + endfunction + After: Restore unlet! g:original_project_filenames + unlet! g:executable_map + + runtime autoload/ale/engine.vim call ale#assert#TearDownLinterTest() -Execute(The C GCC handler should include 'include' directories for projects with a Makefile): - call ale#assert#SetUpLinterTest('c', 'gcc') +Execute(The C cc linter should include 'include' directories for projects with a Makefile): + call ale#assert#SetUpLinterTest('c', 'cc') call ale#test#SetFilename('../test_c_projects/makefile_project/subdir/file.c') - let g:ale_c_gcc_options = '' + let g:ale_c_cc_options = '' AssertLinter 'gcc', \ ale#Escape('gcc') \ . ' -S -x c -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/subdir')) + \ . ' -iquote %s:h' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/include')) \ . ' -' -Execute(The C GCC handler should include 'include' directories for projects with a configure file): - call ale#assert#SetUpLinterTest('c', 'gcc') +Execute(The C cc linter should include 'include' directories for projects with a configure file): + call ale#assert#SetUpLinterTest('c', 'cc') call ale#test#SetFilename('../test_c_projects/configure_project/subdir/file.c') - let g:ale_c_gcc_options = '' + let g:ale_c_cc_options = '' AssertLinter 'gcc', \ ale#Escape('gcc') \ . ' -S -x c -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/configure_project/subdir')) + \ . ' -iquote %s:h' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/configure_project/include')) \ . ' -' -Execute(The C GCC handler should include root directories for projects with .h files in them): - call ale#assert#SetUpLinterTest('c', 'gcc') +Execute(The C cc linter should include root directories for projects with .h files in them): + call ale#assert#SetUpLinterTest('c', 'cc') call ale#test#SetFilename('../test_c_projects/h_file_project/subdir/file.c') - let g:ale_c_gcc_options = '' + let g:ale_c_cc_options = '' AssertLinter 'gcc', \ ale#Escape('gcc') \ . ' -S -x c -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project/subdir')) + \ . ' -iquote %s:h' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project')) \ . ' -' -Execute(The C GCC handler should include root directories for projects with .hpp files in them): - call ale#assert#SetUpLinterTest('c', 'gcc') +Execute(The C cc linter should include root directories for projects with .hpp files in them): + call ale#assert#SetUpLinterTest('c', 'cc') call ale#test#SetFilename('../test_c_projects/hpp_file_project/subdir/file.c') - let g:ale_c_gcc_options = '' + let g:ale_c_cc_options = '' AssertLinter 'gcc', \ ale#Escape('gcc') \ . ' -S -x c -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project/subdir')) - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project')) - \ . ' -' - -Execute(The C Clang handler should include 'include' directories for projects with a Makefile): - call ale#assert#SetUpLinterTest('c', 'clang') - call ale#test#SetFilename('../test_c_projects/makefile_project/subdir/file.c') - let g:ale_c_clang_options = '' - - AssertLinter 'clang', - \ ale#Escape('clang') - \ . ' -S -x c -fsyntax-only' - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/subdir')) - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/include')) - \ . ' -' - -Execute(The C Clang handler should include 'include' directories for projects with a configure file): - call ale#assert#SetUpLinterTest('c', 'clang') - call ale#test#SetFilename('../test_c_projects/h_file_project/subdir/file.c') - let g:ale_c_clang_options = '' - - AssertLinter 'clang', - \ ale#Escape('clang') - \ . ' -S -x c -fsyntax-only' - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project/subdir')) - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project')) - \ . ' -' - -Execute(The C Clang handler should include root directories for projects with .h files in them): - call ale#assert#SetUpLinterTest('c', 'clang') - call ale#test#SetFilename('../test_c_projects/h_file_project/subdir/file.c') - let g:ale_c_clang_options = '' - - AssertLinter 'clang', - \ ale#Escape('clang') - \ . ' -S -x c -fsyntax-only' - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project/subdir')) - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project')) - \ . ' -' - -Execute(The C Clang handler should include root directories for projects with .hpp files in them): - call ale#assert#SetUpLinterTest('c', 'clang') - call ale#test#SetFilename('../test_c_projects/hpp_file_project/subdir/file.c') - let g:ale_c_clang_options = '' - - AssertLinter 'clang', - \ ale#Escape('clang') - \ . ' -S -x c -fsyntax-only' - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project/subdir')) + \ . ' -iquote %s:h' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project')) \ . ' -' @@ -131,99 +93,51 @@ Execute(The C ClangTidy handler should include 'include' directories for project \ . ' %s ' \ . '-- -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/include')) -Execute(The C++ GCC handler should include 'include' directories for projects with a Makefile): - call ale#assert#SetUpLinterTest('cpp', 'gcc') +Execute(The C++ cc linter should include 'include' directories for projects with a Makefile): + call ale#assert#SetUpLinterTest('cpp', 'cc') call ale#test#SetFilename('../test_c_projects/makefile_project/subdir/file.cpp') - let g:ale_cpp_gcc_options = '' + let g:ale_cpp_cc_options = '' AssertLinter 'gcc', \ ale#Escape('gcc') \ . ' -S -x c++ -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/subdir')) + \ . ' -iquote %s:h' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/include')) \ . ' -' -Execute(The C++ GCC handler should include 'include' directories for projects with a configure file): - call ale#assert#SetUpLinterTest('cpp', 'gcc') +Execute(The C++ cc linter should include 'include' directories for projects with a configure file): + call ale#assert#SetUpLinterTest('cpp', 'cc') call ale#test#SetFilename('../test_c_projects/configure_project/subdir/file.cpp') - let g:ale_cpp_gcc_options = '' + let g:ale_cpp_cc_options = '' AssertLinter 'gcc', \ ale#Escape('gcc') \ . ' -S -x c++ -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/configure_project/subdir')) + \ . ' -iquote %s:h' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/configure_project/include')) \ . ' -' -Execute(The C++ GCC handler should include root directories for projects with .h files in them): - call ale#assert#SetUpLinterTest('cpp', 'gcc') +Execute(The C++ cc linter should include root directories for projects with .h files in them): + call ale#assert#SetUpLinterTest('cpp', 'cc') call ale#test#SetFilename('../test_c_projects/h_file_project/subdir/file.cpp') - let g:ale_cpp_gcc_options = '' + let g:ale_cpp_cc_options = '' AssertLinter 'gcc', \ ale#Escape('gcc') \ . ' -S -x c++ -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project/subdir')) + \ . ' -iquote %s:h' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project')) \ . ' -' -Execute(The C++ GCC handler should include root directories for projects with .hpp files in them): - call ale#assert#SetUpLinterTest('cpp', 'gcc') +Execute(The C++ cc linter should include root directories for projects with .hpp files in them): + call ale#assert#SetUpLinterTest('cpp', 'cc') call ale#test#SetFilename('../test_c_projects/hpp_file_project/subdir/file.cpp') - let g:ale_cpp_gcc_options = '' + let g:ale_cpp_cc_options = '' AssertLinter 'gcc', \ ale#Escape('gcc') \ . ' -S -x c++ -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project/subdir')) - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project')) - \ . ' -' - -Execute(The C++ Clang handler should include 'include' directories for projects with a Makefile): - call ale#assert#SetUpLinterTest('cpp', 'clang') - call ale#test#SetFilename('../test_c_projects/makefile_project/subdir/file.cpp') - let g:ale_cpp_clang_options = '' - - AssertLinter 'clang++', - \ ale#Escape('clang++') - \ . ' -S -x c++ -fsyntax-only' - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/subdir')) - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/include')) - \ . ' -' - -Execute(The C++ Clang handler should include 'include' directories for projects with a configure file): - call ale#assert#SetUpLinterTest('cpp', 'clang') - call ale#test#SetFilename('../test_c_projects/configure_project/subdir/file.cpp') - let g:ale_cpp_clang_options = '' - - AssertLinter 'clang++', - \ ale#Escape('clang++') - \ . ' -S -x c++ -fsyntax-only' - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/configure_project/subdir')) - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/configure_project/include')) - \ . ' -' - -Execute(The C++ Clang handler should include root directories for projects with .h files in them): - call ale#assert#SetUpLinterTest('cpp', 'clang') - call ale#test#SetFilename('../test_c_projects/h_file_project/subdir/file.cpp') - let g:ale_cpp_clang_options = '' - - AssertLinter 'clang++', - \ ale#Escape('clang++') - \ . ' -S -x c++ -fsyntax-only' - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project/subdir')) - \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/h_file_project')) - \ . ' -' - -Execute(The C++ Clang handler should include root directories for projects with .hpp files in them): - call ale#assert#SetUpLinterTest('cpp', 'clang') - call ale#test#SetFilename('../test_c_projects/hpp_file_project/subdir/file.cpp') - let g:ale_cpp_clang_options = '' - - AssertLinter 'clang++', - \ ale#Escape('clang++') - \ . ' -S -x c++ -fsyntax-only' - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project/subdir')) + \ . ' -iquote %s:h' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project')) \ . ' -' diff --git a/test/command_callback/test_cargo_command_callbacks.vader b/test/command_callback/test_cargo_command_callbacks.vader index e56551ae..2d83351e 100644 --- a/test/command_callback/test_cargo_command_callbacks.vader +++ b/test/command_callback/test_cargo_command_callbacks.vader @@ -169,10 +169,11 @@ Execute(Build supports all cargo flags): let g:ale_rust_cargo_check_tests = 1 let g:ale_rust_cargo_check_examples = 1 let b:ale_rust_cargo_default_feature_behavior = 'all' + let b:ale_rust_cargo_target_dir = 'target/ale' AssertLinter 'cargo', [ \ ale#Escape('cargo') . ' --version', - \ 'cargo build --all-targets --examples --tests --frozen --message-format=json -q --all-features', + \ 'cargo build --all-targets --examples --tests --target-dir ' . ale#Escape('target/ale') . ' --frozen --message-format=json -q --all-features', \] Execute(Clippy supports all cargo flags): @@ -182,10 +183,11 @@ Execute(Clippy supports all cargo flags): let g:ale_rust_cargo_check_examples = 1 let b:ale_rust_cargo_default_feature_behavior = 'all' let b:ale_rust_cargo_clippy_options = '-D warnings' + let b:ale_rust_cargo_target_dir = 'target/ale' AssertLinter 'cargo', [ \ ale#Escape('cargo') . ' --version', - \ 'cargo clippy --all-targets --examples --tests --frozen --message-format=json -q --all-features -- -D warnings', + \ 'cargo clippy --all-targets --examples --tests --target-dir ' . ale#Escape('target/ale') . ' --frozen --message-format=json -q --all-features -- -D warnings', \] Execute(cargo-check does not refer ale_rust_cargo_clippy_options): @@ -197,3 +199,21 @@ Execute(cargo-check does not refer ale_rust_cargo_clippy_options): \ ale#Escape('cargo') . ' --version', \ 'cargo check --frozen --message-format=json -q', \] + +Execute(`cargo --target-dir` should be used when the version is new enough and it is set): + let b:ale_rust_cargo_target_dir = 'target/ale' + + GivenCommandOutput ['cargo 0.17.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo check --target-dir ' . ale#Escape('target/ale') . g:suffix, + \] + +Execute(`cargo --target-dir` should not be used when the version is not new enough and it is set): + let b:ale_rust_cargo_target_dir = 'target/ale' + + GivenCommandOutput ['cargo 0.16.0 (3423351a5 2017-10-06)'] + AssertLinter 'cargo', [ + \ ale#Escape('cargo') . ' --version', + \ 'cargo build' . g:suffix, + \] diff --git a/test/command_callback/test_checkstyle_command_callback.vader b/test/command_callback/test_checkstyle_command_callback.vader index 7a9f26b3..5824df87 100644 --- a/test/command_callback/test_checkstyle_command_callback.vader +++ b/test/command_callback/test_checkstyle_command_callback.vader @@ -20,11 +20,11 @@ Execute(The checkstyle executable should be configurable): \ . ' %s' Execute(Custom options should be supported): - let b:ale_java_checkstyle_options = '--foobar' + let b:ale_java_checkstyle_options = '--foobar -cp -classpath /path/to/checkstyle-8.7-all.jar' AssertLinter 'checkstyle', \ ale#Escape('checkstyle') - \ . ' --foobar' + \ . ' --foobar -cp -classpath /path/to/checkstyle-8.7-all.jar' \ . ' -c ' . ale#Escape('/google_checks.xml') \ . ' %s' diff --git a/test/command_callback/test_clang_tidy_command_callback.vader b/test/command_callback/test_clang_tidy_command_callback.vader index c2d18dea..f0a07e8c 100644 --- a/test/command_callback/test_clang_tidy_command_callback.vader +++ b/test/command_callback/test_clang_tidy_command_callback.vader @@ -57,7 +57,7 @@ Execute(The build directory setting should override the options): \ . ' -checks=' . ale#Escape('*') . ' %s' \ . ' -p ' . ale#Escape('/foo/bar') -Execute(The build directory should be ignored for header files): +Execute(The build directory should be used for header files): call ale#test#SetFilename('test.h') let b:ale_cpp_clangtidy_checks = ['*'] @@ -66,12 +66,16 @@ Execute(The build directory should be ignored for header files): AssertLinter 'clang-tidy', \ ale#Escape('clang-tidy') - \ . ' -checks=' . ale#Escape('*') . ' %s -- -Wall' + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') + \ . ' -- -x c++' call ale#test#SetFilename('test.hpp') AssertLinter 'clang-tidy', - \ ale#Escape('clang-tidy') . ' -checks=' . ale#Escape('*') . ' %s -- -Wall' + \ ale#Escape('clang-tidy') + \ . ' -checks=' . ale#Escape('*') . ' %s' + \ . ' -p ' . ale#Escape('/foo/bar') Execute(The executable should be configurable): let b:ale_cpp_clangtidy_checks = ['*'] diff --git a/test/command_callback/test_cpp_cc_command_callbacks.vader b/test/command_callback/test_cpp_cc_command_callbacks.vader new file mode 100644 index 00000000..dec3a07c --- /dev/null +++ b/test/command_callback/test_cpp_cc_command_callbacks.vader @@ -0,0 +1,55 @@ +Before: + Save g:ale_c_parse_makefile + Save g:ale_history_enabled + + let g:ale_c_parse_makefile = 0 + let g:ale_history_enabled = 0 + + let g:get_cflags_return_value = '' + let g:executable_map = {} + + runtime autoload/ale/c.vim + runtime autoload/ale/engine.vim + + function! ale#engine#IsExecutable(buffer, executable) abort + return has_key(g:executable_map, a:executable) + endfunction + + function! ale#c#GetCFlags(buffer, output) abort + return g:get_cflags_return_value + endfunction + + call ale#assert#SetUpLinterTest('cpp', 'cc') + + let b:command_tail = ' -S -x c++' + \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') + \ . ' -iquote %s:h' + \ . ' -std=c++14 -Wall -' + +After: + unlet! g:get_cflags_return_value + unlet! g:executable_map + unlet! b:command_tail + + runtime autoload/ale/c.vim + runtime autoload/ale/engine.vim + + call ale#assert#TearDownLinterTest() + +Execute(clang++ should be used instead of gcc, if available): + let g:executable_map = {'clang++': 1} + + AssertLinter 'clang++', [ale#Escape('clang++') . b:command_tail] + +Execute(The executable should be configurable): + AssertLinter 'gcc', [ale#Escape('gcc') . b:command_tail] + + let b:ale_cpp_cc_executable = 'foobar' + + AssertLinter 'foobar', [ale#Escape('foobar') . b:command_tail] + +Execute(The -std flag should be replaced by parsed C flags): + let b:command_tail = substitute(b:command_tail, 'c++14', 'c++11 ', '') + let g:get_cflags_return_value = '-std=c++11' + + AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail diff --git a/test/command_callback/test_cpp_ccls_command_callbacks.vader b/test/command_callback/test_cpp_ccls_command_callbacks.vader index eece42bc..f603ac07 100644 --- a/test/command_callback/test_cpp_ccls_command_callbacks.vader +++ b/test/command_callback/test_cpp_ccls_command_callbacks.vader @@ -4,6 +4,10 @@ Before: call ale#assert#SetUpLinterTest('cpp', 'ccls') + Save b:ale_c_build_dir_names + Save b:ale_cpp_ccls_executable + Save b:ale_cpp_ccls_init_options + After: call ale#assert#TearDownLinterTest() @@ -47,3 +51,19 @@ Execute(The initialization options should be configurable): let b:ale_cpp_ccls_init_options = { 'cacheDirectory': '/tmp/ccls' } AssertLSPOptions { 'cacheDirectory': '/tmp/ccls' } + +Execute(The compile command database should be detected correctly): + call ale#test#SetFilename('ccls_paths/with_ccls/dummy.c') + + AssertLSPOptions {} + + call ale#test#SetFilename('ccls_paths/with_compile_commands_json/dummy.c') + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/ccls_paths/with_compile_commands_json') } + + call ale#test#SetFilename('ccls_paths/with_build_dir/dummy.c') + let b:ale_c_build_dir_names = ['unusual_build_dir_name'] + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/ccls_paths/with_build_dir/unusual_build_dir_name') } diff --git a/test/command_callback/test_cpp_clang_command_callbacks.vader b/test/command_callback/test_cpp_clang_command_callbacks.vader deleted file mode 100644 index e96fd8e7..00000000 --- a/test/command_callback/test_cpp_clang_command_callbacks.vader +++ /dev/null @@ -1,19 +0,0 @@ -Before: - Save g:ale_c_parse_makefile - let g:ale_c_parse_makefile = 0 - - call ale#assert#SetUpLinterTest('cpp', 'clang') - let b:command_tail = ' -S -x c++ -fsyntax-only -iquote' - \ . ' ' . ale#Escape(getcwd()) - \ . ' -std=c++14 -Wall -' - -After: - unlet! b:command_tail - call ale#assert#TearDownLinterTest() - -Execute(The executable should be configurable): - AssertLinter 'clang++', ale#Escape('clang++') . b:command_tail - - let b:ale_cpp_clang_executable = 'foobar' - - AssertLinter 'foobar', ale#Escape('foobar') . b:command_tail diff --git a/test/command_callback/test_cpp_clazy_command_callback.vader b/test/command_callback/test_cpp_clazy_command_callback.vader index 1be43b96..e5a81b8f 100644 --- a/test/command_callback/test_cpp_clazy_command_callback.vader +++ b/test/command_callback/test_cpp_clazy_command_callback.vader @@ -34,18 +34,20 @@ Execute(The build directory should be configurable): \ ale#Escape('clazy-standalone') \ . ' -checks=' . ale#Escape('level1') . ' -p ' . ale#Escape('/foo/bar') . ' %s' -Execute(The build directory should be ignored for header files): +Execute(The build directory should be used for header files): call ale#test#SetFilename('test.h') let b:ale_c_build_dir = '/foo/bar' AssertLinter 'clazy-standalone', - \ ale#Escape('clazy-standalone') . ' -checks=' . ale#Escape('level1') . ' %s' + \ ale#Escape('clazy-standalone') + \ . ' -checks=' . ale#Escape('level1') . ' -p ' . ale#Escape('/foo/bar') . ' %s' call ale#test#SetFilename('test.hpp') AssertLinter 'clazy-standalone', - \ ale#Escape('clazy-standalone') . ' -checks=' . ale#Escape('level1') . ' %s' + \ ale#Escape('clazy-standalone') + \ . ' -checks=' . ale#Escape('level1') . ' -p ' . ale#Escape('/foo/bar') . ' %s' Execute(The executable should be configurable): let b:ale_cpp_clazy_executable = 'foobar' diff --git a/test/command_callback/test_cpp_cppcheck_command_callbacks.vader b/test/command_callback/test_cpp_cppcheck_command_callbacks.vader index 02bdf748..b19c09b1 100644 --- a/test/command_callback/test_cpp_cppcheck_command_callbacks.vader +++ b/test/command_callback/test_cpp_cppcheck_command_callbacks.vader @@ -1,6 +1,6 @@ Before: call ale#assert#SetUpLinterTest('cpp', 'cppcheck') - let b:command_tail = ' -q --language=c++ --enable=style -I' . ale#Escape(ale#path#Simplify(g:dir)) .' %t' + let b:command_tail = ' -q --language=c++ --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}'' --enable=style -I' . ale#Escape(ale#path#Simplify(g:dir)) .' %t' After: " Remove a test file we might open for some tests. @@ -26,6 +26,7 @@ Execute(cppcheck for C++ should detect compile_commands.json files): \ ale#path#CdString(ale#path#Simplify(g:dir . '/cppcheck_paths/one')) \ . ale#Escape('cppcheck') \ . ' -q --language=c++' + \ . ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' \ . ' --project=' . ale#Escape('compile_commands.json') \ . ' --enable=style %t' @@ -36,6 +37,7 @@ Execute(cppcheck for C++ should detect compile_commands.json files in build dire \ ale#path#CdString(ale#path#Simplify(g:dir . '/cppcheck_paths/with_build_dir')) \ . ale#Escape('cppcheck') \ . ' -q --language=c++' + \ . ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' \ . ' --project=' . ale#Escape(ale#path#Simplify('build/compile_commands.json')) \ . ' --enable=style %t' @@ -45,6 +47,7 @@ Execute(cppcheck for C++ should include file dir if compile_commands.json file i AssertLinter 'cppcheck', \ ale#Escape('cppcheck') \ . ' -q --language=c++' + \ . ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' \ . ' --enable=style' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/cppcheck_paths')) \ . ' %t' @@ -59,6 +62,7 @@ Execute(cppcheck for C++ should ignore compile_commands.json file if buffer is m \ ale#path#CdString(ale#path#Simplify(g:dir . '/cppcheck_paths/one')) \ . ale#Escape('cppcheck') \ . ' -q --language=c++' + \ . ' --template=''{file}:{line}:{column}: {severity}:{inconclusive:inconclusive:} {message} [{id}]\\n{code}''' \ . ' --enable=style' \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/cppcheck_paths/one')) \ . ' %t' diff --git a/test/command_callback/test_cpp_gcc_command_callbacks.vader b/test/command_callback/test_cpp_gcc_command_callbacks.vader deleted file mode 100644 index cfa4ecc0..00000000 --- a/test/command_callback/test_cpp_gcc_command_callbacks.vader +++ /dev/null @@ -1,20 +0,0 @@ -Before: - Save g:ale_c_parse_makefile - let g:ale_c_parse_makefile = 0 - - call ale#assert#SetUpLinterTest('cpp', 'gcc') - let b:command_tail = ' -S -x c++' - \ . ' -o ' . (has('win32') ? 'nul': '/dev/null') - \ . ' -iquote ' . ale#Escape(getcwd()) - \ . ' -std=c++14 -Wall -' - -After: - unlet! b:command_tail - call ale#assert#TearDownLinterTest() - -Execute(The executable should be configurable): - AssertLinter 'gcc', ale#Escape('gcc') . b:command_tail - - let b:ale_cpp_gcc_executable = 'foobar' - - AssertLinter 'foobar', ale#Escape('foobar') . b:command_tail diff --git a/test/command_callback/test_eclipselsp_command_callback.vader b/test/command_callback/test_eclipselsp_command_callback.vader index f25ed5fc..6bbc4053 100644 --- a/test/command_callback/test_eclipselsp_command_callback.vader +++ b/test/command_callback/test_eclipselsp_command_callback.vader @@ -54,6 +54,7 @@ Execute(VersionCheck should return correct version): Execute(The eclipselsp callback should return the correct default value): let cmd = [ ale#Escape('java'), + \ '', \ '-Declipse.application=org.eclipse.jdt.ls.core.id1', \ '-Dosgi.bundles.defaultStartLevel=4', \ '-Declipse.product=org.eclipse.jdt.ls.core.product', @@ -72,6 +73,7 @@ Execute(The eclipselsp callback should return the correct default value): Execute(The eclipselsp callback should allow custom executable): let b:ale_java_eclipselsp_executable='/bin/foobar' let cmd = [ ale#Escape('/bin/foobar'), + \ '', \ '-Declipse.application=org.eclipse.jdt.ls.core.id1', \ '-Dosgi.bundles.defaultStartLevel=4', \ '-Declipse.product=org.eclipse.jdt.ls.core.product', @@ -87,9 +89,12 @@ Execute(The eclipselsp callback should allow custom executable): \] AssertLinter '/bin/foobar', join(cmd, ' ') -Execute(The eclipselsp callback should allow custom configuration path): +Execute(The eclipselsp callback should allow custom configuration path and javaagent): let b:ale_java_eclipselsp_config_path = '/home/config' + let b:ale_java_eclipselsp_javaagent = '/home/lombok.jar /home/lombok2.jar' let cmd = [ ale#Escape('java'), + \ ale#Escape('-javaagent:/home/lombok.jar'), + \ ale#Escape('-javaagent:/home/lombok2.jar'), \ '-Declipse.application=org.eclipse.jdt.ls.core.id1', \ '-Dosgi.bundles.defaultStartLevel=4', \ '-Declipse.product=org.eclipse.jdt.ls.core.product', diff --git a/test/command_callback/test_elixir_credo.vader b/test/command_callback/test_elixir_credo.vader index 1a146db8..3eb88846 100644 --- a/test/command_callback/test_elixir_credo.vader +++ b/test/command_callback/test_elixir_credo.vader @@ -8,6 +8,18 @@ After: call ale#assert#TearDownLinterTest() +Execute(Builds credo command with normal project): + AssertLinter 'mix', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/elixir_paths/mix_project')) + \ . 'mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s' + +Execute(Builds credo command with umbrella project): + call ale#test#SetFilename('elixir_paths/umbrella_project/apps/mix_project/lib/app.ex') + + AssertLinter 'mix', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/elixir_paths/umbrella_project')) + \ . 'mix help credo && mix credo suggest --format=flycheck --read-from-stdin %s' + Execute(Builds credo command with --strict mode when set to 1): let g:ale_elixir_credo_strict = 1 diff --git a/test/command_callback/test_embertemplatelint_command_callbacks.vader b/test/command_callback/test_embertemplatelint_command_callbacks.vader new file mode 100644 index 00000000..97687d29 --- /dev/null +++ b/test/command_callback/test_embertemplatelint_command_callbacks.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('handlebars', 'embertemplatelint') + + GivenCommandOutput ['1.6.0'] + +After: + call ale#assert#TearDownLinterTest() + +Execute(ember-template-lint executables runs the right command): + AssertLinter 'ember-template-lint', + \ ale#Escape('ember-template-lint') . ' --json --filename %s' + +Execute(old ember-template-lint executables runs the right command): + GivenCommandOutput [] + + AssertLinter 'ember-template-lint', + \ ale#Escape('ember-template-lint') . ' --json %t' diff --git a/test/command_callback/test_flake8_command_callback.vader b/test/command_callback/test_flake8_command_callback.vader index f082a63a..09f64ee3 100644 --- a/test/command_callback/test_flake8_command_callback.vader +++ b/test/command_callback/test_flake8_command_callback.vader @@ -34,13 +34,52 @@ Execute(The flake8 callbacks should return the correct default values): \] Execute(The option for disabling changing directories should work): - let g:ale_python_flake8_change_directory = 0 + let g:ale_python_flake8_change_directory = 'off' AssertLinter 'flake8', [ \ ale#Escape('flake8') . ' --version', \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', \] + let g:ale_python_flake8_change_directory = 0 + + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + + " Invalid options should be considered the same as turning the setting off. + let g:ale_python_flake8_change_directory = 'xxx' + + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + +Execute(The option for changing directory to project root should work): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/namespace_package_tox/namespace/foo/bar.py') + + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#path#CdString(ale#python#FindProjectRootIni(bufnr(''))) + \ . ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + +Execute(The option for changing directory to file dir should work): + let g:ale_python_flake8_change_directory = 'file' + silent execute 'file ' . fnameescape(g:dir . '/python_paths/namespace_package_tox/namespace/foo/bar.py') + + AssertLinter 'flake8', [ + \ ale#Escape('flake8') . ' --version', + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + + let g:ale_python_flake8_change_directory = 1 + + AssertLinter 'flake8', [ + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('flake8') . ' --format=default --stdin-display-name %s -', + \] + Execute(The flake8 command callback should let you set options): let g:ale_python_flake8_options = '--some-option' @@ -163,5 +202,5 @@ Execute(Pipenv is detected when python_flake8_auto_pipenv is set): call ale#test#SetFilename('../python_fixtures/pipenv/whatever.py') AssertLinter 'pipenv', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(ale#python#FindProjectRootIni(bufnr(''))) \ . ale#Escape('pipenv') . ' run flake8 --format=default --stdin-display-name %s -' diff --git a/test/command_callback/test_gobuild_command_callback.vader b/test/command_callback/test_gobuild_command_callback.vader index fdf23866..063f3f2f 100644 --- a/test/command_callback/test_gobuild_command_callback.vader +++ b/test/command_callback/test_gobuild_command_callback.vader @@ -11,14 +11,14 @@ After: Execute(The default commands should be correct): AssertLinter 'go', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . 'go test -c -o /dev/null ./' Execute(Go environment variables should be supported): let b:ale_go_go111module = 'on' AssertLinter 'go', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Env('GO111MODULE', 'on') \ . 'go test -c -o /dev/null ./' @@ -28,7 +28,7 @@ Execute(Extra options should be supported): let g:ale_go_gobuild_options = '--foo-bar' AssertLinter 'go', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . 'go test --foo-bar -c -o /dev/null ./' let g:ale_go_gobuild_options = '' @@ -37,5 +37,5 @@ Execute(The executable should be configurable): let g:ale_go_go_executable = 'foobar' AssertLinter 'foobar', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . 'foobar test -c -o /dev/null ./' diff --git a/test/command_callback/test_gofmt_command_callback.vader b/test/command_callback/test_gofmt_command_callback.vader index 4da1f6c8..88b2e6b0 100644 --- a/test/command_callback/test_gofmt_command_callback.vader +++ b/test/command_callback/test_gofmt_command_callback.vader @@ -1,5 +1,8 @@ Before: Save g:ale_go_go111module + Save b:ale_go_go111module + + let b:ale_go_go111module = '' call ale#assert#SetUpLinterTest('go', 'gofmt') call ale#test#SetFilename('../go_files/testfile2.go') diff --git a/test/command_callback/test_golangci_lint_command_callback.vader b/test/command_callback/test_golangci_lint_command_callback.vader index 7f1e2ac4..37fb1f7d 100644 --- a/test/command_callback/test_golangci_lint_command_callback.vader +++ b/test/command_callback/test_golangci_lint_command_callback.vader @@ -13,7 +13,7 @@ After: Execute(The golangci-lint defaults should be correct): AssertLinter 'golangci-lint', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('golangci-lint') \ . ' run ' . ale#Escape(expand('%' . ':t')) \ . ' --enable-all' @@ -22,7 +22,7 @@ Execute(The golangci-lint callback should use a configured executable): let b:ale_go_golangci_lint_executable = 'something else' AssertLinter 'something else', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('something else') \ . ' run ' . ale#Escape(expand('%' . ':t')) \ . ' --enable-all' @@ -31,7 +31,7 @@ Execute(The golangci-lint callback should use configured options): let b:ale_go_golangci_lint_options = '--foobar' AssertLinter 'golangci-lint', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('golangci-lint') \ . ' run ' . ale#Escape(expand('%' . ':t')) \ . ' --foobar' @@ -40,7 +40,7 @@ Execute(The golangci-lint callback should support environment variables): let b:ale_go_go111module = 'on' AssertLinter 'golangci-lint', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Env('GO111MODULE', 'on') \ . ale#Escape('golangci-lint') \ . ' run ' . ale#Escape(expand('%' . ':t')) @@ -50,5 +50,5 @@ Execute(The golangci-lint `lint_package` option should use the correct command): let b:ale_go_golangci_lint_package = 1 AssertLinter 'golangci-lint', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('golangci-lint') . ' run --enable-all' diff --git a/test/command_callback/test_gometalinter_command_callback.vader b/test/command_callback/test_gometalinter_command_callback.vader index d922efc6..567997d8 100644 --- a/test/command_callback/test_gometalinter_command_callback.vader +++ b/test/command_callback/test_gometalinter_command_callback.vader @@ -13,7 +13,7 @@ After: Execute(The gometalinter defaults should be correct): AssertLinter 'gometalinter', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('gometalinter') \ . ' --include=' . ale#Escape(ale#util#EscapePCRE(expand('%' . ':t'))) \ . ' .' @@ -22,7 +22,7 @@ Execute(The gometalinter callback should use a configured executable): let b:ale_go_gometalinter_executable = 'something else' AssertLinter 'something else', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('something else') \ . ' --include=' . ale#Escape(ale#util#EscapePCRE(expand('%' . ':t'))) \ . ' .' @@ -31,7 +31,7 @@ Execute(The gometalinter callback should use configured options): let b:ale_go_gometalinter_options = '--foobar' AssertLinter 'gometalinter', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('gometalinter') \ . ' --include=' . ale#Escape(ale#util#EscapePCRE(expand('%' . ':t'))) \ . ' --foobar' . ' .' @@ -40,7 +40,7 @@ Execute(The gometalinter should use configured environment variables): let b:ale_go_go111module = 'off' AssertLinter 'gometalinter', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Env('GO111MODULE', 'off') \ . ale#Escape('gometalinter') \ . ' --include=' . ale#Escape(ale#util#EscapePCRE(expand('%' . ':t'))) @@ -50,5 +50,5 @@ Execute(The gometalinter `lint_package` option should use the correct command): let b:ale_go_gometalinter_lint_package = 1 AssertLinter 'gometalinter', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('gometalinter') . ' .' diff --git a/test/command_callback/test_gopls_command_callback.vader b/test/command_callback/test_gopls_command_callback.vader index 92b20b18..73fcbf03 100644 --- a/test/command_callback/test_gopls_command_callback.vader +++ b/test/command_callback/test_gopls_command_callback.vader @@ -49,7 +49,10 @@ Execute(Should return directory for 'go.mod' if found in parent directory): Execute(Should return nearest directory with '.git' if found in parent directory): call ale#test#SetFilename('test.go') - call mkdir(g:dir . '/.git') + + if !isdirectory(g:dir . '/.git') + call mkdir(g:dir . '/.git') + endif AssertLSPProject g:dir diff --git a/test/command_callback/test_gosimple_command_callback.vader b/test/command_callback/test_gosimple_command_callback.vader index ee89eed8..b006f783 100644 --- a/test/command_callback/test_gosimple_command_callback.vader +++ b/test/command_callback/test_gosimple_command_callback.vader @@ -11,11 +11,12 @@ After: Execute(The default gosimple command should be correct): AssertLinter 'gosimple', - \ ale#path#CdString(expand('%:p:h')) . ' gosimple .' + \ ale#path#BufferCdString(bufnr('')) + \ . ' gosimple .' Execute(The gosimple command should support Go environment variables): let b:ale_go_go111module = 'on' AssertLinter 'gosimple', - \ ale#path#CdString(expand('%:p:h')) . ' ' - \ . ale#Env('GO111MODULE', 'on') . 'gosimple .' + \ ale#path#BufferCdString(bufnr('')) + \ . ' ' . ale#Env('GO111MODULE', 'on') . 'gosimple .' diff --git a/test/command_callback/test_gotype_command_callback.vader b/test/command_callback/test_gotype_command_callback.vader index 1334fcff..204197d9 100644 --- a/test/command_callback/test_gotype_command_callback.vader +++ b/test/command_callback/test_gotype_command_callback.vader @@ -11,7 +11,8 @@ After: Execute(The default gotype command should be correct): AssertLinter 'gotype', - \ ale#path#CdString(expand('%:p:h')) . ' gotype -e .' + \ ale#path#BufferCdString(bufnr('')) + \ . ' gotype -e .' Execute(The gotype callback should ignore test files): call ale#test#SetFilename('bla_test.go') @@ -22,6 +23,6 @@ Execute(The gotype callback should support Go environment variables): let b:ale_go_go111module = 'on' AssertLinter 'gotype', - \ ale#path#CdString(expand('%:p:h')) . ' ' - \ . ale#Env('GO111MODULE', 'on') + \ ale#path#BufferCdString(bufnr('')) + \ . ' ' . ale#Env('GO111MODULE', 'on') \ . 'gotype -e .' diff --git a/test/command_callback/test_govet_command_callback.vader b/test/command_callback/test_govet_command_callback.vader index 59022180..0e1ea092 100644 --- a/test/command_callback/test_govet_command_callback.vader +++ b/test/command_callback/test_govet_command_callback.vader @@ -13,22 +13,22 @@ After: call ale#assert#TearDownLinterTest() Execute(The default command should be correct): - AssertLinter 'go', ale#path#CdString(expand('%:p:h')) . ' go vet .' + AssertLinter 'go', ale#path#BufferCdString(bufnr('')) . ' go vet .' Execute(Extra options should be supported): let g:ale_go_govet_options = '--foo-bar' - AssertLinter 'go', ale#path#CdString(expand('%:p:h')) . ' go vet --foo-bar .' + AssertLinter 'go', ale#path#BufferCdString(bufnr('')) . ' go vet --foo-bar .' Execute(The executable should be configurable): let g:ale_go_go_executable = 'foobar' - AssertLinter 'foobar', ale#path#CdString(expand('%:p:h')) . ' foobar vet .' + AssertLinter 'foobar', ale#path#BufferCdString(bufnr('')) . ' foobar vet .' Execute(Go environment variables should be supported): let b:ale_go_go111module = 'on' AssertLinter 'go', - \ ale#path#CdString(expand('%:p:h')) . ' ' + \ ale#path#BufferCdString(bufnr('')) . ' ' \ . ale#Env('GO111MODULE', 'on') \ . 'go vet .' diff --git a/test/command_callback/test_graphql_gqlint_command_callbacks.vader b/test/command_callback/test_graphql_gqlint_command_callbacks.vader index 0f4e9770..e8ed0e5d 100644 --- a/test/command_callback/test_graphql_gqlint_command_callbacks.vader +++ b/test/command_callback/test_graphql_gqlint_command_callbacks.vader @@ -6,6 +6,6 @@ After: Execute(The linter should run from the directory of the file in the buffer): AssertLinter 'gqlint', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . 'gqlint --reporter=simple' \ . ' %t' diff --git a/test/command_callback/test_javac_command_callback.vader b/test/command_callback/test_javac_command_callback.vader index 42c64e54..ac898e5f 100644 --- a/test/command_callback/test_javac_command_callback.vader +++ b/test/command_callback/test_javac_command_callback.vader @@ -3,7 +3,7 @@ Before: call ale#test#SetFilename('dummy.java') let g:cp_sep = has('unix') ? ':' : ';' - let g:prefix = ale#path#CdString(expand('%:p:h')) + let g:prefix = ale#path#BufferCdString(bufnr('')) \ . ale#Escape('javac') . ' -Xlint' function! GetCommand(previous_output) abort @@ -31,7 +31,7 @@ After: Execute(The javac callback should return the correct default value): AssertLinter 'javac', g:prefix . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' -Execute(The javac callback should use g:ale_java_javac_classpath correctly): +Execute(The javac callback should use string type g:ale_java_javac_classpath correctly): let g:ale_java_javac_classpath = 'foo.jar' AssertLinter 'javac', @@ -39,11 +39,19 @@ Execute(The javac callback should use g:ale_java_javac_classpath correctly): \ . ' -cp ' . ale#Escape('foo.jar') \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' +Execute(The javac callback should use list type g:ale_java_javac_classpath correctly): + let g:ale_java_javac_classpath = ['foo.jar'] + + AssertLinter 'javac', + \ g:prefix + \ . ' -cp ' . ale#Escape('foo.jar') + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + Execute(The executable should be configurable): let g:ale_java_javac_executable = 'foobar' AssertLinter 'foobar', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('foobar') . ' -Xlint' \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' @@ -108,13 +116,149 @@ Execute(The javac callback should combine discovered classpaths and manual ones) \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + let g:ale_java_javac_classpath = ['configured.jar'] + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \], {}) + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join( + \ [ + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ 'configured.jar', + \ ], + \ g:cp_sep + \ )) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_classpath = ['configured.jar', 'configured2.jar'] + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [ + \ '[DEBUG] Ignore this.', + \ '[INFO] Something we should ignore.', + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \], {}) + + AssertEqual + \ g:prefix + \ . ' -cp ' + \ . ale#Escape(join( + \ [ + \ '/foo/bar.jar', + \ '/xyz/abc.jar', + \ 'configured.jar', + \ 'configured2.jar', + \ ], + \ g:cp_sep + \ )) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + +Execute(The javac callback should use string type g:ale_java_javac_sourcepath correctly): + let g:ale_java_javac_sourcepath = 'java_paths/build/gen/main' + + AssertLinter 'javac', + \ g:prefix + \ . ' -sourcepath ' . ale#Escape( + \ ale#path#Simplify(g:dir . '/java_paths/build/gen/main/') + \ ) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should use list type g:ale_java_javac_sourcepath correctly): + let g:ale_java_javac_sourcepath = ['java_paths/build/gen/main'] + + AssertLinter 'javac', + \ g:prefix + \ . ' -sourcepath ' . ale#Escape( + \ ale#path#Simplify(g:dir . '/java_paths/build/gen/main/') + \ ) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback shouldn't add -sourcepath when g:ale_java_javac_sourcepath variable path doesn't exist): + let g:ale_java_javac_sourcepath = 'java_paths/build/gen3/main' + + AssertLinter 'javac', + \ g:prefix + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t' + +Execute(The javac callback should combine discovered sourcepath and manual ones): + call ale#engine#Cleanup(bufnr('')) + call ale#test#SetFilename('java_paths/src/main/java/com/something/dummy.java') + call ale#engine#InitBufferInfo(bufnr('')) + + let g:ale_java_javac_sourcepath = 'java_paths/build/gen/main' + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [], {}) + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/'), + \ ale#path#Simplify(g:dir . '/java_paths/build/gen/main/'), + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_sourcepath = 'java_paths/build/gen/main' + \ . g:cp_sep . 'java_paths/build/gen2/main' + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [], {}) + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/'), + \ ale#path#Simplify(g:dir . '/java_paths/build/gen/main/'), + \ ale#path#Simplify(g:dir . '/java_paths/build/gen2/main/') + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_sourcepath = ['java_paths/build/gen/main'] + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [], {}) + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/'), + \ ale#path#Simplify(g:dir . '/java_paths/build/gen/main/') + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + + let g:ale_java_javac_sourcepath = [ + \ 'java_paths/build/gen/main', + \ 'java_paths/build/gen2/main' + \ ] + let b:command = ale_linters#java#javac#GetCommand(bufnr(''), [], {}) + + AssertEqual + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' + \ . ' -sourcepath ' . ale#Escape(join([ + \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/'), + \ ale#path#Simplify(g:dir . '/java_paths/build/gen/main/'), + \ ale#path#Simplify(g:dir . '/java_paths/build/gen2/main/') + \ ], g:cp_sep)) + \ . ' -d ' . ale#Escape('TEMP_DIR') . ' %t', + \ substitute(b:command, '%e', '\=ale#Escape(''javac'')', 'g') + Execute(The javac callback should detect source directories): call ale#engine#Cleanup(bufnr('')) noautocmd e! java_paths/src/main/java/com/something/dummy call ale#engine#InitBufferInfo(bufnr('')) AssertLinter 'javac', - \ ale#path#CdString(expand('%:p:h')) . ale#Escape('javac') . ' -Xlint' + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' \ . ' -sourcepath ' . ale#Escape( \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/') \ ) @@ -133,7 +277,8 @@ Execute(The javac callback should combine detected source directories and classp \], {}) AssertEqual - \ ale#path#CdString(expand('%:p:h')) . ale#Escape('javac') . ' -Xlint' + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' \ . ' -cp ' . ale#Escape(join(['/foo/bar.jar', '/xyz/abc.jar'], g:cp_sep)) \ . ' -sourcepath ' . ale#Escape( \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/') @@ -155,7 +300,8 @@ Execute(The javac callback should include src/test/java for test paths): call ale#engine#InitBufferInfo(bufnr('')) AssertLinter 'javac', - \ ale#path#CdString(expand('%:p:h')) . ale#Escape('javac') . ' -Xlint' + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' \ . ' -sourcepath ' . ale#Escape(join([ \ ale#path#Simplify(g:dir . '/java_paths/src/main/java/'), \ ale#path#Simplify(g:dir . '/java_paths/src/test/java/'), @@ -168,7 +314,8 @@ Execute(The javac callback should include src/main/jaxb when available): call ale#engine#InitBufferInfo(bufnr('')) AssertLinter 'javac', - \ ale#path#CdString(expand('%:p:h')) . ale#Escape('javac') . ' -Xlint' + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' \ . ' -sourcepath ' . ale#Escape(join([ \ ale#path#Simplify(g:dir . '/java_paths_with_jaxb/src/main/java/'), \ ale#path#Simplify(g:dir . '/java_paths_with_jaxb/src/main/jaxb/'), @@ -181,7 +328,8 @@ Execute(The javac callback should add -sourcepath even if src/java/main doesn't call ale#engine#InitBufferInfo(bufnr('')) AssertLinter 'javac', - \ ale#path#CdString(expand('%:p:h')) . ale#Escape('javac') . ' -Xlint' + \ ale#path#BufferCdString(bufnr('')) + \ . ale#Escape('javac') . ' -Xlint' \ . ' -sourcepath ' . ale#Escape(join([ \ ale#path#Simplify(g:dir . '/java_paths_no_main/src/test/java/'), \ ], g:cp_sep)) diff --git a/test/command_callback/test_lintr_command_callback.vader b/test/command_callback/test_lintr_command_callback.vader index 187d3875..ac4b419b 100644 --- a/test/command_callback/test_lintr_command_callback.vader +++ b/test/command_callback/test_lintr_command_callback.vader @@ -6,7 +6,7 @@ After: Execute(The default lintr command should be correct): AssertLinter 'Rscript', - \ ale#path#CdString(getcwd()) + \ ale#path#BufferCdString(bufnr('')) \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint(cache = FALSE, commandArgs(TRUE), ' @@ -17,7 +17,7 @@ Execute(The lintr options should be configurable): let b:ale_r_lintr_options = 'with_defaults(object_usage_linter = NULL)' AssertLinter 'Rscript', - \ ale#path#CdString(getcwd()) + \ ale#path#BufferCdString(bufnr('')) \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint(cache = FALSE, commandArgs(TRUE), ' @@ -28,7 +28,7 @@ Execute(If the lint_package flag is set, lintr::lint_package should be called): let b:ale_r_lintr_lint_package = 1 AssertLinter 'Rscript', - \ ale#path#CdString(getcwd()) + \ ale#path#BufferCdString(bufnr('')) \ . 'Rscript --vanilla -e ' \ . ale#Escape('suppressPackageStartupMessages(library(lintr));' \ . 'lint_package(cache = FALSE, ' diff --git a/test/command_callback/test_markdown_markdownlint_command_callback.vader b/test/command_callback/test_markdown_markdownlint_command_callback.vader new file mode 100644 index 00000000..12766cfd --- /dev/null +++ b/test/command_callback/test_markdown_markdownlint_command_callback.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('markdown', 'markdownlint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'markdownlint', ale#Escape('markdownlint') . ' %s' + +Execute(The options should be configurable): + let g:ale_markdown_markdownlint_options = '--config ~/custom/.markdownlintrc' + + AssertLinter 'markdownlint', ale#Escape('markdownlint') . ' --config ~/custom/.markdownlintrc %s' diff --git a/test/command_callback/test_mypy_command_callback.vader b/test/command_callback/test_mypy_command_callback.vader index afa9f9af..b9b6ae70 100644 --- a/test/command_callback/test_mypy_command_callback.vader +++ b/test/command_callback/test_mypy_command_callback.vader @@ -75,14 +75,14 @@ Execute(Setting executable to 'pipenv' appends 'run mypy'): let g:ale_python_mypy_executable = 'path/to/pipenv' AssertLinter 'path/to/pipenv', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('path/to/pipenv') . ' run mypy' \ . ' --show-column-numbers --shadow-file %s %t %s' Execute(Pipenv is detected when python_mypy_auto_pipenv is set): let g:ale_python_mypy_auto_pipenv = 1 - call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + call ale#test#SetFilename('../python_fixtures/pipenv/whatever.py') AssertLinter 'pipenv', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('pipenv') . ' run mypy --show-column-numbers --shadow-file %s %t %s' diff --git a/test/command_callback/test_nasm_nasm_command_callbacks.vader b/test/command_callback/test_nasm_nasm_command_callbacks.vader index 8e077306..2bfe2b0d 100644 --- a/test/command_callback/test_nasm_nasm_command_callbacks.vader +++ b/test/command_callback/test_nasm_nasm_command_callbacks.vader @@ -2,9 +2,9 @@ Before: call ale#assert#SetUpLinterTest('nasm', 'nasm') let b:command_tail = - \ ' -X gnu -I ' . ale#Escape(getcwd() . (has('win32') ? '\' : '/')) . ' %s -o ' . (has('win32') ? 'NUL' : '/dev/null') + \ ' -X gnu -I %s:h' . (has('win32') ? '\' : '/') . ' %s -o ' . (has('win32') ? 'NUL' : '/dev/null') let b:command_tail_opt = - \ ' -X gnu -I ' . ale#Escape(getcwd() . (has('win32') ? '\' : '/')) . ' -w+orphan-labels %s -o ' . (has('win32') ? 'NUL' : '/dev/null') + \ ' -X gnu -I %s:h' . (has('win32') ? '\' : '/') . ' -w+orphan-labels %s -o ' . (has('win32') ? 'NUL' : '/dev/null') After: unlet! b:command_tail @@ -23,7 +23,8 @@ Execute(The options should be configurable): let b:ale_nasm_nasm_options = '-w-macro-params' AssertLinter 'nasm', ale#Escape('nasm') - \ . ' -X gnu -I ' . ale#Escape(getcwd() . (has('win32') ? '\' : '/')) . ' -w-macro-params %s -o ' . (has('win32') ? 'NUL' : '/dev/null') + \ . ' -X gnu -I %s:h' . (has('win32') ? '\' : '/') + \ . ' -w-macro-params %s -o ' . (has('win32') ? 'NUL' : '/dev/null') Execute(The options should be used in command): let b:ale_nasm_nasm_options = '-w+orphan-labels' diff --git a/test/command_callback/test_objc_ccls_command_callbacks.vader b/test/command_callback/test_objc_ccls_command_callbacks.vader index 5aa69d6a..34b8539e 100644 --- a/test/command_callback/test_objc_ccls_command_callbacks.vader +++ b/test/command_callback/test_objc_ccls_command_callbacks.vader @@ -1,6 +1,10 @@ Before: call ale#assert#SetUpLinterTest('objc', 'ccls') + Save b:ale_c_build_dir_names + Save b:ale_objc_ccls_executable + Save b:ale_objc_ccls_init_options + After: call ale#assert#TearDownLinterTest() @@ -44,3 +48,19 @@ Execute(The initialization options should be configurable): let b:ale_objc_ccls_init_options = { 'cacheDirectory': '/tmp/ccls' } AssertLSPOptions { 'cacheDirectory': '/tmp/ccls' } + +Execute(The compile command database should be detected correctly): + call ale#test#SetFilename('ccls_paths/with_ccls/dummy.c') + + AssertLSPOptions {} + + call ale#test#SetFilename('ccls_paths/with_compile_commands_json/dummy.c') + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/ccls_paths/with_compile_commands_json') } + + call ale#test#SetFilename('ccls_paths/with_build_dir/dummy.c') + let b:ale_c_build_dir_names = ['unusual_build_dir_name'] + + AssertLSPOptions { 'compilationDatabaseDirectory': + \ ale#path#Simplify(g:dir . '/ccls_paths/with_build_dir/unusual_build_dir_name') } diff --git a/test/command_callback/test_psalm_command_callbacks.vader b/test/command_callback/test_psalm_command_callbacks.vader index 70b5af95..d32780e6 100644 --- a/test/command_callback/test_psalm_command_callbacks.vader +++ b/test/command_callback/test_psalm_command_callbacks.vader @@ -2,6 +2,9 @@ Before: call ale#assert#SetUpLinterTest('php', 'psalm') After: + unlet! g:i + unlet! g:matched + if isdirectory(g:dir . '/.git') call delete(g:dir . '/.git', 'd') endif @@ -22,19 +25,36 @@ Execute(Vendor executables should be detected): \ . '/psalm-project/vendor/bin/psalm' \ )) . ' --language-server' + let g:ale_php_psalm_use_global = 1 + + AssertLinter 'psalm', + \ ale#Escape('psalm') . ' --language-server' + Execute(User provided options should be used): - let g:ale_psalm_langserver_options = '--my-user-provided-option my-value' + let g:ale_php_psalm_options = '--my-user-provided-option my-value' AssertLinter 'psalm', \ ale#Escape('psalm') \ . ' --language-server --my-user-provided-option my-value' - Execute(The project path should be correct for .git directories): call ale#test#SetFilename('psalm-project/test.php') + let g:matched = 0 - if !isdirectory(g:dir . '/.git') - call mkdir(g:dir . '/.git') - endif + for g:i in range(4) + if !isdirectory(g:dir . '/.git') + call mkdir(g:dir . '/.git') + endif + + try + AssertLSPProject g:dir + catch /.+/ + endtry - AssertLSPProject g:dir + let g:matched = 1 + break + endfor + + if !g:matched + AssertLSPProject g:dir + endif diff --git a/test/command_callback/test_pydocstyle_command_callback.vader b/test/command_callback/test_pydocstyle_command_callback.vader index 7e0df9ca..511443a6 100644 --- a/test/command_callback/test_pydocstyle_command_callback.vader +++ b/test/command_callback/test_pydocstyle_command_callback.vader @@ -1,5 +1,6 @@ Before: call ale#assert#SetUpLinterTest('python', 'pydocstyle') + call ale#test#SetFilename('test.py') After: call ale#assert#TearDownLinterTest() @@ -7,33 +8,33 @@ After: Execute(The pydocstyle command callback should return default string): AssertLinter 'pydocstyle', \ ale#path#BufferCdString(bufnr('')) - \ . ale#Escape('pydocstyle') . ' ' . ale#Escape('dummy.txt') + \ . ale#Escape('pydocstyle') . ' %s:t' Execute(The pydocstyle command callback should allow options): let g:ale_python_pydocstyle_options = '--verbose' AssertLinter 'pydocstyle', \ ale#path#BufferCdString(bufnr('')) - \ . ale#Escape('pydocstyle') . ' --verbose ' . ale#Escape('dummy.txt') + \ . ale#Escape('pydocstyle') . ' --verbose %s:t' Execute(The pydocstyle executable should be configurable): let g:ale_python_pydocstyle_executable = '~/.local/bin/pydocstyle' AssertLinter '~/.local/bin/pydocstyle', \ ale#path#BufferCdString(bufnr('')) - \ . ale#Escape('~/.local/bin/pydocstyle') . ' ' . ale#Escape('dummy.txt') + \ . ale#Escape('~/.local/bin/pydocstyle') . ' %s:t' Execute(Setting executable to 'pipenv' appends 'run pydocstyle'): let g:ale_python_pydocstyle_executable = 'path/to/pipenv' AssertLinter 'path/to/pipenv', \ ale#path#BufferCdString(bufnr('')) - \ . ale#Escape('path/to/pipenv') . ' run pydocstyle ' . ale#Escape('dummy.txt') + \ . ale#Escape('path/to/pipenv') . ' run pydocstyle %s:t' Execute(Pipenv is detected when python_pydocstyle_auto_pipenv is set): let g:ale_python_pydocstyle_auto_pipenv = 1 - call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + call ale#test#SetFilename('../python_fixtures/pipenv/whatever.py') AssertLinter 'pipenv', \ ale#path#BufferCdString(bufnr('')) - \ . ale#Escape('pipenv') . ' run pydocstyle ' . ale#Escape('whatever.py') + \ . ale#Escape('pipenv') . ' run pydocstyle %s:t' diff --git a/test/command_callback/test_pylama_command_callback.vader b/test/command_callback/test_pylama_command_callback.vader index 417cb5c9..0aea9a93 100644 --- a/test/command_callback/test_pylama_command_callback.vader +++ b/test/command_callback/test_pylama_command_callback.vader @@ -14,7 +14,7 @@ After: Execute(The pylama command callback should return a default): AssertLinter 'pylama', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('pylama') . b:command_tail Execute(The option for disabling changing directories should work): @@ -26,14 +26,14 @@ Execute(The pylama executable should be configurable, and escaped properly): let g:ale_python_pylama_executable = 'executable with spaces' AssertLinter 'executable with spaces', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('executable with spaces') . b:command_tail Execute(The pylama command callback should let you set options): let g:ale_python_pylama_options = '--some-option' AssertLinter 'pylama', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('pylama') . ' --some-option' . b:command_tail Execute(The pylama command callback should switch directories to the detected project root): @@ -73,13 +73,13 @@ Execute(Setting executable to 'pipenv' appends 'run pylama'): let g:ale_python_pylama_executable = 'path/to/pipenv' AssertLinter 'path/to/pipenv', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('path/to/pipenv') . ' run pylama' . b:command_tail Execute(Pipenv is detected when python_pylama_auto_pipenv is set): let g:ale_python_pylama_auto_pipenv = 1 - call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + call ale#test#SetFilename('../python_fixtures/pipenv/whatever.py') AssertLinter 'pipenv', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('pipenv') . ' run pylama' . b:command_tail diff --git a/test/command_callback/test_pylint_command_callback.vader b/test/command_callback/test_pylint_command_callback.vader index c41c8398..755dd292 100644 --- a/test/command_callback/test_pylint_command_callback.vader +++ b/test/command_callback/test_pylint_command_callback.vader @@ -1,4 +1,8 @@ Before: + Save g:ale_python_auto_pipenv + + let g:ale_python_auto_pipenv = 0 + call ale#assert#SetUpLinterTest('python', 'pylint') let b:bin_dir = has('win32') ? 'Scripts' : 'bin' @@ -13,7 +17,7 @@ After: Execute(The pylint callbacks should return the correct default values): AssertLinter 'pylint', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('pylint') . ' ' . b:command_tail Execute(The option for disabling changing directories should work): @@ -25,14 +29,14 @@ Execute(The pylint executable should be configurable, and escaped properly): let g:ale_python_pylint_executable = 'executable with spaces' AssertLinter 'executable with spaces', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('executable with spaces') . ' ' . b:command_tail Execute(The pylint command callback should let you set options): let g:ale_python_pylint_options = '--some-option' AssertLinter 'pylint', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('pylint') . ' --some-option' . b:command_tail Execute(The pylint callbacks shouldn't detect virtualenv directories where they don't exist): @@ -65,15 +69,15 @@ Execute(Setting executable to 'pipenv' appends 'run pylint'): let g:ale_python_pylint_executable = 'path/to/pipenv' AssertLinter 'path/to/pipenv', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('path/to/pipenv') . ' run pylint' \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' Execute(Pipenv is detected when python_pylint_auto_pipenv is set): let g:ale_python_pylint_auto_pipenv = 1 - call ale#test#SetFilename('/testplugin/test/python_fixtures/pipenv/whatever.py') + call ale#test#SetFilename('../python_fixtures/pipenv/whatever.py') AssertLinter 'pipenv', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('pipenv') . ' run pylint' \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' diff --git a/test/command_callback/test_pyrex_cython_command_callback.vader b/test/command_callback/test_pyrex_cython_command_callback.vader index b9020f11..af86366a 100644 --- a/test/command_callback/test_pyrex_cython_command_callback.vader +++ b/test/command_callback/test_pyrex_cython_command_callback.vader @@ -6,8 +6,8 @@ After: Execute(The default cython command should be correct): AssertLinter 'cython', ale#Escape('cython') - \ . ' --working ' . ale#Escape(g:dir) - \ . ' --include-dir ' . ale#Escape(g:dir) + \ . ' --working %s:h' + \ . ' --include-dir %s:h' \ . ' --warning-extra' \ . ' --output-file ' . g:ale#util#nul_file . ' %t' @@ -15,8 +15,8 @@ Execute(The cython executable should be configurable): let b:ale_pyrex_cython_executable = 'cython_foobar' AssertLinter 'cython_foobar', ale#Escape('cython_foobar') - \ . ' --working ' . ale#Escape(g:dir) - \ . ' --include-dir ' . ale#Escape(g:dir) + \ . ' --working %s:h' + \ . ' --include-dir %s:h' \ . ' --warning-extra' \ . ' --output-file ' . g:ale#util#nul_file . ' %t' @@ -24,7 +24,7 @@ Execute(Additional cython options should be configurable): let b:ale_pyrex_cython_options = '--foobar' AssertLinter 'cython', ale#Escape('cython') - \ . ' --working ' . ale#Escape(g:dir) - \ . ' --include-dir ' . ale#Escape(g:dir) + \ . ' --working %s:h' + \ . ' --include-dir %s:h' \ . ' --foobar' \ . ' --output-file ' . g:ale#util#nul_file . ' %t' diff --git a/test/command_callback/test_pyright_command_callback.vader b/test/command_callback/test_pyright_command_callback.vader new file mode 100644 index 00000000..3e421bd9 --- /dev/null +++ b/test/command_callback/test_pyright_command_callback.vader @@ -0,0 +1,116 @@ +Before: + call ale#assert#SetUpLinterTest('python', 'pyright') + + let b:bin_dir = has('win32') ? 'Scripts' : 'bin' + +After: + unlet! b:bin_dir + unlet! b:executable + + call ale#assert#TearDownLinterTest() + +Execute(The command callback should return the correct default string): + AssertLinter + \ 'pyright-langserver', + \ ale#Escape('pyright-langserver') . ' --stdio' + +Execute(The executable should be configurable): + let g:ale_python_pyright_executable = '/bin/foo-bar' + + AssertLinter + \ '/bin/foo-bar', + \ ale#Escape('/bin/foo-bar') . ' --stdio' + +Execute(The default configuration should be mostly empty): + " The default configuration needs to have at least one key in it, + " or the server won't start up properly. + AssertLSPConfig {'python': {}} + + let b:ale_python_pyright_config = {} + + AssertLSPConfig {'python': {}} + +Execute(virtualenv paths should be set in configuration by default): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + AssertLSPConfig { + \ 'python': { + \ 'pythonPath': ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/python'), + \ 'venvPath': ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env'), + \ }, + \} + +Execute(The pythonPath should be set based on whatever the ovveride for the venvPath is set to): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + " This overrides the default detection of the path. + let b:ale_python_pyright_config = { + \ 'python': { + \ 'venvPath': '/foo/bar', + \ }, + \} + + AssertLSPConfig { + \ 'python': { + \ 'pythonPath': ale#path#Simplify('/foo/bar/' . b:bin_dir . '/python'), + \ 'venvPath': '/foo/bar', + \ }, + \} + +Execute(You should be able to override pythonPath when venvPath is detected): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + " This overrides the default detection of the path. + let b:ale_python_pyright_config = { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ }, + \} + + AssertLSPConfig { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ 'venvPath': ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env'), + \ }, + \} + +Execute(You should be able to override both pythonPath and venvPath): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + " This overrides the default detection of the path. + let b:ale_python_pyright_config = { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ 'venvPath': '/other/dir', + \ }, + \} + + AssertLSPConfig { + \ 'python': { + \ 'pythonPath': '/bin/python', + \ 'venvPath': '/other/dir', + \ }, + \} + +Execute(You should be able to define other settings): + call ale#test#SetFilename('python_paths/with_virtualenv/subdir/foo/bar.py') + + let b:ale_python_pyright_config = { + \ 'python': { + \ 'analysis': {'logLevel': 'warning'}, + \ }, + \ 'pyright': { + \ 'disableLanguageServices': v:true, + \ }, + \} + + AssertLSPConfig { + \ 'python': { + \ 'analysis': {'logLevel': 'warning'}, + \ 'pythonPath': ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/python'), + \ 'venvPath': ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env'), + \ }, + \ 'pyright': { + \ 'disableLanguageServices': v:true, + \ }, + \} diff --git a/test/command_callback/test_revive_command_callbacks.vader b/test/command_callback/test_revive_command_callbacks.vader new file mode 100644 index 00000000..172294f3 --- /dev/null +++ b/test/command_callback/test_revive_command_callbacks.vader @@ -0,0 +1,30 @@ +Before: + Save g:ale_go_go111module + + call ale#assert#SetUpLinterTest('go', 'revive') + +After: + Restore + + unlet! b:ale_go_go111module + + call ale#assert#TearDownLinterTest() + +Execute(The default revive command should be correct): + AssertLinter 'revive', ale#Escape('revive') . ' %t' + +Execute(The revive executable should be configurable): + let b:ale_go_revive_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' %t' + +Execute(The revive options should be configurable): + let b:ale_go_revive_options = '--foo' + + AssertLinter 'revive', ale#Escape('revive') . ' --foo %t' + +Execute(The revive command should support Go environment variables): + let b:ale_go_go111module = 'on' + + AssertLinter 'revive', + \ ale#Env('GO111MODULE', 'on') . ale#Escape('revive') . ' %t' diff --git a/test/command_callback/test_rubocop_command_callback.vader b/test/command_callback/test_rubocop_command_callback.vader index 7f42a8c0..e7cc32e8 100644 --- a/test/command_callback/test_rubocop_command_callback.vader +++ b/test/command_callback/test_rubocop_command_callback.vader @@ -10,20 +10,17 @@ After: Execute(Executable should default to rubocop): AssertLinter 'rubocop', ale#Escape('rubocop') - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + \ . ' --format json --force-exclusion --stdin %s' Execute(Should be able to set a custom executable): let g:ale_ruby_rubocop_executable = 'bin/rubocop' AssertLinter 'bin/rubocop' , ale#Escape('bin/rubocop') - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + \ . ' --format json --force-exclusion --stdin %s' Execute(Setting bundle appends 'exec rubocop'): let g:ale_ruby_rubocop_executable = 'path to/bundle' AssertLinter 'path to/bundle', ale#Escape('path to/bundle') \ . ' exec rubocop' - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + \ . ' --format json --force-exclusion --stdin %s' diff --git a/test/command_callback/test_rust_analyzer_callbacks.vader b/test/command_callback/test_rust_analyzer_callbacks.vader new file mode 100644 index 00000000..95866076 --- /dev/null +++ b/test/command_callback/test_rust_analyzer_callbacks.vader @@ -0,0 +1,20 @@ +Before: + call ale#assert#SetUpLinterTest('rust', 'analyzer') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'rust-analyzer', ale#Escape('rust-analyzer') + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('rust-rls-project/test.rs') + + AssertLSPProject ale#path#Simplify(g:dir . '/rust-rls-project') + +Execute(Should accept configuration settings): + AssertLSPConfig {} + let b:ale_rust_analyzer_config = {'rust': {'clippy_preference': 'on'}} + AssertLSPConfig {'rust': {'clippy_preference': 'on'}} diff --git a/test/command_callback/test_ruumba_command_callback.vader b/test/command_callback/test_ruumba_command_callback.vader index 244b264a..9fa48903 100644 --- a/test/command_callback/test_ruumba_command_callback.vader +++ b/test/command_callback/test_ruumba_command_callback.vader @@ -10,20 +10,17 @@ After: Execute(Executable should default to ruumba): AssertLinter 'ruumba', ale#Escape('ruumba') - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.html.erb')) + \ . ' --format json --force-exclusion --stdin %s' Execute(Should be able to set a custom executable): let g:ale_eruby_ruumba_executable = 'bin/ruumba' AssertLinter 'bin/ruumba' , ale#Escape('bin/ruumba') - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.html.erb')) + \ . ' --format json --force-exclusion --stdin %s' Execute(Setting bundle appends 'exec ruumba'): let g:ale_eruby_ruumba_executable = 'path to/bundle' AssertLinter 'path to/bundle', ale#Escape('path to/bundle') \ . ' exec ruumba' - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.html.erb')) + \ . ' --format json --force-exclusion --stdin %s' diff --git a/test/command_callback/test_shellcheck_command_callback.vader b/test/command_callback/test_shellcheck_command_callback.vader index 1d5b056b..9fb5303a 100644 --- a/test/command_callback/test_shellcheck_command_callback.vader +++ b/test/command_callback/test_shellcheck_command_callback.vader @@ -2,7 +2,7 @@ Before: call ale#assert#SetUpLinterTest('sh', 'shellcheck') call ale#test#SetFilename('test.sh') - let b:prefix = ale#path#CdString(ale#path#Simplify(g:dir)) + let b:prefix = ale#path#BufferCdString(bufnr('')) let b:suffix = ' -f gcc -' After: diff --git a/test/command_callback/test_sqllint_command_callback.vader b/test/command_callback/test_sqllint_command_callback.vader new file mode 100644 index 00000000..eea9b4e0 --- /dev/null +++ b/test/command_callback/test_sqllint_command_callback.vader @@ -0,0 +1,12 @@ +Before: + " Load the linter and set up a series of commands, reset linter variables, + " clear caches, etc. + " + " Vader's 'Save' command will be called here for linter variables. + call ale#assert#SetUpLinterTest('sql', 'sqllint') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'sql-lint', ['sql-lint'] diff --git a/test/command_callback/test_standardrb_command_callback.vader b/test/command_callback/test_standardrb_command_callback.vader index 7bc1c976..108dd870 100644 --- a/test/command_callback/test_standardrb_command_callback.vader +++ b/test/command_callback/test_standardrb_command_callback.vader @@ -10,20 +10,17 @@ After: Execute(Executable should default to standardrb): AssertLinter 'standardrb', ale#Escape('standardrb') - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + \ . ' --format json --force-exclusion --stdin %s' Execute(Should be able to set a custom executable): let g:ale_ruby_standardrb_executable = 'bin/standardrb' AssertLinter 'bin/standardrb' , ale#Escape('bin/standardrb') - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + \ . ' --format json --force-exclusion --stdin %s' Execute(Setting bundle appends 'exec standardrb'): let g:ale_ruby_standardrb_executable = 'path to/bundle' AssertLinter 'path to/bundle', ale#Escape('path to/bundle') \ . ' exec standardrb' - \ . ' --format json --force-exclusion --stdin ' - \ . ale#Escape(ale#path#Simplify(g:dir . '/dummy.rb')) + \ . ' --format json --force-exclusion --stdin %s' diff --git a/test/command_callback/test_staticcheck_command_callback.vader b/test/command_callback/test_staticcheck_command_callback.vader index ae0d3584..871a5510 100644 --- a/test/command_callback/test_staticcheck_command_callback.vader +++ b/test/command_callback/test_staticcheck_command_callback.vader @@ -11,7 +11,7 @@ After: Execute(The staticcheck callback should return the right defaults): AssertLinter 'staticcheck', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . 'staticcheck ' \ . ale#Escape(expand('%' . ':t')) @@ -19,7 +19,7 @@ Execute(The staticcheck callback should use configured options): let b:ale_go_staticcheck_options = '-test' AssertLinter 'staticcheck', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . 'staticcheck ' \ . '-test ' . ale#Escape(expand('%' . ':t')) @@ -27,13 +27,14 @@ Execute(The staticcheck `lint_package` option should use the correct command): let b:ale_go_staticcheck_lint_package = 1 AssertLinter 'staticcheck', - \ ale#path#CdString(expand('%:p:h')) . 'staticcheck .', + \ ale#path#BufferCdString(bufnr('')) + \ . 'staticcheck .', Execute(The staticcheck callback should use the `GO111MODULE` option if set): let b:ale_go_go111module = 'off' AssertLinter 'staticcheck', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Env('GO111MODULE', 'off') \ . 'staticcheck ' \ . ale#Escape(expand('%' . ':t')) @@ -42,6 +43,6 @@ Execute(The staticcheck callback should use the `GO111MODULE` option if set): let b:ale_go_staticcheck_lint_package = 1 AssertLinter 'staticcheck', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Env('GO111MODULE', 'off') \ . 'staticcheck .' diff --git a/test/command_callback/test_swift_swiftformat_command_callbacks.vader b/test/command_callback/test_swift_swiftformat_command_callbacks.vader new file mode 100644 index 00000000..7be20bf7 --- /dev/null +++ b/test/command_callback/test_swift_swiftformat_command_callbacks.vader @@ -0,0 +1,25 @@ +Before: + call ale#assert#SetUpLinterTest('swift', 'swiftformat') + +After: + call ale#assert#TearDownLinterTest() + +Execute(Should use default command when not in a swift package): + call ale#test#SetFilename('../swift-test-files/non-swift-package-project/src/folder/dummy.swift') + + AssertLinter 'swift-format', + \ ale#Escape('swift-format') . ' --mode lint %t' + +Execute(Should use swift run when in a swift package): + call ale#test#SetFilename('../swift-test-files/swift-package-project/src/folder/dummy.swift') + + AssertLinter 'swift', + \ ale#Escape('swift') . ' run swift-format --mode lint %t' + +Execute(Should let users configure a global executable and override local paths): + call ale#test#SetFilename('../swift-test-files/swift-package-project/src/folder/dummy.swift') + + let g:ale_swift_swiftformat_executable = '/path/to/custom/swift-format' + + AssertLinter '/path/to/custom/swift-format', + \ ale#Escape('/path/to/custom/swift-format') . ' --mode lint %t' diff --git a/test/command_callback/test_terraform_lsp_command_callback.vader b/test/command_callback/test_terraform_lsp_command_callback.vader new file mode 100644 index 00000000..7a491d54 --- /dev/null +++ b/test/command_callback/test_terraform_lsp_command_callback.vader @@ -0,0 +1,48 @@ +Before: + call ale#assert#SetUpLinterTest('terraform', 'terraform_lsp') + +After: + if isdirectory(g:dir . '/.terraform') + call delete(g:dir . '/.terraform', 'd') + endif + + unlet! b:ale_terraform_langserver_executable + unlet! b:ale_terraform_langserver_options + + call ale#assert#TearDownLinterTest() + +Execute(Should send correct LSP language): + AssertLSPLanguage 'terraform' + +Execute(Should load default executable): + AssertLinter 'terraform-lsp', ale#Escape('terraform-lsp') + +Execute(Should configure custom executable): + let b:ale_terraform_langserver_executable = 'foo' + AssertLinter 'foo', ale#Escape('foo') + +Execute(Should set custom options): + let b:ale_terraform_langserver_options = '--bar' + + AssertLinter 'terraform-lsp', + \ ale#Escape('terraform-lsp') . ' --bar' + +Execute(Should return current directory if it contains .terraform directory): + call mkdir(g:dir . '/.terraform') + AssertLSPProject g:dir + +Execute(Should return nearest directory with .terraform if found in parent directory): + call ale#test#SetFilename('../terraform_files/main.tf') + + let b:parent_dir = ale#path#Simplify(g:dir . '/..') + let b:tf_dir = b:parent_dir . '/.terraform' + + if !isdirectory(b:tf_dir) + call mkdir(b:tf_dir) + endif + + AssertLSPProject b:parent_dir + + call delete(b:tf_dir, 'd') + unlet!b:parent_dir + unlet!b:tf_dir diff --git a/test/command_callback/test_tslint_command_callback.vader b/test/command_callback/test_tslint_command_callback.vader index 229ccc96..cc5d2666 100644 --- a/test/command_callback/test_tslint_command_callback.vader +++ b/test/command_callback/test_tslint_command_callback.vader @@ -7,14 +7,14 @@ After: Execute(The default tslint command should be correct): AssertLinter 'tslint', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('tslint') . ' --format json %t' Execute(The rules directory option should be included if set): let b:ale_typescript_tslint_rules_dir = '/foo/bar' AssertLinter 'tslint', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('tslint') . ' --format json' \ . ' -r ' . ale#Escape('/foo/bar') \ . ' %t' @@ -23,5 +23,5 @@ Execute(The executable should be configurable and escaped): let b:ale_typescript_tslint_executable = 'foo bar' AssertLinter 'foo bar', - \ ale#path#CdString(expand('%:p:h')) + \ ale#path#BufferCdString(bufnr('')) \ . ale#Escape('foo bar') . ' --format json %t' diff --git a/test/command_callback/test_vim_vimls.vader b/test/command_callback/test_vim_vimls.vader new file mode 100644 index 00000000..ab12b637 --- /dev/null +++ b/test/command_callback/test_vim_vimls.vader @@ -0,0 +1,76 @@ +" Author: Jeffrey Lau https://github.com/zoonfafer +" Description: Tests for the Vim vimls linter + +Before: + call ale#assert#SetUpLinterTest('vim', 'vimls') + +After: + if isdirectory(g:dir . '/.git') + call delete(g:dir . '/.git', 'd') + endif + + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'vim-language-server', ale#Escape('vim-language-server') . ' --stdio' + +Execute(should set correct LSP values): + call ale#test#SetFilename('vim_fixtures/path_with_autoload/test.vim') + AssertLSPLanguage 'vim' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/vim_fixtures/path_with_autoload') + +Execute(should set correct project for .git/): + let b:parent_dir = ale#path#Simplify(g:dir . '/..') + let b:git_dir = b:parent_dir . '/.git' + + call ale#test#SetFilename('vim_fixtures/test.vim') + + if !isdirectory(b:git_dir) + call mkdir(b:git_dir) + endif + + AssertLSPProject ale#path#Simplify(b:parent_dir) + + call delete(b:git_dir, 'd') + unlet! b:git_dir + +Execute(should set correct project for plugin/): + call ale#test#SetFilename('vim_fixtures/path_with_plugin/test.vim') + + AssertLSPProject ale#path#Simplify(g:dir . '/vim_fixtures/path_with_plugin') + +Execute(should accept configuration settings): + AssertLSPConfig {} + + let b:ale_vim_vimls_config = {'vim': {'foobar': v:true}} + AssertLSPConfig {'vim': {'foobar': v:true}} + +Execute(should set correct project for .vimrc): + call ale#test#SetFilename('vim_fixtures/path_with_vimrc/.vimrc') + + AssertLSPProject ale#path#Simplify(g:dir . '/vim_fixtures/path_with_vimrc') + +Execute(should set correct project for init.vim): + call ale#test#SetFilename('vim_fixtures/path_with_initvim/init.vim') + + AssertLSPProject ale#path#Simplify(g:dir . '/vim_fixtures/path_with_initvim') + +Execute(should use the local executable when available): + call ale#test#SetFilename('vim_fixtures/file.vim') + + AssertLinter ale#path#Simplify(g:dir . '/vim_fixtures/node_modules/.bin/vim-language-server'), + \ ale#Escape(ale#path#Simplify(g:dir . '/vim_fixtures/node_modules/.bin/vim-language-server')) . ' --stdio' + +Execute(should let the global executable to be used): + let g:ale_vim_vimls_use_global = 1 + call ale#test#SetFilename('vim_fixtures/file.vim') + + AssertLinter 'vim-language-server', + \ ale#Escape('vim-language-server') . ' --stdio' + +Execute(should let the executable to be configured): + let g:ale_vim_vimls_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdio' diff --git a/test/command_callback/test_vint_command_callback.vader b/test/command_callback/test_vint_command_callback.vader index e0051f26..4ce277e8 100644 --- a/test/command_callback/test_vint_command_callback.vader +++ b/test/command_callback/test_vint_command_callback.vader @@ -1,7 +1,7 @@ Before: call ale#assert#SetUpLinterTest('vim', 'vint') let b:command_tail = (has('nvim') ? ' --enable-neovim' : '') - \ . ' -f "{file_path}:{line_number}:{column_number}: {severity}: {description} (see {reference})" %t' + \ . ' -f "{file_path}:{line_number}:{column_number}: {severity}: {policy_name} - {description} (see {reference})" %t' After: unlet! b:bin_dir diff --git a/test/command_callback/test_vulture_command_callback.vader b/test/command_callback/test_vulture_command_callback.vader index d6c866b9..bacf8f12 100644 --- a/test/command_callback/test_vulture_command_callback.vader +++ b/test/command_callback/test_vulture_command_callback.vader @@ -12,7 +12,7 @@ After: Execute(The vulture command callback should lint file directory by default): AssertLinter 'vulture', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('vulture') . ' .' Execute(The vulture command callback should lint project root, when present): @@ -31,14 +31,14 @@ Execute(The vulture executable should be configurable, and escaped properly): let g:ale_python_vulture_executable = 'executable with spaces' AssertLinter 'executable with spaces', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('executable with spaces') . ' .' Execute(The vulture command callback should let you set options): let g:ale_python_vulture_options = '--some-option' AssertLinter 'vulture', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('vulture') . ' --some-option .' Execute(The vulture command callback should detect virtualenv directories and switch to the project root): @@ -64,5 +64,5 @@ Execute(Setting executable to 'pipenv' appends 'run vulture'): let g:ale_python_vulture_executable = 'path/to/pipenv' AssertLinter 'path/to/pipenv', - \ ale#path#BufferCdString(bufnr('')) + \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('path/to/pipenv') . ' run vulture' . ' .' diff --git a/test/command_callback/test_zig_zls_callbacks.vader b/test/command_callback/test_zig_zls_callbacks.vader new file mode 100644 index 00000000..6e42cc4b --- /dev/null +++ b/test/command_callback/test_zig_zls_callbacks.vader @@ -0,0 +1,15 @@ +Before: + call ale#assert#SetUpLinterTest('zig', 'zls') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default executable path should be correct): + AssertLinter 'zls', ale#Escape('zls') + +Execute(The project root should be detected correctly): + AssertLSPProject '' + + call ale#test#SetFilename('zig-zls-project/main.zig') + + AssertLSPProject ale#path#Simplify(g:dir . '/zig-zls-project') diff --git a/test/command_callback/vim_fixtures/invalid_vim_project/test.vim b/test/command_callback/vim_fixtures/invalid_vim_project/test.vim new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/vim_fixtures/invalid_vim_project/test.vim diff --git a/test/command_callback/vim_fixtures/node_modules/.bin/vim-language-server b/test/command_callback/vim_fixtures/node_modules/.bin/vim-language-server new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/vim_fixtures/node_modules/.bin/vim-language-server diff --git a/test/command_callback/vim_fixtures/path_with_autoload/autoload/test.vim b/test/command_callback/vim_fixtures/path_with_autoload/autoload/test.vim new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/vim_fixtures/path_with_autoload/autoload/test.vim diff --git a/test/command_callback/vim_fixtures/path_with_autoload/test.vim b/test/command_callback/vim_fixtures/path_with_autoload/test.vim new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/vim_fixtures/path_with_autoload/test.vim diff --git a/test/command_callback/vim_fixtures/path_with_initvim/init.vim b/test/command_callback/vim_fixtures/path_with_initvim/init.vim new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/vim_fixtures/path_with_initvim/init.vim diff --git a/test/command_callback/vim_fixtures/path_with_plugin/plugin/test.vim b/test/command_callback/vim_fixtures/path_with_plugin/plugin/test.vim new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/vim_fixtures/path_with_plugin/plugin/test.vim diff --git a/test/command_callback/vim_fixtures/path_with_plugin/test.vim b/test/command_callback/vim_fixtures/path_with_plugin/test.vim new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/vim_fixtures/path_with_plugin/test.vim diff --git a/test/command_callback/vim_fixtures/path_with_vimrc/.vimrc b/test/command_callback/vim_fixtures/path_with_vimrc/.vimrc new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/vim_fixtures/path_with_vimrc/.vimrc diff --git a/test/command_callback/zig-zls-project/build.zig b/test/command_callback/zig-zls-project/build.zig new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/zig-zls-project/build.zig diff --git a/test/completion/test_ale_import_command.vader b/test/completion/test_ale_import_command.vader new file mode 100644 index 00000000..2ba9b8d7 --- /dev/null +++ b/test/completion/test_ale_import_command.vader @@ -0,0 +1,562 @@ +Before: + Save g:ale_enabled + Save b:ale_enabled + Save g:ale_lint_on_text_changed + Save g:ale_completion_enabled + Save g:ale_completion_autoimport + Save g:ale_completion_max_suggestions + Save g:ale_linters + Save b:ale_linters + + let g:ale_enabled = 0 + let b:ale_enabled = 0 + let g:ale_lint_on_text_changed = 'always' + let g:ale_completion_enabled = 0 + let g:ale_completion_autoimport = 0 + let g:ale_completion_max_suggestions = 50 + let g:ale_linters = {'typescript': ['tsserver'], 'python': ['pyre']} + unlet! b:ale_linters + + let g:server_started_value = 1 + let g:request_id = 0 + let g:LastCallback = v:null + let g:LastHandleCallback = v:null + let g:sent_message_list = [] + let g:code_action_list = [] + let g:execute_list = [] + let g:ale_queue_call_list = [] + + runtime autoload/ale.vim + runtime autoload/ale/util.vim + runtime autoload/ale/code_action.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + + function! ale#util#Execute(expr) abort + call add(g:execute_list, a:expr) + endfunction + + function! ale#Queue(...) abort + call add(g:ale_queue_call_list, a:000) + endfunction + + function! ale#lsp#RegisterCallback(id, Callback) abort + let g:LastHandleCallback = a:Callback + endfunction + + function! ale#lsp#NotifyForChanges(id, buffer) abort + endfunction + + function! ale#lsp#HasCapability(id, capability) abort + return 1 + endfunction + + function! ale#lsp#Send(id, message) abort + let g:request_id += 1 + + call add(g:sent_message_list, a:message) + + return g:request_id + endfunction + + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + let g:LastCallback = a:Callback + + return g:server_started_value + endfunction + + function! ale#code_action#HandleCodeAction(code_action, should_save) abort + Assert !a:should_save + + call add(g:code_action_list, a:code_action) + endfunction + + function GetLastMessage() + return get(g:execute_list, -1, '') + endfunction + + function CheckLintStates(conn_id, message) + " Check that we request more linter results after adding completions. + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_enabled = 0 + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_enabled = 1 + let g:ale_lint_on_text_changed = 1 + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'normal' + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'insert' + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_queue_call_list = [] + let g:ale_lint_on_text_changed = 'never' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = '0' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 0 + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'xxx' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + endfunction + +After: + call ale#linter#Reset() + + Restore + + delfunction GetLastMessage + delfunction CheckLintStates + + unlet! g:LastCallback + unlet! g:LastHandleCallback + unlet! g:request_id + unlet! g:server_started_value + unlet! g:sent_message_list + unlet! g:code_action_list + unlet! g:ale_queue_call_list + unlet! g:execute_list + unlet! g:received_message + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completeopt + unlet! b:ale_completion_info + unlet! b:ale_completion_result + unlet! b:ale_complete_done_time + + runtime autoload/ale.vim + runtime autoload/ale/util.vim + runtime autoload/ale/code_action.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + +Given typescript(Some example TypeScript code): + let xyz = 123 + let foo = missingword + + let abc = 456 + +Execute(ALEImport should complain when there's no word at the cursor): + call setpos('.', [bufnr(''), 3, 1, 0]) + ALEImport + + AssertEqual 'echom ''Nothing to complete at cursor!''', GetLastMessage() + +Execute(ALEImport should tell the user if no LSP is available): + let g:server_started_value = 0 + + call setpos('.', [bufnr(''), 2, 16, 0]) + ALEImport + + AssertEqual + \ 'echom ''No completion providers are available.''', + \ GetLastMessage() + +Execute(ALEImport should request imports correctly for tsserver): + call setpos('.', [bufnr(''), 2, 16, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 11, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 11, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + call g:LastHandleCallback(347, { + \ 'request_seq': 1, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'missingwordIgnoreMe'}, + \ {'name': 'missingword'}, + \ ], + \}) + + AssertEqual + \ [ + \ [0, 'ts@completions', { + \ 'file': expand('%:p'), + \ 'includeExternalModuleExports': 1, + \ 'offset': 11, + \ 'line': 2, + \ 'prefix': 'missingword', + \ }], + \ [0, 'ts@completionEntryDetails', { + \ 'file': expand('%:p'), + \ 'entryNames': [{'name': 'missingword'}], + \ 'offset': 11, + \ 'line': 2, + \ }] + \ ], + \ g:sent_message_list + AssertEqual 2, b:ale_completion_info.request_id + + let g:ale_enabled = 1 + let g:received_message = { + \ 'request_seq': 2, + \ 'command': 'completionEntryDetails', + \ 'body': [ + \ { + \ 'name': 'missingword', + \ 'kind': 'className', + \ 'displayParts': [], + \ 'codeActions': [{ + \ 'description': 'import { missingword } from "./Something";', + \ 'changes': [], + \ }], + \ }, + \ ], + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual + \ [ + \ { + \ 'description': 'import { missingword } from "./Something";', + \ 'changes': [], + \ }, + \ ], + \ g:code_action_list + + call CheckLintStates(347, g:received_message) + +Execute(ALEImport should tell the user when no completions were found from tsserver): + call setpos('.', [bufnr(''), 2, 16, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 11, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 11, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + call g:LastHandleCallback(347, { + \ 'request_seq': 1, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'missingwordIgnoreMe'}, + \ ], + \}) + + AssertEqual 'echom ''No possible imports found.''', GetLastMessage() + +Given python(Some example Python code): + xyz = 123 + foo = missingword + + abc = 456 + +Execute(ALEImport should request imports correctly for language servers): + call setpos('.', [bufnr(''), 2, 12, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 7, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 7, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + AssertEqual + \ [ + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'character': 6, 'line': 1} + \ }], + \ ], + \ g:sent_message_list + AssertEqual 1, b:ale_completion_info.request_id + + let g:ale_enabled = 1 + let g:received_message = { + \ 'id': 1, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'Some other word we should ignore', + \ 'filterText': 'missingwordIgnoreMe', + \ 'insertText': 'missingwordIgnoreMe', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingwordIgnoreMe', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingwordIgnoreMe', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'Some word without text edits', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ }, + \ { + \ 'detail': 'The word we should use', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingword', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'The other word we should not use', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something_else import missingword', + \ }, + \ ], + \ }, + \ ], + \ }, + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual + \ [ + \ { + \ 'description': 'completion', + \ 'changes': [ + \ { + \ 'fileName': expand('%:p'), + \ 'textChanges': [ + \ { + \ 'start': {'line': 2, 'offset': 2}, + \ 'end': {'line': 3, 'offset': 2}, + \ 'newText': 'from something import missingword', + \ }, + \ ], + \ }, + \ ], + \ }, + \ ], + \ g:code_action_list + + call CheckLintStates(347, g:received_message) + +Execute(ALEImport should tell the user when no completions were found from a language server): + call setpos('.', [bufnr(''), 2, 12, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 7, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 7, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + AssertEqual + \ [ + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'character': 6, 'line': 1} + \ }], + \ ], + \ g:sent_message_list + AssertEqual 1, b:ale_completion_info.request_id + + let g:received_message = { + \ 'id': 1, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'Some other word we should ignore', + \ 'filterText': 'missingwordIgnoreMe', + \ 'insertText': 'missingwordIgnoreMe', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingwordIgnoreMe', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingwordIgnoreMe', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'Some word without text edits', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ }, + \ ], + \ }, + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual 'echom ''No possible imports found.''', GetLastMessage() diff --git a/test/completion/test_complete_events.vader b/test/completion/test_complete_events.vader new file mode 100644 index 00000000..cee15985 --- /dev/null +++ b/test/completion/test_complete_events.vader @@ -0,0 +1,35 @@ +Before: + let g:complete_post_triggered = 0 + + augroup VaderTest + autocmd! + autocmd User ALECompletePost let g:complete_post_triggered = 1 + augroup END + +After: + unlet! b:ale_completion_info + unlet! g:complete_post_triggered + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + +Execute(ALECompletePost should not be triggered when completion is cancelled): + call ale#completion#HandleUserData({}) + + Assert !g:complete_post_triggered + +Execute(ALECompletePost should not be triggered when tools other than ALE insert completions): + call ale#completion#HandleUserData({'user_data': ''}) + call ale#completion#HandleUserData({'user_data': '{}'}) + + Assert !g:complete_post_triggered + +Execute(ALECompletePost should be triggered when ALE inserts completions): + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \}) + + Assert g:complete_post_triggered diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader index 2ac2b15c..f678e773 100644 --- a/test/completion/test_completion_events.vader +++ b/test/completion/test_completion_events.vader @@ -35,7 +35,7 @@ Before: let g:ale_completion_delay = 0 " Run this check a few times, as it can fail randomly. - for g:i in range(has('nvim-0.3') || has('win32') ? 5 : 1) + for l:i in range(has('nvim-0.3') || has('win32') ? 5 : 1) call ale#completion#Queue() sleep 1m @@ -50,7 +50,8 @@ Before: let g:handle_code_action_called = 0 function! MockHandleCodeAction() abort " delfunction! ale#code_action#HandleCodeAction - function! ale#code_action#HandleCodeAction(action) abort + function! ale#code_action#HandleCodeAction(action, should_save) abort + AssertEqual v:false, a:should_save let g:handle_code_action_called += 1 endfunction endfunction @@ -406,41 +407,75 @@ Execute(HandleUserData should call ale#code_action#HandleCodeAction): AssertEqual g:handle_code_action_called, 0 call ale#completion#HandleUserData({ - \ 'user_data': '' + \ 'user_data': '' \}) AssertEqual g:handle_code_action_called, 0 call ale#completion#HandleUserData({ - \ 'user_data': '{}' + \ 'user_data': json_encode({}), \}) AssertEqual g:handle_code_action_called, 0 call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": []}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [], + \ }), \}) AssertEqual g:handle_code_action_called, 0 call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), \}) AssertEqual g:handle_code_action_called, 1 let b:ale_completion_info = {'source': 'ale-automatic'} call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), \}) AssertEqual g:handle_code_action_called, 2 let b:ale_completion_info = {'source': 'ale-callback'} call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), \}) AssertEqual g:handle_code_action_called, 3 + let b:ale_completion_info = {'source': 'ale-omnifunc'} + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), + \}) + AssertEqual g:handle_code_action_called, 4 + Execute(ale#code_action#HandleCodeAction should not be called when when source is not ALE): call MockHandleCodeAction() let b:ale_completion_info = {'source': 'syntastic'} call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), \}) AssertEqual g:handle_code_action_called, 0 diff --git a/test/completion/test_completion_filtering.vader b/test/completion/test_completion_filtering.vader index c5f14266..172203a4 100644 --- a/test/completion/test_completion_filtering.vader +++ b/test/completion/test_completion_filtering.vader @@ -12,13 +12,24 @@ After: Execute(Prefix filtering should work for Lists of strings): AssertEqual \ ['FooBar', 'foo'], - \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], 'foo') + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], 'foo', 0) AssertEqual \ ['FooBar', 'FongBar', 'baz', 'foo'], - \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.') + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.', 0) AssertEqual \ ['FooBar', 'FongBar', 'baz', 'foo'], - \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '') + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '', 0) + +Execute(Exact filtering should work): + AssertEqual + \ ['foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], 'foo', 1) + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.', 1) + AssertEqual + \ ['Foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'Foo', 'foo'], 'Foo', 1) Execute(Prefix filtering should work for completion items): AssertEqual @@ -32,7 +43,8 @@ Execute(Prefix filtering should work for completion items): \ {'word': 'baz'}, \ {'word': 'foo'}, \ ], - \ 'foo' + \ 'foo', + \ 0, \ ) AssertEqual @@ -51,7 +63,8 @@ Execute(Prefix filtering should work for completion items): \ {'word': 'baz'}, \ {'word': 'foo'}, \ ], - \ '.' + \ '.', + \ 0, \ ) Execute(Excluding words from completion results should work): @@ -66,7 +79,8 @@ Execute(Excluding words from completion results should work): \ {'word': 'Italian'}, \ {'word': 'it'}, \ ], - \ 'it' + \ 'it', + \ 0, \ ) AssertEqual @@ -78,7 +92,8 @@ Execute(Excluding words from completion results should work): \ {'word': 'describe'}, \ {'word': 'Deutsch'}, \ ], - \ 'de' + \ 'de', + \ 0, \ ) AssertEqual @@ -90,7 +105,8 @@ Execute(Excluding words from completion results should work): \ {'word': 'describe'}, \ {'word': 'Deutsch'}, \ ], - \ '.' + \ '.', + \ 0, \ ) Execute(Excluding words from completion results should work with lists of Strings): @@ -98,29 +114,29 @@ Execute(Excluding words from completion results should work with lists of String AssertEqual \ ['Italian'], - \ ale#completion#Filter(bufnr(''), '', ['Italian', 'it'], 'it') + \ ale#completion#Filter(bufnr(''), '', ['Italian', 'it'], 'it', 0) AssertEqual \ ['Deutsch'], - \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], 'de') + \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], 'de', 0) AssertEqual \ ['Deutsch'], - \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], '.') + \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], '.', 0) AssertEqual \ ['Deutsch'], - \ ale#completion#Filter(bufnr(''), '', ['Deutsch'], '') + \ ale#completion#Filter(bufnr(''), '', ['Deutsch'], '', 0) Execute(Filtering shouldn't modify the original list): let b:ale_completion_excluded_words = ['it', 'describe'] let b:suggestions = [{'word': 'describe'}] - AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, '.') + AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, '.', 0) AssertEqual b:suggestions, [{'word': 'describe'}] - AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, 'de') + AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, 'de', 0) AssertEqual b:suggestions, [{'word': 'describe'}] Execute(Filtering should respect filetype triggers): let b:suggestions = [{'word': 'describe'}] - AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), '', b:suggestions, '.') - AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '.') - AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '::') + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), '', b:suggestions, '.', 0) + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '.', 0) + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '::', 0) diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader index 4b7392f5..ee810113 100644 --- a/test/completion/test_lsp_completion_messages.vader +++ b/test/completion/test_lsp_completion_messages.vader @@ -121,7 +121,7 @@ Execute(The right message should be sent for the initial tsserver request): \ 'line': 1, \ 'offset': 3, \ 'prefix': 'fo', - \ 'includeExternalModuleExports': g:ale_completion_tsserver_autoimport, + \ 'includeExternalModuleExports': g:ale_completion_autoimport, \ }]], \ g:message_list " We should set up the completion info correctly. diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader index 1fdbbd96..d989aefe 100644 --- a/test/completion/test_lsp_completion_parsing.vader +++ b/test/completion/test_lsp_completion_parsing.vader @@ -1,37 +1,45 @@ +Before: + Save g:ale_completion_autoimport + Save g:ale_completion_max_suggestions + + let g:ale_completion_max_suggestions = 50 + After: + Restore + unlet! b:ale_completion_info Execute(Should handle Rust completion results correctly): AssertEqual \ [ - \ {'word': 'new', 'menu': 'pub fn new() -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'with_capacity', 'menu': 'pub fn with_capacity(capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf8', 'menu': 'pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf8_lossy', 'menu': 'pub fn from_utf8_lossy<''a>(v: &''a [u8]) -> Cow<''a, str>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf16', 'menu': 'pub fn from_utf16(v: &[u16]) -> Result<String, FromUtf16Error>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf16_lossy', 'menu': 'pub fn from_utf16_lossy(v: &[u16]) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_raw_parts', 'menu': 'pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf8_unchecked', 'menu': 'pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a str>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = Cow<''a, str>>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Searcher', 'menu': 'type Searcher = <&''b str as Pattern<''a>>::Searcher;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'default', 'menu': 'fn default() -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = String;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Target', 'menu': 'type Target = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Err', 'menu': 'type Err = ParseError;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'from_str', 'menu': 'fn from_str(s: &str) -> Result<String, ParseError>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from', 'menu': 'fn from(s: &''a str) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from', 'menu': 'fn from(s: Box<str>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from', 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'new', 'menu': 'pub fn new() -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'with_capacity', 'menu': 'pub fn with_capacity(capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8', 'menu': 'pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8_lossy', 'menu': 'pub fn from_utf8_lossy<''a>(v: &''a [u8]) -> Cow<''a, str>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf16', 'menu': 'pub fn from_utf16(v: &[u16]) -> Result<String, FromUtf16Error>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf16_lossy', 'menu': 'pub fn from_utf16_lossy(v: &[u16]) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_raw_parts', 'menu': 'pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8_unchecked', 'menu': 'pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a str>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = Cow<''a, str>>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Searcher', 'menu': 'type Searcher = <&''b str as Pattern<''a>>::Searcher;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'default', 'menu': 'fn default() -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = String;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Target', 'menu': 'type Target = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Err', 'menu': 'type Err = ParseError;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_str', 'menu': 'fn from_str(s: &str) -> Result<String, ParseError>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: &''a str) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: Box<str>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \], \ ale#completion#ParseLSPCompletions({ \ "jsonrpc":"2.0", @@ -187,7 +195,7 @@ Execute(Should handle Python completion results correctly): AssertEqual \ [ - \ {'word': 'what', 'menu': 'example-python-project.bar.Bar', 'info': "what()\n\n", 'kind': 'f', 'icase': 1}, + \ {'word': 'what', 'menu': 'example-python-project.bar.Bar', 'info': "what()\n\n", 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ "jsonrpc":"2.0", @@ -391,7 +399,7 @@ Execute(Should handle Python completion results correctly): \ } \ }) -Execute(Should handle Python completion results correctly): +Execute(Should handle extra Python completion results correctly): let b:ale_completion_info = { \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', \ 'prefix': 'mig', @@ -399,8 +407,8 @@ Execute(Should handle Python completion results correctly): AssertEqual \ [ - \ {'word': 'migrations', 'menu': 'xxx', 'info': 'migrations', 'kind': 'f', 'icase': 1}, - \ {'word': 'MigEngine', 'menu': 'xxx', 'info': 'mig engine', 'kind': 'f', 'icase': 1}, + \ {'word': 'migrations', 'menu': 'xxx', 'info': 'migrations', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'MigEngine', 'menu': 'xxx', 'info': 'mig engine', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'jsonrpc': '2.0', @@ -433,7 +441,7 @@ Execute(Should handle Python completion results correctly): Execute(Should handle missing keys): AssertEqual \ [ - \ {'word': 'x', 'menu': '', 'info': '', 'kind': 'v', 'icase': 1}, + \ {'word': 'x', 'menu': '', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'jsonrpc': '2.0', @@ -451,7 +459,7 @@ Execute(Should handle missing keys): Execute(Should handle documentation in the markdown format): AssertEqual \ [ - \ {'word': 'migrations', 'menu': 'xxx', 'info': 'Markdown documentation', 'kind': 'f', 'icase': 1}, + \ {'word': 'migrations', 'menu': 'xxx', 'info': 'Markdown documentation', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'jsonrpc': '2.0', @@ -475,7 +483,7 @@ Execute(Should handle documentation in the markdown format): Execute(Should handle completion messages with textEdit objects): AssertEqual \ [ - \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1}, + \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'id': 226, @@ -506,7 +514,153 @@ Execute(Should handle completion messages with textEdit objects): Execute(Should handle completion messages with the deprecated insertText attribute): AssertEqual \ [ - \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1}, + \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'id': 226, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'PlayTimeCallback', + \ 'filterText': 'next_callback', + \ 'insertText': 'next_callback', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' next_callback', + \ 'sortText': '3ee19999next_callback', + \ }, + \ ], + \ }, + \ }) + +Execute(Should handle completion messages with additionalTextEdits when ale_completion_autoimport is turned on): + let g:ale_completion_autoimport = 1 + + AssertEqual + \ [ + \ { + \ 'word': 'next_callback', + \ 'menu': 'PlayTimeCallback', + \ 'info': '', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ { + \ 'description': 'completion', + \ 'changes': [ + \ { + \ 'fileName': expand('#' . bufnr('') . ':p'), + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 11, + \ 'offset': 2, + \ }, + \ 'end': { + \ 'line': 13, + \ 'offset': 4, + \ }, + \ 'newText': 'from "module" import next_callback', + \ }, + \ ], + \ }, + \ ], + \ }, + \ ], + \ }), + \ }, + \ ], + \ ale#completion#ParseLSPCompletions({ + \ 'id': 226, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'PlayTimeCallback', + \ 'filterText': 'next_callback', + \ 'insertText': 'next_callback', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' next_callback', + \ 'sortText': '3ee19999next_callback', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 10, + \ 'character': 1, + \ }, + \ 'end': { + \ 'line': 12, + \ 'character': 3, + \ }, + \ }, + \ 'newText': 'from "module" import next_callback', + \ }, + \ ], + \ }, + \ ], + \ }, + \ }) + +Execute(Should not handle completion messages with additionalTextEdits when ale_completion_autoimport is turned off): + let g:ale_completion_autoimport = 0 + let b:ale_completion_info = {'line': 30} + + AssertEqual + \ [], + \ ale#completion#ParseLSPCompletions({ + \ 'id': 226, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'PlayTimeCallback', + \ 'filterText': 'next_callback', + \ 'insertText': 'next_callback', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' next_callback', + \ 'sortText': '3ee19999next_callback', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 10, + \ 'character': 1, + \ }, + \ 'end': { + \ 'line': 12, + \ 'character': 3, + \ }, + \ }, + \ 'newText': 'from "module" import next_callback', + \ }, + \ ], + \ }, + \ ], + \ }, + \ }) + +Execute(Should still handle completion messages with empty additionalTextEdits with ale_completion_autoimport turned off): + let g:ale_completion_autoimport = 0 + + AssertEqual + \ [ + \ { + \ 'word': 'next_callback', + \ 'menu': 'PlayTimeCallback', + \ 'info': '', + \ 'kind': 'v', + \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ } \ ], \ ale#completion#ParseLSPCompletions({ \ 'id': 226, @@ -522,6 +676,7 @@ Execute(Should handle completion messages with the deprecated insertText attribu \ 'kind': 6, \ 'label': ' next_callback', \ 'sortText': '3ee19999next_callback', + \ 'additionalTextEdits': [], \ }, \ ], \ }, diff --git a/test/completion/test_public_completion_api.vader b/test/completion/test_public_completion_api.vader index c3cd42b8..f26fdc12 100644 --- a/test/completion/test_public_completion_api.vader +++ b/test/completion/test_public_completion_api.vader @@ -37,6 +37,7 @@ Execute(ale#completion#GetCompletionPositionForDeoplete() should return the posi AssertEqual 4, ale#completion#GetCompletionPositionForDeoplete('foo bar') Execute(ale#completion#CanProvideCompletions should return 0 when no completion sources are available): + let b:ale_linters = ['flake8'] AssertEqual 0, ale#completion#CanProvideCompletions() Execute(ale#completion#CanProvideCompletions should return 1 when at least one completion source is available): diff --git a/test/completion/test_tsserver_completion_parsing.vader b/test/completion/test_tsserver_completion_parsing.vader index 6beb7b0a..231c0f95 100644 --- a/test/completion/test_tsserver_completion_parsing.vader +++ b/test/completion/test_tsserver_completion_parsing.vader @@ -1,4 +1,11 @@ +Before: + Save g:ale_completion_tsserver_remove_warnings + + let g:ale_completion_tsserver_remove_warnings = 0 + After: + Restore + unlet! b:ale_tsserver_completion_names Execute(TypeScript completions responses should be parsed correctly): @@ -29,6 +36,51 @@ Execute(TypeScript completions responses should be parsed correctly): \ ], \}) +Execute(TypeScript completions responses should include warnings): + AssertEqual + \ [ + \ { + \ 'word': 'foo', + \ 'source': '/path/to/foo.ts', + \ }, + \ { + \ 'word': 'bar', + \ 'source': '', + \ }, + \ { + \ 'word': 'baz', + \ 'source': '', + \ } + \ ], + \ ale#completion#ParseTSServerCompletions({ + \ 'body': [ + \ {'name': 'foo', 'source': '/path/to/foo.ts'}, + \ {'name': 'bar', 'kind': 'warning'}, + \ {'name': 'baz'}, + \ ], + \}) + +Execute(TypeScript completions responses should not include warnings if excluded): + let g:ale_completion_tsserver_remove_warnings = 1 + AssertEqual + \ [ + \ { + \ 'word': 'foo', + \ 'source': '/path/to/foo.ts', + \ }, + \ { + \ 'word': 'baz', + \ 'source': '', + \ } + \ ], + \ ale#completion#ParseTSServerCompletions({ + \ 'body': [ + \ {'name': 'foo', 'source': '/path/to/foo.ts'}, + \ {'name': 'bar', 'kind': 'warning'}, + \ {'name': 'baz'}, + \ ], + \}) + Execute(TypeScript completion details responses should be parsed correctly): AssertEqual \ [ @@ -38,7 +90,8 @@ Execute(TypeScript completion details responses should be parsed correctly): \ 'info': '', \ 'kind': 'v', \ 'icase': 1, - \ 'dup': g:ale_completion_tsserver_autoimport, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'dup': g:ale_completion_autoimport, \ }, \ { \ 'word': 'def', @@ -46,7 +99,8 @@ Execute(TypeScript completion details responses should be parsed correctly): \ 'info': 'foo bar baz', \ 'kind': 'v', \ 'icase': 1, - \ 'dup': g:ale_completion_tsserver_autoimport, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'dup': g:ale_completion_autoimport, \ }, \ { \ 'word': 'ghi', @@ -54,7 +108,8 @@ Execute(TypeScript completion details responses should be parsed correctly): \ 'info': '', \ 'kind': 'v', \ 'icase': 1, - \ 'dup': g:ale_completion_tsserver_autoimport, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'dup': g:ale_completion_autoimport, \ }, \ ], \ ale#completion#ParseTSServerCompletionEntryDetails({ @@ -127,12 +182,13 @@ Execute(Entries without details should be included in the responses): \ 'kind': 'v', \ 'icase': 1, \ 'user_data': json_encode({ - \ 'codeActions': [{ + \ '_ale_completion_item': 1, + \ 'code_actions': [{ \ 'description': 'import { def } from "./Foo";', \ 'changes': [], \ }], \ }), - \ 'dup': g:ale_completion_tsserver_autoimport, + \ 'dup': g:ale_completion_autoimport, \ }, \ { \ 'word': 'def', @@ -140,13 +196,15 @@ Execute(Entries without details should be included in the responses): \ 'info': 'foo bar baz', \ 'kind': 'v', \ 'icase': 1, - \ 'dup': g:ale_completion_tsserver_autoimport, + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \ 'dup': g:ale_completion_autoimport, \ }, \ { \ 'word': 'xyz', \ 'menu': '', \ 'info': '', \ 'kind': 'v', + \ 'user_data': json_encode({'_ale_completion_item': 1}), \ 'icase': 1, \ }, \ ], @@ -197,3 +255,55 @@ Execute(Entries without details should be included in the responses): \ }, \ ], \}) + +Execute(Default imports should be handled correctly): + AssertEqual + \ [ + \ { + \ 'word': 'abcd', + \ 'menu': 'Import default ''abcd'' from module "./foo" (alias) const abcd: 3', + \ 'info': '', + \ 'kind': 't', + \ 'icase': 1, + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [{ + \ 'description': 'Import default ''abcd'' from module "./foo"', + \ 'changes': [], + \ }], + \ }), + \ 'dup': g:ale_completion_autoimport, + \ }, + \ ], + \ ale#completion#ParseTSServerCompletionEntryDetails({ + \ 'body': [ + \ { + \ 'name': 'default', + \ 'kind': 'alias', + \ 'displayParts': [ + \ {'kind': 'punctuation', 'text': '('}, + \ {'kind': 'text', 'text': 'alias'}, + \ {'kind': 'punctuation', 'text': ')'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'keyword', 'text': 'const'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'localName', 'text': 'abcd'}, + \ {'kind': 'punctuation', 'text': ':'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'stringLiteral', 'text': '3'}, + \ {'kind': 'lineBreak', 'text': '^@'}, + \ {'kind': 'keyword', 'text': 'export'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'keyword', 'text': 'default'}, + \ {'kind': 'space', 'text': ' '}, + \ {'kind': 'aliasName', 'text': 'abcd'} + \ ], + \ 'codeActions': [ + \ { + \ 'description': 'Import default ''abcd'' from module "./foo"', + \ 'changes': [], + \ }, + \ ], + \ }, + \ ], + \ }) diff --git a/test/ember-template-lint-test-files/app/template.hbs b/test/ember-template-lint-test-files/app/template.hbs new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/ember-template-lint-test-files/app/template.hbs diff --git a/test/ember-template-lint-test-files/package.json b/test/ember-template-lint-test-files/package.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/ember-template-lint-test-files/package.json diff --git a/test/eslint-test-files/react-app/subdir-with-package-json/node_modules/.gitkeep b/test/eslint-test-files/react-app/subdir-with-package-json/node_modules/.gitkeep new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/eslint-test-files/react-app/subdir-with-package-json/node_modules/.gitkeep diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader index c3dd20e4..128e3a14 100644 --- a/test/fix/test_ale_fix.vader +++ b/test/fix/test_ale_fix.vader @@ -5,18 +5,22 @@ Before: Save g:ale_fix_on_save Save g:ale_lint_on_save Save g:ale_echo_cursor + Save g:ale_command_wrapper + Save g:ale_filename_mappings silent! cd /testplugin/test/fix unlet! b:ale_lint_on_save let g:ale_enabled = 0 let g:ale_echo_cursor = 0 + let g:ale_command_wrapper = '' let g:ale_run_synchronously = 1 let g:ale_set_lists_synchronously = 1 let g:ale_fix_buffer_data = {} let g:ale_fixers = { \ 'testft': [], \} + let g:ale_filename_mappings = {} let g:pre_success = 0 let g:post_success = 0 @@ -70,6 +74,10 @@ Before: return {'command': 'cat %t <(echo d)'} endfunction + function EchoFilename(buffer, lines) abort + return {'command': 'echo %s'} + endfunction + function RemoveLastLine(buffer, lines) abort return ['a', 'b'] endfunction @@ -82,56 +90,6 @@ Before: return [{'lnum': 1, 'col': 1, 'text': 'xxx'}] endfunction - function! FirstChainCallback(buffer) - return {'command': 'echo echoline', 'chain_with': 'SecondChainCallback'} - endfunction - - function! FirstChainCallbackSkipped(buffer) - let l:ChainWith = 'SecondChainCallback' - - " Test with lambdas where support is available. - if has('lambda') - let l:ChainWith = {buffer, output -> SecondChainCallback(buffer, output)} - endif - - return {'command': '', 'chain_with': l:ChainWith} - endfunction - - function! FirstChainCallbackSecondSkipped(buffer) - return {'command': 'echo skipit', 'chain_with': 'SecondChainCallback'} - endfunction - - function! SecondChainCallback(buffer, output) - let l:previous_line = empty(a:output) - \ ? 'emptydefault' - \ : join(split(a:output[0])) - - if l:previous_line is# 'skipit' - return {'command': '', 'chain_with': 'ThirdChainCallback'} - endif - - return { - \ 'command': 'echo ' . l:previous_line, - \ 'chain_with': 'ThirdChainCallback', - \} - endfunction - - function! ThirdChainCallback(buffer, output, input) - let l:previous_line = empty(a:output) - \ ? 'thirddefault' - \ : join(split(a:output[0])) - - return a:input + [l:previous_line] - endfunction - - function! ChainWhereLastIsSkipped(buffer) - return {'command': 'echo echoline', 'chain_with': 'ChainEndSkipped'} - endfunction - - function! ChainEndSkipped(buffer, output) - return {'command': ''} - endfunction - " echo will output a single blank line, and we should ingore it. function! IgnoredEmptyOutput(buffer, output) return {'command': has('win32') ? 'echo(' : 'echo'} @@ -203,16 +161,10 @@ After: delfunction CatLineDeferred delfunction ReplaceWithTempFile delfunction CatWithTempFile + delfunction EchoFilename delfunction RemoveLastLine delfunction RemoveLastLineOneArg delfunction TestCallback - delfunction FirstChainCallback - delfunction FirstChainCallbackSkipped - delfunction FirstChainCallbackSecondSkipped - delfunction SecondChainCallback - delfunction ThirdChainCallback - delfunction ChainWhereLastIsSkipped - delfunction ChainEndSkipped delfunction SetUpLinters delfunction GetLastMessage delfunction IgnoredEmptyOutput @@ -244,6 +196,10 @@ After: " Clear the messages between tests. echomsg '' + if !exists('g:ale_command_wrapper') + let g:ale_command_wrapper = '' + endif + Given testft (A file with three lines): a b @@ -254,6 +210,13 @@ Execute(ALEFix should complain when there are no functions to call): call ale#test#FlushJobs() AssertEqual 'No fixers have been defined. Try :ALEFixSuggest', GetLastMessage() +Execute(ALEFix should not complain when the command is run with a bang): + echom 'none' + + ALEFix! + call ale#test#FlushJobs() + AssertEqual 'none', GetLastMessage() + Execute(ALEFix should apply simple functions): let g:ale_fixers.testft = ['AddCarets'] ALEFix @@ -264,6 +227,25 @@ Expect(The first function should be used): ^b ^c +Execute(Should apply filename mpapings): + " The command echos %s, and we'll map the current path so we can check + " that ALEFix applies filename mappings, end-to-end. + let g:ale_filename_mappings = { + \ 'echo_filename': [ + \ [expand('%:p:h') . '/', '/some/fake/path/'], + \ ], + \} + + call ale#fix#registry#Add('echo_filename', 'EchoFilename', [], 'echo filename') + let g:ale_fixers.testft = ['echo_filename'] + ALEFix + call ale#test#FlushJobs() + " Remote trailing whitespace from the line. + call setline(1, substitute(getline(1), '[ \r]\+$', '', '')) + +Expect(The mapped filename should be printed): + /some/fake/path/test.txt + Execute(ALEFix should apply simple functions in a chain): let g:ale_fixers.testft = ['AddCarets', 'Capitalize'] ALEFix @@ -744,6 +726,19 @@ Execute(ale#fix#InitBufferData() should set up the correct data): \ 'done': 0, \ 'lines_before': ['a', 'b', 'c'], \ 'should_save': 1, + \ 'ignore_file_changed_errors': 0, + \ }, + \}, g:ale_fix_buffer_data + + call ale#fix#InitBufferData(bufnr(''), '!') + + AssertEqual { + \ bufnr(''): { + \ 'temporary_directory_list': [], + \ 'done': 0, + \ 'lines_before': ['a', 'b', 'c'], + \ 'should_save': 0, + \ 'ignore_file_changed_errors': 1, \ }, \}, g:ale_fix_buffer_data @@ -756,6 +751,19 @@ Expect(There should be only two lines): a b +Execute(ALEFix should modify a buffer that is not modifiable, if it becomes modifiable later): + let g:ale_fixers.testft = ['RemoveLastLineOneArg'] + + set nomodifiable + ALEFix + call ale#test#FlushJobs() + set modifiable + call ale#fix#ApplyQueuedFixes(bufnr('')) + +Expect(There should be only two lines): + a + b + Execute(b:ale_fix_on_save = 1 should override g:ale_fix_on_save = 0): let g:ale_fix_on_save = 0 let b:ale_fix_on_save = 1 @@ -814,57 +822,6 @@ Execute(ALE should tolerate valid fixers with minuses in the name): ALEFix call ale#test#FlushJobs() -Execute(Test fixing with chained callbacks): - let g:ale_fixers.testft = ['FirstChainCallback'] - ALEFix - call ale#test#FlushJobs() - - " The buffer shouldn't be piped in for earlier commands in the chain. - AssertEqual - \ [ - \ string(ale#job#PrepareCommand(bufnr(''), 'echo echoline')), - \ string(ale#job#PrepareCommand(bufnr(''), 'echo echoline')), - \ ], - \ map(ale#history#Get(bufnr(''))[-2:-1], 'string(v:val.command)') - -Expect(The echoed line should be added): - a - b - c - echoline - -Execute(Test fixing with chained callback where the first command is skipped): - let g:ale_fixers.testft = ['FirstChainCallbackSkipped'] - ALEFix - call ale#test#FlushJobs() - -Expect(The default line should be added): - a - b - c - emptydefault - -Execute(Test fixing with chained callback where the second command is skipped): - let g:ale_fixers.testft = ['FirstChainCallbackSecondSkipped'] - ALEFix - call ale#test#FlushJobs() - -Expect(The default line should be added): - a - b - c - thirddefault - -Execute(Test fixing with chained callback where the final callback is skipped): - let g:ale_fixers.testft = ['ChainWhereLastIsSkipped'] - ALEFix - call ale#test#FlushJobs() - -Expect(The lines should be the same): - a - b - c - Execute(Empty output should be ignored): let g:ale_fixers.testft = ['IgnoredEmptyOutput'] ALEFix diff --git a/test/fixers/test_astyle_fixer_callback.vader b/test/fixers/test_astyle_fixer_callback.vader new file mode 100644 index 00000000..ac756870 --- /dev/null +++ b/test/fixers/test_astyle_fixer_callback.vader @@ -0,0 +1,96 @@ +Before: + Save g:ale_c_astyle_executable + Save g:ale_c_astyle_project_options + Save g:ale_cpp_astyle_project_options + + " Use an invalid global executable, so we don't match it. + let g:ale_c_astyle_executable = 'xxxinvalid' + let g:ale_cpp_astyle_executable = 'invalidpp' + let g:ale_c_astyle_project_options = '' + let g:ale_cpp_astyle_project_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The astyle callback should return the correct default values): + " Because this file doesn't exist, no astylrc config + " exists near it. Therefore, project_options is empty. + call ale#test#SetFilename('../c_files/testfile.c') + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_c_astyle_executable) + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should support cpp files): + " Because this file doesn't exist, no astylrc config + " exists near it. Therefore, project_options is empty. + call ale#test#SetFilename('../cpp_files/dummy.cpp') + set filetype=cpp " The test fails without this + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape(g:ale_cpp_astyle_executable) + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should support cpp files with option file set): + call ale#test#SetFilename('../cpp_files/dummy.cpp') + let g:ale_cpp_astyle_project_options = '.astylerc_cpp' + let targetfile = bufname(bufnr('%')) + set filetype=cpp " The test fails without this + + AssertEqual + \ { + \ 'command': ale#Escape('invalidpp') + \ . ' --project=' . g:ale_cpp_astyle_project_options + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should return the correct default values with a specified option file): + call ale#test#SetFilename('../c_files/testfile.c') + let g:ale_c_astyle_project_options = '.astylerc_c' + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' --project=' . g:ale_c_astyle_project_options + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should find nearest default option file _astylrc): + call ale#test#SetFilename('../test_c_projects/makefile_project/subdir/file.c') + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape('xxxinvalid') + \ . ' --project=_astylerc' + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) + +Execute(The astyle callback should find .astylrc in the same directory as src): + call ale#test#SetFilename('../test_cpp_project/dummy.cpp') + set filetype=cpp " The test fails without this + let targetfile = bufname(bufnr('%')) + + AssertEqual + \ { + \ 'command': ale#Escape('invalidpp') + \ . ' --project=.astylerc' + \ . ' --stdin=' . ale#Escape(targetfile) + \ }, + \ ale#fixers#astyle#Fix(bufnr('')) diff --git a/test/fixers/test_clangtidy_fixer_callback.vader b/test/fixers/test_clangtidy_fixer_callback.vader index 68416b36..ca08e6bc 100644 --- a/test/fixers/test_clangtidy_fixer_callback.vader +++ b/test/fixers/test_clangtidy_fixer_callback.vader @@ -1,8 +1,19 @@ Before: + Save g:ale_c_build_dir Save g:ale_c_clangtidy_executable + Save g:ale_c_clangtidy_checks + Save g:ale_c_clangtidy_extra_options + Save g:ale_cpp_clangtidy_executable + Save g:ale_cpp_clangtidy_checks + Save g:ale_cpp_clangtidy_extra_options " Use an invalid global executable, so we don't match it. let g:ale_c_clangtidy_executable = 'xxxinvalid' + let g:ale_c_clangtidy_checks = [] + let g:ale_c_clangtidy_extra_options = '' + let g:ale_cpp_clangtidy_executable = 'xxxinvalidpp' + let g:ale_cpp_clangtidy_checks = [] + let g:ale_cpp_clangtidy_extra_options = '' let g:ale_c_build_dir = '' call ale#test#SetDirectory('/testplugin/test/fixers') @@ -36,16 +47,3 @@ Execute(The clangtidy callback should include any additional options): \ . ' -fix -fix-errors --some-option %t', \ }, \ ale#fixers#clangtidy#Fix(bufnr('')) - -Execute(The clangtidy callback should support cpp files): - call ale#test#SetFilename('c_paths/dummy.cpp') - let g:ale_cpp_clangtidy_executable = 'invalidpp' - set filetype=cpp " The test fails without this - - AssertEqual - \ { - \ 'read_temporary_file': 1, - \ 'command': ale#Escape(g:ale_cpp_clangtidy_executable) - \ . ' -fix -fix-errors %t', - \ }, - \ ale#fixers#clangtidy#Fix(bufnr('')) diff --git a/test/fixers/test_dhall_fixer_callback.vader b/test/fixers/test_dhall_fixer_callback.vader new file mode 100644 index 00000000..f27880b7 --- /dev/null +++ b/test/fixers/test_dhall_fixer_callback.vader @@ -0,0 +1,11 @@ +Before: + call ale#assert#SetUpFixerTest('dhall', 'dhall') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The default command should be correct): + AssertFixer + \ { 'read_temporary_file': 1, + \ 'command': ale#Escape('dhall') . ' format --inplace %t' + \ } diff --git a/test/fixers/test_eslint_fixer_callback.vader b/test/fixers/test_eslint_fixer_callback.vader index ad80bdd5..50fc6672 100644 --- a/test/fixers/test_eslint_fixer_callback.vader +++ b/test/fixers/test_eslint_fixer_callback.vader @@ -1,7 +1,11 @@ Before: call ale#assert#SetUpFixerTest('javascript', 'eslint') + Save g:ale_command_wrapper + runtime autoload/ale/handlers/eslint.vim + let g:ale_command_wrapper = '' + After: call ale#assert#TearDownFixerTest() @@ -13,7 +17,9 @@ Execute(The executable path should be correct): AssertFixer \ { \ 'read_temporary_file': 1, - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js')) \ . ' --fix %t', @@ -150,7 +156,9 @@ Execute(The lower priority configuration file in a nested directory should be pr AssertFixer \ { \ 'read_temporary_file': 1, - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/subdir-with-config/.eslintrc')) \ . ' --fix %t', @@ -164,7 +172,9 @@ Execute(--config in options should override configuration file detection for old AssertFixer \ { \ 'read_temporary_file': 1, - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' --config /foo.cfg' \ . ' --fix %t', @@ -175,7 +185,9 @@ Execute(--config in options should override configuration file detection for old AssertFixer \ { \ 'read_temporary_file': 1, - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' -c /foo.cfg' \ . ' --fix %t', @@ -187,7 +199,9 @@ Execute(package.json should be used as a last resort): AssertFixer \ { \ 'read_temporary_file': 1, - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js')) \ . ' --fix %t', @@ -199,7 +213,8 @@ Execute(package.json should be used as a last resort): \ { \ 'read_temporary_file': 1, \ 'command': - \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/node_modules/.bin/eslint')) + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files')) + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/node_modules/.bin/eslint')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/package.json')) \ . ' --fix %t', \ } @@ -214,7 +229,9 @@ Execute(The version check should be correct): \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' --version', \ { - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', @@ -223,7 +240,9 @@ Execute(The version check should be correct): AssertFixer [ \ { - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', @@ -236,7 +255,9 @@ Execute(--fix-dry-run should be used for 4.9.0 and up): GivenCommandOutput ['4.9.0'] AssertFixer \ { - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) \ . ' --stdin-filename %s --stdin --fix-dry-run --format=json', \ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput', @@ -249,7 +270,8 @@ Execute(--fix-to-stdout should be used for eslint_d): \ { \ 'read_temporary_file': 1, \ 'command': - \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d')) + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) \ . ' -c ' . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/package.json')) \ . ' --fix %t', \ } @@ -260,7 +282,8 @@ Execute(--fix-to-stdout should be used for eslint_d): AssertFixer \ { \ 'command': - \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d')) + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) \ . ' --stdin-filename %s --stdin --fix-to-stdout', \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', \ } @@ -270,7 +293,8 @@ Execute(--fix-to-stdout should be used for eslint_d): AssertFixer \ { \ 'command': - \ ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) + \ ale#path#CdString(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d')) + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/app-with-eslint-d/node_modules/.bin/eslint_d')) \ . ' --stdin-filename %s --stdin --fix-to-stdout', \ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput', \ } diff --git a/test/fixers/test_isort_fixer_callback.vader b/test/fixers/test_isort_fixer_callback.vader index 50818621..7f389dcf 100644 --- a/test/fixers/test_isort_fixer_callback.vader +++ b/test/fixers/test_isort_fixer_callback.vader @@ -4,6 +4,7 @@ Before: " Use an invalid global executable, so we don't match it. let g:ale_python_isort_executable = 'xxxinvalid' + let g:ale_python_isort_options = '' call ale#test#SetDirectory('/testplugin/test/fixers') silent cd .. @@ -27,7 +28,7 @@ Execute(The isort callback should return the correct default values): silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') AssertEqual \ { - \ 'command': ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir/foo')) + \ 'command': ale#path#BufferCdString(bufnr('')) \ . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/isort')) . ' -', \ }, \ ale#fixers#isort#Fix(bufnr('')) @@ -42,7 +43,7 @@ Execute(The isort callback should respect custom options): silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') AssertEqual \ { - \ 'command': ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir/foo')) + \ 'command': ale#path#BufferCdString(bufnr('')) \ . ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/isort')) \ . ' --multi-line=3 --trailing-comma -', \ }, diff --git a/test/fixers/test_ktlint_fixer_callback.vader b/test/fixers/test_ktlint_fixer_callback.vader index 47b37788..ba01a409 100644 --- a/test/fixers/test_ktlint_fixer_callback.vader +++ b/test/fixers/test_ktlint_fixer_callback.vader @@ -21,9 +21,8 @@ Execute(The ktlint callback should return the correct default values): AssertEqual \ { \ 'command': ale#Escape('xxxinvalid') - \ . ' %t' + \ . ' --stdin' \ . ' --format', - \ 'read_temporary_file': 1, \ }, \ ale#fixers#ktlint#Fix(bufnr('')) @@ -37,8 +36,7 @@ Execute(The ktlint callback should include custom ktlint options): \ 'command': ale#Escape('xxxinvalid') \ . ' ' . g:ale_kotlin_ktlint_options \ . ' --ruleset /path/to/custom/ruleset.jar' - \ . ' %t' + \ . ' --stdin' \ . ' --format', - \ 'read_temporary_file': 1, \ }, \ ale#fixers#ktlint#Fix(bufnr('')) diff --git a/test/fixers/test_latexindent_fixer_callback.vader b/test/fixers/test_latexindent_fixer_callback.vader index d0da94a1..a440ed65 100644 --- a/test/fixers/test_latexindent_fixer_callback.vader +++ b/test/fixers/test_latexindent_fixer_callback.vader @@ -18,10 +18,8 @@ Execute(The latexindent callback should return the correct default values): AssertEqual \ { - \ 'read_temporary_file': 1, \ 'command': ale#Escape('xxxinvalid') - \ . ' -l -w' - \ . ' %t', + \ . ' -l' \ }, \ ale#fixers#latexindent#Fix(bufnr('')) @@ -31,10 +29,8 @@ Execute(The latexindent callback should include custom gofmt options): AssertEqual \ { - \ 'read_temporary_file': 1, \ 'command': ale#Escape('xxxinvalid') - \ . ' -l -w' + \ . ' -l' \ . ' ' . g:ale_tex_latexindent_options - \ . ' %t', \ }, \ ale#fixers#latexindent#Fix(bufnr('')) diff --git a/test/fixers/test_ocamlformat_fixer_callback.vader b/test/fixers/test_ocamlformat_fixer_callback.vader index f0c36ed7..0ccdb070 100644 --- a/test/fixers/test_ocamlformat_fixer_callback.vader +++ b/test/fixers/test_ocamlformat_fixer_callback.vader @@ -19,8 +19,7 @@ Execute(The ocamlformat callback should return the correct default values): AssertEqual \ { \ 'command': ale#Escape('xxxinvalid') - \ . ' --name=' . ale#Escape(bufname(bufnr(''))) - \ . ' -', + \ . ' --name=%s -', \ }, \ ale#fixers#ocamlformat#Fix(bufnr('')) @@ -32,7 +31,6 @@ Execute(The ocamlformat callback should include custom ocamlformat options): \ { \ 'command': ale#Escape('xxxinvalid') \ . ' ' . g:ale_ocaml_ocamlformat_options - \ . ' --name=' . ale#Escape(bufname(bufnr(''))) - \ . ' -', + \ . ' --name=%s -', \ }, \ ale#fixers#ocamlformat#Fix(bufnr('')) diff --git a/test/fixers/test_prettier_eslint_fixer.callback.vader b/test/fixers/test_prettier_eslint_fixer.callback.vader index 90e11672..be8f04e3 100644 --- a/test/fixers/test_prettier_eslint_fixer.callback.vader +++ b/test/fixers/test_prettier_eslint_fixer.callback.vader @@ -1,5 +1,8 @@ Before: call ale#assert#SetUpFixerTest('javascript', 'prettier_eslint') + Save g:ale_command_wrapper + + let g:ale_command_wrapper = '' After: call ale#assert#TearDownFixerTest() @@ -70,7 +73,7 @@ Execute(The new --stdin-filepath option should be used when the version is new e GivenCommandOutput ['4.4.0'] AssertFixer \ { - \ 'command': ale#path#CdString(expand('%:p:h')) + \ 'command': ale#path#BufferCdString(bufnr('')) \ . ale#Escape('prettier-eslint') \ . ' --eslint-config-path ' . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/.eslintrc.js')) \ . ' --stdin-filepath %s --stdin', @@ -80,7 +83,7 @@ Execute(The version number should be cached): GivenCommandOutput ['4.4.0'] AssertFixer \ { - \ 'command': ale#path#CdString(expand('%:p:h')) + \ 'command': ale#path#BufferCdString(bufnr('')) \ . ale#Escape('prettier-eslint') \ . ' --stdin-filepath %s --stdin', \ } @@ -88,7 +91,7 @@ Execute(The version number should be cached): GivenCommandOutput [] AssertFixer \ { - \ 'command': ale#path#CdString(expand('%:p:h')) + \ 'command': ale#path#BufferCdString(bufnr('')) \ . ale#Escape('prettier-eslint') \ . ' --stdin-filepath %s --stdin', \ } diff --git a/test/fixers/test_prettier_fixer_callback.vader b/test/fixers/test_prettier_fixer_callback.vader index 062ae8cf..fdd97df3 100644 --- a/test/fixers/test_prettier_fixer_callback.vader +++ b/test/fixers/test_prettier_fixer_callback.vader @@ -1,5 +1,8 @@ Before: call ale#assert#SetUpFixerTest('javascript', 'prettier') + Save g:ale_command_wrapper + + let g:ale_command_wrapper = '' silent cd .. silent cd command_callback @@ -294,6 +297,17 @@ Execute(Should set --parser for experimental language, Handlebars): \ . ' --stdin-filepath %s --stdin', \ } +Execute(Changes to directory where .prettierignore is found): + call ale#test#SetFilename('../prettier-test-files/with_prettierignore/src/testfile.js') + + GivenCommandOutput ['1.6.0'] + AssertFixer + \ { + \ 'command': ale#path#CdString(expand('%:p:h:h')) + \ . ale#Escape(g:ale_javascript_prettier_executable) + \ . ' --stdin-filepath %s --stdin', + \ } + Execute(The prettier_d post-processor should permit regular JavaScript content): AssertEqual \ [ diff --git a/test/fixers/test_prettier_standard_callback.vader b/test/fixers/test_prettier_standard_callback.vader new file mode 100644 index 00000000..ab33fe20 --- /dev/null +++ b/test/fixers/test_prettier_standard_callback.vader @@ -0,0 +1,19 @@ +Before: + call ale#assert#SetUpFixerTest('javascript', 'prettier_standard') + + silent cd .. + silent cd command_callback + let g:dir = getcwd() + +After: + call ale#assert#TearDownFixerTest() + +Execute(The prettier callback should return the correct default values): + call ale#test#SetFilename('../prettier-test-files/testfile.js') + + AssertFixer + \ { + \ 'command': ale#Escape(g:ale_javascript_prettier_standard_executable) + \ . ' --stdin' + \ . ' --stdin-filepath=%s ', + \ } diff --git a/test/fixers/test_remark_lint_fixer_callback.vader b/test/fixers/test_remark_lint_fixer_callback.vader new file mode 100644 index 00000000..5e2e342d --- /dev/null +++ b/test/fixers/test_remark_lint_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_markdown_remark_lint_executable + Save g:ale_markdown_remark_lint_options + +After: + Restore + +Execute(The remark callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('remark') + \ }, + \ ale#fixers#remark_lint#Fix(bufnr('')) + +Execute(The remark executable and options should be configurable): + let g:ale_markdown_remark_lint_executable = '/path/to/remark' + let g:ale_markdown_remark_lint_options = '-h' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/remark') + \ . ' -h', + \ }, + \ ale#fixers#remark_lint#Fix(bufnr('')) diff --git a/test/fixers/test_rubocop_fixer_callback.vader b/test/fixers/test_rubocop_fixer_callback.vader index 866326bf..84579d31 100644 --- a/test/fixers/test_rubocop_fixer_callback.vader +++ b/test/fixers/test_rubocop_fixer_callback.vader @@ -21,9 +21,9 @@ Execute(The rubocop callback should return the correct default values): AssertEqual \ { - \ 'read_temporary_file': 1, + \ 'process_with': 'ale#fixers#rubocop#PostProcess', \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) - \ . ' --auto-correct --force-exclusion %t', + \ . ' --auto-correct --force-exclusion --stdin %s', \ }, \ ale#fixers#rubocop#Fix(bufnr('')) @@ -32,10 +32,10 @@ Execute(The rubocop callback should include configuration files): AssertEqual \ { - \ 'read_temporary_file': 1, + \ 'process_with': 'ale#fixers#rubocop#PostProcess', \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.rubocop.yml')) - \ . ' --auto-correct --force-exclusion %t', + \ . ' --auto-correct --force-exclusion --stdin %s', \ }, \ ale#fixers#rubocop#Fix(bufnr('')) @@ -45,10 +45,62 @@ Execute(The rubocop callback should include custom rubocop options): AssertEqual \ { - \ 'read_temporary_file': 1, + \ 'process_with': 'ale#fixers#rubocop#PostProcess', \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.rubocop.yml')) \ . ' --except Lint/Debugger' - \ . ' --auto-correct --force-exclusion %t', + \ . ' --auto-correct --force-exclusion --stdin %s', \ }, \ ale#fixers#rubocop#Fix(bufnr('')) + +Execute(The rubocop callback should use auto-correct-all option when set): + let g:ale_ruby_rubocop_auto_correct_all = 1 + call ale#test#SetFilename('ruby_paths/with_config/dummy.rb') + + AssertEqual + \ { + \ 'process_with': 'ale#fixers#rubocop#PostProcess', + \ 'command': ale#Escape(g:ale_ruby_rubocop_executable) + \ . ' --config ' . ale#Escape(ale#path#Simplify(g:dir . '/ruby_paths/with_config/.rubocop.yml')) + \ . ' --auto-correct-all --force-exclusion --stdin %s' + \ }, + \ ale#fixers#rubocop#Fix(bufnr('')) + +Execute(The rubocop post-processor should remove diagnostics content): + AssertEqual + \ [ + \ 'class MyModel < ApplicationRecord', + \ ' # rubocop:disable Rails/InverseOf', + \ ' has_one :something', + \ ' # rubocop:enable Rails/InverseOf', + \ 'end', + \ '', + \ 'array = [1, 2, 3,', + \ ' 4, 5, 6]', + \ 'array = [''run'',', + \ ' ''forrest'',', + \ ' ''run'']', + \ ], + \ ale#fixers#rubocop#PostProcess(bufnr(''), [ + \ 'Inspecting 1 file', + \ 'C', + \ '', + \ 'Offenses:', + \ 'app/models/my_model.rb:8:3: C: [Corrected] Layout/ArrayAlignment: ', + \ '4, 5, 6]', + \ '^', + \ '', + \ '1 file inspected, 3 offenses detected, 3 offenses corrected', + \ '====================', + \ 'class MyModel < ApplicationRecord', + \ ' # rubocop:disable Rails/InverseOf', + \ ' has_one :something', + \ ' # rubocop:enable Rails/InverseOf', + \ 'end', + \ '', + \ 'array = [1, 2, 3,', + \ ' 4, 5, 6]', + \ 'array = [''run'',', + \ ' ''forrest'',', + \ ' ''run'']', + \ ]) diff --git a/test/fixers/test_standard_fixer_callback.vader b/test/fixers/test_standard_fixer_callback.vader index db9f20f6..f5e9c487 100644 --- a/test/fixers/test_standard_fixer_callback.vader +++ b/test/fixers/test_standard_fixer_callback.vader @@ -15,7 +15,7 @@ Execute(The executable path should be correct): \ 'read_temporary_file': 1, \ 'command': (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/standard/bin/cmd.js')) - \ . ' --fix %t', + \ . ' --fix --stdin < %s > %t', \ }, \ ale#fixers#standard#Fix(bufnr('')) @@ -26,6 +26,6 @@ Execute(Custom options should be supported): AssertEqual \ { \ 'read_temporary_file': 1, - \ 'command': ale#Escape('standard') . ' --foo-bar --fix %t', + \ 'command': ale#Escape('standard') . ' --foo-bar --fix --stdin < %s > %t', \ }, \ ale#fixers#standard#Fix(bufnr('')) diff --git a/test/fixers/test_stylelint_fixer_callback.vader b/test/fixers/test_stylelint_fixer_callback.vader index f677cdf7..8fbd8a51 100644 --- a/test/fixers/test_stylelint_fixer_callback.vader +++ b/test/fixers/test_stylelint_fixer_callback.vader @@ -1,4 +1,8 @@ Before: + Save g:ale_stylelint_options + + let g:ale_stylelint_options = '' + call ale#assert#SetUpFixerTest('css', 'stylelint') After: @@ -10,7 +14,7 @@ Execute(The stylelint callback should return the correct default values): AssertFixer \ { \ 'read_temporary_file': 1, - \ 'command': ale#path#CdString(expand('%:p:h')) + \ 'command': ale#path#BufferCdString(bufnr('')) \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js')) \ . ' %t' @@ -24,7 +28,7 @@ Execute(The stylelint callback should include custom stylelint options): AssertFixer \ { \ 'read_temporary_file': 1, - \ 'command': ale#path#CdString(expand('%:p:h')) + \ 'command': ale#path#BufferCdString(bufnr('')) \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js')) \ . ' %t' diff --git a/test/fixers/test_tslint_fixer_callback.vader b/test/fixers/test_tslint_fixer_callback.vader index aa9c0322..a88992fa 100644 --- a/test/fixers/test_tslint_fixer_callback.vader +++ b/test/fixers/test_tslint_fixer_callback.vader @@ -27,7 +27,7 @@ Execute(The tslint callback should return the correct default values): \ 'read_temporary_file': 1, \ 'command': ale#Escape('tslint') \ . ' -c ' . ale#Escape('tslint.json') - \ . ' --fix %t', + \ . ' --outputAbsolutePaths --fix %t', \ }, \ ale#fixers#tslint#Fix(bufnr('')) @@ -40,6 +40,6 @@ Execute(The tslint callback should include custom tslint config option): \ 'read_temporary_file': 1, \ 'command': ale#Escape('tslint') \ . ' -c ' . ale#Escape('.tslintrc') - \ . ' --fix %t', + \ . ' --outputAbsolutePaths --fix %t', \ }, \ ale#fixers#tslint#Fix(bufnr('')) diff --git a/test/handler/test_bashate_handler.vader b/test/handler/test_bashate_handler.vader new file mode 100644 index 00000000..b61bb956 --- /dev/null +++ b/test/handler/test_bashate_handler.vader @@ -0,0 +1,36 @@ +Before: + runtime ale_linters/sh/bashate.vim + +After: + call ale#linter#Reset() + +Execute(The bashate handler should handle basic errors): + AssertEqual + \ [ + \ { + \ 'lnum': 777, + \ 'col': 1, + \ 'text': 'E003 Indent not multiple of 4', + \ }, + \ { + \ 'lnum': 783, + \ 'col': 1, + \ 'text': 'E020 Function declaration not in format ^function name {$', + \ }, + \ { + \ 'lnum': 786, + \ 'col': 1, + \ 'text': 'E010 The "do" should be on same line as for', + \ }, + \ { + \ 'lnum': 791, + \ 'col': 1, + \ 'text': 'E006 Line too long', + \ }, + \ ], + \ ale_linters#sh#bashate#Handle(bufnr(''), [ + \ 'run:777:1: E003 Indent not multiple of 4', + \ 'run:783:1: E020 Function declaration not in format ^function name {$', + \ 'run:786:1: E010 The "do" should be on same line as for', + \ 'run:791:1: E006 Line too long', + \ ]) diff --git a/test/handler/test_cppcheck_handler.vader b/test/handler/test_cppcheck_handler.vader index f153b9b5..55a5d29b 100644 --- a/test/handler/test_cppcheck_handler.vader +++ b/test/handler/test_cppcheck_handler.vader @@ -10,19 +10,29 @@ Execute(Basic errors should be handled by cppcheck): AssertEqual \ [ \ { - \ 'lnum': 5, + \ 'lnum': 974, + \ 'col' : 6, \ 'type': 'E', - \ 'text': 'Array ''a[10]'' accessed at index 10, which is out of bounds', + \ 'sub_type': '', + \ 'text': 'Array ''n[3]'' accessed at index 3, which is out of bounds.', + \ 'code': 'arrayIndexOutOfBounds' \ }, \ { - \ 'lnum': 7, + \ 'lnum': 1185, + \ 'col' : 10, \ 'type': 'W', - \ 'text': 'Some other problem', + \ 'sub_type': 'style', + \ 'text': 'The scope of the variable ''indxStr'' can be reduced.', + \ 'code': 'variableScope' \ }, \ ], \ ale#handlers#cppcheck#HandleCppCheckFormat(bufnr(''), [ - \ '[test.cpp:5]: (error) Array ''a[10]'' accessed at index 10, which is out of bounds', - \ '[test.cpp:7]: (warning) Some other problem', + \ 'test.cpp:974:6: error: Array ''n[3]'' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\', + \ ' n[3]=3;', + \ ' ^', + \ 'test.cpp:1185:10: style: The scope of the variable ''indxStr'' can be reduced. [variableScope]\', + \ ' char indxStr[16];', + \ ' ^', \ ]) Execute(Problems from other files should be ignored by cppcheck): @@ -32,5 +42,7 @@ Execute(Problems from other files should be ignored by cppcheck): \ [ \ ], \ ale#handlers#cppcheck#HandleCppCheckFormat(bufnr(''), [ - \ '[bar.cpp:5]: (error) Array ''a[10]'' accessed at index 10, which is out of bounds', + \ 'bar.cpp:974:6: error: Array ''n[3]'' accessed at index 3, which is out of bounds. [arrayIndexOutOfBounds]\', + \ ' n[3]=3;', + \ ' ^', \ ]) diff --git a/test/handler/test_dockerfile_lint_handler.vader b/test/handler/test_dockerfile_lint_handler.vader index 619b7bde..a73db8cd 100644 --- a/test/handler/test_dockerfile_lint_handler.vader +++ b/test/handler/test_dockerfile_lint_handler.vader @@ -26,21 +26,25 @@ Execute(The dockerfile_lint handler should handle a normal example): \ 'lnum': -1, \ 'type': 'E', \ 'text': "Required LABEL name/key 'Name' is not defined", + \ 'detail': "Required LABEL name/key 'Name' is not defined\n\nhttp://docs.projectatomic.io/container-best-practices/#_recommended_labels_for_your_project", \ }, \ { \ 'lnum': -1, \ 'type': 'E', \ 'text': "Required LABEL name/key 'Version' is not defined", + \ 'detail': "Required LABEL name/key 'Version' is not defined\n\nhttp://docs.projectatomic.io/container-best-practices/#_recommended_labels_for_your_project", \ }, \ { \ 'lnum': 3, \ 'type': 'I', - \ 'text': "the MAINTAINER command is deprecated. MAINTAINER is deprecated in favor of using LABEL since Docker v1.13.0", + \ 'text': "the MAINTAINER command is deprecated", + \ 'detail': "the MAINTAINER command is deprecated\n\nMAINTAINER is deprecated in favor of using LABEL since Docker v1.13.0\n\nhttps://github.com/docker/cli/blob/master/docs/deprecated.md#maintainer-in-dockerfile", \ }, \ { \ 'lnum': -1, \ 'type': 'I', \ 'text': "There is no 'CMD' instruction", + \ 'detail': "There is no 'CMD' instruction\n\nhttps://docs.docker.com/engine/reference/builder/#cmd", \ }, \ ], \ ale_linters#dockerfile#dockerfile_lint#Handle(bufnr(''), [ diff --git a/test/handler/test_gcc_handler.vader b/test/handler/test_gcc_handler.vader index b67483a6..a4231cab 100644 --- a/test/handler/test_gcc_handler.vader +++ b/test/handler/test_gcc_handler.vader @@ -279,3 +279,38 @@ Execute(The GCC handler should handle errors for inlined header functions): \ ' __open_too_many_args ();', \ ' ^~~~~~~~~~~~~~~~~~~~~~~', \ ]) + +Execute(The GCC handler should handle macro expansion errors in current file): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 19, + \ 'type': 'E', + \ 'text': 'error message', + \ 'detail': "error message\n<stdin>:1:19: note: in expansion of macro 'TEST'", + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ '<command-line>: error: error message', + \ '<stdin>:1:19: note: in expansion of macro ‘TEST’', + \ ' 1 | std::string str = TEST;', + \ ' | ^~~~', + \ ]) + +Execute(The GCC handler should handle macro expansion errors in other files): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'type': 'E', + \ 'text': 'Error found in macro expansion. See :ALEDetail', + \ 'detail': "error message\ninc.h:1:19: note: in expansion of macro 'TEST'", + \ }, + \ ], + \ ale#handlers#gcc#HandleGCCFormatWithIncludes(347, [ + \ '<command-line>: error: error message', + \ 'inc.h:1:19: note: in expansion of macro ‘TEST’', + \ ' 1 | std::string str = TEST;', + \ ' | ^~~~', + \ ]) diff --git a/test/handler/test_glslang_handler.vader b/test/handler/test_glslang_handler.vader index d51c9852..6d3a7999 100644 --- a/test/handler/test_glslang_handler.vader +++ b/test/handler/test_glslang_handler.vader @@ -1,3 +1,6 @@ +Before: + runtime ale_linters/glsl/glslang.vim + Execute(The glsl glslang handler should parse lines correctly): AssertEqual \ [ diff --git a/test/handler/test_go_generic_handler.vader b/test/handler/test_go_generic_handler.vader index 624e56c1..2b17fdcb 100644 --- a/test/handler/test_go_generic_handler.vader +++ b/test/handler/test_go_generic_handler.vader @@ -15,8 +15,24 @@ Execute(The golang handler should return the correct filenames): \ 'type': 'E', \ 'filename': ale#path#Simplify(expand('%:p:h') . '/other.go'), \ }, + \ { + \ 'lnum': 18, + \ 'col': 0, + \ 'text': 'random error', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/go1.14.go'), + \ }, + \ { + \ 'lnum': 36, + \ 'col': 2, + \ 'text': 'another random error', + \ 'type': 'E', + \ 'filename': ale#path#Simplify(expand('%:p:h') . '/anothergo1.14.go'), + \ }, \ ], \ ale#handlers#go#Handler(bufnr(''), [ \ 'test.go:27: some error', \ 'other.go:27:5: some error with a column', + \ 'vet: go1.14.go:18:0: random error', + \ 'vet: anothergo1.14.go:36:2: another random error', \ ]) diff --git a/test/handler/test_markdownlint_handler.vader b/test/handler/test_markdownlint_handler.vader index db6acc66..f2e6e328 100644 --- a/test/handler/test_markdownlint_handler.vader +++ b/test/handler/test_markdownlint_handler.vader @@ -4,21 +4,88 @@ Before: After: call ale#linter#Reset() -Execute(The Markdownlint handler should parse output correctly): +Execute(The Markdownlint handler should parse pre v0.19.0 output with single digit line correctly): AssertEqual \ [ \ { \ 'lnum': 1, - \ 'text': '(MD002/first-header-h1) First header should be a top level header [Expected: h1; Actual: h2]', + \ 'code': 'MD013/line-length', + \ 'text': 'Line length [Expected: 80; Actual: 114]', \ 'type': 'W' - \ }, + \ } + \ ], + \ ale#handlers#markdownlint#Handle(0, [ + \ 'README.md: 1: MD013/line-length Line length [Expected: 80; Actual: 114]' + \ ]) + +Execute(The Markdownlint handler should parse pre v0.19.0 output with multi digit line correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 100, + \ 'code': 'MD013/line-length', + \ 'text': 'Line length [Expected: 80; Actual: 114]', + \ 'type': 'W' + \ } + \ ], + \ ale#handlers#markdownlint#Handle(0, [ + \ 'README.md: 100: MD013/line-length Line length [Expected: 80; Actual: 114]' + \ ]) + +Execute(The Markdownlint handler should parse post v0.19.0 output with single digit line correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'code': 'MD013/line-length', + \ 'text': 'Line length [Expected: 80; Actual: 114]', + \ 'type': 'W' + \ } + \ ], + \ ale#handlers#markdownlint#Handle(0, [ + \ 'README.md:1 MD013/line-length Line length [Expected: 80; Actual: 114]' + \ ]) + +Execute(The Markdownlint handler should parse post v0.19.0 output with multi digit line correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 100, + \ 'code': 'MD013/line-length', + \ 'text': 'Line length [Expected: 80; Actual: 114]', + \ 'type': 'W' + \ } + \ ], + \ ale#handlers#markdownlint#Handle(0, [ + \ 'README.md:100 MD013/line-length Line length [Expected: 80; Actual: 114]' + \ ]) + + +Execute(The Markdownlint handler should parse post v0.22.0 output with column correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 10, + \ 'col': 20, + \ 'code': 'MD013/line-length', + \ 'text': 'Line length [Expected: 80; Actual: 114]', + \ 'type': 'W' + \ } + \ ], + \ ale#handlers#markdownlint#Handle(0, [ + \ 'README.md:10:20 MD013/line-length Line length [Expected: 80; Actual: 114]' + \ ]) + +Execute(The Markdownlint handler should parse output with multiple slashes in rule name correctly): + AssertEqual + \ [ \ { - \ 'lnum': 298, - \ 'text': '(MD033/no-inline-html) Inline HTML [Element: p]', + \ 'lnum': 10, + \ 'code': 'MD022/blanks-around-headings/blanks-around-headers', + \ 'text': 'Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Above] [Context: "### something"]', \ 'type': 'W' \ } \ ], \ ale#handlers#markdownlint#Handle(0, [ - \ 'README.md: 1: MD002/first-header-h1 First header should be a top level header [Expected: h1; Actual: h2]', - \ 'README.md: 298: MD033/no-inline-html Inline HTML [Element: p]' + \ 'README.md:10 MD022/blanks-around-headings/blanks-around-headers Headings should be surrounded by blank lines [Expected: 1; Actual: 0; Above] [Context: "### something"]' \ ]) diff --git a/test/handler/test_puppet_handler.vader b/test/handler/test_puppet_handler.vader index e73c9dc7..03adc9f0 100644 --- a/test/handler/test_puppet_handler.vader +++ b/test/handler/test_puppet_handler.vader @@ -49,3 +49,29 @@ Execute(The puppet handler should parse lines and column correctly): \ "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)", \ ]) + +Execute(The puppet handler should correctly parse errors that are reported before even trying to parse for an environment): + " Line Error + AssertEqual + \ [ + \ { + \ 'lnum': 5, + \ 'col': 11, + \ 'text': "Illegal attempt to assign to 'a Name'. Not an assignable reference" + \ }, + \ ], + \ ale_linters#puppet#puppet#Handle(255, [ + \ "Error: Illegal attempt to assign to 'a Name'. Not an assignable reference (file: /tmp/modules/waffles/manifests/syrup.pp, line: 5, column: 11)", + \ ]) +Execute(The puppet handler should parse lines when end of input is the location): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'col': 0, + \ 'text': "Syntax error at end of input" + \ }, + \ ], + \ ale_linters#puppet#puppet#Handle(255, [ + \ "Error: Could not parse for environment production: Syntax error at end of input (file: /tmp//modules/test/manifests/init.pp)", + \ ]) diff --git a/test/handler/test_shellcheck_handler.vader b/test/handler/test_shellcheck_handler.vader index bfb73ffa..33f12989 100644 --- a/test/handler/test_shellcheck_handler.vader +++ b/test/handler/test_shellcheck_handler.vader @@ -22,7 +22,7 @@ Execute(The shellcheck handler should handle basic errors or warnings): \ 'code': 'SC1068', \ }, \ ], - \ ale_linters#sh#shellcheck#Handle(bufnr(''), [ + \ ale#handlers#shellcheck#Handle(bufnr(''), [ \ '-:2:1: warning: In POSIX sh, ''let'' is not supported. [SC2039]', \ '-:2:3: error: Don''t put spaces around the = in assignments. [SC1068]', \ ]) @@ -38,6 +38,6 @@ Execute(The shellcheck handler should handle notes): \ 'code': 'SC2086', \ }, \ ], - \ ale_linters#sh#shellcheck#Handle(bufnr(''), [ + \ ale#handlers#shellcheck#Handle(bufnr(''), [ \ '-:3:3: note: Double quote to prevent globbing and word splitting. [SC2086]', \ ]) diff --git a/test/handler/test_sqllint_handler.vader b/test/handler/test_sqllint_handler.vader new file mode 100644 index 00000000..2f2283c8 --- /dev/null +++ b/test/handler/test_sqllint_handler.vader @@ -0,0 +1,23 @@ +Before: + " Load the file which defines the linter. + runtime ale_linters/sql/sqllint.vim + +After: + " Unload all linters again. + call ale#linter#Reset() + +Execute (The output should be correct): + + " Test that the right loclist items are parsed from the handler. + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 0, + \ 'type': '', + \ 'text': 'stdin:1 [ER_NO_DB_ERROR] No database selected' + \ }, + \ ], + \ ale_linters#sql#sqllint#Handle(bufnr(''), [ + \ 'stdin:1 [ER_NO_DB_ERROR] No database selected' + \ ]) diff --git a/test/handler/test_standard_handler.vader b/test/handler/test_standard_handler.vader index 59ebe531..31e3a36b 100644 --- a/test/handler/test_standard_handler.vader +++ b/test/handler/test_standard_handler.vader @@ -1,3 +1,11 @@ +Before: + Save g:ale_javascript_eslint_suppress_eslintignore + + let g:ale_javascript_eslint_suppress_eslintignore = 0 + +After: + Restore + Execute(The standard handler should parse lines correctly): AssertEqual \ [ diff --git a/test/handler/test_swiftformat_handler.vader b/test/handler/test_swiftformat_handler.vader new file mode 100644 index 00000000..3dcc4f1a --- /dev/null +++ b/test/handler/test_swiftformat_handler.vader @@ -0,0 +1,28 @@ +Before: + runtime ale_linters/swift/swiftformat.vim + +After: + call ale#linter#Reset() + +Execute(The swiftformat handler should parse lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 4, + \ 'col': 21, + \ 'type': 'W', + \ 'code': 'DoNotUseSemicolons', + \ 'text': 'remove '';'' and move the next statement to the new line', + \ }, + \ { + \ 'lnum': 3, + \ 'col': 12, + \ 'type': 'W', + \ 'code': 'Spacing', + \ 'text': 'remove 1 space' + \ }, + \ ], + \ ale_linters#swift#swiftformat#Handle(bufnr(''), [ + \ 'Sources/main.swift:4:21: warning: [DoNotUseSemicolons]: remove '';'' and move the next statement to the new line', + \ 'Sources/main.swift:3:12: warning: [Spacing]: remove 1 space', + \ ]) diff --git a/test/handler/test_verilator_handler.vader b/test/handler/test_verilator_handler.vader new file mode 100644 index 00000000..5e51b5c9 --- /dev/null +++ b/test/handler/test_verilator_handler.vader @@ -0,0 +1,48 @@ +Before: + runtime ale_linters/verilog/verilator.vim + +After: + call ale#linter#Reset() + + +Execute (The verilator handler should parse legacy messages with only line numbers): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected IDENTIFIER' + \ }, + \ { + \ 'lnum': 10, + \ 'type': 'W', + \ 'text': 'Blocking assignments (=) in sequential (flop or latch) block; suggest delayed assignments (<=).' + \ }, + \ ], + \ ale_linters#verilog#verilator#Handle(bufnr(''), [ + \ '%Error: foo_verilator_linted.v:3: syntax error, unexpected IDENTIFIER', + \ '%Warning-BLKSEQ: bar_verilator_linted.v:10: Blocking assignments (=) in sequential (flop or latch) block; suggest delayed assignments (<=).', + \ ]) + + +Execute (The verilator handler should parse new format messages with line and column numbers): + AssertEqual + \ [ + \ { + \ 'lnum': 3, + \ 'col' : 1, + \ 'type': 'E', + \ 'text': 'syntax error, unexpected endmodule, expecting ;' + \ }, + \ { + \ 'lnum': 4, + \ 'col' : 6, + \ 'type': 'W', + \ 'text': 'Signal is not used: r' + \ }, + \ ], + \ ale_linters#verilog#verilator#Handle(bufnr(''), [ + \ '%Error: bar_verilator_linted.v:3:1: syntax error, unexpected endmodule, expecting ;', + \ '%Warning-UNUSED: foo_verilator_linted.v:4:6: Signal is not used: r', + \ ]) + diff --git a/test/handler/test_vlog_handler.vader b/test/handler/test_vlog_handler.vader index daf3cdcf..7262f63d 100644 --- a/test/handler/test_vlog_handler.vader +++ b/test/handler/test_vlog_handler.vader @@ -10,12 +10,14 @@ Execute(The vlog handler should parse old-style lines correctly): \ { \ 'lnum': 7, \ 'type': 'W', - \ 'text': '(vlog-2623) Undefined variable: C.' + \ 'text': '(vlog-2623) Undefined variable: C.', + \ 'filename': 'add.v' \ }, \ { \ 'lnum': 1, \ 'type': 'E', - \ 'text': '(vlog-13294) Identifier must be declared with a port mode: C.' + \ 'text': '(vlog-13294) Identifier must be declared with a port mode: C.', + \ 'filename': 'file.v' \ }, \ ], \ ale_linters#verilog#vlog#Handle(bufnr(''), [ @@ -29,12 +31,14 @@ Execute(The vlog handler should parse new-style lines correctly): \ { \ 'lnum': 7, \ 'type': 'W', - \ 'text': '(vlog-2623) Undefined variable: C.' + \ 'text': '(vlog-2623) Undefined variable: C.', + \ 'filename': 'add.v' \ }, \ { \ 'lnum': 1, \ 'type': 'E', - \ 'text': '(vlog-13294) Identifier must be declared with a port mode: C.' + \ 'text': '(vlog-13294) Identifier must be declared with a port mode: C.', + \ 'filename': 'file.v' \ }, \ ], \ ale_linters#verilog#vlog#Handle(bufnr(''), [ diff --git a/test/lsp/test_did_save_event.vader b/test/lsp/test_did_save_event.vader index bdea6d98..1d811363 100644 --- a/test/lsp/test_did_save_event.vader +++ b/test/lsp/test_did_save_event.vader @@ -33,8 +33,8 @@ Before: \ 'lsp': 'stdio', \ 'command': 'cat - > /dev/null', \ 'executable': has('win32') ? 'cmd' : 'echo', - \ 'language_callback': 'LanguageCallback', - \ 'project_root_callback': 'ProjectRootCallback', + \ 'language': function('LanguageCallback'), + \ 'project_root': function('ProjectRootCallback'), \ }) let g:ale_linters = {'foobar': ['dummy_linter']} diff --git a/test/lsp/test_engine_lsp_response_handling.vader b/test/lsp/test_engine_lsp_response_handling.vader index 9abfa087..8261f1da 100644 --- a/test/lsp/test_engine_lsp_response_handling.vader +++ b/test/lsp/test_engine_lsp_response_handling.vader @@ -44,9 +44,21 @@ After: Given foobar(An empty file): Execute(tsserver syntax error responses should be handled correctly): runtime ale_linters/typescript/tsserver.vim - call ale#test#SetFilename('filename.ts') + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.ts') + endif + call ale#engine#InitBufferInfo(bufnr('')) + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t') + endif + " When we get syntax errors and no semantic errors, we should keep the " syntax errors. call ale#lsp_linter#HandleLSPResponse(1, { @@ -54,7 +66,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ 'type': 'event', \ 'event': 'syntaxDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ { \ 'start': { @@ -76,7 +88,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ 'type': 'event', \ 'event': 'semanticDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ ], \ }, @@ -104,7 +116,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ 'type': 'event', \ 'event': 'syntaxDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ ], \ }, @@ -146,9 +158,21 @@ Execute(tsserver syntax error responses should be handled correctly): Execute(tsserver semantic error responses should be handled correctly): runtime ale_linters/typescript/tsserver.vim - call ale#test#SetFilename('filename.ts') + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.ts') + endif + call ale#engine#InitBufferInfo(bufnr('')) + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t') + endif + " When we get syntax errors and no semantic errors, we should keep the " syntax errors. call ale#lsp_linter#HandleLSPResponse(1, { @@ -156,7 +180,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ 'type': 'event', \ 'event': 'syntaxDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ ], \ }, @@ -166,7 +190,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ 'type': 'event', \ 'event': 'semanticDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ { \ 'start': { @@ -206,7 +230,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ 'type': 'event', \ 'event': 'semanticDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ ], \ }, @@ -270,15 +294,27 @@ Execute(tsserver errors should mark tsserver no longer active): Execute(LSP diagnostics responses should be handled correctly): let b:ale_linters = ['eclipselsp'] runtime ale_linters/java/eclipselsp.vim - call ale#test#SetFilename('filename.java') + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.java') + endif + call ale#engine#InitBufferInfo(bufnr('')) call ale#lsp_linter#SetLSPLinterMap({'1': 'eclipselsp'}) + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t') + endif + call ale#lsp_linter#HandleLSPResponse(1, { \ 'jsonrpc':'2.0', \ 'method':'textDocument/publishDiagnostics', \ 'params': { - \ 'uri':'file://' . g:dir . '/filename.java', + \ 'uri': ale#path#ToURI(expand('%:p')), \ 'diagnostics': [ \ { \ 'range': { diff --git a/test/lsp/test_lsp_command_formatting.vader b/test/lsp/test_lsp_command_formatting.vader index ec3b4120..e99e1dad 100644 --- a/test/lsp/test_lsp_command_formatting.vader +++ b/test/lsp/test_lsp_command_formatting.vader @@ -1,6 +1,10 @@ Before: + Save g:ale_command_wrapper + runtime autoload/ale/lsp.vim + let g:ale_command_wrapper = '' + let g:args = [] " Mock the StartProgram function so we can just capture the arguments. @@ -9,6 +13,8 @@ Before: endfunction After: + Restore + unlet! g:args runtime autoload/ale/lsp.vim @@ -18,8 +24,8 @@ Execute(Command formatting should be applied correctly for LSP linters): \ bufnr(''), \ { \ 'name': 'linter', - \ 'language_callback': {-> 'x'}, - \ 'project_root_callback': {-> '/foo/bar'}, + \ 'language': {-> 'x'}, + \ 'project_root': {-> '/foo/bar'}, \ 'lsp': 'stdio', \ 'executable': has('win32') ? 'cmd': 'true', \ 'command': '%e --foo', diff --git a/test/lsp/test_lsp_custom_request.vader b/test/lsp/test_lsp_custom_request.vader index 04f044af..c8767e59 100644 --- a/test/lsp/test_lsp_custom_request.vader +++ b/test/lsp/test_lsp_custom_request.vader @@ -25,7 +25,6 @@ Before: \ 'name': g:linter_name, \ 'project_root': {b -> g:project_root}, \ 'aliases': [], - \ 'language_callback': {b -> 'cpp'}, \ 'read_buffer': 1, \ 'command': '%e' \ }] diff --git a/test/lsp/test_lsp_startup.vader b/test/lsp/test_lsp_startup.vader index c29690bf..cd9b59dd 100644 --- a/test/lsp/test_lsp_startup.vader +++ b/test/lsp/test_lsp_startup.vader @@ -422,3 +422,13 @@ Execute(Deferred addresses should be handled correctly): Assert Start() call ale#test#FlushJobs() call AssertInitSuccess('foo', 'localhost:1234', 'foobar', '/foo/bar', '') + +Execute(Servers that have crashed should be restarted): + call ale#lsp#Register('foo', '/foo/bar', {}) + call extend(ale#lsp#GetConnections()['foo:/foo/bar'], {'initialized': 1}) + + " Starting the program again should reset initialized to `0`. + call ale#lsp#StartProgram('foo:/foo/bar', 'foobar', 'foobar --start') + + AssertEqual 0, ale#lsp#GetConnections()['foo:/foo/bar']['initialized'] + AssertEqual ['initialize'], map(PopMessages(), 'v:val[''method'']') diff --git a/test/lsp/test_other_initialize_message_handling.vader b/test/lsp/test_other_initialize_message_handling.vader index 6473e283..b6ef852a 100644 --- a/test/lsp/test_other_initialize_message_handling.vader +++ b/test/lsp/test_other_initialize_message_handling.vader @@ -131,7 +131,7 @@ Execute(Disabled capabilities should be recognised correctly): \ }, \ 'definitionProvider': v:false, \ 'experimental': {}, - \ 'documentHighlightProvider': v:true + \ 'documentHighlightProvider': v:true, \ }, \ }, \}) @@ -150,6 +150,57 @@ Execute(Disabled capabilities should be recognised correctly): \ b:conn.capabilities AssertEqual [[1, 'initialized', {}]], g:message_list +Execute(Capabilities should be enabled when send as Dictionaries): + call ale#lsp#HandleInitResponse(b:conn, { + \ 'jsonrpc': '2.0', + \ 'id': 1, + \ 'result': { + \ 'capabilities': { + \ 'renameProvider': {}, + \ 'executeCommandProvider': { + \ 'commands': [], + \ }, + \ 'hoverProvider': {}, + \ 'documentSymbolProvider': v:true, + \ 'documentRangeFormattingProvider': v:true, + \ 'codeLensProvider': { + \ 'resolveProvider': v:false + \ }, + \ 'completionProvider': { + \ 'triggerCharacters': ['.'], + \ 'resolveProvider': v:false + \ }, + \ 'referencesProvider': {}, + \ 'textDocumentSync': 2, + \ 'documentFormattingProvider': v:true, + \ 'codeActionProvider': v:true, + \ 'signatureHelpProvider': { + \ 'triggerCharacters': ['(', ','], + \ }, + \ 'definitionProvider': {}, + \ 'typeDefinitionProvider': {}, + \ 'experimental': {}, + \ 'documentHighlightProvider': v:true, + \ 'workspaceSymbolProvider': {} + \ }, + \ }, + \}) + + AssertEqual 1, b:conn.initialized + AssertEqual + \ { + \ 'completion_trigger_characters': ['.'], + \ 'completion': 1, + \ 'references': 1, + \ 'hover': 1, + \ 'definition': 1, + \ 'typeDefinition': 1, + \ 'symbol_search': 1, + \ 'rename': 1, + \ }, + \ b:conn.capabilities + AssertEqual [[1, 'initialized', {}]], g:message_list + Execute(Results that are not dictionaries should be handled correctly): call ale#lsp#HandleInitResponse(b:conn, { \ 'jsonrpc': '2.0', diff --git a/test/prettier-test-files/with_prettierignore/.prettierignore b/test/prettier-test-files/with_prettierignore/.prettierignore new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/prettier-test-files/with_prettierignore/.prettierignore diff --git a/test/prettier-test-files/with_prettierignore/src/testfile.js b/test/prettier-test-files/with_prettierignore/src/testfile.js new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/prettier-test-files/with_prettierignore/src/testfile.js diff --git a/test/sign/test_linting_sets_signs.vader b/test/sign/test_linting_sets_signs.vader index 60ae0a83..1624449a 100644 --- a/test/sign/test_linting_sets_signs.vader +++ b/test/sign/test_linting_sets_signs.vader @@ -10,7 +10,9 @@ Before: Save g:ale_set_loclist Save g:ale_set_quickfix Save g:ale_set_signs + Save g:ale_command_wrapper + let g:ale_command_wrapper = '' let g:ale_buffer_info = {} let g:ale_run_synchronously = 1 unlet! g:ale_run_synchronously_callbacks @@ -32,7 +34,7 @@ Before: function! CollectSigns() redir => l:output - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') silent exec 'sign place group=ale' else silent exec 'sign place' diff --git a/test/sign/test_sign_parsing.vader b/test/sign/test_sign_parsing.vader index 157ff2f4..c0967f43 100644 --- a/test/sign/test_sign_parsing.vader +++ b/test/sign/test_sign_parsing.vader @@ -1,5 +1,5 @@ Execute (Parsing English signs should work): - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') AssertEqual \ [0, [[9, 1000001, 'ALEWarningSign']]], \ ale#sign#ParseSigns([ @@ -16,7 +16,7 @@ Execute (Parsing English signs should work): endif Execute (Parsing Russian signs should work): - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') AssertEqual \ [0, [[1, 1000001, 'ALEErrorSign']]], \ ale#sign#ParseSigns([' строка=1 id=1000001 группа=ale имя=ALEErrorSign']) @@ -27,7 +27,7 @@ Execute (Parsing Russian signs should work): endif Execute (Parsing Japanese signs should work): - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') AssertEqual \ [0, [[1, 1000001, 'ALEWarningSign']]], \ ale#sign#ParseSigns([' 行=1 識別子=1000001 グループ=ale 名前=ALEWarningSign']) @@ -38,7 +38,7 @@ Execute (Parsing Japanese signs should work): endif Execute (Parsing Spanish signs should work): - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') AssertEqual \ [0, [[12, 1000001, 'ALEWarningSign']]], \ ale#sign#ParseSigns([' línea=12 id=1000001 grupo=ale nombre=ALEWarningSign']) @@ -49,7 +49,7 @@ Execute (Parsing Spanish signs should work): endif Execute (Parsing Italian signs should work): - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') AssertEqual \ [0, [[1, 1000001, 'ALEWarningSign']]], \ ale#sign#ParseSigns([' riga=1 id=1000001, gruppo=ale nome=ALEWarningSign']) @@ -60,7 +60,7 @@ Execute (Parsing Italian signs should work): endif Execute (Parsing German signs should work): - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') AssertEqual \ [0, [[235, 1000001, 'ALEErrorSign']]], \ ale#sign#ParseSigns([' Zeile=235 id=1000001 Gruppe=ale Name=ALEErrorSign']) @@ -71,7 +71,7 @@ Execute (Parsing German signs should work): endif Execute (The sign parser should indicate if the dummy sign is set): - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') AssertEqual \ [1, [[1, 1000001, 'ALEErrorSign']]], \ ale#sign#ParseSigns([ diff --git a/test/sign/test_sign_placement.vader b/test/sign/test_sign_placement.vader index 80153b22..7b80d83c 100644 --- a/test/sign/test_sign_placement.vader +++ b/test/sign/test_sign_placement.vader @@ -6,7 +6,9 @@ Before: Save g:ale_set_loclist Save g:ale_set_quickfix Save g:ale_set_signs + Save g:ale_command_wrapper + let g:ale_command_wrapper = '' let g:ale_buffer_info = {} let g:ale_run_synchronously = 1 let g:ale_set_signs = 1 @@ -68,7 +70,7 @@ Before: function! ParseSigns() redir => l:output - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') silent sign place group=ale else silent sign place @@ -152,7 +154,7 @@ Execute(The current signs should be set for running a job): \ ParseSigns() Execute(Loclist items with sign_id values should be kept): - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') exec 'sign place 1000347 group=ale line=3 name=ALEErrorSign buffer=' . bufnr('') exec 'sign place 1000348 group=ale line=15 name=ALEErrorSign buffer=' . bufnr('') exec 'sign place 1000349 group=ale line=16 name=ALEWarningSign buffer=' . bufnr('') @@ -297,7 +299,7 @@ Execute(No exceptions should be thrown when setting signs for invalid buffers): Execute(Signs should be removed when lines have multiple sign IDs on them): " We can fail to remove signs if there are multiple signs on one line, " say after deleting lines in Vim, etc. - if has('nvim-0.4.2') || (v:version >= 801 && has('patch614')) + if has('nvim-0.4.2') || has('patch-8.1.614') exec 'sign place 1000347 group=ale line=3 name=ALEErrorSign buffer=' . bufnr('') exec 'sign place 1000348 group=ale line=3 name=ALEWarningSign buffer=' . bufnr('') exec 'sign place 1000349 group=ale line=10 name=ALEErrorSign buffer=' . bufnr('') diff --git a/test/smoke_test.vader b/test/smoke_test.vader index 53e08a8d..0b126cc6 100644 --- a/test/smoke_test.vader +++ b/test/smoke_test.vader @@ -1,8 +1,10 @@ Before: + Save g:ale_enabled Save g:ale_set_lists_synchronously Save g:ale_buffer_info Save &shell + let g:ale_enabled = 1 let g:ale_buffer_info = {} let g:ale_set_lists_synchronously = 1 diff --git a/test/terraform_files/main.tf b/test/terraform_files/main.tf new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/terraform_files/main.tf diff --git a/test/test_ale_has.vader b/test/test_ale_has.vader index b8a99103..633c7186 100644 --- a/test/test_ale_has.vader +++ b/test/test_ale_has.vader @@ -1,4 +1,5 @@ Execute(Checks for versions below the current version should succeed): + AssertEqual 1, ale#Has('ale-2.7.0') AssertEqual 1, ale#Has('ale-2.6.0') AssertEqual 1, ale#Has('ale-2.5.0') AssertEqual 1, ale#Has('ale-2.4.0') diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index 2bc8c635..895ed2a7 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -6,6 +6,7 @@ Before: Save g:ale_completion_delay Save g:ale_completion_enabled Save g:ale_completion_max_suggestions + Save g:ale_disable_lsp Save g:ale_echo_cursor Save g:ale_echo_msg_error_str Save g:ale_echo_msg_format @@ -24,6 +25,7 @@ Before: Save g:ale_lint_on_text_changed Save g:ale_linters Save g:ale_linters_explicit + Save g:ale_linters_ignore Save g:ale_list_vertical Save g:ale_list_window_size Save g:ale_loclist_msg_format @@ -64,6 +66,7 @@ Before: let g:ale_completion_delay = 100 let g:ale_completion_enabled = 0 let g:ale_completion_max_suggestions = 50 + let g:ale_disable_lsp = 0 let g:ale_echo_cursor = 1 let g:ale_echo_msg_error_str = 'Error' let g:ale_echo_msg_format = '%code: %%s' @@ -80,6 +83,7 @@ Before: let g:ale_lint_on_save = 1 let g:ale_lint_on_text_changed = 'normal' let g:ale_linters_explicit = 0 + let g:ale_linters_ignore = {'python': ['pyright']} let g:ale_list_vertical = 0 let g:ale_list_window_size = 10 let g:ale_loclist_msg_format = '%code: %%s' @@ -138,6 +142,7 @@ Before: \ 'let g:ale_completion_delay = 100', \ 'let g:ale_completion_enabled = 0', \ 'let g:ale_completion_max_suggestions = 50', + \ 'let g:ale_disable_lsp = 0', \ 'let g:ale_echo_cursor = 1', \ 'let g:ale_echo_msg_error_str = ''Error''', \ 'let g:ale_echo_msg_format = ''%code: %%s''', @@ -158,6 +163,7 @@ Before: \ 'let g:ale_linter_aliases = {}', \ 'let g:ale_linters = {}', \ 'let g:ale_linters_explicit = 0', + \ 'let g:ale_linters_ignore = {''python'': [''pyright'']}', \ 'let g:ale_list_vertical = 0', \ 'let g:ale_list_window_size = 10', \ 'let g:ale_loclist_msg_format = ''%code: %%s''', @@ -243,6 +249,7 @@ Execute (ALEInfo with no linters should return the right output): \ ' Current Filetype: nolintersft', \ 'Available Linters: []', \ ' Enabled Linters: []', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -265,6 +272,7 @@ Execute (ALEInfo should return buffer-local global ALE settings): \ ' Current Filetype: ', \ 'Available Linters: []', \ ' Enabled Linters: []', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -279,6 +287,7 @@ Execute (ALEInfo with no filetype should return the right output): \ ' Current Filetype: ', \ 'Available Linters: []', \ ' Enabled Linters: []', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -295,6 +304,7 @@ Execute (ALEInfo with a single linter should return the right output): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -312,6 +322,7 @@ Execute (ALEInfo with two linters should return the right output): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -333,6 +344,7 @@ Execute (ALEInfo should calculate enabled linters correctly): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -350,6 +362,7 @@ Execute (ALEInfo should only return linters for current filetype): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -367,6 +380,7 @@ Execute (ALEInfo with compound filetypes should return linters for both of them) \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -389,6 +403,7 @@ Execute (ALEInfo should return appropriately named global variables): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + [ @@ -420,6 +435,7 @@ Execute (ALEInfoToFile should write to a file correctly): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + [ @@ -447,6 +463,7 @@ Execute (ALEInfo should buffer-local linter variables): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + [ @@ -478,6 +495,7 @@ Execute (ALEInfo should output linter aliases): \ '''testlinter1'' -> [''testftalias1'', ''testftalias2'']', \ '''testlinter2'' -> [''testftalias3'', ''testftalias4'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + [ @@ -505,6 +523,7 @@ Execute (ALEInfo should return command history): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -532,6 +551,7 @@ Execute (ALEInfo command history should print exit codes correctly): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -580,6 +600,7 @@ Execute (ALEInfo command history should print command output if logging is on): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -618,6 +639,7 @@ Execute (ALEInfo should include executable checks in the history): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -647,6 +669,7 @@ Execute (The option for caching failing executable checks should work): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -669,6 +692,7 @@ Execute (LSP errors for a linter should be outputted): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -693,6 +717,7 @@ Execute (LSP errors for other linters shouldn't appear): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -715,6 +740,7 @@ Execute (ALEInfo should include linter global options): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -740,6 +766,7 @@ Execute (ALEInfo should include linter global options for enabled linters): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines diff --git a/test/test_ale_lint_stop_command.vader b/test/test_ale_lint_stop_command.vader new file mode 100644 index 00000000..c50db5a8 --- /dev/null +++ b/test/test_ale_lint_stop_command.vader @@ -0,0 +1,27 @@ +Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = {} + + call ale#linter#PreventLoading('testft') + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': {-> []}, + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'sleep 9001', + \}) + +After: + Restore + + call ale#linter#Reset() + +Given testft (An empty file): +Execute(ALELintStop should stop ALE from linting): + ALELint + + Assert ale#engine#IsCheckingBuffer(bufnr('')), 'ALE did not start checking the buffer' + + ALELintStop + + Assert !ale#engine#IsCheckingBuffer(bufnr('')), 'ALELintStop didn''t work' diff --git a/test/test_autocmd_commands.vader b/test/test_autocmd_commands.vader index 355b4c77..2f0a893f 100644 --- a/test/test_autocmd_commands.vader +++ b/test/test_autocmd_commands.vader @@ -49,6 +49,7 @@ Before: Save g:ale_lint_on_save Save g:ale_lint_on_text_changed Save g:ale_pattern_options_enabled + Save g:ale_hover_cursor " Turn everything on by defaul for these tests. let g:ale_completion_enabled = 1 @@ -61,6 +62,7 @@ Before: let g:ale_lint_on_save = 1 let g:ale_lint_on_text_changed = 1 let g:ale_pattern_options_enabled = 1 + let g:ale_hover_cursor = 1 After: delfunction CheckAutocmd @@ -84,20 +86,42 @@ Execute (All events should be set up when everything is on): \ 'BufWinEnter * call ale#events#LintOnEnter(str2nr(expand(''<abuf>'')))', \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand(''<abuf>'')))', \ 'CursorHold * if exists(''*ale#engine#Cleanup'') | call ale#cursor#EchoCursorWarningWithDelay() | endif', + \ 'CursorHold if exists(''*ale#lsp#Send'') | call ale#hover#ShowTruncatedMessageAtCursor() | endif', \ 'CursorMoved * if exists(''*ale#engine#Cleanup'') | call ale#cursor#EchoCursorWarningWithDelay() | endif', \ 'FileChangedShellPost * call ale#events#FileChangedEvent(str2nr(expand(''<abuf>'')))', \ 'FileType * call ale#events#FileTypeEvent( str2nr(expand(''<abuf>'')), expand(''<amatch>''))', \ 'InsertLeave * if ale#Var(str2nr(expand(''<abuf>'')), ''lint_on_insert_leave'') | call ale#Queue(0) | endif', \ 'InsertLeave if exists(''*ale#engine#Cleanup'') | call ale#cursor#EchoCursorWarning() | endif', - \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', - \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \ 'TextChanged * call ale#Queue(ale#Var(str2nr(expand(''<abuf>'')), ''lint_delay''))', + \ 'TextChangedI * call ale#Queue(ale#Var(str2nr(expand(''<abuf>'')), ''lint_delay''))', \ ], \ CheckAutocmd('ALEEvents') Execute (Only the required events should be bound even if various settings are off): + let g:ale_enabled = 1 + let g:ale_completion_enabled = 0 + let g:ale_echo_cursor = 0 + let g:ale_fix_on_save = 0 + let g:ale_lint_on_enter = 0 + let g:ale_lint_on_filetype_changed = 0 + let g:ale_lint_on_insert_leave = 0 + let g:ale_lint_on_save = 0 + let g:ale_lint_on_text_changed = 0 + let g:ale_pattern_options_enabled = 0 + let g:ale_hover_cursor = 0 + + AssertEqual + \ [ + \ 'BufEnter * call ale#events#ReadOrEnterEvent(str2nr(expand(''<abuf>'')))', + \ 'BufReadPost * call ale#events#ReadOrEnterEvent(str2nr(expand(''<abuf>'')))', + \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand(''<abuf>'')))', + \ ], + \ CheckAutocmd('ALEEvents') + +Execute (The cursor hoever event should be enabled with g:ale_hover_cursor = 1): + let g:ale_enabled = 1 let g:ale_completion_enabled = 0 let g:ale_echo_cursor = 0 - let g:ale_enabled = 0 let g:ale_fix_on_save = 0 let g:ale_lint_on_enter = 0 let g:ale_lint_on_filetype_changed = 0 @@ -105,12 +129,14 @@ Execute (Only the required events should be bound even if various settings are o let g:ale_lint_on_save = 0 let g:ale_lint_on_text_changed = 0 let g:ale_pattern_options_enabled = 0 + let g:ale_hover_cursor = 1 AssertEqual \ [ \ 'BufEnter * call ale#events#ReadOrEnterEvent(str2nr(expand(''<abuf>'')))', \ 'BufReadPost * call ale#events#ReadOrEnterEvent(str2nr(expand(''<abuf>'')))', \ 'BufWritePost * call ale#events#SaveEvent(str2nr(expand(''<abuf>'')))', + \ 'CursorHold * if exists(''*ale#lsp#Send'') | call ale#hover#ShowTruncatedMessageAtCursor() | endif', \ ], \ CheckAutocmd('ALEEvents') @@ -119,8 +145,8 @@ Execute (g:ale_lint_on_text_changed = 1 bind both events): AssertEqual \ [ - \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', - \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \ 'TextChanged * call ale#Queue(ale#Var(str2nr(expand(''<abuf>'')), ''lint_delay''))', + \ 'TextChangedI * call ale#Queue(ale#Var(str2nr(expand(''<abuf>'')), ''lint_delay''))', \ ], \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') @@ -129,8 +155,8 @@ Execute (g:ale_lint_on_text_changed = 'always' should bind both events): AssertEqual \ [ - \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', - \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \ 'TextChanged * call ale#Queue(ale#Var(str2nr(expand(''<abuf>'')), ''lint_delay''))', + \ 'TextChangedI * call ale#Queue(ale#Var(str2nr(expand(''<abuf>'')), ''lint_delay''))', \ ], \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') @@ -139,7 +165,7 @@ Execute (g:ale_lint_on_text_changed = 'normal' should bind only TextChanged): AssertEqual \ [ - \ 'TextChanged * call ale#Queue(g:ale_lint_delay)', + \ 'TextChanged * call ale#Queue(ale#Var(str2nr(expand(''<abuf>'')), ''lint_delay''))', \ ], \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') @@ -148,7 +174,7 @@ Execute (g:ale_lint_on_text_changed = 'insert' should bind only TextChangedI): AssertEqual \ [ - \ 'TextChangedI * call ale#Queue(g:ale_lint_delay)', + \ 'TextChangedI * call ale#Queue(ale#Var(str2nr(expand(''<abuf>'')), ''lint_delay''))', \ ], \ filter(CheckAutocmd('ALEEvents'), 'v:val =~ ''^TextChanged''') diff --git a/test/test_c_flag_parsing.vader b/test/test_c_flag_parsing.vader index 8ae6f9dc..99722b17 100644 --- a/test/test_c_flag_parsing.vader +++ b/test/test_c_flag_parsing.vader @@ -1,15 +1,43 @@ Before: Save g:ale_c_parse_makefile + Save g:ale_c_always_make + Save b:ale_c_always_make call ale#test#SetDirectory('/testplugin/test') let g:ale_c_parse_makefile = 1 + let g:ale_c_always_make = 1 + let b:ale_c_always_make = 1 + + function SplitAndParse(path_prefix, command) abort + let l:args = ale#c#ShellSplit(a:command) + + return ale#c#ParseCFlags(a:path_prefix, 0, l:args) + endfunction After: + delfunction SplitAndParse + Restore call ale#test#RestoreDirectory() +Execute(The make command should be correct): + call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') + + AssertEqual + \ ale#path#CdString(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project')) + \ . 'make -n --always-make', + \ ale#c#GetMakeCommand(bufnr('')) + + " You should be able to disable --always-make for a buffer. + let b:ale_c_always_make = 0 + + AssertEqual + \ ale#path#CdString(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project')) + \ . 'make -n', + \ ale#c#GetMakeCommand(bufnr('')) + Execute(The CFlags parser should be able to parse include directives): call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') @@ -18,7 +46,7 @@ Execute(The CFlags parser should be able to parse include directives): \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -c file.c']) AssertEqual - \ '-isystem ' . '/usr/include/dir', + \ '-isystem ' . ale#Escape('/usr/include/dir'), \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -isystem /usr/include/dir -c file.c']) Execute(ParseCFlags should ignore -c and -o): @@ -57,48 +85,21 @@ Execute(ParseCFlags should be able to parse flags with relative paths): \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')) \ . ' -DTEST=`date +%s`', - \ ale#c#ParseCFlags( + \ SplitAndParse( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), \ 'gcc -Isubdir ' \ . '-I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) -Execute(ParseCFlags should be able to parse -Dgoal): - AssertEqual - \ '-Dgoal=9' - \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) - \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')) - \ . ' -DTEST=`date +%s`', - \ ale#c#ParseCFlags( - \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Isubdir ' - \ . '-I'. ale#path#Simplify('kernel/include') - \ . ' -DTEST=`date +%s` -c file.c' - \ ) - -Execute(ParseCFlags should ignore -T and other arguments): - AssertEqual - \ '-Dgoal=9' - \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) - \ . ' ' . '--sysroot=subdir' - \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')) - \ . ' -DTEST=`date +%s`', - \ ale#c#ParseCFlags( - \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir --sysroot=subdir ' - \ . '-I'. ale#path#Simplify('kernel/include') - \ . ' -DTEST=`date +%s` -c file.c' - \ ) - -Execute(ParseCFlags should handle paths with spaces in double quotes): +Execute(We should handle paths with spaces in double quotes): AssertEqual \ '-Dgoal=9' \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir with spaces')) \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')) \ . ' -DTEST=`date +%s`', - \ ale#c#ParseCFlags( + \ SplitAndParse( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' \ . '-I"dir with spaces"' . ' -I'. ale#path#Simplify('kernel/include') @@ -112,7 +113,7 @@ Execute(ParseCFlags should handle paths with spaces in single quotes): \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir with spaces')) \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')) \ . ' -DTEST=`date +%s`', - \ ale#c#ParseCFlags( + \ SplitAndParse( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' \ . '-I''dir with spaces''' . ' -I'. ale#path#Simplify('kernel/include') @@ -127,7 +128,7 @@ Execute(ParseCFlags should handle paths with minuses): \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir-with-dash')) \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')) \ . ' -DTEST=`date +%s`', - \ ale#c#ParseCFlags( + \ SplitAndParse( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' \ . '-I''dir with spaces''' . ' -Idir-with-dash' @@ -135,7 +136,7 @@ Execute(ParseCFlags should handle paths with minuses): \ . ' -DTEST=`date +%s` -c file.c' \ ) -Execute(ParseCFlags should handle -D with minuses): +Execute(We should handle -D with minuses): AssertEqual \ '-Dgoal=9' \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) @@ -144,7 +145,7 @@ Execute(ParseCFlags should handle -D with minuses): \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir-with-dash')) \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')) \ . ' -DTEST=`date +%s`', - \ ale#c#ParseCFlags( + \ SplitAndParse( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' \ . '-Dmacro-with-dash ' @@ -153,7 +154,7 @@ Execute(ParseCFlags should handle -D with minuses): \ . ' -DTEST=`date +%s` -c file.c' \ ) -Execute(ParseCFlags should handle flags at the end of the line): +Execute(We should handle flags at the end of the line): AssertEqual \ '-Dgoal=9' \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) @@ -161,7 +162,7 @@ Execute(ParseCFlags should handle flags at the end of the line): \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir with spaces')) \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir-with-dash')) \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), - \ ale#c#ParseCFlags( + \ SplitAndParse( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' \ . '-Dmacro-with-dash ' @@ -178,50 +179,170 @@ Execute(ParseCompileCommandsFlags should tolerate empty values): Execute(ParseCompileCommandsFlags should parse some basic flags): silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + " We should read the absolute path filename entry, not the other ones. AssertEqual - \ '-I ' . ale#path#Simplify('/usr/include/xmms2'), - \ ale#c#ParseCompileCommandsFlags(bufnr(''), { "xmms2-mpris.c": [ + \ '-I ' . ale#Escape(ale#path#Simplify('/usr/include/xmms2')), + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), \ { - \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), - \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/xmms2') - \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' - \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), - \ 'file': ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'): [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/xmms2') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ }, + \ ], + \ "xmms2-mpris.c": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ }, + \ ], \ }, - \ ] }, {}) + \ { + \ ale#path#Simplify('/foo/bar/xmms2-mpris/src'): [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris/src'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': 'other.c', + \ }, + \ ], + \ "src": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c'), + \ }, + \ ], + \ }, + \ ) -Execute(ParseCompileCommandsFlags should tolerate items without commands): +Execute(ParseCompileCommandsFlags should fall back to files with the same name): silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + " We should prefer the basename file flags, not the base dirname flags. AssertEqual - \ '', - \ ale#c#ParseCompileCommandsFlags(bufnr(''), { "xmms2-mpris.c": [ + \ '-I ' . ale#Escape(ale#path#Simplify('/usr/include/xmms2')), + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), + \ { + \ "xmms2-mpris.c": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/xmms2') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ }, + \ ], + \ }, + \ { + \ "src": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c'), + \ }, + \ ], + \ }, + \ ) + +Execute(ParseCompileCommandsFlags should parse flags for exact directory matches): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + + " We should ues the exact directory flags, not the file basename flags. + AssertEqual + \ '-I ' . ale#Escape(ale#path#Simplify('/usr/include/xmms2')), + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), \ { - \ 'directory': '/foo/bar/xmms2-mpris', - \ 'file': '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ "xmms2-mpris.c": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ }, + \ ], \ }, - \ ] }, {}) + \ { + \ ale#path#Simplify('/foo/bar/xmms2-mpris/src'): [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris/src'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/xmms2') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': 'other.c', + \ }, + \ ], + \ "src": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/ignoreme') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c'), + \ }, + \ ], + \ }, + \ ) Execute(ParseCompileCommandsFlags should fall back to files in the same directory): silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) AssertEqual - \ '-I ' . ale#path#Simplify('/usr/include/xmms2'), - \ ale#c#ParseCompileCommandsFlags(bufnr(''), {}, { "src": [ + \ '-I ' . ale#Escape(ale#path#Simplify('/usr/include/xmms2')), + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), + \ {}, + \ { + \ "src": [ + \ { + \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), + \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/xmms2') + \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c'), + \ }, + \ ], + \ }, + \ ) + +Execute(ParseCompileCommandsFlags should tolerate items without commands): + silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) + + AssertEqual + \ '', + \ ale#c#ParseCompileCommandsFlags( + \ bufnr(''), \ { - \ 'directory': ale#path#Simplify('/foo/bar/xmms2-mpris'), - \ 'command': '/usr/bin/cc -I' . ale#path#Simplify('/usr/include/xmms2') - \ . ' -o CMakeFiles/xmms2-mpris.dir/src/xmms2-mpris.c.o' - \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), - \ 'file': ale#path#Simplify((has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c'), + \ "xmms2-mpris.c": [ + \ { + \ 'directory': '/foo/bar/xmms2-mpris', + \ 'file': '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ }, + \ ], \ }, - \ ] }) + \ {}, + \ ) Execute(ParseCompileCommandsFlags should take commands from matching .c files for .h files): silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.h')) AssertEqual - \ '-I /usr/include/xmms2', + \ '-I ' . ale#Escape('/usr/include/xmms2'), \ ale#c#ParseCompileCommandsFlags( \ bufnr(''), \ { @@ -235,15 +356,14 @@ Execute(ParseCompileCommandsFlags should take commands from matching .c files fo \ }, \ ], \ }, - \ { - \ }, + \ {}, \ ) Execute(ParseCompileCommandsFlags should take commands from matching .cpp files for .hpp files): silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.hpp')) AssertEqual - \ '-I /usr/include/xmms2', + \ '-I ' . ale#Escape('/usr/include/xmms2'), \ ale#c#ParseCompileCommandsFlags( \ bufnr(''), \ { @@ -265,7 +385,7 @@ Execute(ParseCompileCommandsFlags should take commands from matching .cpp files silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.h')) AssertEqual - \ '-I /usr/include/xmms2', + \ '-I ' . ale#Escape('/usr/include/xmms2'), \ ale#c#ParseCompileCommandsFlags( \ bufnr(''), \ { @@ -305,42 +425,69 @@ Execute(ParseCompileCommandsFlags should not take commands from .c files for .h \ }, \ ) -Execute(ParseCFlags should not merge flags): +Execute(ShellSplit should not merge flags): AssertEqual - \ '-Dgoal=9' - \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) - \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir with spaces')) - \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir-with-dash')) - \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include')), - \ ale#c#ParseCFlags( - \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ [ + \ 'gcc', + \ '-Dgoal=9', + \ '-Tlinkerfile.ld', + \ 'blabla', + \ '-Isubdir', + \ 'subdir/somedep1.o', + \ 'subdir/somedep2.o', + \ '-I''dir with spaces''', + \ '-Idir-with-dash', + \ 'subdir/somedep3.o', + \ 'subdir/somedep4.o', + \ '-I' . ale#path#Simplify('kernel/include'), + \ 'subdir/somedep5.o', + \ 'subdir/somedep6.o', + \ ], + \ ale#c#ShellSplit( \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' \ . 'subdir/somedep1.o ' . 'subdir/somedep2.o ' \ . '-I''dir with spaces''' . ' -Idir-with-dash ' \ . 'subdir/somedep3.o ' . 'subdir/somedep4.o ' \ . ' -I'. ale#path#Simplify('kernel/include') . ' ' - \ . 'subdir/somedep5.o ' . 'subdir/somedep6.o ' + \ . 'subdir/somedep5.o ' . 'subdir/somedep6.o' \ ) -Execute(ParseCFlags should handle parenthesis and quotes): +Execute(ShellSplit should handle parenthesis and quotes): AssertEqual - \ '-Dgoal=9 -Dtest1="('' '')" -Dtest2=''(` `)'' -Dtest3=`(" ")`', - \ ale#c#ParseCFlags( - \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ [ + \ 'gcc', + \ '-Dgoal=9', + \ '-Tlinkerfile.ld', + \ 'blabla', + \ '-Dtest1="('' '')"', + \ 'file1.o', + \ '-Dtest2=''(` `)''', + \ 'file2.o', + \ '-Dtest3=`(" ")`', + \ 'file3.o', + \ ] , + \ ale#c#ShellSplit( \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla ' \ . '-Dtest1="('' '')" file1.o ' \ . '-Dtest2=''(` `)'' file2.o ' - \ . '-Dtest3=`(" ")` file3.o ' + \ . '-Dtest3=`(" ")` file3.o' \ ) -Execute(CFlags we want to pass): +Execute(We should include several important flags): AssertEqual - \ '-I ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/inc')) - \ . ' -I ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/include')) - \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/incquote')) - \ . ' -isystem ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/incsystem')) + \ '-I ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/inc')) + \ . ' -I ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) + \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/incquote')) + \ . ' -isystem ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/incsystem')) \ . ' -idirafter ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/incafter')) - \ . ' -Dmacro=value -D macro2 -Bbdir -B bdir2' + \ . ' -iframework ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/incframework')) + \ . ' -include ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/foo bar')) + \ . ' -Dmacro="value"' + \ . ' -DGoal=9' + \ . ' -D macro2' + \ . ' -D macro3="value"' + \ . ' -Bbdir' + \ . ' -B bdir2' \ . ' -iprefix prefix -iwithprefix prefix2 -iwithprefixbefore prefix3' \ . ' -isysroot sysroot --sysroot=test --no-sysroot-suffix -imultilib multidir' \ . ' -Wsome-warning -std=c89 -pedantic -pedantic-errors -ansi' @@ -348,21 +495,175 @@ Execute(CFlags we want to pass): \ . ' -iplugindir=dir -march=native -w', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc' - \ . ' -Iinc -I include -iquote incquote -isystem incsystem -idirafter incafter' - \ . ' -Dmacro=value -D macro2 -Bbdir -B bdir2' + \ 0, + \ [ + \ 'gcc', + \ '-Iinc', + \ '-I', + \ 'include', + \ '-iquote', + \ 'incquote', + \ '-isystem', + \ 'incsystem', + \ '-idirafter', + \ 'incafter', + \ '-iframework', + \ 'incframework', + \ '-include', + \ '''foo bar''', + \ '-Dmacro="value"', + \ '-DGoal=9', + \ '-D', + \ 'macro2', + \ '-D', + \ 'macro3="value"', + \ '-Bbdir', + \ '-B', + \ 'bdir2', + \ '-iprefix', + \ 'prefix', + \ '-iwithprefix', + \ 'prefix2', + \ '-iwithprefixbefore', + \ 'prefix3', + \ '-isysroot', + \ 'sysroot', + \ '--sysroot=test', + \ '--no-sysroot-suffix', + \ '-imultilib', + \ 'multidir', + \ '-Wsome-warning', + \ '-std=c89', + \ '-pedantic', + \ '-pedantic-errors', + \ '-ansi', + \ '-foption', + \ '-O2', + \ '-C', + \ '-CC', + \ '-trigraphs', + \ '-nostdinc', + \ '-nostdinc++', + \ '-iplugindir=dir', + \ '-march=native', + \ '-w', + \ ], + \ ) + +Execute(We should quote the flags we need to quote): + AssertEqual + \ '-I ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/inc')) + \ . ' -I ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/include')) + \ . ' -iquote ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/incquote')) + \ . ' -isystem ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/incsystem')) + \ . ' -idirafter ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/incafter')) + \ . ' -iframework ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/incframework')) + \ . ' -include ' . ale#Escape(ale#path#Simplify(g:dir . '/test_c_projects/makefile_project/foo bar')) + \ . ' ' . ale#Escape('-Dmacro="value"') + \ . ' -DGoal=9' + \ . ' -D macro2' + \ . ' -D ' . ale#Escape('macro3="value"') + \ . ' -Bbdir' + \ . ' -B bdir2' \ . ' -iprefix prefix -iwithprefix prefix2 -iwithprefixbefore prefix3' - \ . ' -isysroot sysroot --sysroot=test --no-sysroot-suffix -imultilib multidir' + \ . ' -isysroot sysroot --sysroot=test' + \ . ' ' . ale#Escape('--sysroot="quoted"') + \ . ' ' . ale#Escape('--sysroot=foo bar') + \ . ' --no-sysroot-suffix -imultilib multidir' \ . ' -Wsome-warning -std=c89 -pedantic -pedantic-errors -ansi' \ . ' -foption -O2 -C -CC -trigraphs -nostdinc -nostdinc++' - \ . ' -iplugindir=dir -march=native -w' + \ . ' -iplugindir=dir -march=native -w', + \ ale#c#ParseCFlags( + \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ 1, + \ [ + \ 'gcc', + \ '-Iinc', + \ '-I', + \ 'include', + \ '-iquote', + \ 'incquote', + \ '-isystem', + \ 'incsystem', + \ '-idirafter', + \ 'incafter', + \ '-iframework', + \ 'incframework', + \ '-include', + \ '''foo bar''', + \ '-Dmacro="value"', + \ '-DGoal=9', + \ '-D', + \ 'macro2', + \ '-D', + \ 'macro3="value"', + \ '-Bbdir', + \ '-B', + \ 'bdir2', + \ '-iprefix', + \ 'prefix', + \ '-iwithprefix', + \ 'prefix2', + \ '-iwithprefixbefore', + \ 'prefix3', + \ '-isysroot', + \ 'sysroot', + \ '--sysroot=test', + \ '--sysroot="quoted"', + \ '--sysroot=foo bar', + \ '--no-sysroot-suffix', + \ '-imultilib', + \ 'multidir', + \ '-Wsome-warning', + \ '-std=c89', + \ '-pedantic', + \ '-pedantic-errors', + \ '-ansi', + \ '-foption', + \ '-O2', + \ '-C', + \ '-CC', + \ '-trigraphs', + \ '-nostdinc', + \ '-nostdinc++', + \ '-iplugindir=dir', + \ '-march=native', + \ '-w', + \ ], \ ) -Execute(CFlags we dont want to pass): +Execute(We should exclude other flags that cause problems): AssertEqual \ '', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Wl,option -Wa,option -Wp,option filename.c somelib.a ' - \ . '-fdump-file=name -fdiagnostics-arg -fno-show-column' + \ 0, + \ [ + \ 'gcc', + \ '-Wl,option', + \ '-Wa,option', + \ '-Wp,option', + \ '-c', + \ 'filename.c', + \ 'somelib.a', + \ '-fdump-file=name', + \ '-fdiagnostics-arg', + \ '-fno-show-column', + \ '-fstack-usage', + \ '-Tlinkerfile.ld', + \ ], + \ ) + +Execute(We should expand @file in CFlags): + AssertEqual + \ '-DARGS1 -DARGS2 -O2', + \ ale#c#ParseCFlags( + \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), + \ 0, + \ [ + \ 'gcc', + \ '-g', + \ '@./args', + \ '-O2', + \ ], \ ) diff --git a/test/test_c_projects/makefile_project/_astylerc b/test/test_c_projects/makefile_project/_astylerc new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/test_c_projects/makefile_project/_astylerc diff --git a/test/test_c_projects/makefile_project/args b/test/test_c_projects/makefile_project/args new file mode 100644 index 00000000..ccaf82ad --- /dev/null +++ b/test/test_c_projects/makefile_project/args @@ -0,0 +1,3 @@ +foolib.a +-DARGS1 +@subdir/args diff --git a/test/test_c_projects/makefile_project/subdir/args b/test/test_c_projects/makefile_project/subdir/args new file mode 100644 index 00000000..3fe9c3fe --- /dev/null +++ b/test/test_c_projects/makefile_project/subdir/args @@ -0,0 +1 @@ +-DARGS2 diff --git a/test/test_code_action.vader b/test/test_code_action.vader index ffaca630..19de7268 100644 --- a/test/test_code_action.vader +++ b/test/test_code_action.vader @@ -1,4 +1,8 @@ Before: + Save g:ale_enabled + + let g:ale_enabled = 0 + runtime autoload/ale/code_action.vim runtime autoload/ale/util.vim @@ -35,12 +39,14 @@ Before: endfunction! After: + Restore + " Close the extra buffers if we opened it. if bufnr(g:file1) != -1 - execute ':bp | :bd ' . bufnr(g:file1) + execute ':bp! | :bd! ' . bufnr(g:file1) endif if bufnr(g:file2) != -1 - execute ':bp | :bd ' . bufnr(g:file2) + execute ':bp! | :bd! ' . bufnr(g:file2) endif if filereadable(g:file1) @@ -50,9 +56,10 @@ After: call delete(g:file2) endif - unlet g:file1 - unlet g:file2 - unlet g:test + unlet! g:file1 + unlet! g:file2 + unlet! g:test + unlet! g:changes delfunction WriteFileAndEdit runtime autoload/ale/code_action.vim @@ -116,7 +123,7 @@ Execute(It should modify and save multiple files): \ 'newText': "import {A, B} from 'module'\n\n", \ }] \ }], - \}) + \}, v:true) AssertEqual [ \ 'class Value {', @@ -161,7 +168,7 @@ Execute(Beginning of file can be modified): \ 'newText': "type A: string\ntype B: number\n", \ }], \ }] - \}) + \}, v:true) AssertEqual [ \ 'type A: string', @@ -192,7 +199,7 @@ Execute(End of file can be modified): \ 'newText': "type A: string\ntype B: number\n", \ }], \ }] - \}) + \}, v:true) AssertEqual g:test.text + [ \ 'type A: string', @@ -227,7 +234,7 @@ Execute(Current buffer contents will be reloaded): \ 'newText': "type A: string\ntype B: number\n", \ }], \ }] - \}) + \}, v:true) AssertEqual [ \ 'type A: string', @@ -249,11 +256,11 @@ Execute(Cursor will not move when it is before text change): let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2') call setpos('.', [0, 1, 1, 0]) - call ale#code_action#HandleCodeAction(g:test.changes) + call ale#code_action#HandleCodeAction(g:test.changes, v:true) AssertEqual [1, 1], getpos('.')[1:2] call setpos('.', [0, 2, 2, 0]) - call ale#code_action#HandleCodeAction(g:test.changes) + call ale#code_action#HandleCodeAction(g:test.changes, v:true) AssertEqual [2, 2], getpos('.')[1:2] # ====C==== @@ -264,7 +271,7 @@ Execute(Cursor column will move to the change end when cursor between start/end) call WriteFileAndEdit() call setpos('.', [0, 2, r, 0]) AssertEqual ' value: string', getline('.') - call ale#code_action#HandleCodeAction(g:test.changes) + call ale#code_action#HandleCodeAction(g:test.changes, v:true) AssertEqual ' value2: string', getline('.') AssertEqual [2, 9], getpos('.')[1:2] endfor @@ -275,7 +282,8 @@ Execute(Cursor column will move back when new text is shorter): call WriteFileAndEdit() call setpos('.', [0, 2, 8, 0]) AssertEqual ' value: string', getline('.') - call ale#code_action#HandleCodeAction(g:test.create_change(2, 3, 2, 8, 'val')) + call ale#code_action#HandleCodeAction( + \ g:test.create_change(2, 3, 2, 8, 'val'), v:true) AssertEqual ' val: string', getline('.') AssertEqual [2, 6], getpos('.')[1:2] @@ -286,7 +294,8 @@ Execute(Cursor column will move forward when new text is longer): call setpos('.', [0, 2, 8, 0]) AssertEqual ' value: string', getline('.') - call ale#code_action#HandleCodeAction(g:test.create_change(2, 3, 2, 8, 'longValue')) + call ale#code_action#HandleCodeAction( + \ g:test.create_change(2, 3, 2, 8, 'longValue'), v:true) AssertEqual ' longValue: string', getline('.') AssertEqual [2, 12], getpos('.')[1:2] @@ -297,7 +306,8 @@ Execute(Cursor line will move when updates are happening on lines above): call WriteFileAndEdit() call setpos('.', [0, 3, 1, 0]) AssertEqual '}', getline('.') - call ale#code_action#HandleCodeAction(g:test.create_change(1, 1, 2, 1, "test\ntest\n")) + call ale#code_action#HandleCodeAction( + \ g:test.create_change(1, 1, 2, 1, "test\ntest\n"), v:true) AssertEqual '}', getline('.') AssertEqual [4, 1], getpos('.')[1:2] @@ -308,7 +318,8 @@ Execute(Cursor line and column will move when change on lines above and just bef call WriteFileAndEdit() call setpos('.', [0, 2, 2, 0]) AssertEqual ' value: string', getline('.') - call ale#code_action#HandleCodeAction(g:test.create_change(1, 1, 2, 1, "test\ntest\n123")) + call ale#code_action#HandleCodeAction( + \ g:test.create_change(1, 1, 2, 1, "test\ntest\n123"), v:true) AssertEqual '123 value: string', getline('.') AssertEqual [3, 5], getpos('.')[1:2] @@ -319,7 +330,8 @@ Execute(Cursor line and column will move at the end of changes): call WriteFileAndEdit() call setpos('.', [0, 2, 10, 0]) AssertEqual ' value: string', getline('.') - call ale#code_action#HandleCodeAction(g:test.create_change(1, 1, 3, 1, "test\n")) + call ale#code_action#HandleCodeAction( + \ g:test.create_change(1, 1, 3, 1, "test\n"), v:true) AssertEqual '}', getline('.') AssertEqual [2, 1], getpos('.')[1:2] @@ -329,6 +341,52 @@ Execute(Cursor will not move when changes happening on lines >= cursor, but afte call WriteFileAndEdit() call setpos('.', [0, 2, 3, 0]) AssertEqual ' value: string', getline('.') - call ale#code_action#HandleCodeAction(g:test.create_change(2, 10, 3, 1, "number\n")) + call ale#code_action#HandleCodeAction( + \ g:test.create_change(2, 10, 3, 1, "number\n"), v:true) AssertEqual ' value: number', getline('.') AssertEqual [2, 3], getpos('.')[1:2] + +Execute(It should just modify file when should_save is set to v:false): + call WriteFileAndEdit() + let g:test.change = g:test.create_change(1, 1, 1, 1, "import { writeFile } from 'fs';\n") + call ale#code_action#HandleCodeAction(g:test.change, v:false) + AssertEqual 1, getbufvar(bufnr(''), '&modified') + AssertEqual [ + \ 'import { writeFile } from ''fs'';', + \ 'class Name {', + \ ' value: string', + \ '}', + \], getline(1, '$') + +Given typescript(An example TypeScript file): + type Foo = {} + + export interface ISomething { + fooLongName: Foo | null + } + + export class SomethingElse implements ISomething { + // Bindings + fooLongName!: ISomething['fooLongName'] + } + +Execute(): + let g:changes = [ + \ {'end': {'offset': 14, 'line': 4}, 'newText': 'foo', 'start': {'offset': 3, 'line': 4}}, + \ {'end': {'offset': 40, 'line': 9}, 'newText': 'foo', 'start': {'offset': 29, 'line': 9}}, + \ {'end': {'offset': 14, 'line': 9}, 'newText': 'foo', 'start': {'offset': 3, 'line': 9}}, + \] + + call ale#code_action#ApplyChanges(expand('%:p'), g:changes, 0) + +Expect(The changes should be applied correctly): + type Foo = {} + + export interface ISomething { + foo: Foo | null + } + + export class SomethingElse implements ISomething { + // Bindings + foo!: ISomething['foo'] + } diff --git a/test/test_command_chain.vader b/test/test_command_chain.vader deleted file mode 100644 index 329ebc97..00000000 --- a/test/test_command_chain.vader +++ /dev/null @@ -1,73 +0,0 @@ -Before: - Save &shell, g:ale_run_synchronously - let g:ale_run_synchronously = 1 - unlet! g:ale_run_synchronously_callbacks - - if !has('win32') - set shell=/bin/sh - endif - - let g:linter_output = [] - let g:first_echo_called = 0 - let g:second_echo_called = 0 - let g:final_callback_called = 0 - - function! CollectResults(buffer, output) - let g:final_callback_called = 1 - let g:linter_output = map(copy(a:output), 'join(split(v:val))') - return [] - endfunction - function! RunFirstEcho(buffer) - let g:first_echo_called = 1 - - return 'echo foo' - endfunction - function! RunSecondEcho(buffer, output) - let g:second_echo_called = 1 - - return 'echo bar' - endfunction - - call ale#linter#Define('foobar', { - \ 'name': 'testlinter', - \ 'callback': 'CollectResults', - \ 'executable': has('win32') ? 'cmd' : 'echo', - \ 'command_chain': [ - \ { - \ 'callback': 'RunFirstEcho', - \ 'output_stream': 'stdout', - \ 'read_buffer': 0, - \ }, - \ { - \ 'callback': 'RunSecondEcho', - \ 'output_stream': 'stdout', - \ 'read_buffer': 0, - \ }, - \ ], - \}) - -After: - Restore - unlet! g:ale_run_synchronously_callbacks - unlet! g:first_echo_called - unlet! g:second_echo_called - unlet! g:final_callback_called - unlet! g:linter_output - let g:ale_buffer_info = {} - call ale#linter#Reset() - delfunction CollectResults - delfunction RunFirstEcho - delfunction RunSecondEcho - -Given foobar (Some imaginary filetype): - anything - -Execute(Check the results of running the chain): - AssertEqual 'foobar', &filetype - call ale#Queue(0) - call ale#test#FlushJobs() - - Assert g:first_echo_called, 'The first chain item was not called' - Assert g:second_echo_called, 'The second chain item was not called' - Assert g:final_callback_called, 'The final callback was not called' - AssertEqual ['bar'], g:linter_output diff --git a/test/test_computed_lint_file_values.vader b/test/test_computed_lint_file_values.vader new file mode 100644 index 00000000..399e96fe --- /dev/null +++ b/test/test_computed_lint_file_values.vader @@ -0,0 +1,134 @@ +Before: + Save g:ale_enabled + Save g:ale_run_synchronously + Save g:ale_set_lists_synchronously + Save g:ale_buffer_info + + let g:ale_enabled = 1 + let g:ale_buffer_info = {} + let g:ale_run_synchronously = 1 + let g:ale_set_lists_synchronously = 1 + + function! TestCallback(buffer, output) + " Windows adds extra spaces to the text from echo. + return [{ + \ 'lnum': 2, + \ 'col': 3, + \ 'text': 'testlinter1', + \}] + endfunction + function! TestCallback2(buffer, output) + " Windows adds extra spaces to the text from echo. + return [{ + \ 'lnum': 1, + \ 'col': 3, + \ 'text': 'testlinter2', + \}] + endfunction + function! TestCallback3(buffer, output) + " Windows adds extra spaces to the text from echo. + return [{ + \ 'lnum': 3, + \ 'col': 3, + \ 'text': 'testlinter3', + \}] + endfunction + + " These two linters computer their lint_file values after running commands. + call ale#linter#Define('foobar', { + \ 'name': 'testlinter1', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \ 'lint_file': {b -> ale#command#Run(b, 'echo', {-> 1})}, + \}) + call ale#linter#Define('foobar', { + \ 'name': 'testlinter2', + \ 'callback': 'TestCallback2', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \ 'lint_file': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 1})})}, + \}) + " This one directly computes the result. + call ale#linter#Define('foobar', { + \ 'name': 'testlinter3', + \ 'callback': 'TestCallback3', + \ 'executable': has('win32') ? 'cmd' : 'echo', + \ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''', + \ 'lint_file': {b -> 1}, + \}) + + let g:filename = tempname() + call writefile([], g:filename) + call ale#test#SetFilename(g:filename) + +After: + delfunction TestCallback + + call ale#engine#Cleanup(bufnr('')) + Restore + call ale#linter#Reset() + + " Items and markers, etc. + call setloclist(0, []) + call clearmatches() + call ale#sign#Clear() + + if filereadable(g:filename) + call delete(g:filename) + endif + + unlet g:filename + +Given foobar(A file with some lines): + foo + bar + baz + +Execute(lint_file results where the result is eventually computed should be run): + call ale#Queue(0, 'lint_file') + call ale#test#FlushJobs() + + AssertEqual + \ [ + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 1, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'testlinter2', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }, + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 2, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'testlinter1', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }, + \ { + \ 'bufnr': bufnr('%'), + \ 'lnum': 3, + \ 'vcol': 0, + \ 'col': 3, + \ 'text': 'testlinter3', + \ 'type': 'E', + \ 'nr': -1, + \ 'pattern': '', + \ 'valid': 1, + \ }, + \ ], + \ ale#test#GetLoclistWithoutModule() + +Execute(Linters where lint_file eventually evaluates to 1 shouldn't be run if we don't want to run them): + call ale#Queue(0, '') + call ale#test#FlushJobs() + + AssertEqual [], ale#test#GetLoclistWithoutModule() diff --git a/test/test_cpp_project/.astylerc b/test/test_cpp_project/.astylerc new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/test_cpp_project/.astylerc diff --git a/test/test_cpp_project/dummy.cpp b/test/test_cpp_project/dummy.cpp new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/test_cpp_project/dummy.cpp diff --git a/test/test_cursor_warnings.vader b/test/test_cursor_warnings.vader index 2a6156f0..ef385061 100644 --- a/test/test_cursor_warnings.vader +++ b/test/test_cursor_warnings.vader @@ -15,7 +15,7 @@ Before: \ 'col': 10, \ 'bufnr': bufnr('%'), \ 'vcol': 0, - \ 'linter_name': 'eslint', + \ 'linter_name': 'bettercode', \ 'nr': -1, \ 'type': 'W', \ 'code': 'semi', @@ -26,11 +26,11 @@ Before: \ 'col': 10, \ 'bufnr': bufnr('%'), \ 'vcol': 0, - \ 'linter_name': 'eslint', + \ 'linter_name': 'bettercode', \ 'nr': -1, \ 'type': 'E', \ 'code': 'semi', - \ 'text': 'Missing semicolon.', + \ 'text': "Missing semicolon.\r", \ 'detail': "Every statement should end with a semicolon\nsecond line", \ }, \ { @@ -38,7 +38,7 @@ Before: \ 'col': 14, \ 'bufnr': bufnr('%'), \ 'vcol': 0, - \ 'linter_name': 'eslint', + \ 'linter_name': 'bettercode', \ 'nr': -1, \ 'type': 'I', \ 'text': 'Some information', @@ -48,7 +48,7 @@ Before: \ 'col': 10, \ 'bufnr': bufnr('%'), \ 'vcol': 0, - \ 'linter_name': 'eslint', + \ 'linter_name': 'bettercode', \ 'nr': -1, \ 'type': 'W', \ 'code': 'space-infix-ops', @@ -59,7 +59,7 @@ Before: \ 'col': 15, \ 'bufnr': bufnr('%'), \ 'vcol': 0, - \ 'linter_name': 'eslint', + \ 'linter_name': 'bettercode', \ 'nr': -1, \ 'type': 'E', \ 'code': 'radix', @@ -70,7 +70,7 @@ Before: \ 'col': 1, \ 'bufnr': bufnr('%'), \ 'vcol': 0, - \ 'linter_name': 'eslint', + \ 'linter_name': 'bettercode', \ 'nr': -1, \ 'type': 'E', \ 'text': 'lowercase error', @@ -196,7 +196,7 @@ Execute(The linter name should be formatted into the message correctly): call ale#cursor#EchoCursorWarning() AssertEqual - \ 'eslint: Infix operators must be spaced.', + \ 'bettercode: Infix operators must be spaced.', \ GetLastMessage() Execute(The severity should be formatted into the message correctly): diff --git a/test/test_deferred_command_string.vader b/test/test_deferred_command_string.vader index 026be6fe..173b6bb2 100644 --- a/test/test_deferred_command_string.vader +++ b/test/test_deferred_command_string.vader @@ -12,7 +12,7 @@ Before: call ale#linter#Define('foobar', { \ 'name': 'lint_file_linter', \ 'callback': 'LintFileCallback', - \ 'executable': 'echo', + \ 'executable': has('win32') ? 'cmd' : 'echo', \ 'command': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 'foo'})})}, \ 'read_buffer': 0, \}) @@ -28,7 +28,7 @@ After: Given foobar (Some imaginary filetype): Execute(It should be possible to compute an executable to check based on the result of commands): - AssertLinter 'echo', 'foo' + AssertLinter has('win32') ? 'cmd' : 'echo', 'foo' ALELint call ale#test#FlushJobs() @@ -40,7 +40,7 @@ Execute(It should be possible to compute an executable to check based on the res Execute(It handle the deferred command failing): let g:ale_emulate_job_failure = 1 - AssertLinter 'echo', 0 + AssertLinter has('win32') ? 'cmd' : 'echo', 0 ALELint call ale#test#FlushJobs() diff --git a/test/test_engine_invocation.vader b/test/test_engine_invocation.vader deleted file mode 100644 index af713953..00000000 --- a/test/test_engine_invocation.vader +++ /dev/null @@ -1,108 +0,0 @@ -Before: - function! CollectResults(buffer, output) - return [] - endfunction - - function! FirstChainFunction(buffer) - return 'first' - endfunction - - function! SecondChainFunction(buffer, output) - " We'll skip this command - return '' - endfunction - - function! ThirdChainFunction(buffer, output) - return 'third' - endfunction - - function! FourthChainFunction(buffer, output) - return 'fourth' - endfunction - - let g:linter = { - \ 'name': 'testlinter', - \ 'callback': 'CollectResults', - \ 'executable': 'echo', - \ 'command_chain': [ - \ {'callback': 'FirstChainFunction'}, - \ {'callback': 'SecondChainFunction'}, - \ {'callback': 'ThirdChainFunction'}, - \ {'callback': 'FourthChainFunction'}, - \ ], - \ 'read_buffer': 1, - \} - - function! ProcessIndex(chain_index) - let [l:command, l:options] = ale#engine#ProcessChain(347, '', g:linter, a:chain_index, []) - let l:options.command = l:command - - return l:options - endfunction - -After: - delfunction CollectResults - delfunction FirstChainFunction - delfunction SecondChainFunction - delfunction ThirdChainFunction - delfunction ProcessIndex - unlet! g:linter - unlet! g:result - -Execute(Engine invocation should return the command for the first item correctly): - let g:result = ProcessIndex(0) - - AssertEqual 'first', g:result.command - AssertEqual 1, g:result.next_chain_index - -Execute(Engine invocation should return the command for the second item correctly): - let g:result = ProcessIndex(1) - - AssertEqual 'third', g:result.command - AssertEqual 3, g:result.next_chain_index - -Execute(Engine invocation should return the command for the fourth item correctly): - let g:result = ProcessIndex(3) - - AssertEqual 'fourth', g:result.command - AssertEqual 4, g:result.next_chain_index - -Execute(Engine invocation should allow read_buffer to be enabled for a command in the middle of a chain): - let g:linter.command_chain[2].read_buffer = 1 - - let g:result = ProcessIndex(2) - - AssertEqual g:result.command, 'third' - AssertEqual g:result.read_buffer, 1 - -Execute(Engine invocation should allow read_buffer to be disabled for the end of a chain): - let g:linter.command_chain[3].read_buffer = 0 - - let g:result = ProcessIndex(3) - - AssertEqual g:result.command, 'fourth' - AssertEqual g:result.read_buffer, 0 - -Execute(Engine invocation should not use read_buffer from earlier items in a chain): - let g:linter.command_chain[1].read_buffer = 1 - - let g:result = ProcessIndex(1) - - AssertEqual g:result.command, 'third' - AssertEqual g:result.read_buffer, 0 - -Execute(Engine invocation should allow the output_stream setting to be changed in the middle of a chain): - let g:linter.command_chain[2].output_stream = 'both' - - let g:result = ProcessIndex(2) - - AssertEqual g:result.command, 'third' - AssertEqual g:result.output_stream, 'both' - -Execute(Engine invocation should not use output_stream from earlier items in a chain): - let g:linter.command_chain[1].output_stream = 'both' - - let g:result = ProcessIndex(1) - - AssertEqual g:result.command, 'third' - AssertEqual g:result.output_stream, 'stdout' diff --git a/test/test_eslint_executable_detection.vader b/test/test_eslint_executable_detection.vader index c2071131..3fed63da 100644 --- a/test/test_eslint_executable_detection.vader +++ b/test/test_eslint_executable_detection.vader @@ -70,6 +70,25 @@ Execute(eslint.js executables should be run with node on Windows): \ ale#handlers#eslint#GetCommand(bufnr('')) endif +Execute(eslint.js should be run from containing project with eslint): + call ale#test#SetFilename('eslint-test-files/react-app/subdir-with-package-json/testfile.js') + + " We have to execute the file with node. + if has('win32') + AssertEqual + \ ale#path#CdString(ale#path#Simplify(g:dir . '/eslint-test-files/react-app')) + \ . ale#Escape('node.exe') . ' ' + \ . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -f json --stdin --stdin-filename %s', + \ ale#handlers#eslint#GetCommand(bufnr('')) + else + AssertEqual + \ ale#path#CdString(ale#path#Simplify(g:dir . '/eslint-test-files/react-app')) + \ . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ . ' -f json --stdin --stdin-filename %s', + \ ale#handlers#eslint#GetCommand(bufnr('')) + endif + Execute(eslint.js executables can be run outside project dir): " Set filename above eslint-test-files (which contains node_modules) call ale#test#SetFilename('testfile.js') diff --git a/test/test_filename_mapping.vader b/test/test_filename_mapping.vader new file mode 100644 index 00000000..e9af539a --- /dev/null +++ b/test/test_filename_mapping.vader @@ -0,0 +1,62 @@ +Before: + Save g:ale_filename_mappings + Save b:ale_filename_mappings + + let g:ale_filename_mappings = {} + unlet! b:ale_filename_mappings + +After: + Restore + +Execute(ale#GetFilenameMappings should return the correct mappings for given linters/fixers): + let g:ale_filename_mappings = {'a': [['foo', 'bar']], 'b': [['baz', 'foo']]} + + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['baz', 'foo']], ale#GetFilenameMappings(bufnr(''), 'b') + AssertEqual [], ale#GetFilenameMappings(bufnr(''), 'c') + + let b:ale_filename_mappings = {'b': [['abc', 'xyz']]} + + AssertEqual [], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), 'b') + AssertEqual [], ale#GetFilenameMappings(bufnr(''), 'c') + +Execute(ale#GetFilenameMappings should return Lists set for use with all tools): + let g:ale_filename_mappings = [['foo', 'bar']] + + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), '') + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), v:null) + + let b:ale_filename_mappings = [['abc', 'xyz']] + + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), '') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), v:null) + +Execute(ale#GetFilenameMappings should let you use * as a fallback): + let g:ale_filename_mappings = {'a': [['foo', 'bar']], '*': [['abc', 'xyz']]} + + AssertEqual [['foo', 'bar']], ale#GetFilenameMappings(bufnr(''), 'a') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), 'b') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), '') + AssertEqual [['abc', 'xyz']], ale#GetFilenameMappings(bufnr(''), v:null) + +Execute(ale#filename_mapping#Invert should invert filename mappings): + AssertEqual + \ [['b', 'a'], ['y', 'x']], + \ ale#filename_mapping#Invert([['a', 'b'], ['x', 'y']]) + \ +Execute(ale#filename_mapping#Map return the filename as-is if there are no mappings): + AssertEqual + \ '/foo//bar', + \ ale#filename_mapping#Map('/foo//bar', [['/bar', '/data/']]) + +Execute(ale#filename_mapping#Map should map filenames): + AssertEqual + \ '/data/bar', + \ ale#filename_mapping#Map('/foo//bar', [ + \ ['/data/', '/baz/'], + \ ['/foo/', '/data/'], + \ ['/foo/', '/xyz/'], + \ ]) diff --git a/test/test_filetype_linter_defaults.vader b/test/test_filetype_linter_defaults.vader index 9c40cb23..e9980536 100644 --- a/test/test_filetype_linter_defaults.vader +++ b/test/test_filetype_linter_defaults.vader @@ -32,14 +32,14 @@ Execute(The defaults for the help filetype should be correct): AssertEqual [], GetLinterNames('help') Execute(The defaults for the python filetype should be correct): - AssertEqual ['flake8', 'mypy', 'pylint'], GetLinterNames('python') + AssertEqual ['flake8', 'mypy', 'pylint', 'pyright'], GetLinterNames('python') let g:ale_linters_explicit = 1 AssertEqual [], GetLinterNames('python') Execute(The defaults for the rust filetype should be correct): - AssertEqual ['cargo'], GetLinterNames('rust') + AssertEqual ['cargo', 'rls'], GetLinterNames('rust') let g:ale_linters_explicit = 1 @@ -61,7 +61,7 @@ Execute(The defaults for the zsh filetype should be correct): Execute(The defaults for the verilog filetype should be correct): " This filetype isn't configured with default, so we can test loading all " available linters with this. - AssertEqual ['iverilog', 'verilator', 'vlog', 'xvlog'], GetLinterNames('verilog') + AssertEqual ['hdl-checker', 'iverilog', 'verilator', 'vlog', 'xvlog'], GetLinterNames('verilog') let g:ale_linters_explicit = 1 diff --git a/test/test_find_references.vader b/test/test_find_references.vader index 1a147849..ca05f631 100644 --- a/test/test_find_references.vader +++ b/test/test_find_references.vader @@ -2,6 +2,8 @@ Before: call ale#test#SetDirectory('/testplugin/test') call ale#test#SetFilename('dummy.txt') + Save g:ale_default_navigation + let g:old_filename = expand('%:p') let g:Callback = '' let g:expr_list = [] @@ -12,6 +14,7 @@ Before: let g:capability_checked = '' let g:conn_id = v:null let g:InitCallback = v:null + let g:ale_default_navigation = 'buffer' runtime autoload/ale/lsp_linter.vim runtime autoload/ale/lsp.vim @@ -60,9 +63,13 @@ Before: let g:preview_called = 1 let g:item_list = a:item_list let g:options = a:options + + call ale#preview#SetLastSelection(a:item_list, a:options) endfunction After: + Restore + if g:conn_id isnot v:null call ale#lsp#RemoveConnectionWithID(g:conn_id) endif @@ -105,7 +112,16 @@ Given typescript(Some typescript file): bazxyzxyzxyz Execute(Results should be shown for tsserver responses): - call ale#references#SetMap({3: {}}) + " We should remember these options when we repeat the selection. + call ale#references#SetMap( + \ { + \ 3: { + \ 'ignorethis': 'x', + \ 'open_in': 'tab', + \ 'use_relative_paths': 1, + \ } + \ } + \) call ale#references#HandleTSServerResponse(1, { \ 'command': 'references', \ 'request_seq': 3, @@ -152,6 +168,25 @@ Execute(Results should be shown for tsserver responses): \ g:item_list AssertEqual {}, ale#references#GetMap() + " We should be able to repeat selections with ALERepeatSelection + let g:item_list = [] + ALERepeatSelection + + AssertEqual + \ [ + \ {'filename': '/foo/bar/app.ts', 'column': 9, 'line': 9, 'match': 'import {doSomething} from ''./whatever'''}, + \ {'filename': '/foo/bar/app.ts', 'column': 3, 'line': 804, 'match': 'doSomething()'}, + \ {'filename': '/foo/bar/other/app.ts', 'column': 3, 'line': 51, 'match': 'doSomething()'}, + \ ], + \ g:item_list + AssertEqual {}, ale#references#GetMap() + AssertEqual + \ { + \ 'open_in': 'tab', + \ 'use_relative_paths': 1, + \ }, + \ g:options + Execute(The preview window should not be opened for empty tsserver responses): call ale#references#SetMap({3: {}}) call ale#references#HandleTSServerResponse(1, { @@ -195,7 +230,7 @@ Execute(tsserver reference requests should be sent): \ [0, 'ts@references', {'file': expand('%:p'), 'line': 2, 'offset': 5}] \ ], \ g:message_list - AssertEqual {'42': {'use_relative_paths': 0}}, ale#references#GetMap() + AssertEqual {'42': {'open_in': 'current-buffer', 'use_relative_paths': 0}}, ale#references#GetMap() Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServerResponse): runtime ale_linters/typescript/tsserver.vim @@ -205,7 +240,48 @@ Execute('-relative' argument should enable 'use_relative_paths' in HandleTSServe call g:InitCallback() - AssertEqual {'42': {'use_relative_paths': 1}}, ale#references#GetMap() + AssertEqual {'42': {'open_in': 'current-buffer', 'use_relative_paths': 1}}, ale#references#GetMap() + +Execute(`-tab` should display results in tabs): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences -tab + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'tab', 'use_relative_paths': 0}}, ale#references#GetMap() + +Execute(The default navigation type should be used): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + let g:ale_default_navigation = 'tab' + ALEFindReferences + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'tab', 'use_relative_paths': 0}}, ale#references#GetMap() + +Execute(`-split` should display results in splits): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences -split + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'split', 'use_relative_paths': 0}}, ale#references#GetMap() + +Execute(`-vsplit` should display results in vsplits): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALEFindReferences -vsplit + + call g:InitCallback() + + AssertEqual {'42': {'open_in': 'vsplit', 'use_relative_paths': 0}}, ale#references#GetMap() Given python(Some Python file): foo @@ -302,7 +378,7 @@ Execute(LSP reference requests should be sent): \ ], \ g:message_list - AssertEqual {'42': {'use_relative_paths': 0}}, ale#references#GetMap() + AssertEqual {'42': {'open_in': 'current-buffer', 'use_relative_paths': 0}}, ale#references#GetMap() Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResponse): runtime ale_linters/python/pyls.vim @@ -313,4 +389,4 @@ Execute('-relative' argument should enable 'use_relative_paths' in HandleLSPResp call g:InitCallback() - AssertEqual {'42': {'use_relative_paths': 1}}, ale#references#GetMap() + AssertEqual {'42': {'open_in': 'current-buffer', 'use_relative_paths': 1}}, ale#references#GetMap() diff --git a/test/test_format_command.vader b/test/test_format_command.vader index 15435326..9d730fce 100644 --- a/test/test_format_command.vader +++ b/test/test_format_command.vader @@ -25,12 +25,12 @@ After: Execute(FormatCommand should do nothing to basic command strings): AssertEqual \ ['', 'awesome-linter do something', 0], - \ ale#command#FormatCommand(bufnr('%'), '', 'awesome-linter do something', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), '', 'awesome-linter do something', 0, v:null, []) Execute(FormatCommand should handle %%, and ignore other percents): AssertEqual \ ['', '% %%d %%f %x %', 0], - \ ale#command#FormatCommand(bufnr('%'), '', '%% %%%d %%%f %x %', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), '', '%% %%%d %%%f %x %', 0, v:null, []) Execute(FormatCommand should convert %s to the current filename): AssertEqual @@ -39,10 +39,10 @@ Execute(FormatCommand should convert %s to the current filename): \ 'foo ' . ale#Escape(expand('%:p')) . ' bar ' . ale#Escape(expand('%:p')), \ 0, \ ], - \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s bar %s', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s bar %s', 0, v:null, []) Execute(FormatCommand should convert %t to a new temporary filename): - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0, v:null) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0, v:null, []) call CheckTempFile(g:result[0]) @@ -56,21 +56,21 @@ Execute(FormatCommand should convert %t to a new temporary filename): AssertEqual g:match[1], g:match[2] Execute(FormatCommand should not convert %t to a new temporary filename when the input is given as v:false): - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0, v:false) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %t', 0, v:false, []) AssertEqual ['', 'foo %t bar %t', 0], g:result Execute(FormatCommand should signal that files are created when temporary files are needed): AssertEqual \ 1, - \ ale#command#FormatCommand(bufnr('%'), '', 'foo %t', 0, v:null)[2] + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %t', 0, v:null, [])[2] AssertEqual \ 0, - \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s', 0, v:null)[2] + \ ale#command#FormatCommand(bufnr('%'), '', 'foo %s', 0, v:null, [])[2] Execute(FormatCommand should let you combine %s and %t): - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %s', 0, v:null) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo %t bar %s', 0, v:null, []) call CheckTempFile(g:result[0]) @@ -87,30 +87,30 @@ Execute(FormatCommand should replace %e with the escaped executable): if has('win32') AssertEqual \ ['', 'foo foo', 0], - \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0, v:null, []) AssertEqual \ ['', '"foo bar"', 0], - \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0, v:null, []) AssertEqual \ ['', '%e %e', 0], - \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0, v:null, []) else AssertEqual \ ['', '''foo'' ''foo''', 0], - \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), 'foo', '%e %e', 0, v:null, []) AssertEqual \ ['', '''foo bar''', 0], - \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), 'foo bar', '%e', 0, v:null, []) AssertEqual \ ['', '%e %e', 0], - \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0, v:null) + \ ale#command#FormatCommand(bufnr('%'), '', '%e %e', 0, v:null, []) endif Execute(EscapeCommandPart should escape all percent signs): AssertEqual '%%s %%t %%%% %%s %%t %%%%', ale#engine#EscapeCommandPart('%s %t %% %s %t %%') Execute(EscapeCommandPart should pipe in temporary files appropriately): - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar', 1, v:null) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar', 1, v:null, []) call CheckTempFile(g:result[0]) @@ -118,10 +118,57 @@ Execute(EscapeCommandPart should pipe in temporary files appropriately): Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] AssertEqual ale#Escape(g:result[0]), g:match[1] - let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar %t', 1, v:null) + let g:result = ale#command#FormatCommand(bufnr('%'), '', 'foo bar %t', 1, v:null, []) call CheckTempFile(g:result[0]) let g:match = matchlist(g:result[1], '\v^foo bar (.*)$') Assert !empty(g:match), 'No match found! Result was: ' . g:result[1] AssertEqual ale#Escape(g:result[0]), g:match[1] + +Execute(FormatCommand should apply filename modifiers to the current file): + AssertEqual + \ ale#Escape(expand('%:p:h')) + \ . ' ' . ale#Escape('dummy.txt') + \ . ' ' . ale#Escape(expand('%:p:h:t')) + \ . ' ' . ale#Escape('txt') + \ . ' ' . ale#Escape(expand('%:p:r')), + \ ale#command#FormatCommand(bufnr(''), '', '%s:h %s:t %s:h:t %s:e %s:r', 0, v:null, [])[1] + +Execute(FormatCommand should apply filename modifiers to the temporary file): + let g:result = ale#command#FormatCommand(bufnr(''), '', '%t:h %t:t %t:h:t %t:e %t:r', 0, v:null, []) + + AssertEqual + \ ale#Escape(fnamemodify(g:result[0], ':h')) + \ . ' ' . ale#Escape('dummy.txt') + \ . ' ' . ale#Escape(fnamemodify(g:result[0], ':h:t')) + \ . ' ' . ale#Escape('txt') + \ . ' ' . ale#Escape(fnamemodify(g:result[0], ':r')), + \ g:result[1] + +Execute(FormatCommand should apply filename mappings the current file): + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%s', 0, v:null, [ + \ [expand('%:p:h'), '/foo/bar'], + \]) + + Assert g:result[1] =~# '/foo/bar' + +Execute(FormatCommand should apply filename mappings to temporary files): + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%t', 0, v:null, [ + \ [fnamemodify(tempname(), ':h:h'), '/foo/bar'] + \]) + + Assert g:result[1] =~# '/foo/bar' + +Execute(FormatCommand should apply filename modifiers to mapped filenames): + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%s:h', 0, v:null, [ + \ [expand('%:p:h'), '/foo/bar'], + \]) + + AssertEqual ale#Escape('/foo/bar'), g:result[1] + + let g:result = ale#command#FormatCommand(bufnr('%'), '', '%t:h:h:h', 0, v:null, [ + \ [fnamemodify(tempname(), ':h:h'), '/foo/bar'] + \]) + + AssertEqual ale#Escape('/foo/bar'), g:result[1] diff --git a/test/test_go_to_definition.vader b/test/test_go_to_definition.vader index 3479d7b5..f2f34280 100644 --- a/test/test_go_to_definition.vader +++ b/test/test_go_to_definition.vader @@ -2,6 +2,8 @@ Before: call ale#test#SetDirectory('/testplugin/test') call ale#test#SetFilename('dummy.txt') + Save g:ale_default_navigation + let g:old_filename = expand('%:p') let g:Callback = '' let g:message_list = [] @@ -9,6 +11,7 @@ Before: let g:capability_checked = '' let g:conn_id = v:null let g:InitCallback = v:null + let g:ale_default_navigation = 'buffer' runtime autoload/ale/linter.vim runtime autoload/ale/lsp_linter.vim @@ -54,6 +57,8 @@ Before: endfunction After: + Restore + if g:conn_id isnot v:null call ale#lsp#RemoveConnectionWithID(g:conn_id) endif @@ -164,7 +169,7 @@ Execute(Other files should be jumped to for definition responses in tabs too): AssertEqual {}, ale#definition#GetMap() Execute(Other files should be jumped to for definition responses in splits too): - call ale#definition#SetMap({3: {'open_in': 'horizontal-split'}}) + call ale#definition#SetMap({3: {'open_in': 'split'}}) call ale#definition#HandleTSServerResponse( \ 1, \ { @@ -189,7 +194,7 @@ Execute(Other files should be jumped to for definition responses in splits too): AssertEqual {}, ale#definition#GetMap() Execute(Other files should be jumped to for definition responses in vsplits too): - call ale#definition#SetMap({3: {'open_in': 'vertical-split'}}) + call ale#definition#SetMap({3: {'open_in': 'vsplit'}}) call ale#definition#HandleTSServerResponse( \ 1, \ { @@ -241,7 +246,32 @@ Execute(tsserver tab definition requests should be sent): runtime ale_linters/typescript/tsserver.vim call setpos('.', [bufnr(''), 2, 5, 0]) - ALEGoToDefinitionInTab + ALEGoToDefinition -tab + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'definition', g:capability_checked + AssertEqual + \ 'function(''ale#definition#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@definition', {'file': expand('%:p'), 'line': 2, 'offset': 5}] + \ ], + \ g:message_list + AssertEqual {'42': {'open_in': 'tab'}}, ale#definition#GetMap() + +Execute(The default navigation type should be used): + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + let g:ale_default_navigation = 'tab' + ALEGoToDefinition " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) @@ -448,7 +478,7 @@ Execute(LSP tab definition requests should be sent): let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) - ALEGoToDefinitionInTab + ALEGoToDefinition -tab " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) @@ -484,7 +514,7 @@ Execute(LSP tab type definition requests should be sent): let b:ale_linters = ['pyls'] call setpos('.', [bufnr(''), 1, 5, 0]) - ALEGoToTypeDefinitionInTab + ALEGoToTypeDefinition -tab " We shouldn't register the callback yet. AssertEqual '''''', string(g:Callback) diff --git a/test/test_hdl_checker_options.vader b/test/test_hdl_checker_options.vader new file mode 100644 index 00000000..4bee0f55 --- /dev/null +++ b/test/test_hdl_checker_options.vader @@ -0,0 +1,78 @@ +Before: + call ale#assert#SetUpLinterTest('vhdl', 'hdl_checker') + + Save g:ale_hdl_checker_config_file + Save g:ale_hdl_checker_options + + let g:default_config_file = has('unix') ? '.hdl_checker.config' : '_hdl_checker.config' + +After: + Restore + call ale#assert#TearDownLinterTest() + unlet! g:default_config_file + +Execute(Get default initialization dict): + AssertEqual + \ {'project_file': g:default_config_file}, + \ ale#handlers#hdl_checker#GetInitOptions(bufnr('')) + +Execute(Get custom initialization dict): + let g:ale_hdl_checker_config_file = 'some_file_name' + + AssertEqual + \ {'project_file': 'some_file_name'}, + \ ale#handlers#hdl_checker#GetInitOptions(bufnr('')) + +Execute(Get the checker command without extra user parameters): + AssertEqual + \ ale#Escape('hdl_checker') . ' --lsp', + \ ale#handlers#hdl_checker#GetCommand(bufnr('')) + +Execute(Get the checker command with user configured parameters): + let g:ale_hdl_checker_options = '--log-level DEBUG' + + AssertEqual + \ ale#Escape('hdl_checker') . ' --lsp --log-level DEBUG', + \ ale#handlers#hdl_checker#GetCommand(bufnr('')) + +Execute(Customize executable): + let g:ale_hdl_checker_executable = '/some/other/path' + AssertEqual + \ ale#Escape('/some/other/path') . ' --lsp', + \ ale#handlers#hdl_checker#GetCommand(bufnr('')) + +Execute(Get project root based on .git): + call ale#test#SetFilename('hdl_server/with_git/files/foo.vhd') + " Create .git file + silent! call mkdir(g:dir . '/hdl_server/with_git/.git') + AssertNotEqual '', glob(g:dir . '/hdl_server/with_git/.git') + + AssertEqual + \ ale#path#Simplify(g:dir . '/hdl_server/with_git'), + \ ale#handlers#hdl_checker#GetProjectRoot(bufnr('')) + +Execute(Get project root based on config file): + call ale#test#SetFilename('hdl_server/with_config_file/foo.vhd') + + AssertEqual + \ ale#path#Simplify(g:dir . '/hdl_server/with_config_file'), + \ ale#handlers#hdl_checker#GetProjectRoot(bufnr('')) + +Execute(Return no project root if neither .git or config file are found): + let g:call_count = 0 + + " Mock this command to avoid the test to find ale's own .git folder + function! ale#handlers#hdl_checker#IsDotGit(path) abort + let g:call_count += 1 + return 0 + endfunction + + call ale#test#SetFilename('hdl_server/foo.vhd') + + AssertEqual + \ '', + \ ale#handlers#hdl_checker#GetProjectRoot(bufnr('')) + + AssertEqual g:call_count, 1 + + unlet! g:call_count diff --git a/test/test_hover.vader b/test/test_hover.vader index 917694a2..9689cda2 100644 --- a/test/test_hover.vader +++ b/test/test_hover.vader @@ -5,7 +5,7 @@ Before: let g:Callback = 0 let g:message_list = [] let g:item_list = [] - let g:echo_list = [] + let g:show_message_arg_list = [] runtime autoload/ale/linter.vim runtime autoload/ale/lsp.vim @@ -27,8 +27,8 @@ Before: return 42 endfunction - function! ale#util#ShowMessage(string) abort - call add(g:echo_list, a:string) + function! ale#util#ShowMessage(string, ...) abort + call add(g:show_message_arg_list, [a:string] + a:000) endfunction function! HandleValidLSPResult(result) abort @@ -58,7 +58,7 @@ After: unlet! g:Callback unlet! g:message_list unlet! b:ale_linters - unlet! g:echo_list + unlet! g:show_message_arg_list delfunction HandleValidLSPResult @@ -112,25 +112,31 @@ Execute(tsserver quickinfo displayString values should be displayed): \ } \) - AssertEqual ['foo bar'], g:echo_list + AssertEqual [['foo bar']], g:show_message_arg_list AssertEqual {}, ale#hover#GetMap() Execute(LSP hover responses with just a string should be handled): call HandleValidLSPResult({'contents': 'foobar'}) - AssertEqual ['foobar'], g:echo_list + AssertEqual [['foobar', {'commands': []}]], g:show_message_arg_list AssertEqual {}, ale#hover#GetMap() Execute(LSP hover null responses should be handled): call HandleValidLSPResult(v:null) - AssertEqual [], g:echo_list + AssertEqual [], g:show_message_arg_list AssertEqual {}, ale#hover#GetMap() Execute(LSP hover responses with markup content should be handled): - call HandleValidLSPResult({'contents': {'kind': 'something', 'value': 'markup'}}) + call HandleValidLSPResult({'contents': {'kind': 'markdown', 'value': 'markup'}}) - AssertEqual ['markup'], g:echo_list + AssertEqual [['markup', {'commands': []}]], g:show_message_arg_list + AssertEqual {}, ale#hover#GetMap() + +Execute(LSP hover responses with markup content missing values should be handled): + call HandleValidLSPResult({'contents': {'kind': 'markdown'}}) + + AssertEqual [], g:show_message_arg_list AssertEqual {}, ale#hover#GetMap() Execute(LSP hover response with lists of strings should be handled): @@ -139,7 +145,7 @@ Execute(LSP hover response with lists of strings should be handled): \ "bar\n", \]}) - AssertEqual ["foo\n\nbar\n"], g:echo_list + AssertEqual [["foo\n\nbar", {'commands': []}]], g:show_message_arg_list AssertEqual {}, ale#hover#GetMap() Execute(LSP hover response with lists of strings and marked strings should be handled): @@ -148,7 +154,18 @@ Execute(LSP hover response with lists of strings and marked strings should be ha \ "bar\n", \]}) - AssertEqual ["foo\nbar\n"], g:echo_list + AssertEqual [ + \ [ + \ "foo\n\nbar", + \ { + \ 'commands': [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_rust syntax/rust.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%2l/ contains=@ALE_hover_rust', + \ ], + \ }, + \ ], + \], g:show_message_arg_list AssertEqual {}, ale#hover#GetMap() Execute(tsserver responses for documentation requests should be handled): diff --git a/test/test_hover_parsing.vader b/test/test_hover_parsing.vader new file mode 100644 index 00000000..4129c26a --- /dev/null +++ b/test/test_hover_parsing.vader @@ -0,0 +1,173 @@ +Execute(Invalid results should be handled): + AssertEqual [[], []], ale#hover#ParseLSPResult(0) + AssertEqual [[], []], ale#hover#ParseLSPResult([0]) + AssertEqual [[], []], ale#hover#ParseLSPResult('') + AssertEqual [[], []], ale#hover#ParseLSPResult({}) + AssertEqual [[], []], ale#hover#ParseLSPResult([{}]) + AssertEqual [[], []], ale#hover#ParseLSPResult(['']) + AssertEqual [[], []], ale#hover#ParseLSPResult({'value': ''}) + AssertEqual [[], []], ale#hover#ParseLSPResult([{'value': ''}]) + AssertEqual [[], []], ale#hover#ParseLSPResult({'kind': 'markdown'}) + AssertEqual [[], []], ale#hover#ParseLSPResult({'kind': 'plaintext'}) + AssertEqual [[], []], ale#hover#ParseLSPResult({'kind': 'x', 'value': 'xxx'}) + +Execute(A string with a code fence should be handled): + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ ], "\n")) + + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_typescript syntax/typescript.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ 'syntax region ALE_hover_2 start=/\%5l/ end=/\%8l/ contains=@ALE_hover_python', + \ 'syntax region ALE_hover_3 start=/\%8l/ end=/\%10l/ contains=@ALE_hover_typescript', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ 'middle line', + \ '', + \ 'def bar():', + \ ' pass', + \ '', + \ 'const baz = () => undefined', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ 'middle line', + \ '```python', + \ 'def bar():', + \ ' pass', + \ '```', + \ '```typescript', + \ 'const baz = () => undefined', + \ '```', + \ ], "\n")) + +Execute(Multiple strings with fences should be handled): + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_typescript syntax/typescript.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ 'syntax region ALE_hover_2 start=/\%5l/ end=/\%8l/ contains=@ALE_hover_python', + \ 'syntax region ALE_hover_3 start=/\%8l/ end=/\%10l/ contains=@ALE_hover_typescript', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ 'middle line', + \ '', + \ 'def bar():', + \ ' pass', + \ '', + \ 'const baz = () => undefined', + \ ], + \ ], + \ ale#hover#ParseLSPResult([ + \ join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ ], "\n"), + \ join([ + \ 'middle line', + \ '```python', + \ 'def bar():', + \ ' pass', + \ '```', + \ '```typescript', + \ 'const baz = () => undefined', + \ '```', + \ ], "\n"), + \ ]) + +Execute(Objects with kinds should be handled): + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ '```typescript', + \ 'const baz = () => undefined', + \ '```', + \ ], + \ ], + \ ale#hover#ParseLSPResult([ + \ { + \ 'kind': 'markdown', + \ 'value': join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ ], "\n"), + \ }, + \ { + \ 'kind': 'plaintext', + \ 'value': join([ + \ '```typescript', + \ 'const baz = () => undefined', + \ '```', + \ ], "\n"), + \ }, + \ ]) + +Execute(Simple markdown formatting should be handled): + AssertEqual + \ [ + \ [ + \ 'unlet! b:current_syntax', + \ 'syntax include @ALE_hover_python syntax/python.vim', + \ 'syntax region ALE_hover_1 start=/\%1l/ end=/\%3l/ contains=@ALE_hover_python', + \ ], + \ [ + \ 'def foo():', + \ ' pass', + \ '', + \ 'formatted _ line _', + \ ], + \ ], + \ ale#hover#ParseLSPResult(join([ + \ '```python', + \ 'def foo():', + \ ' pass', + \ '```', + \ 'formatted \_ line \_', + \ ], "\n")) diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader index cd32ebc8..d000b158 100644 --- a/test/test_linter_defintion_processing.vader +++ b/test/test_linter_defintion_processing.vader @@ -39,13 +39,13 @@ Execute (PreProcess should throw when then callback is not a function): \}) AssertEqual '`callback` must be defined with a callback to accept output', g:vader_exception -Execute (PreProcess should throw when there is no executable or executable_callback): +Execute (PreProcess should throw when there is no executable): AssertThrows call ale#linter#PreProcess('testft', { \ 'name': 'foo', \ 'callback': 'SomeFunction', \ 'command': 'echo', \}) - AssertEqual 'Either `executable` or `executable_callback` must be defined', g:vader_exception + AssertEqual '`executable` must be defined', g:vader_exception Execute (PreProcess should throw when executable is not a string): AssertThrows call ale#linter#PreProcess('testft', { @@ -56,15 +56,6 @@ Execute (PreProcess should throw when executable is not a string): \}) AssertEqual '`executable` must be a String or Function if defined', g:vader_exception -Execute (PreProcess should throw when executable_callback is not a callback): - AssertThrows call ale#linter#PreProcess('testft', { - \ 'name': 'foo', - \ 'callback': 'SomeFunction', - \ 'executable_callback': 123, - \ 'command': 'echo', - \}) - AssertEqual '`executable_callback` must be a callback if defined', g:vader_exception - Execute (PreProcess should allow executable to be a callback): call ale#linter#PreProcess('testft', { \ 'name': 'foo', @@ -79,7 +70,7 @@ Execute (PreProcess should throw when there is no command): \ 'callback': 'SomeFunction', \ 'executable': 'echo', \}) - AssertEqual 'Either `command`, `executable_callback`, `command_chain` must be defined', g:vader_exception + AssertEqual '`command` must be defined', g:vader_exception Execute (PreProcess should throw when command is not a string): AssertThrows call ale#linter#PreProcess('testft', { @@ -98,15 +89,6 @@ Execute (PreProcess should allow command to be a callback): \ 'command': function('type'), \}) -Execute (PreProcess should throw when command_callback is not a callback): - AssertThrows call ale#linter#PreProcess('testft', { - \ 'name': 'foo', - \ 'callback': 'SomeFunction', - \ 'executable': 'echo', - \ 'command_callback': 123, - \}) - AssertEqual '`command_callback` must be a callback if defined', g:vader_exception - Execute (PreProcess should when the output stream isn't a valid string): AssertThrows call ale#linter#PreProcess('testft', { \ 'name': 'foo', @@ -152,117 +134,12 @@ Execute (PreProcess should accept a 'both' output_stream): \ 'output_stream': 'both', \}) -Execute(PreProcess should complain if the command_chain is not a List): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command_chain': 'x', - \} - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual '`command_chain` must be a List', g:vader_exception - -Execute(PreProcess should complain if the command_chain is empty): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command_chain': [], - \} - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual '`command_chain` must contain at least one item', g:vader_exception - -Execute(PreProcess should complain if the command_chain has no callback): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command_chain': [{}], - \} - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'The `command_chain` item 0 must define a `callback` function', g:vader_exception - -Execute(PreProcess should complain if the command_chain callback is not a function): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command_chain': [{'callback': 2}], - \} - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'The `command_chain` item 0 must define a `callback` function', g:vader_exception - -Execute(PreProcess should accept a chain with one callback): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command_chain': [{'callback': 'foo'}], - \} - call ale#linter#PreProcess('testft', g:linter) - -Execute(PreProcess should complain about invalid output_stream values in the chain): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command_chain': [{'callback': 'foo', 'output_stream': ''}], - \} - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual "The `command_chain` item 0 `output_stream` flag must be 'stdout', 'stderr', or 'both'", g:vader_exception - -Execute(PreProcess should complain about valid output_stream values in the chain): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command_chain': [{'callback': 'foo', 'output_stream': 'stdout'}], - \} - call ale#linter#PreProcess('testft', g:linter) - let g:linter.command_chain[0].output_stream = 'stderr' - call ale#linter#PreProcess('testft', g:linter) - let g:linter.command_chain[0].output_stream = 'both' - call ale#linter#PreProcess('testft', g:linter) - -Execute(PreProcess should complain about invalid chain items at higher indices): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command_chain': [{'callback': 'foo'}, {'callback': 123}], - \} - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'The `command_chain` item 1 must define a `callback` function', g:vader_exception - -Execute(PreProcess should complain when conflicting command options are used): - let g:linter = { - \ 'name': 'x', - \ 'callback': 'x', - \ 'executable': 'x', - \ 'command': 'foo', - \ 'command_chain': [{'callback': 'foo'}], - \} - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'Only one of `command`, `command_callback`, or `command_chain` should be set', g:vader_exception - - unlet g:linter.command - let g:linter.command_callback = 'foo' - - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'Only one of `command`, `command_callback`, or `command_chain` should be set', g:vader_exception - - let g:linter.command = 'foo' - unlet g:linter.command_chain - - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'Only one of `command`, `command_callback`, or `command_chain` should be set', g:vader_exception - Execute(PreProcess should process the read_buffer option correctly): let g:linter = { \ 'name': 'x', \ 'callback': 'x', \ 'executable': 'x', - \ 'command_chain': [{'callback': 'foo'}, {'callback': 'bar'}], + \ 'command': 'x', \ 'read_buffer': '0', \} @@ -277,25 +154,6 @@ Execute(PreProcess should process the read_buffer option correctly): call ale#linter#PreProcess('testft', g:linter) - unlet g:linter.read_buffer - let g:linter.command_chain[0].read_buffer = '0' - - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'The `command_chain` item 0 value for `read_buffer` must be `0` or `1`', g:vader_exception - - let g:linter.command_chain[0].read_buffer = 0 - - call ale#linter#PreProcess('testft', g:linter) - - let g:linter.command_chain[1].read_buffer = '0' - - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'The `command_chain` item 1 value for `read_buffer` must be `0` or `1`', g:vader_exception - - let g:linter.command_chain[1].read_buffer = 1 - - call ale#linter#PreProcess('testft', g:linter) - Execute(PreProcess should set a default value for read_buffer): let g:linter = { \ 'name': 'x', @@ -316,7 +174,7 @@ Execute(PreProcess should process the lint_file option correctly): \} AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual '`lint_file` must be `0` or `1`', g:vader_exception + AssertEqual '`lint_file` must be `0`, `1`, or a Function', g:vader_exception let g:linter.lint_file = 0 @@ -327,14 +185,17 @@ Execute(PreProcess should process the lint_file option correctly): let g:linter.lint_file = 1 AssertEqual 1, ale#linter#PreProcess('testft', g:linter).lint_file - " The default for read_buffer should change to 0 when lint_file is 1. - AssertEqual 0, ale#linter#PreProcess('testft', g:linter).read_buffer + " The default for read_buffer should still be 1 + AssertEqual 1, ale#linter#PreProcess('testft', g:linter).read_buffer let g:linter.read_buffer = 1 - " We shouldn't be able to set both options to 1 at the same time. - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'Only one of `lint_file` or `read_buffer` can be `1`', g:vader_exception + " We should be able to set `read_buffer` and `lint_file` at the same time. + AssertEqual 1, ale#linter#PreProcess('testft', g:linter).read_buffer + + let g:linter.lint_file = function('type') + + Assert type(ale#linter#PreProcess('testft', g:linter).lint_file) is v:t_func Execute(PreProcess should set a default value for lint_file): let g:linter = { @@ -394,151 +255,96 @@ Execute(PreProcess should accept tsserver LSP configuration): \ 'executable': 'x', \ 'command': 'x', \ 'lsp': 'tsserver', - \ 'language_callback': 'x', - \ 'project_root_callback': 'x', + \ 'language': 'x', + \ 'project_root': 'x', \} AssertEqual 'tsserver', ale#linter#PreProcess('testft', g:linter).lsp - call remove(g:linter, 'executable') - let g:linter.executable_callback = 'X' - - call ale#linter#PreProcess('testft', g:linter) - - call remove(g:linter, 'command') - let g:linter.command_callback = 'X' - - call ale#linter#PreProcess('testft', g:linter) - Execute(PreProcess should accept stdio LSP configuration): let g:linter = { \ 'name': 'x', \ 'executable': 'x', \ 'command': 'x', \ 'lsp': 'stdio', - \ 'language_callback': 'x', - \ 'project_root_callback': 'x', + \ 'language': 'x', + \ 'project_root': 'x', \} AssertEqual 'stdio', ale#linter#PreProcess('testft', g:linter).lsp - call remove(g:linter, 'executable') - let g:linter.executable_callback = 'X' - - call ale#linter#PreProcess('testft', g:linter) - - call remove(g:linter, 'command') - let g:linter.command_callback = 'X' - - call ale#linter#PreProcess('testft', g:linter) - Execute(PreProcess should accept LSP server configurations): let g:linter = { \ 'name': 'x', \ 'lsp': 'socket', - \ 'address_callback': 'X', - \ 'language_callback': 'x', - \ 'project_root_callback': 'x', - \} - - AssertEqual 'socket', ale#linter#PreProcess('testft', g:linter).lsp - -Execute(PreProcess should accept let you specify the language as just a string): - let g:linter = { - \ 'name': 'x', - \ 'lsp': 'socket', - \ 'address_callback': 'X', + \ 'address': 'X', \ 'language': 'foobar', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \} - AssertEqual 'foobar', ale#linter#PreProcess('testft', g:linter).language_callback(0) + AssertEqual 'socket', ale#linter#PreProcess('testft', g:linter).lsp -Execute(PreProcess should complain about using language and language_callback together): +Execute(PreProcess should accept let you specify the `language` as a Function): let g:linter = { \ 'name': 'x', \ 'lsp': 'socket', - \ 'address_callback': 'X', - \ 'language': 'x', - \ 'language_callback': 'x', - \ 'project_root_callback': 'x', + \ 'address': 'X', + \ 'language': {-> 'foobar'}, + \ 'project_root': 'x', \} - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'Only one of `language` or `language_callback` should be set', g:vader_exception + AssertEqual 'foobar', ale#linter#PreProcess('testft', g:linter).language(bufnr('')) Execute(PreProcess should complain about invalid language values): let g:linter = { \ 'name': 'x', \ 'lsp': 'socket', - \ 'address_callback': 'X', + \ 'address': 'X', \ 'language': 0, - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \} AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual '`language` must be a String or Funcref', g:vader_exception + AssertEqual '`language` must be a String or Funcref if defined', g:vader_exception Execute(PreProcess should use the filetype as the language string by default): let g:linter = { \ 'name': 'x', \ 'lsp': 'socket', - \ 'address_callback': 'X', - \ 'project_root_callback': 'x', - \} - - AssertEqual 'testft', ale#linter#PreProcess('testft', g:linter).language_callback(0) - -Execute(PreProcess should allow language to be set to a callback): - let g:linter = { - \ 'name': 'x', - \ 'lsp': 'socket', - \ 'address_callback': 'X', - \ 'language': {-> 'foo'}, - \ 'project_root_callback': 'x', + \ 'address': 'X', + \ 'project_root': 'x', \} - AssertEqual 'foo', ale#linter#PreProcess('testft', g:linter).language_callback(0) + AssertEqual 'testft', ale#linter#PreProcess('testft', g:linter).language -Execute(PreProcess should require an address_callback for LSP socket configurations): +Execute(PreProcess should require an `address` for LSP socket configurations): let g:linter = { \ 'name': 'x', \ 'lsp': 'socket', \} AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual '`address` or `address_callback` must be defined for getting the LSP address', g:vader_exception + AssertEqual '`address` must be defined for getting the LSP address', g:vader_exception -Execute(PreProcess should complain about address_callback for non-LSP linters): +Execute(PreProcess should complain about `address` for non-LSP linters): let g:linter = { \ 'name': 'x', \ 'callback': 'SomeFunction', \ 'executable': 'echo', \ 'command': 'echo', - \ 'address_callback': 'X', + \ 'address': 'X', \} AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual '`address` or `address_callback` cannot be used when lsp != ''socket''', g:vader_exception - -Execute(PreProcess accept valid address_callback values): - let g:linter = ale#linter#PreProcess('testft', { - \ 'name': 'x', - \ 'lsp': 'socket', - \ 'address_callback': {-> 'foo:123'}, - \ 'language': 'x', - \ 'project_root_callback': 'x', - \}) - - AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter) + AssertEqual '`address` cannot be used when lsp != ''socket''', g:vader_exception -Execute(PreProcess accept address as a String): +Execute(PreProcess accept `address` as a String): let g:linter = ale#linter#PreProcess('testft', { \ 'name': 'x', \ 'lsp': 'socket', \ 'address': 'foo:123', \ 'language': 'x', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \}) AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter) @@ -549,7 +355,7 @@ Execute(PreProcess accept address as a Function): \ 'lsp': 'socket', \ 'address': {-> 'foo:123'}, \ 'language': 'x', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \}) AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter) @@ -560,11 +366,11 @@ Execute(PreProcess should complain about invalid address values): \ 'lsp': 'socket', \ 'address': 0, \ 'language': 'x', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \}) AssertEqual '`address` must be a String or Function if defined', g:vader_exception -Execute(PreProcess should accept allow the project root be set as a String): +Execute(PreProcess should allow the `project_root` to be set as a String): let g:linter = ale#linter#PreProcess('testft', { \ 'name': 'x', \ 'lsp': 'socket', @@ -575,7 +381,7 @@ Execute(PreProcess should accept allow the project root be set as a String): AssertEqual '/foo/bar', ale#lsp_linter#FindProjectRoot(0, g:linter) -Execute(PreProcess should accept allow the project root be set as a Function): +Execute(PreProcess should `project_root` be set as a Function): let g:linter = ale#linter#PreProcess('testft', { \ 'name': 'x', \ 'lsp': 'socket', @@ -586,7 +392,7 @@ Execute(PreProcess should accept allow the project root be set as a Function): AssertEqual '/foo/bar', ale#lsp_linter#FindProjectRoot(0, g:linter) -Execute(PreProcess should complain when the project_root valid is invalid): +Execute(PreProcess should complain when `project_root` is invalid): AssertThrows call ale#linter#PreProcess('testft', { \ 'name': 'x', \ 'lsp': 'socket', @@ -594,154 +400,74 @@ Execute(PreProcess should complain when the project_root valid is invalid): \ 'language': 'x', \ 'project_root': 0, \}) - AssertEqual '`project_root` must be a String or Function if defined', g:vader_exception + AssertEqual '`project_root` must be a String or Function', g:vader_exception -Execute(PreProcess should accept project_root_callback as a String): - call ale#linter#PreProcess('testft', { - \ 'name': 'x', - \ 'lsp': 'socket', - \ 'address': 'foo:123', - \ 'language': 'x', - \ 'project_root_callback': 'Foobar', - \}) - -Execute(PreProcess should accept project_root_callback as a Function): - let g:linter = ale#linter#PreProcess('testft', { - \ 'name': 'x', - \ 'lsp': 'socket', - \ 'address': 'foo:123', - \ 'language': 'x', - \ 'project_root_callback': {-> '/foo/bar'}, - \}) - - AssertEqual '/foo/bar', ale#lsp_linter#FindProjectRoot(0, g:linter) - -Execute(PreProcess should complain when the project_root_callback valid is invalid): - AssertThrows call ale#linter#PreProcess('testft', { - \ 'name': 'x', - \ 'lsp': 'socket', - \ 'address': 'foo:123', - \ 'language': 'x', - \ 'project_root_callback': 0, - \}) - AssertEqual '`project_root_callback` must be a callback if defined', 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('testft', 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('testft', { - \ '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 - -Execute(PreProcess should throw when initialization_options is not a Dictionary or callback): +Execute(PreProcess should throw when `initialization_options` is not a Dictionary or callback): AssertThrows call ale#linter#PreProcess('testft', { \ 'name': 'foo', \ 'lsp': 'socket', - \ 'address_callback': 'X', + \ 'address': 'X', \ 'language': 'x', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \ 'initialization_options': 0, \}) - AssertEqual '`initialization_options` must be a String or Function if defined', g:vader_exception + AssertEqual '`initialization_options` must be a Dictionary or Function if defined', g:vader_exception -Execute(PreProcess should accept initialization_options as a Dictionary): +Execute(PreProcess should accept `initialization_options` as a Dictionary): let g:linter = ale#linter#PreProcess('testft', { \ 'name': 'foo', \ 'lsp': 'socket', - \ 'address_callback': 'X', + \ 'address': 'X', \ 'language': 'x', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \ 'initialization_options': {'foo': v:true}, \}) AssertEqual {'foo': v:true}, ale#lsp_linter#GetOptions(0, g:linter) -Execute(PreProcess should accept initialization_options as a Funcref): +Execute(PreProcess should accept `initialization_options` as a Function): let g:linter = ale#linter#PreProcess('testft', { \ 'name': 'foo', \ 'lsp': 'socket', - \ 'address_callback': 'X', + \ 'address': 'X', \ 'language': 'x', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \ 'initialization_options': {-> {'foo': v:true}}, \}) AssertEqual {'foo': v:true}, ale#lsp_linter#GetOptions(0, g:linter) -Execute(PreProcess should complain about using lsp_config and lsp_config_callback together): +Execute(PreProcess should accept `lsp_config` as a Dictionary): let g:linter = { \ 'name': 'x', \ 'lsp': 'socket', - \ 'address_callback': 'X', + \ 'address': 'X', \ 'language': 'x', - \ 'project_root_callback': 'x', - \ 'lsp_config': 'x', - \ 'lsp_config_callback': 'x', - \} - - AssertThrows call ale#linter#PreProcess('testft', g:linter) - AssertEqual 'Only one of `lsp_config` or `lsp_config_callback` should be set', g:vader_exception - -Execute(PreProcess should throw when lsp_config_callback is not a callback): - AssertThrows call ale#linter#PreProcess('testft', { - \ 'name': 'foo', - \ 'lsp': 'socket', - \ 'address_callback': 'X', - \ 'language': 'x', - \ 'project_root_callback': 'x', - \ 'lsp_config_callback': {}, - \}) - AssertEqual '`lsp_config_callback` must be a callback if defined', g:vader_exception - -Execute(PreProcess should accept LSP configuration options via lsp_config): - let g:linter = { - \ 'name': 'x', - \ 'lsp': 'socket', - \ 'address_callback': 'X', - \ 'language_callback': 'x', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \ 'lsp_config': {'foo': 'bar'}, \} AssertEqual {'foo': 'bar'}, ale#lsp_linter#GetConfig(0, g:linter) -Execute(PreProcess should accept LSP configuration options via lsp_config as a function): +Execute(PreProcess should accept `lsp_config` as a Function): let g:linter = { \ 'name': 'x', \ 'lsp': 'socket', - \ 'address_callback': 'X', - \ 'language_callback': 'x', - \ 'project_root_callback': 'x', + \ 'address': 'X', + \ 'language': 'x', + \ 'project_root': 'x', \ 'lsp_config': {-> {'foo': 'bar'}}, \} AssertEqual {'foo': 'bar'}, ale#lsp_linter#GetConfig(0, g:linter) -Execute(PreProcess should throw when lsp_config is not a Dictionary or Function): +Execute(PreProcess should throw when `lsp_config` is not a Dictionary or Function): AssertThrows call ale#linter#PreProcess('testft', { \ 'name': 'foo', \ 'lsp': 'socket', - \ 'address_callback': 'X', + \ 'address': 'X', \ 'language': 'x', - \ 'project_root_callback': 'x', + \ 'project_root': 'x', \ 'lsp_config': 'x', \}) AssertEqual '`lsp_config` must be a Dictionary or Function if defined', g:vader_exception diff --git a/test/test_list_formatting.vader b/test/test_list_formatting.vader index dcefac53..d79a664b 100644 --- a/test/test_list_formatting.vader +++ b/test/test_list_formatting.vader @@ -36,7 +36,7 @@ After: call setqflist([]) Execute(Formatting with codes should work for the loclist): - call AddItem({'text': 'nocode'}) + call AddItem({'text': "nocode\r"}) call ale#list#SetLists(bufnr(''), g:loclist) AssertEqual @@ -79,7 +79,7 @@ Execute(Formatting with codes should work for the quickfix list): let g:ale_set_loclist = 0 let g:ale_set_quickfix = 1 - call AddItem({'text': 'nocode'}) + call AddItem({'text': "nocode\r"}) call ale#list#SetLists(bufnr(''), g:loclist) AssertEqual diff --git a/test/test_loclist_corrections.vader b/test/test_loclist_corrections.vader index 343620a5..d53b1411 100644 --- a/test/test_loclist_corrections.vader +++ b/test/test_loclist_corrections.vader @@ -1,7 +1,50 @@ +Before: + Save g:ale_filename_mappings + + let g:ale_filename_mappings = {} + After: unlet! b:temp_name unlet! b:other_bufnr + Restore + + +Execute(FixLocList should map filenames): + " Paths converted back into temporary filenames shouldn't be included. + let g:ale_filename_mappings = { + \ 'linter2': [['/xxx/', '/data/']], + \ 'linter1': [ + \ ['/bar/', '/data/special/'], + \ ['/foo/', '/data/'], + \ [ + \ ale#path#Simplify(fnamemodify(ale#util#Tempname(), ':h:h')) . '/', + \ '/x-tmp/', + \ ], + \ ], + \} + + AssertEqual + \ [ + \ '/foo/file.txt', + \ v:null, + \ '/bar/file.txt', + \ ], + \ map( + \ ale#engine#FixLocList( + \ bufnr('%'), + \ 'linter1', + \ 0, + \ [ + \ {'text': 'x', 'lnum': 1, 'filename': '/data/file.txt'}, + \ {'text': 'x', 'lnum': 1, 'filename': '/x-tmp/file.txt'}, + \ {'text': 'x', 'lnum': 1, 'filename': '/data/special/file.txt'}, + \ ], + \ ), + \ 'get(v:val, ''filename'', v:null)', + \ ) + + Given foo (Some file with lines to count): foo12345678 bar12345678 @@ -37,7 +80,7 @@ Execute(FixLocList should set all the default values correctly): \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -58,7 +101,7 @@ Execute(FixLocList should use the values we supply): \ 'nr': 42, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -87,7 +130,7 @@ Execute(FixLocList should set items with lines beyond the end to the last line): \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -108,7 +151,7 @@ Execute(FixLocList should move line 0 to line 1): \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -130,7 +173,7 @@ Execute(FixLocList should convert line and column numbers correctly): \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -164,7 +207,7 @@ Execute(FixLocList should pass on end_col values): \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -202,7 +245,7 @@ Execute(FixLocList should pass on end_lnum values): \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -227,7 +270,7 @@ Execute(FixLocList should allow subtypes to be set): \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -289,7 +332,7 @@ Execute(FixLocList should accept filenames): \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -327,7 +370,7 @@ Execute(FixLocList should interpret temporary filenames as being the current buf \ 'nr': -1, \ 'linter_name': 'foobar', \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr(''), \ 'foobar', @@ -352,7 +395,7 @@ Execute(The error code should be passed on): \ 'linter_name': 'foobar', \ 'code': 'some-code' \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -385,7 +428,7 @@ Execute(FixLocList should mark problems as coming from other sources if requeste \ 'linter_name': 'foobar', \ 'from_other_source': 1, \ }, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', @@ -407,7 +450,7 @@ Execute(character positions should be converted to byte positions): \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'end_col': 13, 'end_lnum': 1, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, \ {'lnum': 1, 'bufnr': bufnr(''), 'col': 7, 'end_col': 17, 'end_lnum': 2, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, \ {'lnum': 2, 'bufnr': bufnr(''), 'col': 17, 'linter_name': 'foobar', 'nr': -1, 'type': 'E', 'vcol': 0, 'text': 'a'}, - \], + \ ], \ ale#engine#FixLocList( \ bufnr('%'), \ 'foobar', diff --git a/test/test_organize_imports.vader b/test/test_organize_imports.vader index 137326a9..c51ff1c0 100644 --- a/test/test_organize_imports.vader +++ b/test/test_organize_imports.vader @@ -57,8 +57,9 @@ Before: call add(g:expr_list, a:expr) endfunction - function! ale#code_action#HandleCodeAction(code_action) abort + function! ale#code_action#HandleCodeAction(code_action, should_save) abort let g:handle_code_action_called = 1 + AssertEqual v:false, a:should_save call add(g:code_actions, a:code_action) endfunction diff --git a/test/test_path_uri.vader b/test/test_path_uri.vader index cc2287cb..0f2cba7e 100644 --- a/test/test_path_uri.vader +++ b/test/test_path_uri.vader @@ -1,3 +1,6 @@ +Before: + scriptencoding utf-8 + Execute(ale#path#ToURI should work for Windows paths): AssertEqual 'file:///C:/foo/bar/baz.tst', ale#path#ToURI('C:\foo\bar\baz.tst') AssertEqual 'foo/bar/baz.tst', ale#path#ToURI('foo\bar\baz.tst') @@ -62,3 +65,9 @@ Execute(ale#path#ToURI should percent encode unsafe characters): Execute(ale#path#FromURI should decode percent encodings): AssertEqual ' +:?&=', ale#path#FromURI('%20%2b%3a%3f%26%3d') + +Execute(ale#path#ToURI should handle UTF-8): + AssertEqual 'file:///T%c3%a9l%c3%a9chargement', ale#path#ToURI('/Téléchargement') + +Execute(ale#path#FromURI should handle UTF-8): + AssertEqual '/Téléchargement', ale#path#FromURI('file:///T%C3%A9l%C3%A9chargement') diff --git a/test/test_rename.vader b/test/test_rename.vader index 98e3ef30..34d9e32e 100644 --- a/test/test_rename.vader +++ b/test/test_rename.vader @@ -57,8 +57,9 @@ Before: call add(g:expr_list, a:expr) endfunction - function! ale#code_action#HandleCodeAction(code_action) abort + function! ale#code_action#HandleCodeAction(code_action, should_save) abort let g:handle_code_action_called = 1 + AssertEqual v:true, a:should_save call add(g:code_actions, a:code_action) endfunction @@ -326,6 +327,115 @@ Execute(Code actions from LSP should be handled): \ ], \ g:code_actions +Execute(DocumentChanges from LSP should be handled): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': { + \ 'documentChanges': [ + \ { + \ 'textDocument': { + \ 'version': 1.0, + \ 'uri': 'file:///foo/bar/file1.ts', + \ }, + \ 'edits': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 1, + \ 'character': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'character': 4, + \ }, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ ], + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'description': 'rename', + \ 'changes': [ + \ { + \ 'fileName': '/foo/bar/file1.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 2, + \ 'offset': 3, + \ }, + \ 'end': { + \ 'line': 4, + \ 'offset': 5, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ ], + \ } + \ ], + \ g:code_actions + +Execute(Single DocumentChange from LSP should be handled): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': { + \ 'documentChanges': { + \ 'textDocument': { + \ 'version': 1.0, + \ 'uri': 'file:///foo/bar/file1.ts', + \ }, + \ 'edits': [ + \ { + \ 'range': { + \ 'start': { + \ 'line': 1, + \ 'character': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'character': 4, + \ }, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'description': 'rename', + \ 'changes': [ + \ { + \ 'fileName': '/foo/bar/file1.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 2, + \ 'offset': 3, + \ }, + \ 'end': { + \ 'line': 4, + \ 'offset': 5, + \ }, + \ 'newText': 'bla123', + \ }, + \ ], + \ }, + \ ], + \ } + \ ], + \ g:code_actions Execute(LSP should perform no action when no result): call ale#rename#HandleLSPResponse(1, { \ 'id': 3, diff --git a/test/test_shell_detection.vader b/test/test_shell_detection.vader index 88ee462d..697054d0 100644 --- a/test/test_shell_detection.vader +++ b/test/test_shell_detection.vader @@ -15,7 +15,7 @@ Given(A file with a Bash hashbang): Execute(/bin/bash should be detected appropriately): AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'bash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with /bin/sh): #!/usr/bin/env sh -eu --foobar @@ -23,7 +23,7 @@ Given(A file with /bin/sh): Execute(/bin/sh should be detected appropriately): AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'sh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with bash as an argument to env): #!/usr/bin/env bash @@ -31,7 +31,7 @@ Given(A file with bash as an argument to env): Execute(/usr/bin/env bash should be detected appropriately): AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'bash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with a tcsh hash bang and arguments): #!/usr/bin/env tcsh -eu --foobar @@ -39,7 +39,7 @@ Given(A file with a tcsh hash bang and arguments): Execute(tcsh should be detected appropriately): AssertEqual 'tcsh', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'tcsh', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'tcsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'tcsh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with a zsh hash bang and arguments): #!/usr/bin/env zsh -eu --foobar @@ -47,7 +47,7 @@ Given(A file with a zsh hash bang and arguments): Execute(zsh should be detected appropriately): AssertEqual 'zsh', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'zsh', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'zsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'zsh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with a csh hash bang and arguments): #!/usr/bin/env csh -eu --foobar @@ -55,7 +55,7 @@ Given(A file with a csh hash bang and arguments): Execute(csh should be detected appropriately): AssertEqual 'csh', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'csh', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'csh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'csh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with a ksh hashbang): #!/bin/ksh @@ -63,7 +63,7 @@ Given(A file with a ksh hashbang): Execute(/bin/ksh should be detected appropriately): AssertEqual 'ksh', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'ksh', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'ksh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with a ksh as an argument to env): #!/usr/bin/env ksh @@ -71,7 +71,7 @@ Given(A file with a ksh as an argument to env): Execute(ksh should be detected appropriately): AssertEqual 'ksh', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'ksh', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'ksh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with a sh hash bang and arguments): #!/usr/bin/env sh -eu --foobar @@ -79,24 +79,34 @@ Given(A file with a sh hash bang and arguments): Execute(sh should be detected appropriately): AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr('')) AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr('')) - AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'sh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file without a hashbang): Execute(The bash dialect should be used for shellcheck if b:is_bash is 1): let b:is_bash = 1 - AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'bash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Execute(The sh dialect should be used for shellcheck if b:is_sh is 1): let b:is_sh = 1 - AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'sh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Execute(The ksh dialect should be used for shellcheck if b:is_kornshell is 1): let b:is_kornshell = 1 - AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'ksh', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) + +Execute(The filetype should be used as the default shell type when there is no hashbang line): + set filetype=zsh + AssertEqual 'zsh', ale#handlers#sh#GetShellType(bufnr('')) + + set filetype=tcsh + AssertEqual 'tcsh', ale#handlers#sh#GetShellType(bufnr('')) + + set filetype=python + AssertEqual '', ale#handlers#sh#GetShellType(bufnr('')) Given(A file with /bin/ash): #!/bin/ash @@ -106,7 +116,7 @@ Execute(The ash dialect should be used for the shell and the base function): AssertEqual 'ash', ale_linters#sh#shell#GetExecutable(bufnr('')) Execute(dash should be used for shellcheck, which has no ash dialect): - AssertEqual 'dash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) Given(A file with /bin/dash): #!/bin/dash @@ -116,4 +126,4 @@ Execute(The dash dialect should be used for the shell and the base function): AssertEqual 'dash', ale_linters#sh#shell#GetExecutable(bufnr('')) Execute(dash should be used for shellcheck): - AssertEqual 'dash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr('')) + AssertEqual 'dash', ale#handlers#shellcheck#GetDialectArgument(bufnr('')) diff --git a/test/test_swiftlint_executable_detection.vader b/test/test_swiftlint_executable_detection.vader index dfd4930b..ac83ff8f 100644 --- a/test/test_swiftlint_executable_detection.vader +++ b/test/test_swiftlint_executable_detection.vader @@ -23,22 +23,22 @@ Execute(React Native apps using CocoaPods should take precedence over the defaul call ale#test#SetFilename('swiftlint-test-files/react-native/testfile.swift') AssertEqual - \ ale#path#Simplify(g:dir . '/swiftlint-test-files/react-native/ios/Pods/SwiftLint/swiftlint'), - \ ale_linters#swift#swiftlint#GetExecutable(bufnr('')) + \ tolower(ale#path#Simplify(g:dir . '/swiftlint-test-files/react-native/ios/Pods/SwiftLint/swiftlint')), + \ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr(''))) Execute(CocoaPods installation should take precedence over the default executable): call ale#test#SetFilename('swiftlint-test-files/cocoapods/testfile.swift') AssertEqual - \ ale#path#Simplify(g:dir . '/swiftlint-test-files/cocoapods/Pods/SwiftLint/swiftlint'), - \ ale_linters#swift#swiftlint#GetExecutable(bufnr('')) + \ tolower(ale#path#Simplify(g:dir . '/swiftlint-test-files/cocoapods/Pods/SwiftLint/swiftlint')), + \ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr(''))) Execute(Top level CocoaPods installation should take precedence over React Native installation): call ale#test#SetFilename('swiftlint-test-files/cocoapods-and-react-native/testfile.swift') AssertEqual - \ ale#path#Simplify(g:dir . '/swiftlint-test-files/cocoapods-and-react-native/Pods/SwiftLint/swiftlint'), - \ ale_linters#swift#swiftlint#GetExecutable(bufnr('')) + \ tolower(ale#path#Simplify(g:dir . '/swiftlint-test-files/cocoapods-and-react-native/Pods/SwiftLint/swiftlint')), + \ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr(''))) Execute(use-global should override other versions): let g:ale_swift_swiftlint_use_global = 1 diff --git a/test/test_temporary_file_management.vader b/test/test_temporary_file_management.vader index 9fff1ace..bb735886 100644 --- a/test/test_temporary_file_management.vader +++ b/test/test_temporary_file_management.vader @@ -40,7 +40,7 @@ Before: \ 'name': 'testlinter', \ 'executable': has('win32') ? 'cmd' : 'echo', \ 'callback': 'TestCallback', - \ 'command_callback': 'TestCommandCallback', + \ 'command': function('TestCommandCallback'), \}) call ale#command#ClearData() diff --git a/test/test_writefile_function.vader b/test/test_writefile_function.vader index 811d59e8..53a88331 100644 --- a/test/test_writefile_function.vader +++ b/test/test_writefile_function.vader @@ -69,3 +69,49 @@ Execute(Unix file lines should be written as normal): AssertEqual \ ['first', 'second', 'third', ''], \ readfile(g:new_line_test_file, 'b') + +Execute(Newline at end of file should be preserved even when nofixeol): + call ale#test#SetFilename(g:new_line_test_file) + + setlocal buftype= + noautocmd :w + noautocmd :e! ++ff=unix + set eol + set nofixeol + + call ale#util#Writefile(bufnr(''), getline(1, '$'), g:new_line_test_file) + + AssertEqual + \ ['first', 'second', 'third', ''], + \ readfile(g:new_line_test_file, 'b') + +Execute(Newline should not be appended on write when noeol and nofixeol): + call ale#test#SetFilename(g:new_line_test_file) + + setlocal buftype= + noautocmd :w + noautocmd :e! ++ff=unix + set noeol + set nofixeol + + call ale#util#Writefile(bufnr(''), getline(1, '$'), g:new_line_test_file) + + AssertEqual + \ ['first', 'second', 'third'], + \ readfile(g:new_line_test_file, 'b') + +Execute(Newline should be appended on write when noeol and fixeol): + call ale#test#SetFilename(g:new_line_test_file) + + setlocal buftype= + noautocmd :w + noautocmd :e! ++ff=unix + set noeol + set fixeol + + call ale#util#Writefile(bufnr(''), getline(1, '$'), g:new_line_test_file) + + AssertEqual + \ ['first', 'second', 'third', ''], + \ readfile(g:new_line_test_file, 'b') + diff --git a/test/util/test_cd_string_commands.vader b/test/util/test_cd_string_commands.vader index f2102e48..85c5065f 100644 --- a/test/util/test_cd_string_commands.vader +++ b/test/util/test_cd_string_commands.vader @@ -16,5 +16,5 @@ Execute(BufferCdString should output the correct command string): call ale#test#SetFilename('foo.txt') AssertEqual - \ has('unix') ? 'cd ' . ale#Escape(g:dir) . ' && ' : 'cd /d ' . ale#Escape(g:dir) . ' && ', + \ has('unix') ? 'cd %s:h && ' : 'cd /d %s:h && ', \ ale#path#BufferCdString(bufnr('')) |