diff options
194 files changed, 5564 insertions, 758 deletions
diff --git a/.appveyor.yml b/.appveyor.yml index 54113953..aca83191 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -5,6 +5,11 @@ clone_depth: 10 # Use the directory C:\testplugin so test directories will mostly work. clone_folder: C:\testplugin +branches: + only: + - master + - /v\d+\.\d+\.(x|\d+)/ + # Cache the vim and vader directories between builds. cache: - C:\vim -> .appveyor.yml diff --git a/.github/ISSUE_TEMPLATE/report-a-bug.md b/.github/ISSUE_TEMPLATE/report-a-bug.md index 6d345c4a..4169029d 100644 --- a/.github/ISSUE_TEMPLATE/report-a-bug.md +++ b/.github/ISSUE_TEMPLATE/report-a-bug.md @@ -1,5 +1,6 @@ --- name: Report a bug +labels: bug about: Report a bug with ALE. --- diff --git a/.github/ISSUE_TEMPLATE/suggest-a-new-linter-or-fixer.md b/.github/ISSUE_TEMPLATE/suggest-a-new-linter-or-fixer.md index 75c7637f..ad235e57 100644 --- a/.github/ISSUE_TEMPLATE/suggest-a-new-linter-or-fixer.md +++ b/.github/ISSUE_TEMPLATE/suggest-a-new-linter-or-fixer.md @@ -1,5 +1,6 @@ --- name: Suggest a new linter or fixer +labels: new tool about: Suggest a new tool ALE can officially integrate with. --- diff --git a/.github/ISSUE_TEMPLATE/suggest-an-improvement.md b/.github/ISSUE_TEMPLATE/suggest-an-improvement.md index 855a6493..d39d0ac8 100644 --- a/.github/ISSUE_TEMPLATE/suggest-an-improvement.md +++ b/.github/ISSUE_TEMPLATE/suggest-an-improvement.md @@ -1,5 +1,6 @@ --- name: Suggest an improvement +labels: enhancement about: Suggest some way to improve ALE, or add a new feature. --- diff --git a/.travis.yml b/.travis.yml index d48c1e08..00baff1c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,10 @@ sudo: required services: - docker language: generic +branches: + only: + - master + - /^v\d+\.\d+\.(x|\d+)$/ env: - OPTIONS=--vim-80-only - OPTIONS=--vim-81-only @@ -35,6 +35,10 @@ If you don't care about Language Server Protocol, ALE won't load any of the code for working with it unless needed. One of ALE's general missions is that you won't pay for the features that you don't use. +**Help Wanted:** If you would like to help maintain this plugin by managing the +many issues and pull requests that are submitted, please send the author an +email at [dev@w0rp.com](mailto:dev@w0rp.com?subject=Helping%20with%20ALE). + If you enjoy this plugin, feel free to contribute or check out the author's other content at [w0rp.com](https://w0rp.com). @@ -189,6 +193,14 @@ 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: + +```vim +let g:ale_completion_tsserver_autoimport = 1 +``` + See `:help ale-completion` for more information. <a name="usage-go-to-definition"></a> diff --git a/ale_linters/c/clangd.vim b/ale_linters/c/clangd.vim index 79b600fa..c42d4497 100644 --- a/ale_linters/c/clangd.vim +++ b/ale_linters/c/clangd.vim @@ -3,9 +3,14 @@ call ale#Set('c_clangd_executable', 'clangd') call ale#Set('c_clangd_options', '') +call ale#Set('c_build_dir', '') function! ale_linters#c#clangd#GetCommand(buffer) abort - return '%e' . ale#Pad(ale#Var(a:buffer, 'c_clangd_options')) + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'c_clangd_options')) + \ . (!empty(l:build_dir) ? ' -compile-commands-dir=' . ale#Escape(l:build_dir) : '') endfunction call ale#linter#Define('c', { diff --git a/ale_linters/c/clangtidy.vim b/ale_linters/c/clangtidy.vim index f998866a..553cc23b 100644 --- a/ale_linters/c/clangtidy.vim +++ b/ale_linters/c/clangtidy.vim @@ -19,14 +19,17 @@ call ale#Set('c_clangtidy_options', '') call ale#Set('c_clangtidy_extra_options', '') call ale#Set('c_build_dir', '') -function! ale_linters#c#clangtidy#GetCommand(buffer) abort +function! ale_linters#c#clangtidy#GetCommand(buffer, output) abort let l:checks = join(ale#Var(a:buffer, 'c_clangtidy_checks'), ',') let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + let l:options = '' " Get the extra options if we couldn't find a build directory. - let l:options = empty(l:build_dir) - \ ? ale#Var(a:buffer, 'c_clangtidy_options') - \ : '' + if empty(l:build_dir) + let l:options = ale#Var(a:buffer, 'c_clangtidy_options') + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) + let l:options .= !empty(l:options) ? ale#Pad(l:cflags) : l:cflags + endif " Get the options to pass directly to clang-tidy let l:extra_options = ale#Var(a:buffer, 'c_clangtidy_extra_options') @@ -43,7 +46,7 @@ call ale#linter#Define('c', { \ 'name': 'clangtidy', \ 'output_stream': 'stdout', \ 'executable': {b -> ale#Var(b, 'c_clangtidy_executable')}, -\ 'command': function('ale_linters#c#clangtidy#GetCommand'), +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#c#clangtidy#GetCommand'))}, \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \ 'lint_file': 1, \}) diff --git a/ale_linters/cpp/clangcheck.vim b/ale_linters/cpp/clangcheck.vim index 7d32a57c..4cb04864 100644 --- a/ale_linters/cpp/clangcheck.vim +++ b/ale_linters/cpp/clangcheck.vim @@ -20,7 +20,7 @@ function! ale_linters#cpp#clangcheck#GetCommand(buffer) abort " being generated. These are only added if no build directory can be " detected. return '%e -analyze %s' - \ . (empty(l:build_dir) ? ' -extra-arg -Xclang -extra-arg -analyzer-output=text' : '') + \ . (empty(l:build_dir) ? ' --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics': '') \ . ale#Pad(l:user_options) \ . (!empty(l:build_dir) ? ' -p ' . ale#Escape(l:build_dir) : '') endfunction diff --git a/ale_linters/cpp/clangd.vim b/ale_linters/cpp/clangd.vim index fab605f4..14f3fe55 100644 --- a/ale_linters/cpp/clangd.vim +++ b/ale_linters/cpp/clangd.vim @@ -3,9 +3,14 @@ call ale#Set('cpp_clangd_executable', 'clangd') call ale#Set('cpp_clangd_options', '') +call ale#Set('c_build_dir', '') function! ale_linters#cpp#clangd#GetCommand(buffer) abort - return '%e' . ale#Pad(ale#Var(a:buffer, 'cpp_clangd_options')) + let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + + return '%e' + \ . ale#Pad(ale#Var(a:buffer, 'cpp_clangd_options')) + \ . (!empty(l:build_dir) ? ' -compile-commands-dir=' . ale#Escape(l:build_dir) : '') endfunction call ale#linter#Define('cpp', { diff --git a/ale_linters/cpp/clangtidy.vim b/ale_linters/cpp/clangtidy.vim index 085bc332..191b7b07 100644 --- a/ale_linters/cpp/clangtidy.vim +++ b/ale_linters/cpp/clangtidy.vim @@ -13,14 +13,17 @@ call ale#Set('cpp_clangtidy_options', '') call ale#Set('cpp_clangtidy_extra_options', '') call ale#Set('c_build_dir', '') -function! ale_linters#cpp#clangtidy#GetCommand(buffer) abort +function! ale_linters#cpp#clangtidy#GetCommand(buffer, output) abort let l:checks = join(ale#Var(a:buffer, 'cpp_clangtidy_checks'), ',') let l:build_dir = ale#c#GetBuildDirectory(a:buffer) + let l:options = '' " Get the extra options if we couldn't find a build directory. - let l:options = empty(l:build_dir) - \ ? ale#Var(a:buffer, 'cpp_clangtidy_options') - \ : '' + if empty(l:build_dir) + let l:options = ale#Var(a:buffer, 'cpp_clangtidy_options') + let l:cflags = ale#c#GetCFlags(a:buffer, a:output) + let l:options .= !empty(l:options) ? ale#Pad(l:cflags) : l:cflags + endif " Get the options to pass directly to clang-tidy let l:extra_options = ale#Var(a:buffer, 'cpp_clangtidy_extra_options') @@ -37,7 +40,7 @@ call ale#linter#Define('cpp', { \ 'name': 'clangtidy', \ 'output_stream': 'stdout', \ 'executable': {b -> ale#Var(b, 'cpp_clangtidy_executable')}, -\ 'command': function('ale_linters#cpp#clangtidy#GetCommand'), +\ 'command': {b -> ale#c#RunMakeCommand(b, function('ale_linters#cpp#clangtidy#GetCommand'))}, \ 'callback': 'ale#handlers#gcc#HandleGCCFormat', \ 'lint_file': 1, \}) diff --git a/ale_linters/crystal/crystal.vim b/ale_linters/crystal/crystal.vim index 3c2fefb7..8a905b12 100644 --- a/ale_linters/crystal/crystal.vim +++ b/ale_linters/crystal/crystal.vim @@ -5,6 +5,10 @@ function! ale_linters#crystal#crystal#Handle(buffer, lines) abort let l:output = [] for l:error in ale#util#FuzzyJSONDecode(a:lines, []) + if !has_key(l:error, 'file') + continue + endif + call add(l:output, { \ 'lnum': l:error.line + 0, \ 'col': l:error.column + 0, diff --git a/ale_linters/elm/elm_ls.vim b/ale_linters/elm/elm_ls.vim index 374ef8de..2fa71adb 100644 --- a/ale_linters/elm/elm_ls.vim +++ b/ale_linters/elm/elm_ls.vim @@ -3,9 +3,12 @@ call ale#Set('elm_ls_executable', 'elm-language-server') call ale#Set('elm_ls_use_global', get(g:, 'ale_use_global_executables', 1)) -call ale#Set('elm_ls_elm_path', 'elm') -call ale#Set('elm_ls_elm_format_path', 'elm-format') -call ale#Set('elm_ls_elm_test_path', 'elm-test') + +" elm-language-server will search for local and global binaries, if empty +call ale#Set('elm_ls_elm_path', '') +call ale#Set('elm_ls_elm_format_path', '') +call ale#Set('elm_ls_elm_test_path', '') +call ale#Set('elm_ls_elm_analyse_trigger', 'change') function! elm_ls#GetRootDir(buffer) abort let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm.json') @@ -15,10 +18,10 @@ endfunction function! elm_ls#GetOptions(buffer) abort return { - \ 'runtime': 'node', \ 'elmPath': ale#Var(a:buffer, 'elm_ls_elm_path'), \ 'elmFormatPath': ale#Var(a:buffer, 'elm_ls_elm_format_path'), \ 'elmTestPath': ale#Var(a:buffer, 'elm_ls_elm_test_path'), + \ 'elmAnalyseTrigger': ale#Var(a:buffer, 'elm_ls_elm_analyse_trigger'), \} endfunction diff --git a/ale_linters/erlang/dialyzer.vim b/ale_linters/erlang/dialyzer.vim index 7af64c4f..395647a0 100644 --- a/ale_linters/erlang/dialyzer.vim +++ b/ale_linters/erlang/dialyzer.vim @@ -15,10 +15,10 @@ endfunction function! ale_linters#erlang#dialyzer#FindPlt(buffer) abort let l:plt_file = '' let l:rebar3_profile = ale_linters#erlang#dialyzer#GetRebar3Profile(a:buffer) - let l:plt_file_directory = ale#path#FindNearestDirectory(a:buffer, '_build' . l:rebar3_profile) + let l:plt_file_directory = ale#path#FindNearestDirectory(a:buffer, '_build/' . l:rebar3_profile) if !empty(l:plt_file_directory) - let l:plt_file = split(globpath(l:plt_file_directory, '/*_plt'), '\n') + let l:plt_file = globpath(l:plt_file_directory, '*_plt', 0, 1) endif if !empty(l:plt_file) diff --git a/ale_linters/eruby/ruumba.vim b/ale_linters/eruby/ruumba.vim index e68bb51d..2e84acf7 100644 --- a/ale_linters/eruby/ruumba.vim +++ b/ale_linters/eruby/ruumba.vim @@ -8,7 +8,7 @@ call ale#Set('eruby_ruumba_options', '') function! ale_linters#eruby#ruumba#GetCommand(buffer) abort let l:executable = ale#Var(a:buffer, 'eruby_ruumba_executable') - return ale#handlers#ruby#EscapeExecutable(l:executable, 'ruumba') + 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')) diff --git a/ale_linters/graphql/eslint.vim b/ale_linters/graphql/eslint.vim index 654b8c17..aed1a371 100644 --- a/ale_linters/graphql/eslint.vim +++ b/ale_linters/graphql/eslint.vim @@ -5,5 +5,5 @@ call ale#linter#Define('graphql', { \ 'name': 'eslint', \ 'executable': function('ale#handlers#eslint#GetExecutable'), \ 'command': function('ale#handlers#eslint#GetCommand'), -\ 'callback': 'ale#handlers#eslint#Handle', +\ 'callback': 'ale#handlers#eslint#HandleJSON', \}) diff --git a/ale_linters/ink/ls.vim b/ale_linters/ink/ls.vim new file mode 100644 index 00000000..1cc93583 --- /dev/null +++ b/ale_linters/ink/ls.vim @@ -0,0 +1,35 @@ +" Author: Andreww Hayworth <ahayworth@gmail.com> +" Description: Integrate ALE with ink-language-server + +call ale#Set('ink_ls_executable', 'ink-language-server') +call ale#Set('ink_ls_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('ink_ls_initialization_options', {}) + +function! ale_linters#ink#ls#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'ink_ls', [ + \ 'ink-language-server', + \ 'node_modules/.bin/ink-language-server', + \]) +endfunction + +function! ale_linters#ink#ls#GetCommand(buffer) abort + let l:executable = ale_linters#ink#ls#GetExecutable(a:buffer) + + return ale#Escape(l:executable) . ' --stdio' +endfunction + +function! ale_linters#ink#ls#FindProjectRoot(buffer) abort + let l:main_file = get(ale#Var(a:buffer, 'ink_ls_initialization_options'), 'mainStoryPath', 'main.ink') + let l:config = ale#path#ResolveLocalPath(a:buffer, l:main_file, expand('#' . a:buffer . ':p')) + + return ale#path#Dirname(l:config) +endfunction + +call ale#linter#Define('ink', { +\ 'name': 'ink-language-server', +\ 'lsp': 'stdio', +\ 'executable': function('ale_linters#ink#ls#GetExecutable'), +\ 'command': function('ale_linters#ink#ls#GetCommand'), +\ 'project_root': function('ale_linters#ink#ls#FindProjectRoot'), +\ 'initialization_options': {b -> ale#Var(b, 'ink_ls_initialization_options')}, +\}) diff --git a/ale_linters/javascript/flow.vim b/ale_linters/javascript/flow.vim index 3135e2e9..3135e2e9 100755..100644 --- a/ale_linters/javascript/flow.vim +++ b/ale_linters/javascript/flow.vim diff --git a/ale_linters/javascript/standard.vim b/ale_linters/javascript/standard.vim index 4cd2c303..1990adce 100644 --- a/ale_linters/javascript/standard.vim +++ b/ale_linters/javascript/standard.vim @@ -7,7 +7,9 @@ call ale#Set('javascript_standard_options', '') function! ale_linters#javascript#standard#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'javascript_standard', [ + \ 'node_modules/standardx/bin/cmd.js', \ 'node_modules/standard/bin/cmd.js', + \ 'node_modules/semistandard/bin/cmd.js', \ 'node_modules/.bin/standard', \]) endfunction diff --git a/ale_linters/less/lessc.vim b/ale_linters/less/lessc.vim index 4ec8b00e..4ec8b00e 100755..100644 --- a/ale_linters/less/lessc.vim +++ b/ale_linters/less/lessc.vim diff --git a/ale_linters/markdown/mdl.vim b/ale_linters/markdown/mdl.vim index 305f5359..fd44de6e 100644 --- a/ale_linters/markdown/mdl.vim +++ b/ale_linters/markdown/mdl.vim @@ -17,18 +17,17 @@ function! ale_linters#markdown#mdl#GetCommand(buffer) abort let l:options = ale#Var(a:buffer, 'markdown_mdl_options') return ale#Escape(l:executable) . l:exec_args - \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -j' . (!empty(l:options) ? ' ' . l:options : '') endfunction function! ale_linters#markdown#mdl#Handle(buffer, lines) abort - " matches: '(stdin):173: MD004 Unordered list style' - let l:pattern = ':\(\d*\): \(.*\)$' let l:output = [] - for l:match in ale#util#GetMatches(a:lines, l:pattern) + for l:error in ale#util#FuzzyJSONDecode(a:lines, []) call add(l:output, { - \ 'lnum': l:match[1] + 0, - \ 'text': l:match[2], + \ 'lnum': l:error['line'], + \ 'code': l:error['rule'] . '/' . join(l:error['aliases'], '/'), + \ 'text': l:error['description'], \ 'type': 'W', \}) endfor diff --git a/ale_linters/nim/nimcheck.vim b/ale_linters/nim/nimcheck.vim index b5796dcd..b739ca04 100644 --- a/ale_linters/nim/nimcheck.vim +++ b/ale_linters/nim/nimcheck.vim @@ -1,6 +1,15 @@ " Author: Baabelfish " Description: Typechecking for nim files +let s:end_col_patterns = [ +\ '\v''([^'']+)'' is declared but not used.*', +\ '\videntifier expected, but found ''([^'']+)''', +\ '\vimported and not used: ''([^'']+)''.*', +\ '\vundeclared identifier: ''([^'']+)''', +\ '\v''([^'']+)'' cannot be assigned to', +\ '\vredefinition of ''([^'']+)'';', +\] + function! ale_linters#nim#nimcheck#Handle(buffer, lines) abort let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p:t') let l:pattern = '^\(.\+\.nim\)(\(\d\+\), \(\d\+\)) \(.\+\)' @@ -43,6 +52,11 @@ function! ale_linters#nim#nimcheck#Handle(buffer, lines) abort let l:item.code = l:code_match[2] endif + " Find position end_col. + for l:col_match in ale#util#GetMatches(l:item.text, s:end_col_patterns) + let l:item.end_col = l:item.col + len(l:col_match[1]) - 1 + endfor + call add(l:output, l:item) endfor diff --git a/ale_linters/nim/nimlsp.vim b/ale_linters/nim/nimlsp.vim new file mode 100644 index 00000000..5d041043 --- /dev/null +++ b/ale_linters/nim/nimlsp.vim @@ -0,0 +1,33 @@ +" Author: jeremija <https://github.com/jeremija> +" Description: Support for nimlsp (language server for nim) + +call ale#Set('nim_nimlsp_nim_sources', '') + +function! ale_linters#nim#nimlsp#GetProjectRoot(buffer) abort + let l:project_root = ale#path#FindNearestDirectory(a:buffer, '.git') + + if !empty(l:project_root) + return fnamemodify(l:project_root, ':h:h') + endif + + return '' +endfunction + +function! ale_linters#nim#nimlsp#GetCommand(buffer) abort + let l:nim_sources = ale#Var(a:buffer, 'nim_nimlsp_nim_sources') + + if !empty(l:nim_sources) + let l:nim_sources = ale#Escape(l:nim_sources) + endif + + return '%e' . ale#Pad(l:nim_sources) +endfunction + +call ale#linter#Define('nim', { +\ 'name': 'nimlsp', +\ 'lsp': 'stdio', +\ 'executable': 'nimlsp', +\ 'command': function('ale_linters#nim#nimlsp#GetCommand'), +\ 'language': 'nim', +\ 'project_root': function('ale_linters#nim#nimlsp#GetProjectRoot'), +\}) diff --git a/ale_linters/php/psalm.vim b/ale_linters/php/psalm.vim index 3cdb026a..ab4dbbc9 100644 --- a/ale_linters/php/psalm.vim +++ b/ale_linters/php/psalm.vim @@ -1,7 +1,8 @@ " Author: Matt Brown <https://github.com/muglug> " Description: plugin for Psalm, static analyzer for PHP -call ale#Set('psalm_langserver_executable', 'psalm-language-server') +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)) function! ale_linters#php#psalm#GetProjectRoot(buffer) abort @@ -10,12 +11,16 @@ function! ale_linters#php#psalm#GetProjectRoot(buffer) abort return !empty(l:git_path) ? fnamemodify(l:git_path, ':h:h') : '' endfunction +function! ale_linters#php#psalm#GetCommand(buffer) abort + return '%e --language-server' . ale#Pad(ale#Var(a:buffer, 'psalm_langserver_options')) +endfunction + call ale#linter#Define('php', { \ 'name': 'psalm', \ 'lsp': 'stdio', \ 'executable': {b -> ale#node#FindExecutable(b, 'psalm_langserver', [ -\ 'vendor/bin/psalm-language-server', +\ 'vendor/bin/psalm', \ ])}, -\ 'command': '%e', +\ 'command': function('ale_linters#php#psalm#GetCommand'), \ 'project_root': function('ale_linters#php#psalm#GetProjectRoot'), \}) diff --git a/ale_linters/powershell/powershell.vim b/ale_linters/powershell/powershell.vim index a63191fd..5f49f72c 100755..100644 --- a/ale_linters/powershell/powershell.vim +++ b/ale_linters/powershell/powershell.vim @@ -12,6 +12,7 @@ endfunction " https://rkeithhill.wordpress.com/2007/10/30/powershell-quicktip-preparsing-scripts-to-check-for-syntax-errors/ function! ale_linters#powershell#powershell#GetCommand(buffer) abort let l:script = ['Param($Script); + \ $ErrorView = "Normal"; \ trap {$_;continue} & { \ $Contents = Get-Content -Path $Script; \ $Contents = [string]::Join([Environment]::NewLine, $Contents); diff --git a/ale_linters/python/mypy.vim b/ale_linters/python/mypy.vim index c4c6507f..94dfae7d 100644 --- a/ale_linters/python/mypy.vim +++ b/ale_linters/python/mypy.vim @@ -3,6 +3,7 @@ call ale#Set('python_mypy_executable', 'mypy') call ale#Set('python_mypy_ignore_invalid_syntax', 0) +call ale#Set('python_mypy_show_notes', 1) call ale#Set('python_mypy_options', '') call ale#Set('python_mypy_use_global', get(g:, 'ale_use_global_executables', 0)) call ale#Set('python_mypy_auto_pipenv', 0) @@ -18,6 +19,15 @@ endfunction " The directory to change to before running mypy function! s:GetDir(buffer) abort + " If we find a directory with "mypy.ini" in it use that, + " else try and find the "python project" root, or failing + " that, run from the same folder as the current file + for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h')) + if filereadable(l:path . '/mypy.ini') + return l:path + endif + endfor + let l:project_root = ale#python#FindProjectRoot(a:buffer) return !empty(l:project_root) @@ -51,7 +61,16 @@ function! ale_linters#python#mypy#Handle(buffer, lines) abort " Lines like these should be ignored below: " " file.py:4: note: (Stub files are from https://github.com/python/typeshed) - let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?: (error|warning): (.+)$' + + let l:types = 'error|warning' + + if ale#Var(a:buffer, 'python_mypy_show_notes') + let l:types = 'error|warning|note' + endif + + let l:pattern = '\v^([a-zA-Z]?:?[^:]+):(\d+):?(\d+)?: (' + \ . l:types + \ . '): (.+)$' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) @@ -65,7 +84,7 @@ function! ale_linters#python#mypy#Handle(buffer, lines) abort \ 'filename': ale#path#GetAbsPath(l:dir, l:match[1]), \ 'lnum': l:match[2] + 0, \ 'col': l:match[3] + 0, - \ 'type': l:match[4] is# 'error' ? 'E' : 'W', + \ 'type': l:match[4] is# 'error' ? 'E' : (l:match[4] is# 'note' ? 'I': 'W'), \ 'text': l:match[5], \}) endfor @@ -78,4 +97,5 @@ call ale#linter#Define('python', { \ 'executable': function('ale_linters#python#mypy#GetExecutable'), \ 'command': function('ale_linters#python#mypy#GetCommand'), \ 'callback': 'ale_linters#python#mypy#Handle', +\ 'output_stream': 'both' \}) diff --git a/ale_linters/ruby/brakeman.vim b/ale_linters/ruby/brakeman.vim index a8088080..2dc48740 100644 --- a/ale_linters/ruby/brakeman.vim +++ b/ale_linters/ruby/brakeman.vim @@ -36,7 +36,7 @@ function! ale_linters#ruby#brakeman#GetCommand(buffer) abort let l:executable = ale#Var(a:buffer, 'ruby_brakeman_executable') - return ale#handlers#ruby#EscapeExecutable(l:executable, 'brakeman') + return ale#ruby#EscapeExecutable(l:executable, 'brakeman') \ . ' -f json -q ' \ . ale#Var(a:buffer, 'ruby_brakeman_options') \ . ' -p ' . ale#Escape(l:rails_root) diff --git a/ale_linters/ruby/debride.vim b/ale_linters/ruby/debride.vim new file mode 100644 index 00000000..3b2cc443 --- /dev/null +++ b/ale_linters/ruby/debride.vim @@ -0,0 +1,42 @@ +" Author: Eddie Lebow https://github.com/elebow +" Description: debride, a dead method detector for Ruby files + +call ale#Set('ruby_debride_executable', 'debride') +call ale#Set('ruby_debride_options', '') + +function! ale_linters#ruby#debride#GetCommand(buffer) abort + let l:executable = ale#Var(a:buffer, 'ruby_debride_executable') + + return ale#ruby#EscapeExecutable(l:executable, 'debride') + \ . ale#Var(a:buffer, 'ruby_debride_options') + \ . ' %s' +endfunction + +function! ale_linters#ruby#debride#HandleOutput(buffer, lines) abort + let l:output = [] + + for l:line in a:lines + if l:line !~# '^ ' + continue + endif + + let l:elements = split(l:line) + let l:method_name = l:elements[0] + let l:lnum = split(l:elements[1], ':')[1] + + call add(l:output, { + \ 'lnum': 0 + l:lnum, + \ 'text': 'Possible unused method: ' . l:method_name, + \ 'type': 'W', + \}) + endfor + + return l:output +endfunction + +call ale#linter#Define('ruby', { +\ 'name': 'debride', +\ 'executable': {b -> ale#Var(b, 'ruby_debride_executable')}, +\ 'command': function('ale_linters#ruby#debride#GetCommand'), +\ 'callback': 'ale_linters#ruby#debride#HandleOutput', +\}) diff --git a/ale_linters/ruby/rails_best_practices.vim b/ale_linters/ruby/rails_best_practices.vim index a94fb671..36646647 100644 --- a/ale_linters/ruby/rails_best_practices.vim +++ b/ale_linters/ruby/rails_best_practices.vim @@ -33,7 +33,7 @@ function! ale_linters#ruby#rails_best_practices#GetCommand(buffer) abort let l:output_file = has('win32') ? '%t ' : '/dev/stdout ' let l:cat_file = has('win32') ? '; type %t' : '' - return ale#handlers#ruby#EscapeExecutable(l:executable, 'rails_best_practices') + return ale#ruby#EscapeExecutable(l:executable, 'rails_best_practices') \ . ' --silent -f json --output-file ' . l:output_file \ . ale#Var(a:buffer, 'ruby_rails_best_practices_options') \ . ale#Escape(l:rails_root) diff --git a/ale_linters/ruby/reek.vim b/ale_linters/ruby/reek.vim index e39e366f..226b452e 100644 --- a/ale_linters/ruby/reek.vim +++ b/ale_linters/ruby/reek.vim @@ -14,7 +14,7 @@ function! ale_linters#ruby#reek#GetCommand(buffer, version) abort \ ? ' --stdin-filename %s' \ : '' - return ale#handlers#ruby#EscapeExecutable(l:executable, 'reek') + return ale#ruby#EscapeExecutable(l:executable, 'reek') \ . ' -f json --no-progress --no-color --force-exclusion' \ . l:display_name_args endfunction diff --git a/ale_linters/ruby/rubocop.vim b/ale_linters/ruby/rubocop.vim index 8b9e9c84..410ed0ea 100644 --- a/ale_linters/ruby/rubocop.vim +++ b/ale_linters/ruby/rubocop.vim @@ -7,7 +7,7 @@ call ale#Set('ruby_rubocop_options', '') function! ale_linters#ruby#rubocop#GetCommand(buffer) abort let l:executable = ale#Var(a:buffer, 'ruby_rubocop_executable') - return ale#handlers#ruby#EscapeExecutable(l:executable, 'rubocop') + 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')) diff --git a/ale_linters/ruby/sorbet.vim b/ale_linters/ruby/sorbet.vim index ee765a6e..cae0683c 100644 --- a/ale_linters/ruby/sorbet.vim +++ b/ale_linters/ruby/sorbet.vim @@ -5,7 +5,7 @@ function! ale_linters#ruby#sorbet#GetCommand(buffer) abort let l:executable = ale#Var(a:buffer, 'ruby_sorbet_executable') let l:options = ale#Var(a:buffer, 'ruby_sorbet_options') - return ale#handlers#ruby#EscapeExecutable(l:executable, 'srb') + return ale#ruby#EscapeExecutable(l:executable, 'srb') \ . ' tc' \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --lsp --disable-watchman' diff --git a/ale_linters/ruby/standardrb.vim b/ale_linters/ruby/standardrb.vim index f075a7d5..f751e803 100644 --- a/ale_linters/ruby/standardrb.vim +++ b/ale_linters/ruby/standardrb.vim @@ -8,7 +8,7 @@ call ale#Set('ruby_standardrb_options', '') function! ale_linters#ruby#standardrb#GetCommand(buffer) abort let l:executable = ale#Var(a:buffer, 'ruby_standardrb_executable') - return ale#handlers#ruby#EscapeExecutable(l:executable, 'standardrb') + 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')) diff --git a/ale_linters/scala/metals.vim b/ale_linters/scala/metals.vim new file mode 100644 index 00000000..f78c7119 --- /dev/null +++ b/ale_linters/scala/metals.vim @@ -0,0 +1,48 @@ +" Author: Jeffrey Lau - https://github.com/zoonfafer +" Description: Metals Language Server for Scala https://scalameta.org/metals/ + +call ale#Set('scala_metals_executable', 'metals-vim') +call ale#Set('scala_metals_project_root', '') + +function! ale_linters#scala#metals#GetProjectRoot(buffer) abort + let l:project_root = ale#Var(a:buffer, 'scala_metals_project_root') + + if !empty(l:project_root) + return l:project_root + endif + + let l:potential_roots = [ + \ 'build.sc', + \ 'build.sbt', + \ '.bloop', + \ '.metals', + \] + + for l:root in l:potential_roots + let l:project_root = ale#path#ResolveLocalPath( + \ a:buffer, + \ l:root, + \ '' + \) + + if !empty(l:project_root) + return fnamemodify( + \ l:project_root, + \ ':h', + \) + endif + endfor +endfunction + +function! ale_linters#scala#metals#GetCommand(buffer) abort + return '%e' . ale#Pad('stdio') +endfunction + +call ale#linter#Define('scala', { +\ 'name': 'metals', +\ 'lsp': 'stdio', +\ 'language': 'scala', +\ 'executable': {b -> ale#Var(b, 'scala_metals_executable')}, +\ 'command': function('ale_linters#scala#metals#GetCommand'), +\ 'project_root': function('ale_linters#scala#metals#GetProjectRoot'), +\}) diff --git a/ale_linters/sh/shell.vim b/ale_linters/sh/shell.vim index 189dc21d..171fe64e 100644 --- a/ale_linters/sh/shell.vim +++ b/ale_linters/sh/shell.vim @@ -34,8 +34,10 @@ function! ale_linters#sh#shell#Handle(buffer, lines) abort " Matches patterns line the following: " " bash: line 13: syntax error near unexpected token `d' + " bash:行0: 未预期的符号“done”附近有语法错误 + " bash: 列 90: 尋找匹配的「"」時遇到了未預期的檔案結束符 " sh: 11: Syntax error: "(" unexpected - let l:pattern = '\v(line |: ?)(\d+): (.+)$' + let l:pattern = '\v([^:]+:\D*)(\d+): (.+)$' let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) diff --git a/ale_linters/solidity/solc.vim b/ale_linters/solidity/solc.vim new file mode 100644 index 00000000..e4f220ac --- /dev/null +++ b/ale_linters/solidity/solc.vim @@ -0,0 +1,35 @@ +" Author: Karl Bartel <karl42@gmail.com> - http://karl.berlin/ +" Description: Report solc compiler errors in Solidity code + +call ale#Set('solidity_solc_options', '') + +function! ale_linters#solidity#solc#Handle(buffer, lines) abort + " Matches patterns like the following: + " /path/to/file/file.sol:1:10: Error: Identifier not found or not unique. + let l:pattern = '\v^[^:]+:(\d+):(\d+): (Error|Warning): (.*)$' + let l:output = [] + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + let l:isError = l:match[3] is? 'error' + call add(l:output, { + \ 'lnum': l:match[1] + 0, + \ 'col': l:match[2] + 0, + \ 'text': l:match[4], + \ 'type': l:isError ? 'E' : 'W', + \}) + endfor + + return l:output +endfunction + +function! ale_linters#solidity#solc#GetCommand(buffer) abort + return 'solc' . ale#Pad(ale#Var(a:buffer, 'solidity_solc_options')) . ' %s' +endfunction + +call ale#linter#Define('solidity', { +\ 'name': 'solc', +\ 'executable': 'solc', +\ 'command': function('ale_linters#solidity#solc#GetCommand'), +\ 'callback': 'ale_linters#solidity#solc#Handle', +\ 'output_stream': 'stderr', +\}) diff --git a/ale_linters/terraform/terraform.vim b/ale_linters/terraform/terraform.vim index 0429cb7a..0429cb7a 100755..100644 --- a/ale_linters/terraform/terraform.vim +++ b/ale_linters/terraform/terraform.vim diff --git a/ale_linters/terraform/tflint.vim b/ale_linters/terraform/tflint.vim index 6d54a8b1..f57ee6b6 100644 --- a/ale_linters/terraform/tflint.vim +++ b/ale_linters/terraform/tflint.vim @@ -9,23 +9,69 @@ call ale#Set('terraform_tflint_executable', 'tflint') function! ale_linters#terraform#tflint#Handle(buffer, lines) abort let l:output = [] + let l:pattern = '\v^(.*):(\d+),(\d+)-(\d+)?,?(\d+): (.{-1,}); (.+)$' + let l:json = ale#util#FuzzyJSONDecode(a:lines, {}) - for l:error in ale#util#FuzzyJSONDecode(a:lines, []) - if l:error.type is# 'ERROR' - let l:type = 'E' - elseif l:error.type is# 'NOTICE' - let l:type = 'I' - else - let l:type = 'W' - endif - - call add(l:output, { - \ 'lnum': l:error.line, - \ 'text': l:error.message, - \ 'type': l:type, - \ 'code': l:error.detector, - \}) - endfor + " This is a rough test for tflint's output format + " On versions prior to 0.11 it outputs all errors as a single level list + if type(l:json) is v:t_list + for l:error in l:json + if l:error.type is# 'ERROR' + let l:type = 'E' + elseif l:error.type is# 'NOTICE' + let l:type = 'I' + else + let l:type = 'W' + endif + + call add(l:output, { + \ 'lnum': l:error.line, + \ 'text': l:error.message, + \ 'type': l:type, + \ 'code': l:error.detector, + \}) + endfor + else + for l:error in get(l:json, 'errors', []) + for l:match in ale#util#GetMatches(l:error.message, [l:pattern]) + if l:match[4] is# '' + let l:match[4] = l:match[2] + endif + + call add(l:output, { + \ 'filename': l:match[1], + \ 'lnum': str2nr(l:match[2]), + \ 'col': str2nr(l:match[3]), + \ 'end_lnum': str2nr(l:match[4]), + \ 'end_col': str2nr(l:match[5]), + \ 'text': l:match[7], + \ 'code': l:match[6], + \ 'type': 'E', + \}) + endfor + endfor + + for l:error in get(l:json, 'issues', []) + if l:error.rule.severity is# 'ERROR' + let l:type = 'E' + elseif l:error.rule.severity is# 'NOTICE' + let l:type = 'I' + else + let l:type = 'W' + endif + + call add(l:output, { + \ 'filename': l:error.range.filename, + \ 'lnum': l:error.range.start.line, + \ 'col': l:error.range.start.column, + \ 'end_lnum': l:error.range.end.line, + \ 'end_col': l:error.range.end.column, + \ 'text': l:error.message, + \ 'code': l:error.rule.name, + \ 'type': l:type, + \}) + endfor + endif return l:output endfunction diff --git a/ale_linters/typescript/standard.vim b/ale_linters/typescript/standard.vim new file mode 100644 index 00000000..da8f14eb --- /dev/null +++ b/ale_linters/typescript/standard.vim @@ -0,0 +1,31 @@ +" Author: Ahmed El Gabri <@ahmedelgabri> +" Description: standardjs for typescript files + +call ale#Set('typescript_standard_executable', 'standard') +call ale#Set('typescript_standard_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('typescript_standard_options', '') + +function! ale_linters#typescript#standard#GetExecutable(buffer) abort + return ale#node#FindExecutable(a:buffer, 'typescript_standard', [ + \ 'node_modules/standardx/bin/cmd.js', + \ 'node_modules/standard/bin/cmd.js', + \ 'node_modules/.bin/standard', + \]) +endfunction + +function! ale_linters#typescript#standard#GetCommand(buffer) abort + let l:executable = ale_linters#typescript#standard#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'typescript_standard_options') + + return ale#node#Executable(a:buffer, l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' --stdin %s' +endfunction + +" standard uses eslint and the output format is the same +call ale#linter#Define('typescript', { +\ 'name': 'standard', +\ 'executable': function('ale_linters#typescript#standard#GetExecutable'), +\ 'command': function('ale_linters#typescript#standard#GetCommand'), +\ 'callback': 'ale#handlers#eslint#Handle', +\}) diff --git a/ale_linters/verilog/vlog.vim b/ale_linters/verilog/vlog.vim index 37d21c4c..951e2037 100644 --- a/ale_linters/verilog/vlog.vim +++ b/ale_linters/verilog/vlog.vim @@ -24,6 +24,20 @@ function! ale_linters#verilog#vlog#Handle(buffer, lines) abort \}) endfor + "Matches patterns like the following: + "** 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\+\(.*\)' + + for l:match in ale#util#GetMatches(a:lines, l:pattern) + call add(l:output, { + \ 'lnum': l:match[3] + 0, + \ 'type': l:match[1] is? 'Error' ? 'E' : 'W', + \ 'text': l:match[2] . ' ' . l:match[4], + \}) + endfor + return l:output endfunction diff --git a/autoload/ale.vim b/autoload/ale.vim index 3a4e79c8..ee1a0d54 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -5,11 +5,18 @@ " Strings used for severity in the echoed message let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error') let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info') +let g:ale_echo_msg_log_str = get(g:, 'ale_echo_msg_log_str', 'Log') let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning') " Ignoring linters, for disabling some, or ignoring LSP diagnostics. let g:ale_linters_ignore = get(g:, 'ale_linters_ignore', {}) let g:ale_disable_lsp = get(g:, 'ale_disable_lsp', 0) +" LSP window/showMessage format +let g:ale_lsp_show_message_format = get(g:, 'ale_lsp_show_message_format', '%severity%:%linter%: %s') +" Valid values mimic LSP definitions (error, warning and information; log is +" never shown) +let g:ale_lsp_show_message_severity = get(g:, 'ale_lsp_show_message_severity', 'error') + let s:lint_timer = -1 let s:getcmdwintype_exists = exists('*getcmdwintype') @@ -156,7 +163,7 @@ function! ale#Queue(delay, ...) abort endif endfunction -let s:current_ale_version = [2, 5, 0] +let s:current_ale_version = [2, 6, 0] " A function used to check for ALE features in files outside of the project. function! ale#Has(feature) abort diff --git a/autoload/ale/ant.vim b/autoload/ale/ant.vim index 689b444b..7d02484e 100644 --- a/autoload/ale/ant.vim +++ b/autoload/ale/ant.vim @@ -1,4 +1,4 @@ -" Author: Andrew Lee <andrewl@mbda.fun>. +" Author: Andrew Lee <andrew.lambda@tuta.io>. " Inspired by ale/gradle.vim by Michael Pardo <michael@michaelpardo.com> " Description: Functions for working with Ant projects. diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim index dac5efb7..291edcee 100644 --- a/autoload/ale/assert.vim +++ b/autoload/ale/assert.vim @@ -267,14 +267,22 @@ function! ale#assert#TearDownLinterTest() abort endif endfunction -function! ale#assert#SetUpFixerTest(filetype, name) abort +function! ale#assert#SetUpFixerTest(filetype, name, ...) abort + " If the suffix of the option names format is different, an additional + " argument can be used for that instead. + if a:0 > 1 + throw 'Too many arguments' + endif + " Set up a marker so ALE doesn't create real random temporary filenames. let g:ale_create_dummy_temporary_file = 1 let l:function_name = ale#fix#registry#GetFunc(a:name) let s:FixerFunction = function(l:function_name) - let l:prefix = 'ale_' . a:filetype . '_' . a:name + let l:option_suffix = get(a:000, 0, a:name) + let l:prefix = 'ale_' . a:filetype . '_' + \ . substitute(l:option_suffix, '-', '_', 'g') let b:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix' for l:key in filter(keys(g:), b:filter_expr) @@ -286,7 +294,7 @@ function! ale#assert#SetUpFixerTest(filetype, name) abort unlet b:[l:key] endfor - execute 'runtime autoload/ale/fixers/' . a:name . '.vim' + execute 'runtime autoload/ale/fixers/' . substitute(a:name, '-', '_', 'g') . '.vim' if !exists('g:dir') call ale#test#SetDirectory('/testplugin/test/fixers') diff --git a/autoload/ale/c.vim b/autoload/ale/c.vim index 2d2083da..9b428700 100644 --- a/autoload/ale/c.vim +++ b/autoload/ale/c.vim @@ -28,76 +28,107 @@ function! ale#c#GetBuildDirectory(buffer) abort return ale#path#Dirname(l:json_file) endfunction -function! ale#c#AreSpecialCharsBalanced(option) abort - " Escape \" - let l:option_escaped = substitute(a:option, '\\"', '', 'g') - - " Retain special chars only - let l:special_chars = substitute(l:option_escaped, '[^"''()`]', '', 'g') - let l:special_chars = split(l:special_chars, '\zs') - - " Check if they are balanced +function! ale#c#ShellSplit(line) abort let l:stack = [] + let l:args = [''] + let l:prev = '' - for l:char in l:special_chars - if l:char is# ')' - if len(l:stack) == 0 || get(l:stack, -1) isnot# '(' - return 0 + for l:char in split(a:line, '\zs') + if l:char is# '''' + if len(l:stack) > 0 && get(l:stack, -1) is# '''' + call remove(l:stack, -1) + elseif (len(l:stack) == 0 || get(l:stack, -1) isnot# '"') && l:prev isnot# '\' + call add(l:stack, l:char) endif - - call remove(l:stack, -1) - elseif l:char is# '(' - call add(l:stack, l:char) - else + elseif (l:char is# '"' || l:char is# '`') && l:prev isnot# '\' if len(l:stack) > 0 && get(l:stack, -1) is# l:char call remove(l:stack, -1) - else + elseif len(l:stack) == 0 || get(l:stack, -1) isnot# '''' + call add(l:stack, l:char) + endif + elseif (l:char is# '(' || l:char is# '[' || l:char is# '{') && l:prev isnot# '\' + if len(l:stack) == 0 || get(l:stack, -1) isnot# '''' call add(l:stack, l:char) endif + elseif (l:char is# ')' || l:char is# ']' || l:char is# '}') && l:prev isnot# '\' + if len(l:stack) > 0 && get(l:stack, -1) is# {')': '(', ']': '[', '}': '{'}[l:char] + call remove(l:stack, -1) + endif + elseif l:char is# ' ' && len(l:stack) == 0 + if len(get(l:args, -1)) > 0 + call add(l:args, '') + endif + + continue endif + + let l:args[-1] = get(l:args, -1) . l:char endfor - return len(l:stack) == 0 + return l:args endfunction function! ale#c#ParseCFlags(path_prefix, cflag_line) abort - let l:split_lines = split(a:cflag_line) + let l:cflags_list = [] + + let l:split_lines = ale#c#ShellSplit(a:cflag_line) let l:option_index = 0 while l:option_index < len(l:split_lines) - let l:next_option_index = l:option_index + 1 - - " Join space-separated option - while l:next_option_index < len(l:split_lines) - \&& stridx(l:split_lines[l:next_option_index], '-') != 0 - let l:next_option_index += 1 - endwhile - - let l:option = join(l:split_lines[l:option_index : l:next_option_index-1], ' ') - call remove(l:split_lines, l:option_index, l:next_option_index-1) - call insert(l:split_lines, l:option, l:option_index) - - " Ignore invalid or conflicting options - if stridx(l:option, '-') != 0 - \|| stridx(l:option, '-o') == 0 - \|| stridx(l:option, '-c') == 0 - call remove(l:split_lines, l:option_index) - let l:option_index = l:option_index - 1 - " Fix relative path - elseif stridx(l:option, '-I') == 0 - if !(stridx(l:option, ':') == 2+1 || stridx(l:option, '/') == 2+0) - let l:option = '-I' . a:path_prefix . s:sep . l:option[2:] - call remove(l:split_lines, l:option_index) - call insert(l:split_lines, l:option, l:option_index) + let l:option = l:split_lines[l:option_index] + let l:option_index = l:option_index + 1 + + " Include options, that may need relative path fix + if stridx(l:option, '-I') == 0 + \ || stridx(l:option, '-iquote') == 0 + \ || stridx(l:option, '-isystem') == 0 + \ || stridx(l:option, '-idirafter') == 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:option_index = l:option_index + 1 endif - endif - let l:option_index = l:option_index + 1 - endwhile + " Fix relative paths if needed + if stridx(l:arg, s:sep) != 0 && stridx(l:arg, '/') != 0 + 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) + endif - call uniq(l:split_lines) + call add(l:cflags_list, l:option) + call add(l:cflags_list, 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) - return join(l:split_lines, ' ') + if l:option is# '-D' || l:option is# '-B' + call add(l:cflags_list, l:split_lines[l:option_index]) + let l:option_index = l:option_index + 1 + 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]) + 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, '-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) + endif + endwhile + + return join(l:cflags_list, ' ') endfunction function! ale#c#ParseCFlagsFromMakeOutput(buffer, make_output) abort @@ -234,6 +265,16 @@ 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, ' ') + endif + + return '' +endfunction + function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort " Search for an exact file match first. let l:basename = tolower(expand('#' . a:buffer . ':t')) @@ -256,15 +297,14 @@ function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort for l:item in l:file_list " Load the flags for this file, or for a source file matching the " header file. - if has_key(l:item, 'command') - \&& ( + if ( \ bufnr(l:item.file) is a:buffer \ || ( \ !empty(l:source_file) \ && l:item.file[-len(l:source_file):] is? l:source_file \ ) \) - return ale#c#ParseCFlags(l:item.directory, l:item.command) + return ale#c#ParseCFlags(l:item.directory, ale#c#GetCompileCommand(l:item)) endif endfor @@ -276,8 +316,7 @@ function! ale#c#ParseCompileCommandsFlags(buffer, file_lookup, dir_lookup) abort for l:item in l:dir_list if ale#path#Simplify(fnamemodify(l:item.file, ':h')) is? l:dir - \&& has_key(l:item, 'command') - return ale#c#ParseCFlags(l:item.directory, l:item.command) + return ale#c#ParseCFlags(l:item.directory, ale#c#GetCompileCommand(l:item)) endif endfor diff --git a/autoload/ale/code_action.vim b/autoload/ale/code_action.vim new file mode 100644 index 00000000..60c3bbef --- /dev/null +++ b/autoload/ale/code_action.vim @@ -0,0 +1,173 @@ +" Author: Jerko Steiner <jerko.steiner@gmail.com> +" Description: Code action support for LSP / tsserver + +function! ale#code_action#HandleCodeAction(code_action, should_save) abort + let l:current_buffer = bufnr('') + let l:changes = a:code_action.changes + + for l:file_code_edit in l:changes + let l:buf = bufnr(l:file_code_edit.fileName) + + if l:buf != -1 && l:buf != l:current_buffer && getbufvar(l:buf, '&mod') + call ale#util#Execute('echom ''Aborting action, file is unsaved''') + + return + endif + endfor + + for l:file_code_edit in l:changes + call ale#code_action#ApplyChanges( + \ l:file_code_edit.fileName, + \ l:file_code_edit.textChanges, + \ a:should_save, + \ ) + endfor +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) + let l:is_current_buffer = l:buffer > 0 && l:buffer == l:current_buffer + + if l:buffer > 0 + let l:lines = getbufline(l:buffer, 1, '$') + else + let l:lines = readfile(a:filename, 'b') + endif + + if l:is_current_buffer + let l:pos = getpos('.')[1:2] + else + let l:pos = [1, 1] + endif + + " We have to keep track of how many lines we have added, and offset + " changes accordingly. + let l:line_offset = 0 + let l:column_offset = 0 + let l:last_end_line = 0 + + for l:code_edit in a:changes + if l:code_edit.start.line isnot l:last_end_line + let l:column_offset = 0 + endif + + let l:line = l:code_edit.start.line + l:line_offset + let l:column = l:code_edit.start.offset + l:column_offset + let l:end_line = l:code_edit.end.line + l:line_offset + let l:end_column = l:code_edit.end.offset + l:column_offset + let l:text = l:code_edit.newText + + let l:cur_line = l:pos[0] + let l:cur_column = l:pos[1] + + let l:last_end_line = l:end_line + + " Adjust the ends according to previous edits. + if l:end_line > len(l:lines) + let l:end_line_len = 0 + else + let l:end_line_len = len(l:lines[l:end_line - 1]) + endif + + let l:insertions = split(l:text, '\n', 1) + + if l:line is 1 + " Same logic as for column below. Vimscript's slice [:-1] will not + " be an empty list. + let l:start = [] + else + let l:start = l:lines[: l:line - 2] + endif + + if l:column is 1 + " We need to handle column 1 specially, because we can't slice an + " empty string ending on index 0. + let l:middle = [l:insertions[0]] + else + let l:middle = [l:lines[l:line - 1][: l:column - 2] . l:insertions[0]] + endif + + call extend(l:middle, l:insertions[1:]) + let l:middle[-1] .= l:lines[l:end_line - 1][l:end_column - 1 :] + + let l:lines_before_change = len(l:lines) + let l:lines = l:start + l:middle + l:lines[l:end_line :] + + let l:current_line_offset = len(l:lines) - l:lines_before_change + let l:line_offset += l:current_line_offset + let l:column_offset = len(l:middle[-1]) - l:end_line_len + + let l:pos = s:UpdateCursor(l:pos, + \ [l:line, l:column], + \ [l:end_line, l:end_column], + \ [l:current_line_offset, l:column_offset]) + endfor + + if l:lines[-1] is# '' + call remove(l:lines, -1) + endif + + 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 + if a:should_save + call ale#util#Execute(':e!') + endif + + call setpos('.', [0, l:pos[0], l:pos[1], 0]) + endif +endfunction + +function! s:UpdateCursor(cursor, start, end, offset) abort + let l:cur_line = a:cursor[0] + let l:cur_column = a:cursor[1] + let l:line = a:start[0] + let l:column = a:start[1] + let l:end_line = a:end[0] + let l:end_column = a:end[1] + let l:line_offset = a:offset[0] + let l:column_offset = a:offset[1] + + if l:end_line < l:cur_line + " both start and end lines are before the cursor. only line offset + " needs to be updated + let l:cur_line += l:line_offset + elseif l:end_line == l:cur_line + " end line is at the same location as cursor, which means + " l:line <= l:cur_line + if l:line < l:cur_line || l:column <= l:cur_column + " updates are happening either before or around the cursor + if l:end_column < l:cur_column + " updates are happening before the cursor, update the + " column offset for cursor + let l:cur_line += l:line_offset + let l:cur_column += l:column_offset + else + " updates are happening around the cursor, move the cursor + " to the end of the changes + let l:cur_line += l:line_offset + let l:cur_column = l:end_column + l:column_offset + endif + " else is not necessary, it means modifications are happening + " after the cursor so no cursor updates need to be done + endif + else + " end line is after the cursor + if l:line < l:cur_line || l:line == l:cur_line && l:column <= l:cur_column + " changes are happening around the cursor, move the cursor + " to the end of the changes + let l:cur_line = l:end_line + l:line_offset + let l:cur_column = l:end_column + l:column_offset + " else is not necesary, it means modifications are happening + " after the cursor so no cursor updates need to be done + endif + endif + + return [l:cur_line, l:cur_column] +endfunction diff --git a/autoload/ale/completion.vim b/autoload/ale/completion.vim index c0e8abd9..2b5756e4 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -1,5 +1,6 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Completion support for LSP linters +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 @@ -15,29 +16,107 @@ 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 s:timer_id = -1 let s:last_done_pos = [] " CompletionItemKind values from the LSP protocol. -let s:LSP_COMPLETION_TEXT_KIND = 1 -let s:LSP_COMPLETION_METHOD_KIND = 2 -let s:LSP_COMPLETION_FUNCTION_KIND = 3 -let s:LSP_COMPLETION_CONSTRUCTOR_KIND = 4 -let s:LSP_COMPLETION_FIELD_KIND = 5 -let s:LSP_COMPLETION_VARIABLE_KIND = 6 -let s:LSP_COMPLETION_CLASS_KIND = 7 -let s:LSP_COMPLETION_INTERFACE_KIND = 8 -let s:LSP_COMPLETION_MODULE_KIND = 9 -let s:LSP_COMPLETION_PROPERTY_KIND = 10 -let s:LSP_COMPLETION_UNIT_KIND = 11 -let s:LSP_COMPLETION_VALUE_KIND = 12 -let s:LSP_COMPLETION_ENUM_KIND = 13 -let s:LSP_COMPLETION_KEYWORD_KIND = 14 -let s:LSP_COMPLETION_SNIPPET_KIND = 15 -let s:LSP_COMPLETION_COLOR_KIND = 16 -let s:LSP_COMPLETION_FILE_KIND = 17 -let s:LSP_COMPLETION_REFERENCE_KIND = 18 +let g:ale_lsp_types = { +\ 1: 'text', +\ 2: 'method', +\ 3: 'function', +\ 4: 'constructor', +\ 5: 'field', +\ 6: 'variable', +\ 7: 'class', +\ 8: 'interface', +\ 9: 'module', +\ 10: 'property', +\ 11: 'unit', +\ 12: 'value', +\ 13: 'enum', +\ 14: 'keyword', +\ 15: 'snippet', +\ 16: 'color', +\ 17: 'file', +\ 18: 'reference', +\ 19: 'folder', +\ 20: 'enum_member', +\ 21: 'constant', +\ 22: 'struct', +\ 23: 'event', +\ 24: 'operator', +\ 25: 'type_parameter', +\ } + +" from https://github.com/microsoft/TypeScript/blob/29becf05012bfa7ba20d50b0d16813971e46b8a6/lib/protocol.d.ts#L2472 +let g:ale_tsserver_types = { +\ 'warning': 'text', +\ 'keyword': 'keyword', +\ 'script': 'file', +\ 'module': 'module', +\ 'class': 'class', +\ 'local class': 'class', +\ 'interface': 'interface', +\ 'type': 'class', +\ 'enum': 'enum', +\ 'enum member': 'enum_member', +\ 'var': 'variable', +\ 'local var': 'variable', +\ 'function': 'function', +\ 'local function': 'function', +\ 'method': 'method', +\ 'getter': 'property', +\ 'setter': 'method', +\ 'property': 'property', +\ 'constructor': 'constructor', +\ 'call': 'method', +\ 'index': 'index', +\ 'construct': 'constructor', +\ 'parameter': 'parameter', +\ 'type parameter': 'type_parameter', +\ 'primitive type': 'unit', +\ 'label': 'text', +\ 'alias': 'class', +\ 'const': 'constant', +\ 'let': 'variable', +\ 'directory': 'folder', +\ 'external module name': 'text', +\ 'JSX attribute': 'parameter', +\ 'string': 'text' +\ } + +" For compatibility reasons, we only use built in VIM completion kinds +" See :help complete-items for Vim completion kinds +let g:ale_completion_symbols = get(g:, 'ale_completion_symbols', { +\ 'text': 'v', +\ 'method': 'f', +\ 'function': 'f', +\ 'constructor': 'f', +\ 'field': 'm', +\ 'variable': 'v', +\ 'class': 't', +\ 'interface': 't', +\ 'module': 'd', +\ 'property': 'm', +\ 'unit': 'v', +\ 'value': 'v', +\ 'enum': 't', +\ 'keyword': 'v', +\ 'snippet': 'v', +\ 'color': 'v', +\ 'file': 'v', +\ 'reference': 'v', +\ 'folder': 'v', +\ 'enum_member': 'm', +\ 'constant': 'm', +\ 'struct': 't', +\ 'event': 'v', +\ 'operator': 'f', +\ 'type_parameter': 'p', +\ '<default>': 'v' +\ }) let s:LSP_INSERT_TEXT_FORMAT_PLAIN = 1 let s:LSP_INSERT_TEXT_FORMAT_SNIPPET = 2 @@ -182,6 +261,8 @@ function! s:ReplaceCompletionOptions() abort if &l:completeopt =~# 'preview' let &l:completeopt = 'menu,menuone,preview,noselect,noinsert' + elseif &l:completeopt =~# 'popup' + let &l:completeopt = 'menu,menuone,popup,noselect,noinsert' else let &l:completeopt = 'menu,menuone,noselect,noinsert' endif @@ -267,6 +348,35 @@ function! ale#completion#Show(result) abort \ {-> ale#util#FeedKeys("\<Plug>(ale_show_completion_menu)")} \) endif + + if l:source is# 'ale-callback' + call b:CompleteCallback(b:ale_completion_result) + endif +endfunction + +function! ale#completion#GetAllTriggers() abort + return deepcopy(s:trigger_character_map) +endfunction + +function! ale#completion#GetCompletionKind(kind) abort + let l:lsp_symbol = get(g:ale_lsp_types, a:kind, '') + + if !empty(l:lsp_symbol) + return l:lsp_symbol + endif + + return get(g:ale_tsserver_types, a:kind, '') +endfunction + +function! ale#completion#GetCompletionSymbols(kind) abort + let l:kind = ale#completion#GetCompletionKind(a:kind) + let l:symbol = get(g:ale_completion_symbols, l:kind, '') + + if !empty(l:symbol) + return l:symbol + endif + + return get(g:ale_completion_symbols, '<default>', 'v') endfunction function! s:CompletionStillValid(request_id) abort @@ -278,8 +388,8 @@ function! s:CompletionStillValid(request_id) abort \&& b:ale_completion_info.line == l:line \&& ( \ b:ale_completion_info.column == l:column - \ || b:ale_completion_info.source is# 'deoplete' \ || b:ale_completion_info.source is# 'ale-omnifunc' + \ || b:ale_completion_info.source is# 'ale-callback' \) endfunction @@ -287,7 +397,10 @@ function! ale#completion#ParseTSServerCompletions(response) abort let l:names = [] for l:suggestion in a:response.body - call add(l:names, l:suggestion.name) + call add(l:names, { + \ 'word': l:suggestion.name, + \ 'source': get(l:suggestion, 'source', ''), + \}) endfor return l:names @@ -301,6 +414,10 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort for l:suggestion in a:response.body let l:displayParts = [] + for l:action in get(l:suggestion, 'codeActions', []) + call add(l:displayParts, l:action.description . ' ') + endfor + for l:part in l:suggestion.displayParts call add(l:displayParts, l:part.text) endfor @@ -312,22 +429,23 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort call add(l:documentationParts, l:part.text) endfor - if l:suggestion.kind is# 'className' - let l:kind = 'f' - elseif l:suggestion.kind is# 'parameterName' - let l:kind = 'f' - else - let l:kind = 'v' - endif - " See :help complete-items - call add(l:results, { + let l:result = { \ 'word': l:suggestion.name, - \ 'kind': l:kind, + \ 'kind': ale#completion#GetCompletionSymbols(l:suggestion.kind), \ 'icase': 1, \ 'menu': join(l:displayParts, ''), + \ 'dup': g:ale_completion_tsserver_autoimport, \ 'info': join(l:documentationParts, ''), - \}) + \} + + if has_key(l:suggestion, 'codeActions') + let l:result.user_data = json_encode({ + \ 'codeActions': l:suggestion.codeActions, + \ }) + endif + + call add(l:results, l:result) endfor let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', []) @@ -336,12 +454,12 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort let l:names_with_details = map(copy(l:results), 'v:val.word') let l:missing_names = filter( \ copy(l:names), - \ 'index(l:names_with_details, v:val) < 0', + \ 'index(l:names_with_details, v:val.word) < 0', \) for l:name in l:missing_names call add(l:results, { - \ 'word': l:name, + \ 'word': l:name.word, \ 'kind': 'v', \ 'icase': 1, \ 'menu': '', @@ -399,23 +517,6 @@ function! ale#completion#ParseLSPCompletions(response) abort continue endif - " See :help complete-items for Vim completion kinds - if !has_key(l:item, 'kind') - let l:kind = 'v' - elseif l:item.kind is s:LSP_COMPLETION_METHOD_KIND - let l:kind = 'm' - elseif l:item.kind is s:LSP_COMPLETION_CONSTRUCTOR_KIND - let l:kind = 'm' - elseif l:item.kind is s:LSP_COMPLETION_FUNCTION_KIND - let l:kind = 'f' - elseif l:item.kind is s:LSP_COMPLETION_CLASS_KIND - let l:kind = 'f' - elseif l:item.kind is s:LSP_COMPLETION_INTERFACE_KIND - let l:kind = 'f' - else - let l:kind = 'v' - endif - let l:doc = get(l:item, 'documentation', '') if type(l:doc) is v:t_dict && has_key(l:doc, 'value') @@ -424,7 +525,7 @@ function! ale#completion#ParseLSPCompletions(response) abort call add(l:results, { \ 'word': l:word, - \ 'kind': l:kind, + \ '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 : ''), @@ -463,13 +564,29 @@ function! ale#completion#HandleTSServerResponse(conn_id, response) abort call setbufvar(l:buffer, 'ale_tsserver_completion_names', l:names) if !empty(l:names) + let l:identifiers = [] + + for l:name in l:names + let l:identifier = { + \ 'name': l:name.word, + \} + let l:source = get(l:name, 'source', '') + + " Empty source results in no details for the completed item + if !empty(l:source) + call extend(l:identifier, { 'source': l:source }) + endif + + call add(l:identifiers, l:identifier) + endfor + let b:ale_completion_info.request_id = ale#lsp#Send( \ b:ale_completion_info.conn_id, \ ale#lsp#tsserver_message#CompletionEntryDetails( \ l:buffer, \ b:ale_completion_info.line, \ b:ale_completion_info.column, - \ l:names, + \ l:identifiers, \ ), \) endif @@ -516,6 +633,7 @@ function! s:OnReady(linter, lsp_details) abort \ b:ale_completion_info.line, \ b:ale_completion_info.column, \ b:ale_completion_info.prefix, + \ g:ale_completion_tsserver_autoimport, \) else " Send a message saying the buffer has changed first, otherwise @@ -560,12 +678,25 @@ endfunction " This function can be used to manually trigger autocomplete, even when " g:ale_completion_enabled is set to false -function! ale#completion#GetCompletions(source) abort +function! ale#completion#GetCompletions(...) abort + let l:source = get(a:000, 0, '') + let l:options = get(a:000, 1, {}) + + if len(a:000) > 2 + throw 'Too many arguments!' + endif + + let l:CompleteCallback = get(l:options, 'callback', v:null) + + if l:CompleteCallback isnot v:null + let b:CompleteCallback = l:CompleteCallback + endif + let [l:line, l:column] = getpos('.')[1:2] let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) - if a:source is# 'ale-automatic' && empty(l:prefix) + if l:source is# 'ale-automatic' && empty(l:prefix) return 0 endif @@ -578,7 +709,7 @@ function! ale#completion#GetCompletions(source) abort \ 'prefix': l:prefix, \ 'conn_id': 0, \ 'request_id': 0, - \ 'source': a:source, + \ 'source': l:source, \} unlet! b:ale_completion_result @@ -670,6 +801,32 @@ function! ale#completion#Queue() abort let s:timer_id = timer_start(g:ale_completion_delay, function('s:TimerHandler')) 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', '') + + if empty(l:user_data_json) + return + endif + + let l:user_data = json_decode(l:user_data_json) + + if type(l:user_data) isnot v:t_dict + return + endif + + for l:code_action in get(l:user_data, 'codeActions', []) + call ale#code_action#HandleCodeAction(l:code_action, v:false) + endfor +endfunction + function! ale#completion#Done() abort silent! pclose @@ -678,6 +835,10 @@ function! ale#completion#Done() abort let s:last_done_pos = getpos('.')[1:2] endfunction +augroup ALECompletionActions + autocmd CompleteDone * call ale#completion#HandleUserData(v:completed_item) +augroup END + function! s:Setup(enabled) abort augroup ALECompletionGroup autocmd! diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim index 7cdbabaa..4e134f8c 100644 --- a/autoload/ale/debugging.vim +++ b/autoload/ale/debugging.vim @@ -50,6 +50,7 @@ let s:global_variable_list = [ \ 'ale_sign_style_error', \ 'ale_sign_style_warning', \ 'ale_sign_warning', +\ 'ale_sign_highlight_linenrs', \ 'ale_statusline_format', \ 'ale_type_map', \ 'ale_use_global_executables', @@ -62,7 +63,7 @@ function! s:Echo(message) abort execute 'echo a:message' endfunction -function! s:GetLinterVariables(filetype, linter_names) abort +function! s:GetLinterVariables(filetype, exclude_linter_names) abort let l:variable_list = [] let l:filetype_parts = split(a:filetype, '\.') @@ -73,7 +74,7 @@ function! s:GetLinterVariables(filetype, linter_names) abort " Include matching variables. if !empty(l:match) \&& index(l:filetype_parts, l:match[1]) >= 0 - \&& index(a:linter_names, l:match[2]) >= 0 + \&& index(a:exclude_linter_names, l:match[2]) == -1 call add(l:variable_list, l:key) endif endfor @@ -211,10 +212,11 @@ function! ale#debugging#Info() abort let l:all_names = map(copy(l:all_linters), 'v:val[''name'']') let l:enabled_names = map(copy(l:enabled_linters), 'v:val[''name'']') + let l:exclude_names = filter(copy(l:all_names), 'index(l:enabled_names, v:val) == -1') " Load linter variables to display " This must be done after linters are loaded. - let l:variable_list = s:GetLinterVariables(l:filetype, l:enabled_names) + let l:variable_list = s:GetLinterVariables(l:filetype, l:exclude_names) let l:fixers = ale#fix#registry#SuggestedFixers(l:filetype) let l:fixers = uniq(sort(l:fixers[0] + l:fixers[1])) diff --git a/autoload/ale/definition.vim b/autoload/ale/definition.vim index 3915cac1..ffcd9d10 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 @@ -134,6 +135,10 @@ function! s:GoToLSPDefinition(linter, options, capability) abort endfunction function! ale#definition#GoTo(options) abort + if !get(g:, 'ale_ignore_2_7_warnings') && has_key(a:options, 'deprecated_command') + execute 'echom '':' . a:options.deprecated_command . ' is deprecated. Use `let g:ale_ignore_2_7_warnings = 1` to disable this message.''' + endif + for l:linter in ale#linter#Get(&filetype) if !empty(l:linter.lsp) call s:GoToLSPDefinition(l:linter, a:options, 'definition') @@ -142,6 +147,10 @@ function! ale#definition#GoTo(options) abort endfunction function! ale#definition#GoToType(options) abort + if !get(g:, 'ale_ignore_2_7_warnings') && has_key(a:options, 'deprecated_command') + execute 'echom '':' . a:options.deprecated_command . ' is deprecated. Use `let g:ale_ignore_2_7_warnings = 1` to disable this message.''' + endif + for l:linter in ale#linter#Get(&filetype) if !empty(l:linter.lsp) " TODO: handle typeDefinition for tsserver if supported by the @@ -154,3 +163,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/fix.vim b/autoload/ale/fix.vim index 9987fbdd..69817b36 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -4,40 +4,15 @@ call ale#Set('fix_on_save_ignore', {}) " 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 + let l:new_lines = ale#util#SetBufferContents(a:buffer, l:data.output) if l:data.should_save if a:buffer is bufnr('') @@ -47,7 +22,7 @@ function! ale#fix#ApplyQueuedFixes(buffer) abort set nomodified endif else - call writefile(l:new_lines, expand(a:buffer . ':p')) " no-custom-checks + call writefile(l:new_lines, expand('#' . a:buffer . ':p')) " no-custom-checks call setbufvar(a:buffer, '&modified', 0) endif endif @@ -74,7 +49,7 @@ endfunction function! ale#fix#ApplyFixes(buffer, output) abort let l:data = g:ale_fix_buffer_data[a:buffer] let l:data.output = a:output - let l:data.changes_made = l:data.lines_before != l:data.output + let l:data.changes_made = l:data.lines_before !=# l:data.output " no-custom-checks let l:data.done = 1 call ale#command#RemoveManagedFiles(a:buffer) diff --git a/autoload/ale/fix/registry.vim b/autoload/ale/fix/registry.vim index c5ef1544..1b3ca1a8 100644 --- a/autoload/ale/fix/registry.vim +++ b/autoload/ale/fix/registry.vim @@ -27,6 +27,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['python'], \ 'description': 'Fix PEP8 issues with black.', \ }, +\ 'dfmt': { +\ 'function': 'ale#fixers#dfmt#Fix', +\ 'suggested_filetypes': ['d'], +\ 'description': 'Fix D files with dfmt.', +\ }, \ 'fecs': { \ 'function': 'ale#fixers#fecs#Fix', \ 'suggested_filetypes': ['javascript', 'css', 'html'], @@ -49,6 +54,11 @@ let s:default_registry = { \ 'description': 'Apply elm-format to a file.', \ 'aliases': ['format'], \ }, +\ 'nimpretty': { +\ 'function': 'ale#fixers#nimpretty#Fix', +\ 'suggested_filetypes': ['nim'], +\ 'description': 'Apply nimpretty to a file.', +\ }, \ 'eslint': { \ 'function': 'ale#fixers#eslint#Fix', \ 'suggested_filetypes': ['javascript', 'typescript'], @@ -215,6 +225,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['haskell'], \ 'description': 'Fix Haskell files with brittany.', \ }, +\ 'hindent': { +\ 'function': 'ale#fixers#hindent#Fix', +\ 'suggested_filetypes': ['haskell'], +\ 'description': 'Fix Haskell files with hindent.', +\ }, \ 'hlint': { \ 'function': 'ale#fixers#hlint#Fix', \ 'suggested_filetypes': ['haskell'], @@ -225,6 +240,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['haskell'], \ 'description': 'Refactor Haskell files with stylish-haskell.', \ }, +\ 'purty': { +\ 'function': 'ale#fixers#purty#Fix', +\ 'suggested_filetypes': ['purescript'], +\ 'description': 'Format PureScript files with purty.', +\ }, \ 'ocamlformat': { \ 'function': 'ale#fixers#ocamlformat#Fix', \ 'suggested_filetypes': ['ocaml'], @@ -250,6 +270,11 @@ let s:default_registry = { \ 'suggested_filetypes': ['sql'], \ 'description': 'Fix SQL files with sqlfmt.', \ }, +\ 'sqlformat': { +\ 'function': 'ale#fixers#sqlformat#Fix', +\ 'suggested_filetypes': ['sql'], +\ 'description': 'Fix SQL files with sqlformat.', +\ }, \ 'google_java_format': { \ 'function': 'ale#fixers#google_java_format#Fix', \ 'suggested_filetypes': ['java'], @@ -307,7 +332,7 @@ let s:default_registry = { \ }, \ 'styler': { \ 'function': 'ale#fixers#styler#Fix', -\ 'suggested_filetypes': ['r', 'rmarkdown'], +\ 'suggested_filetypes': ['r', 'rmarkdown', 'rmd'], \ 'description': 'Fix R files with styler.', \ }, \ 'latexindent': { @@ -325,6 +350,21 @@ let s:default_registry = { \ 'suggested_filetypes': ['python'], \ 'description': 'Sort Python imports with reorder-python-imports.', \ }, +\ 'gnatpp': { +\ 'function': 'ale#fixers#gnatpp#Fix', +\ 'suggested_filetypes': ['ada'], +\ 'description': 'Format Ada files with gnatpp.', +\ }, +\ 'nixpkgs-fmt': { +\ 'function': 'ale#fixers#nixpkgsfmt#Fix', +\ 'suggested_filetypes': ['nix'], +\ 'description': 'A formatter for Nix code', +\ }, +\ 'html-beautify': { +\ 'function': 'ale#fixers#html_beautify#Fix', +\ 'suggested_filetypes': ['html', 'htmldjango'], +\ 'description': 'Fix HTML files with html-beautify.', +\ }, \} " Reset the function registry to the default entries. diff --git a/autoload/ale/fixers/black.vim b/autoload/ale/fixers/black.vim index 367b8d52..fba6c3b4 100644 --- a/autoload/ale/fixers/black.vim +++ b/autoload/ale/fixers/black.vim @@ -29,6 +29,10 @@ function! ale#fixers#black#Fix(buffer) abort let l:options = ale#Var(a:buffer, 'python_black_options') + if expand('#' . a:buffer . ':e') is? 'pyi' + let l:options .= '--pyi' + endif + return { \ 'command': l:cd_string . ale#Escape(l:executable) . l:exec_args \ . (!empty(l:options) ? ' ' . l:options : '') diff --git a/autoload/ale/fixers/dfmt.vim b/autoload/ale/fixers/dfmt.vim new file mode 100644 index 00000000..0072e045 --- /dev/null +++ b/autoload/ale/fixers/dfmt.vim @@ -0,0 +1,18 @@ +" Author: theoldmoon0602 +" Description: Integration of dfmt with ALE. + +call ale#Set('d_dfmt_executable', 'dfmt') +call ale#Set('d_dfmt_options', '') + +function! ale#fixers#dfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'd_dfmt_executable') + let l:options = ale#Var(a:buffer, 'd_dfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . ' -i' + \ . (empty(l:options) ? '' : ' ' . l:options) + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/gnatpp.vim b/autoload/ale/fixers/gnatpp.vim new file mode 100644 index 00000000..bf3d484e --- /dev/null +++ b/autoload/ale/fixers/gnatpp.vim @@ -0,0 +1,17 @@ +" Author: tim <tim@inept.tech> +" Description: Fix files with gnatpp. + +call ale#Set('ada_gnatpp_executable', 'gnatpp') +call ale#Set('ada_gnatpp_options', '') + +function! ale#fixers#gnatpp#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'ada_gnatpp_executable') + let l:options = ale#Var(a:buffer, 'ada_gnatpp_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/hindent.vim b/autoload/ale/fixers/hindent.vim new file mode 100644 index 00000000..b6009a2c --- /dev/null +++ b/autoload/ale/fixers/hindent.vim @@ -0,0 +1,20 @@ +" Author: AlexeiDrake <drake.alexei@gmail.com> +" Description: Integration of hindent formatting with ALE. +" +call ale#Set('haskell_hindent_executable', 'hindent') + +function! ale#fixers#hindent#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'haskell_hindent_executable') + + return ale#handlers#haskell_stack#EscapeExecutable(l:executable, 'hindent') +endfunction + +function! ale#fixers#hindent#Fix(buffer) abort + let l:executable = ale#fixers#hindent#GetExecutable(a:buffer) + + return { + \ 'command': l:executable + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/html_beautify.vim b/autoload/ale/fixers/html_beautify.vim new file mode 100644 index 00000000..236cb6ec --- /dev/null +++ b/autoload/ale/fixers/html_beautify.vim @@ -0,0 +1,21 @@ +" Author: WhyNotHugo <hugo@barrera.io> +" Description: Lint HTML files with html-beautify. +" +call ale#Set('html_beautify_executable', 'html-beautify') +call ale#Set('html_beautify_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('html_beautify_options', '') +call ale#Set('html_beautify_change_directory', 1) + +function! ale#fixers#html_beautify#Fix(buffer) abort + let l:executable = ale#python#FindExecutable( + \ a:buffer, + \ 'html_beautify', + \ ['html-beautify'] + \) + + let l:options = ale#Var(a:buffer, 'html_beautify_options') + + return { + \ 'command': ale#Escape(l:executable). ' ' . l:options . ' -', + \} +endfunction diff --git a/autoload/ale/fixers/nimpretty.vim b/autoload/ale/fixers/nimpretty.vim new file mode 100644 index 00000000..fe2e7136 --- /dev/null +++ b/autoload/ale/fixers/nimpretty.vim @@ -0,0 +1,15 @@ +" Author: Nhan <hi@imnhan.com> +" Description: Integration of nimpretty with ALE. + +call ale#Set('nim_nimpretty_executable', 'nimpretty') +call ale#Set('nim_nimpretty_options', '--maxLineLen:80') + +function! ale#fixers#nimpretty#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'nim_nimpretty_executable') + let l:options = ale#Var(a:buffer, 'nim_nimpretty_options') + + return { + \ 'command': ale#Escape(l:executable) . ' %t' . ale#Pad(l:options), + \ 'read_temporary_file': 1, + \} +endfunction diff --git a/autoload/ale/fixers/nixpkgsfmt.vim b/autoload/ale/fixers/nixpkgsfmt.vim new file mode 100644 index 00000000..403ce798 --- /dev/null +++ b/autoload/ale/fixers/nixpkgsfmt.vim @@ -0,0 +1,12 @@ +call ale#Set('nix_nixpkgsfmt_executable', 'nixpkgs-fmt') +call ale#Set('nix_nixpkgsfmt_options', '') + +function! ale#fixers#nixpkgsfmt#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'nix_nixpkgsfmt_executable') + let l:options = ale#Var(a:buffer, 'nix_nixpkgsfmt_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options), + \} +endfunction diff --git a/autoload/ale/fixers/purty.vim b/autoload/ale/fixers/purty.vim new file mode 100644 index 00000000..46d2cacd --- /dev/null +++ b/autoload/ale/fixers/purty.vim @@ -0,0 +1,22 @@ +" Author: iclanzan <sorin@iclanzan.com> +" Description: Integration of purty with ALE. + +call ale#Set('purescript_purty_executable', 'purty') + +function! ale#fixers#purty#GetExecutable(buffer) abort + let l:executable = ale#Var(a:buffer, 'purescript_purty_executable') + + return ale#Escape(l:executable) +endfunction + +function! ale#fixers#purty#Fix(buffer) abort + let l:executable = ale#fixers#purty#GetExecutable(a:buffer) + + return { + \ 'command': l:executable + \ . ' --write' + \ . ' %t', + \ 'read_temporary_file': 1, + \} +endfunction + diff --git a/autoload/ale/fixers/rubocop.vim b/autoload/ale/fixers/rubocop.vim index 33ba6887..0c7441e4 100644 --- a/autoload/ale/fixers/rubocop.vim +++ b/autoload/ale/fixers/rubocop.vim @@ -6,7 +6,7 @@ function! ale#fixers#rubocop#GetCommand(buffer) abort let l:config = ale#path#FindNearestFile(a:buffer, '.rubocop.yml') let l:options = ale#Var(a:buffer, 'ruby_rubocop_options') - return ale#handlers#ruby#EscapeExecutable(l:executable, 'rubocop') + 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' diff --git a/autoload/ale/fixers/sorbet.vim b/autoload/ale/fixers/sorbet.vim index 182f7300..7c12fa1e 100644 --- a/autoload/ale/fixers/sorbet.vim +++ b/autoload/ale/fixers/sorbet.vim @@ -5,7 +5,7 @@ function! ale#fixers#sorbet#GetCommand(buffer) abort let l:executable = ale#Var(a:buffer, 'ruby_sorbet_executable') let l:options = ale#Var(a:buffer, 'ruby_sorbet_options') - return ale#handlers#ruby#EscapeExecutable(l:executable, 'srb') + return ale#ruby#EscapeExecutable(l:executable, 'srb') \ . ' tc' \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --autocorrect --file %t' diff --git a/autoload/ale/fixers/sqlformat.vim b/autoload/ale/fixers/sqlformat.vim new file mode 100644 index 00000000..6319c1ac --- /dev/null +++ b/autoload/ale/fixers/sqlformat.vim @@ -0,0 +1,16 @@ +" Author: Cluas <Cluas@live.cn> +" Description: Fixing files with sqlformat. + +call ale#Set('sql_sqlformat_executable', 'sqlformat') +call ale#Set('sql_sqlformat_options', '') + +function! ale#fixers#sqlformat#Fix(buffer) abort + let l:executable = ale#Var(a:buffer, 'sql_sqlformat_executable') + let l:options = ale#Var(a:buffer, 'sql_sqlformat_options') + + return { + \ 'command': ale#Escape(l:executable) + \ . (!empty(l:options) ? ' ' . l:options : '') + \ . ' -' + \} +endfunction diff --git a/autoload/ale/fixers/standard.vim b/autoload/ale/fixers/standard.vim index 77712d40..cffa9f9d 100644 --- a/autoload/ale/fixers/standard.vim +++ b/autoload/ale/fixers/standard.vim @@ -7,6 +7,7 @@ call ale#Set('javascript_standard_options', '') function! ale#fixers#standard#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'javascript_standard', [ + \ 'node_modules/standardx/bin/cmd.js', \ 'node_modules/standard/bin/cmd.js', \ 'node_modules/.bin/standard', \]) @@ -14,7 +15,14 @@ endfunction function! ale#fixers#standard#Fix(buffer) abort let l:executable = ale#fixers#standard#GetExecutable(a:buffer) - let l:options = ale#Var(a:buffer, 'javascript_standard_options') + let l:filetype = getbufvar(a:buffer, '&filetype') + let l:options_type = 'javascript_standard_options' + + if l:filetype =~# 'typescript' + let l:options_type = 'typescript_standard_options' + endif + + let l:options = ale#Var(a:buffer, l:options_type) return { \ 'command': ale#node#Executable(a:buffer, l:executable) diff --git a/autoload/ale/fixers/standardrb.vim b/autoload/ale/fixers/standardrb.vim index fab1e2bc..54330a37 100644 --- a/autoload/ale/fixers/standardrb.vim +++ b/autoload/ale/fixers/standardrb.vim @@ -9,7 +9,7 @@ function! ale#fixers#standardrb#GetCommand(buffer) abort let l:config = ale#path#FindNearestFile(a:buffer, '.standard.yml') let l:options = ale#Var(a:buffer, 'ruby_standardrb_options') - return ale#handlers#ruby#EscapeExecutable(l:executable, 'standardrb') + return ale#ruby#EscapeExecutable(l:executable, 'standardrb') \ . (!empty(l:config) ? ' --config ' . ale#Escape(l:config) : '') \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' --fix --force-exclusion %t' diff --git a/autoload/ale/fixers/stylelint.vim b/autoload/ale/fixers/stylelint.vim index 6bfb2fde..6f4cf177 100644 --- a/autoload/ale/fixers/stylelint.vim +++ b/autoload/ale/fixers/stylelint.vim @@ -3,6 +3,7 @@ call ale#Set('stylelint_executable', 'stylelint') call ale#Set('stylelint_use_global', get(g:, 'ale_use_global_executables', 0)) +call ale#Set('stylelint_options', '') function! ale#fixers#stylelint#GetExecutable(buffer) abort return ale#node#FindExecutable(a:buffer, 'stylelint', [ @@ -13,10 +14,14 @@ endfunction function! ale#fixers#stylelint#Fix(buffer) abort let l:executable = ale#fixers#stylelint#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'stylelint_options') return { - \ 'command': ale#node#Executable(a:buffer, l:executable) - \ . ' --fix %t', + \ 'command': ale#path#BufferCdString(a:buffer) + \ . ale#node#Executable(a:buffer, l:executable) + \ . ' %t' + \ . ale#Pad(l:options) + \ . ' --fix', \ 'read_temporary_file': 1, \} endfunction diff --git a/autoload/ale/fixers/styler.vim b/autoload/ale/fixers/styler.vim index 7ff3275c..1c7607bd 100644 --- a/autoload/ale/fixers/styler.vim +++ b/autoload/ale/fixers/styler.vim @@ -2,13 +2,13 @@ " Description: Fixing R files with styler. call ale#Set('r_styler_executable', 'Rscript') -call ale#Set('r_styler_options', 'tidyverse_style') +call ale#Set('r_styler_options', 'tidyverse_style()') function! ale#fixers#styler#Fix(buffer) abort return { \ 'command': 'Rscript --vanilla -e ' \ . '"suppressPackageStartupMessages(library(styler));' - \ . 'style_file(commandArgs(TRUE), style = ' + \ . 'style_file(commandArgs(TRUE), transformers = ' \ . ale#Var(a:buffer, 'r_styler_options') . ')"' \ . ' %t', \ 'read_temporary_file': 1, diff --git a/autoload/ale/handlers/eslint.vim b/autoload/ale/handlers/eslint.vim index 4d533ff2..156b939f 100644 --- a/autoload/ale/handlers/eslint.vim +++ b/autoload/ale/handlers/eslint.vim @@ -42,7 +42,18 @@ function! ale#handlers#eslint#GetCommand(buffer) abort let l:options = ale#Var(a:buffer, 'javascript_eslint_options') - return ale#node#Executable(a:buffer, l:executable) + " 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. + " 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) : '' + + return l:cd_command + \ . ale#node#Executable(a:buffer, l:executable) \ . (!empty(l:options) ? ' ' . l:options : '') \ . ' -f json --stdin --stdin-filename %s' endfunction @@ -84,11 +95,14 @@ function! s:CheckForBadConfig(buffer, lines) abort endfunction function! s:parseJSON(buffer, lines) abort - try - let l:parsed = json_decode(a:lines[-1]) - catch - return [] - endtry + let l:parsed = [] + + for l:line in a:lines + try + let l:parsed = extend(l:parsed, json_decode(l:line)) + catch + endtry + endfor if type(l:parsed) != v:t_list || empty(l:parsed) return [] diff --git a/autoload/ale/handlers/languagetool.vim b/autoload/ale/handlers/languagetool.vim index 10e049df..73974ceb 100644 --- a/autoload/ale/handlers/languagetool.vim +++ b/autoload/ale/handlers/languagetool.vim @@ -2,6 +2,7 @@ " Description: languagetool for markdown files " call ale#Set('languagetool_executable', 'languagetool') +call ale#Set('languagetool_options', '--autoDetect') function! ale#handlers#languagetool#GetExecutable(buffer) abort return ale#Var(a:buffer, 'languagetool_executable') @@ -9,8 +10,10 @@ endfunction function! ale#handlers#languagetool#GetCommand(buffer) abort let l:executable = ale#handlers#languagetool#GetExecutable(a:buffer) + let l:options = ale#Var(a:buffer, 'languagetool_options') - return ale#Escape(l:executable) . ' --autoDetect %s' + return ale#Escape(l:executable) + \ . (empty(l:options) ? '' : ' ' . l:options) . ' %s' endfunction function! ale#handlers#languagetool#HandleOutput(buffer, lines) abort diff --git a/autoload/ale/handlers/ruby.vim b/autoload/ale/handlers/ruby.vim index c28b8b75..7a1c5765 100644 --- a/autoload/ale/handlers/ruby.vim +++ b/autoload/ale/handlers/ruby.vim @@ -36,11 +36,3 @@ endfunction function! ale#handlers#ruby#HandleSyntaxErrors(buffer, lines) abort return s:HandleSyntaxError(a:buffer, a:lines) endfunction - -function! ale#handlers#ruby#EscapeExecutable(executable, bundle_exec) abort - let l:exec_args = a:executable =~? 'bundle' - \ ? ' exec ' . a:bundle_exec - \ : '' - - return ale#Escape(a:executable) . l:exec_args -endfunction diff --git a/autoload/ale/highlight.vim b/autoload/ale/highlight.vim index cb7911e1..473ad354 100644 --- a/autoload/ale/highlight.vim +++ b/autoload/ale/highlight.vim @@ -26,6 +26,25 @@ endif let s:MAX_POS_VALUES = 8 let s:MAX_COL_SIZE = 1073741824 " pow(2, 30) +let s:has_nvim_highlight = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace') + +if s:has_nvim_highlight + let s:ns_id = nvim_create_namespace('ale_highlight') +endif + +" Wrappers are necessary to test this functionality by faking the calls in tests. +function! ale#highlight#nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) abort + " Ignore all errors for adding highlights. + try + call nvim_buf_add_highlight(a:buffer, a:ns_id, a:hl_group, a:line, a:col_start, a:col_end) + catch + endtry +endfunction + +function! ale#highlight#nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end) abort + call nvim_buf_clear_namespace(a:buffer, a:ns_id, a:line_start, a:line_end) +endfunction + function! ale#highlight#CreatePositions(line, col, end_line, end_col) abort if a:line >= a:end_line " For single lines, just return the one position. @@ -51,15 +70,53 @@ endfunction " except these which have matching loclist item entries. function! ale#highlight#RemoveHighlights() abort - for l:match in getmatches() - if l:match.group =~? '\v^ALE(Style)?(Error|Warning|Info)(Line)?$' - call matchdelete(l:match.id) - endif - endfor + if s:has_nvim_highlight + call ale#highlight#nvim_buf_clear_namespace(bufnr(''), s:ns_id, 0, -1) + else + for l:match in getmatches() + if l:match.group =~? '\v^ALE(Style)?(Error|Warning|Info)(Line)?$' + call matchdelete(l:match.id) + endif + endfor + endif +endfunction + +" Same semantics of matchaddpos but will use nvim_buf_add_highlight if +" available. This involves iterating over the position list, switching from +" 1-based indexing to 0-based indexing, and translating the multiple ways +" that position can be specified for matchaddpos into line + col_start + +" col_end. +function! s:matchaddpos(group, pos_list) abort + if s:has_nvim_highlight + for l:pos in a:pos_list + let l:line = type(l:pos) == v:t_number + \ ? l:pos - 1 + \ : l:pos[0] - 1 + + if type(l:pos) == v:t_number || len(l:pos) == 1 + let l:col_start = 0 + let l:col_end = s:MAX_COL_SIZE + else + let l:col_start = l:pos[1] - 1 + let l:col_end = l:col_start + get(l:pos, 2, 1) + endif + + call ale#highlight#nvim_buf_add_highlight( + \ bufnr(''), + \ s:ns_id, + \ a:group, + \ l:line, + \ l:col_start, + \ l:col_end, + \) + endfor + else + call matchaddpos(a:group, a:pos_list) + endif endfunction function! s:highlight_line(bufnr, lnum, group) abort - call matchaddpos(a:group, [a:lnum]) + call s:matchaddpos(a:group, [a:lnum]) endfunction function! s:highlight_range(bufnr, range, group) abort @@ -72,7 +129,7 @@ function! s:highlight_range(bufnr, range, group) abort \ a:range.end_lnum, \ a:range.end_col \ ), - \ 'matchaddpos(a:group, v:val)' + \ 's:matchaddpos(a:group, v:val)' \) endfunction @@ -153,6 +210,12 @@ function! ale#highlight#SetHighlights(buffer, loclist) abort " Set the list in the buffer variable. call setbufvar(str2nr(a:buffer), 'ale_highlight_items', l:new_list) + let l:exclude_list = ale#Var(a:buffer, 'exclude_highlights') + + if !empty(l:exclude_list) + call filter(l:new_list, 'empty(ale#util#GetMatches(v:val.text, l:exclude_list))') + endif + " Update highlights for the current buffer, which may or may not " be the buffer we just set highlights for. call ale#highlight#UpdateHighlights() diff --git a/autoload/ale/hover.vim b/autoload/ale/hover.vim index 2af35aa4..8fdd288c 100644 --- a/autoload/ale/hover.vim +++ b/autoload/ale/hover.vim @@ -42,6 +42,11 @@ 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 g:ale_hover_to_preview + call ale#preview#Show(split(a:response.body.displayString, "\n"), { + \ 'filetype': 'ale-preview.message', + \ 'stay_here': 1, + \}) else call ale#util#ShowMessage(a:response.body.displayString) endif @@ -98,6 +103,11 @@ function! ale#hover#HandleLSPResponse(conn_id, response) abort \&& 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 diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index 78dcd3a2..a85f06e2 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -12,9 +12,12 @@ let s:linters = {} let s:default_ale_linter_aliases = { \ 'Dockerfile': 'dockerfile', \ 'csh': 'sh', +\ 'javascriptreact': ['javascript', 'jsx'], \ 'plaintex': 'tex', \ 'rmarkdown': 'r', +\ 'rmd': 'r', \ 'systemverilog': 'verilog', +\ 'typescriptreact': ['typescript', 'tsx'], \ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'], \ 'vimwiki': 'markdown', \ 'vue': ['vue', 'javascript'], diff --git a/autoload/ale/list.vim b/autoload/ale/list.vim index e9f3f4ec..4bfe2a7b 100644 --- a/autoload/ale/list.vim +++ b/autoload/ale/list.vim @@ -103,6 +103,9 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort endfor endif + " Save the current view before opening/closing any window + call setbufvar(a:buffer, 'ale_winview', winsaveview()) + " Open a window to show the problems if we need to. " " We'll check if the current buffer's List is not empty here, so the @@ -141,6 +144,8 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort normal! "\<c-g>" endif endif + + call s:RestoreViewIfNeeded(a:buffer) endif " If ALE isn't currently checking for more problems, close the window if @@ -151,6 +156,30 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort endif endfunction +" Try to restore the window view after closing any of the lists to avoid making +" the it moving around, especially useful when on insert mode +function! s:RestoreViewIfNeeded(buffer) abort + let l:saved_view = getbufvar(a:buffer, 'ale_winview', {}) + + " Saved view is empty, can't do anything + if empty(l:saved_view) + return + endif + + " Check wether the cursor has moved since linting was actually requested. If + " the user has indeed moved lines, do nothing + let l:current_view = winsaveview() + + if l:current_view['lnum'] != l:saved_view['lnum'] + return + endif + + " Anchor view by topline if the list is set to open horizontally + if ale#Var(a:buffer, 'list_vertical') == 0 + call winrestview({'topline': l:saved_view['topline']}) + endif +endfunction + function! ale#list#SetLists(buffer, loclist) abort if get(g:, 'ale_set_lists_synchronously') == 1 \|| getbufvar(a:buffer, 'ale_save_event_fired', 0) @@ -174,12 +203,15 @@ function! s:CloseWindowIfNeeded(buffer) abort return endif + let l:did_close_any_list = 0 + try " Only close windows if the quickfix list or loclist is completely empty, " including errors set through other means. if g:ale_set_quickfix if empty(getqflist()) cclose + let l:did_close_any_list = 1 endif else let l:win_ids = s:WinFindBuf(a:buffer) @@ -187,10 +219,15 @@ function! s:CloseWindowIfNeeded(buffer) abort for l:win_id in l:win_ids if g:ale_set_loclist && empty(getloclist(l:win_id)) lclose + let l:did_close_any_list = 1 endif endfor endif " Ignore 'Cannot close last window' errors. catch /E444/ endtry + + if l:did_close_any_list + call s:RestoreViewIfNeeded(a:buffer) + endif endfunction diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 017096cd..2509174e 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -37,6 +37,7 @@ function! ale#lsp#Register(executable_or_address, project, init_options) abort \ 'init_queue': [], \ 'capabilities': { \ 'hover': 0, + \ 'rename': 0, \ 'references': 0, \ 'completion': 0, \ 'completion_trigger_characters': [], @@ -199,6 +200,10 @@ function! s:UpdateCapabilities(conn, capabilities) abort let a:conn.capabilities.references = 1 endif + if get(a:capabilities, 'renameProvider') is v:true + let a:conn.capabilities.rename = 1 + endif + if !empty(get(a:capabilities, 'completionProvider')) let a:conn.capabilities.completion = 1 endif @@ -317,6 +322,7 @@ function! ale#lsp#MarkConnectionAsTsserver(conn_id) abort let l:conn.capabilities.completion_trigger_characters = ['.'] let l:conn.capabilities.definition = 1 let l:conn.capabilities.symbol_search = 1 + let l:conn.capabilities.rename = 1 endfunction function! s:SendInitMessage(conn) abort diff --git a/autoload/ale/lsp/message.vim b/autoload/ale/lsp/message.vim index b6b14a22..5b0cb8b7 100644 --- a/autoload/ale/lsp/message.vim +++ b/autoload/ale/lsp/message.vim @@ -162,3 +162,13 @@ function! ale#lsp#message#DidChangeConfiguration(buffer, config) abort \ 'settings': a:config, \}] endfunction + +function! ale#lsp#message#Rename(buffer, line, column, new_name) abort + return [0, 'textDocument/rename', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('#' . a:buffer . ':p')), + \ }, + \ 'position': {'line': a:line - 1, 'character': a:column - 1}, + \ 'newName': a:new_name, + \}] +endfunction diff --git a/autoload/ale/lsp/tsserver_message.vim b/autoload/ale/lsp/tsserver_message.vim index d6919516..b9fafaa0 100644 --- a/autoload/ale/lsp/tsserver_message.vim +++ b/autoload/ale/lsp/tsserver_message.vim @@ -36,12 +36,14 @@ function! ale#lsp#tsserver_message#Geterr(buffer) abort return [1, 'ts@geterr', {'files': [expand('#' . a:buffer . ':p')]}] endfunction -function! ale#lsp#tsserver_message#Completions(buffer, line, column, prefix) abort +function! ale#lsp#tsserver_message#Completions( +\ buffer, line, column, prefix, include_external) abort return [0, 'ts@completions', { \ 'line': a:line, \ 'offset': a:column, \ 'file': expand('#' . a:buffer . ':p'), \ 'prefix': a:prefix, + \ 'includeExternalModuleExports': a:include_external, \}] endfunction @@ -77,3 +79,27 @@ function! ale#lsp#tsserver_message#Quickinfo(buffer, line, column) abort \ 'file': expand('#' . a:buffer . ':p'), \}] endfunction + +function! ale#lsp#tsserver_message#Rename( +\ buffer, line, column, find_in_comments, find_in_strings) abort + return [0, 'ts@rename', { + \ 'line': a:line, + \ 'offset': a:column, + \ 'file': expand('#' . a:buffer . ':p'), + \ 'arguments': { + \ 'findInComments': a:find_in_comments, + \ 'findInStrings': a:find_in_strings, + \ } + \}] +endfunction + +function! ale#lsp#tsserver_message#OrganizeImports(buffer) abort + return [0, 'ts@organizeImports', { + \ 'scope': { + \ 'type': 'file', + \ 'args': { + \ 'file': expand('#' . a:buffer . ':p'), + \ }, + \ }, + \}] +endfunction diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 190a16b4..e4148ceb 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -130,6 +130,12 @@ function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort call s:HandleLSPErrorMessage(l:linter_name, a:response) elseif l:method is# 'textDocument/publishDiagnostics' call s:HandleLSPDiagnostics(a:conn_id, a:response) + elseif l:method is# 'window/showMessage' + call ale#lsp_window#HandleShowMessage( + \ s:lsp_linter_map[a:conn_id], + \ g:ale_lsp_show_message_format, + \ a:response.params + \) elseif get(a:response, 'type', '') is# 'event' \&& get(a:response, 'event', '') is# 'semanticDiag' call s:HandleTSServerDiagnostics(a:response, 'semantic') diff --git a/autoload/ale/lsp_window.vim b/autoload/ale/lsp_window.vim new file mode 100644 index 00000000..9a27f2f1 --- /dev/null +++ b/autoload/ale/lsp_window.vim @@ -0,0 +1,58 @@ +" Author: suoto <andre820@gmail.com> +" Description: Handling of window/* LSP methods, although right now only +" handles window/showMessage + +" Constants for message type codes +let s:LSP_MESSAGE_TYPE_DISABLED = 0 +let s:LSP_MESSAGE_TYPE_ERROR = 1 +let s:LSP_MESSAGE_TYPE_WARNING = 2 +let s:LSP_MESSAGE_TYPE_INFORMATION = 3 +let s:LSP_MESSAGE_TYPE_LOG = 4 + +" Translate strings from the user config to a number so we can check +" severities +let s:CFG_TO_LSP_SEVERITY = { +\ 'disabled': s:LSP_MESSAGE_TYPE_DISABLED, +\ 'error': s:LSP_MESSAGE_TYPE_ERROR, +\ 'warning': s:LSP_MESSAGE_TYPE_WARNING, +\ 'information': s:LSP_MESSAGE_TYPE_INFORMATION, +\ 'info': s:LSP_MESSAGE_TYPE_INFORMATION, +\ 'log': s:LSP_MESSAGE_TYPE_LOG +\} + +" Handle window/showMessage response. +" - details: dict containing linter name and format (g:ale_lsp_show_message_format) +" - params: dict with the params for the call in the form of {type: number, message: string} +function! ale#lsp_window#HandleShowMessage(linter_name, format, params) abort + let l:message = a:params.message + let l:type = a:params.type + + " Get the configured severity level threshold and check if the message + " should be displayed or not + let l:configured_severity = tolower(get(g:, 'ale_lsp_show_message_severity', 'error')) + " If the user has configured with a value we can't find on the conversion + " dict, fall back to warning + let l:cfg_severity_threshold = get(s:CFG_TO_LSP_SEVERITY, l:configured_severity, s:LSP_MESSAGE_TYPE_WARNING) + + if l:type > l:cfg_severity_threshold + return + endif + + " Severity will depend on the message type + if l:type is# s:LSP_MESSAGE_TYPE_ERROR + let l:severity = g:ale_echo_msg_error_str + elseif l:type is# s:LSP_MESSAGE_TYPE_INFORMATION + let l:severity = g:ale_echo_msg_info_str + elseif l:type is# s:LSP_MESSAGE_TYPE_LOG + let l:severity = g:ale_echo_msg_log_str + else + " Default to warning just in case + let l:severity = g:ale_echo_msg_warning_str + endif + + let l:string = substitute(a:format, '\V%severity%', l:severity, 'g') + let l:string = substitute(l:string, '\V%linter%', a:linter_name, 'g') + let l:string = substitute(l:string, '\V%s\>', l:message, 'g') + + call ale#util#ShowMessage(l:string) +endfunction diff --git a/autoload/ale/organize_imports.vim b/autoload/ale/organize_imports.vim new file mode 100644 index 00000000..e89c832c --- /dev/null +++ b/autoload/ale/organize_imports.vim @@ -0,0 +1,59 @@ +" Author: Jerko Steiner <jerko.steiner@gmail.com> +" Description: Organize imports support for tsserver +" +function! ale#organize_imports#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') isnot# 'organizeImports' + return + endif + + if get(a:response, 'success', v:false) isnot v:true + return + endif + + let l:file_code_edits = a:response.body + + call ale#code_action#HandleCodeAction({ + \ 'description': 'Organize Imports', + \ 'changes': l:file_code_edits, + \}, v:false) +endfunction + +function! s:OnReady(linter, lsp_details) abort + let l:id = a:lsp_details.connection_id + + if a:linter.lsp isnot# 'tsserver' + call ale#util#Execute('echom ''OrganizeImports currently only works with tsserver''') + + return + endif + + let l:buffer = a:lsp_details.buffer + + let l:Callback = function('ale#organize_imports#HandleTSServerResponse') + + call ale#lsp#RegisterCallback(l:id, l:Callback) + + let l:message = ale#lsp#tsserver_message#OrganizeImports(l:buffer) + + let l:request_id = ale#lsp#Send(l:id, l:message) +endfunction + +function! s:OrganizeImports(linter) abort + let l:buffer = bufnr('') + let [l:line, l:column] = getpos('.')[1:2] + + if a:linter.lsp isnot# 'tsserver' + let l:column = min([l:column, len(getline(l:line))]) + endif + + let l:Callback = function('s:OnReady') + call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) +endfunction + +function! ale#organize_imports#Execute() abort + for l:linter in ale#linter#Get(&filetype) + if !empty(l:linter.lsp) + call s:OrganizeImports(l:linter) + endif + endfor +endfunction diff --git a/autoload/ale/path.vim b/autoload/ale/path.vim index 84c26d0a..30550503 100644 --- a/autoload/ale/path.vim +++ b/autoload/ale/path.vim @@ -54,14 +54,14 @@ function! ale#path#FindNearestDirectory(buffer, directory_name) abort return '' endfunction -" Given a buffer, a string to search for, an a global fallback for when +" Given a buffer, a string to search for, and a global fallback for when " the search fails, look for a file in parent paths, and if that fails, " use the global fallback path instead. function! ale#path#ResolveLocalPath(buffer, search_string, global_fallback) abort " Search for a locally installed file first. let l:path = ale#path#FindNearestFile(a:buffer, a:search_string) - " If the serach fails, try the global executable instead. + " If the search fails, try the global executable instead. if empty(l:path) let l:path = a:global_fallback endif diff --git a/autoload/ale/preview.vim b/autoload/ale/preview.vim index 6d58aca9..7902ec63 100644 --- a/autoload/ale/preview.vim +++ b/autoload/ale/preview.vim @@ -1,6 +1,14 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Preview windows for showing whatever information in. +if !has_key(s:, 'last_selection_list') + let s:last_selection_list = [] +endif + +if !has_key(s:, 'last_selection_open_in') + let s:last_selection_open_in = 'current-buffer' +endif + " Open a preview window and show some lines in it. " A second argument can be passed as a Dictionary with options. They are... " @@ -67,9 +75,24 @@ 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') + + " Remove the last preview + let s:last_selection_list = b:ale_preview_item_list + let s:last_selection_open_in = b:ale_preview_item_open_in endfunction -function! s:Open(open_in_tab) abort +function! ale#preview#RepeatSelection() abort + if empty(s:last_selection_list) + return + endif + + call ale#preview#ShowSelection(s:last_selection_list, { + \ 'open_in': s:last_selection_open_in, + \}) +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 +100,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 new file mode 100644 index 00000000..fbd6c2ad --- /dev/null +++ b/autoload/ale/rename.vim @@ -0,0 +1,225 @@ +" Author: Jerko Steiner <jerko.steiner@gmail.com> +" Description: Rename symbol support for LSP / tsserver + +let s:rename_map = {} + +" Used to get the rename map in tests. +function! ale#rename#GetMap() abort + return deepcopy(s:rename_map) +endfunction + +" Used to set the rename map in tests. +function! ale#rename#SetMap(map) abort + let s:rename_map = a:map +endfunction + +function! ale#rename#ClearLSPData() abort + let s:rename_map = {} +endfunction + +let g:ale_rename_tsserver_find_in_comments = get(g:, 'ale_rename_tsserver_find_in_comments') +let g:ale_rename_tsserver_find_in_strings = get(g:, 'ale_rename_tsserver_find_in_strings') + +function! s:message(message) abort + call ale#util#Execute('echom ' . string(a:message)) +endfunction + +function! ale#rename#HandleTSServerResponse(conn_id, response) abort + if get(a:response, 'command', '') isnot# 'rename' + return + endif + + if !has_key(s:rename_map, a:response.request_seq) + return + endif + + let l:old_name = s:rename_map[a:response.request_seq].old_name + let l:new_name = s:rename_map[a:response.request_seq].new_name + call remove(s:rename_map, a:response.request_seq) + + if get(a:response, 'success', v:false) is v:false + let l:message = get(a:response, 'message', 'unknown') + call s:message('Error renaming "' . l:old_name . '" to: "' . l:new_name + \ . '". Reason: ' . l:message) + + return + endif + + let l:changes = [] + + for l:response_item in a:response.body.locs + let l:filename = l:response_item.file + let l:text_changes = [] + + for l:loc in l:response_item.locs + call add(l:text_changes, { + \ 'start': { + \ 'line': l:loc.start.line, + \ 'offset': l:loc.start.offset, + \ }, + \ 'end': { + \ 'line': l:loc.end.line, + \ 'offset': l:loc.end.offset, + \ }, + \ 'newText': l:new_name, + \}) + endfor + + call add(l:changes, { + \ 'fileName': l:filename, + \ 'textChanges': l:text_changes, + \}) + endfor + + if empty(l:changes) + call s:message('Error renaming "' . l:old_name . '" to: "' . l:new_name . '"') + + return + endif + + call ale#code_action#HandleCodeAction({ + \ 'description': 'rename', + \ 'changes': l:changes, + \}, v:true) +endfunction + +function! ale#rename#HandleLSPResponse(conn_id, response) abort + if has_key(a:response, 'id') + \&& has_key(s:rename_map, a:response.id) + call remove(s:rename_map, a:response.id) + + if !has_key(a:response, 'result') + call s:message('No rename result received from server') + + return + endif + + let l:workspace_edit = a:response.result + + if !has_key(l:workspace_edit, 'changes') || empty(l:workspace_edit.changes) + call s:message('No changes received from server') + + return + endif + + let l:changes = [] + + for l:file_name in keys(l:workspace_edit.changes) + let l:text_edits = l:workspace_edit.changes[l:file_name] + let l:text_changes = [] + + for l:edit in l:text_edits + let l:range = l:edit.range + let l:new_text = l:edit.newText + + call add(l:text_changes, { + \ 'start': { + \ 'line': l:range.start.line + 1, + \ 'offset': l:range.start.character + 1, + \ }, + \ 'end': { + \ 'line': l:range.end.line + 1, + \ 'offset': l:range.end.character + 1, + \ }, + \ 'newText': l:new_text, + \}) + endfor + + call add(l:changes, { + \ 'fileName': ale#path#FromURI(l:file_name), + \ 'textChanges': l:text_changes, + \}) + endfor + + call ale#code_action#HandleCodeAction({ + \ 'description': 'rename', + \ 'changes': l:changes, + \}, v:true) + endif +endfunction + +function! s:OnReady(line, column, old_name, new_name, linter, lsp_details) abort + let l:id = a:lsp_details.connection_id + + if !ale#lsp#HasCapability(l:id, 'rename') + return + endif + + let l:buffer = a:lsp_details.buffer + + let l:Callback = a:linter.lsp is# 'tsserver' + \ ? function('ale#rename#HandleTSServerResponse') + \ : function('ale#rename#HandleLSPResponse') + + call ale#lsp#RegisterCallback(l:id, l:Callback) + + if a:linter.lsp is# 'tsserver' + let l:message = ale#lsp#tsserver_message#Rename( + \ l:buffer, + \ a:line, + \ a:column, + \ g:ale_rename_tsserver_find_in_comments, + \ g:ale_rename_tsserver_find_in_strings, + \) + else + " Send a message saying the buffer has changed first, or the + " rename position probably won't make sense. + call ale#lsp#NotifyForChanges(l:id, l:buffer) + + let l:message = ale#lsp#message#Rename( + \ l:buffer, + \ a:line, + \ a:column, + \ a:new_name + \) + endif + + let l:request_id = ale#lsp#Send(l:id, l:message) + + let s:rename_map[l:request_id] = { + \ 'new_name': a:new_name, + \ 'old_name': a:old_name, + \} +endfunction + +function! s:ExecuteRename(linter, old_name, new_name) abort + let l:buffer = bufnr('') + let [l:line, l:column] = getpos('.')[1:2] + + if a:linter.lsp isnot# 'tsserver' + let l:column = min([l:column, len(getline(l:line))]) + endif + + let l:Callback = function( + \ 's:OnReady', [l:line, l:column, a:old_name, a:new_name]) + call ale#lsp_linter#StartLSP(l:buffer, a:linter, l:Callback) +endfunction + +function! ale#rename#Execute() abort + let l:lsp_linters = [] + + for l:linter in ale#linter#Get(&filetype) + if !empty(l:linter.lsp) + call add(l:lsp_linters, l:linter) + endif + endfor + + if empty(l:lsp_linters) + call s:message('No active LSPs') + + return + endif + + let l:old_name = expand('<cword>') + let l:new_name = ale#util#Input('New name: ', l:old_name) + + if empty(l:new_name) + call s:message('New name cannot be empty!') + + return + endif + + for l:lsp_linter in l:lsp_linters + call s:ExecuteRename(l:lsp_linter, l:old_name, l:new_name) + endfor +endfunction diff --git a/autoload/ale/ruby.vim b/autoload/ale/ruby.vim index 15e835c9..d941bb2c 100644 --- a/autoload/ale/ruby.vim +++ b/autoload/ale/ruby.vim @@ -74,3 +74,10 @@ function! ale#ruby#HandleRubocopOutput(buffer, lines) abort return l:output endfunction +function! ale#ruby#EscapeExecutable(executable, bundle_exec) abort + let l:exec_args = a:executable =~? 'bundle' + \ ? ' exec ' . a:bundle_exec + \ : '' + + return ale#Escape(a:executable) . l:exec_args +endfunction diff --git a/autoload/ale/sign.vim b/autoload/ale/sign.vim index eb0dd1cd..8109c60e 100644 --- a/autoload/ale/sign.vim +++ b/autoload/ale/sign.vim @@ -14,12 +14,16 @@ let g:ale_sign_style_error = get(g:, 'ale_sign_style_error', g:ale_sign_error) let g:ale_sign_warning = get(g:, 'ale_sign_warning', '--') let g:ale_sign_style_warning = get(g:, 'ale_sign_style_warning', g:ale_sign_warning) let g:ale_sign_info = get(g:, 'ale_sign_info', g:ale_sign_warning) +let g:ale_sign_priority = get(g:, 'ale_sign_priority', 30) " This variable sets an offset which can be set for sign IDs. " This ID can be changed depending on what IDs are set for other plugins. " The dummy sign will use the ID exactly equal to the offset. let g:ale_sign_offset = get(g:, 'ale_sign_offset', 1000000) " This flag can be set to 1 to keep sign gutter always open let g:ale_sign_column_always = get(g:, 'ale_sign_column_always', 0) +let g:ale_sign_highlight_linenrs = get(g:, 'ale_sign_highlight_linenrs', 0) + +let s:supports_sign_groups = has('nvim-0.4.2') || has('patch-8.1.614') if !hlexists('ALEErrorSign') highlight link ALEErrorSign error @@ -82,7 +86,7 @@ execute 'sign define ALEInfoSign text=' . s:EscapeSignText(g:ale_sign_info) \ . ' texthl=ALEInfoSign linehl=ALEInfoLine' sign define ALEDummySign -if has('nvim-0.3.2') +if g:ale_sign_highlight_linenrs && has('nvim-0.3.2') if !hlexists('ALEErrorSignLineNr') highlight link ALEErrorSignLineNr CursorLineNr endif @@ -146,24 +150,59 @@ function! ale#sign#GetSignName(sublist) abort return 'ALEErrorSign' endfunction +function! s:PriorityCmd() abort + if s:supports_sign_groups + return ' priority=' . g:ale_sign_priority . ' ' + else + return '' + endif +endfunction + +function! s:GroupCmd() abort + if s:supports_sign_groups + return ' group=ale ' + else + return ' ' + endif +endfunction + " Read sign data for a buffer to a list of lines. function! ale#sign#ReadSigns(buffer) abort redir => l:output - silent execute 'sign place buffer=' . a:buffer + silent execute 'sign place ' . s:GroupCmd() . s:PriorityCmd() + \ . ' buffer=' . a:buffer redir end return split(l:output, "\n") endfunction +function! ale#sign#ParsePattern() abort + if s:supports_sign_groups + " Matches output like : + " line=4 id=1 group=ale name=ALEErrorSign + " строка=1 id=1000001 группа=ale имя=ALEErrorSign + " 行=1 識別子=1000001 グループ=ale 名前=ALEWarningSign + " línea=12 id=1000001 grupo=ale nombre=ALEWarningSign + " riga=1 id=1000001 gruppo=ale nome=ALEWarningSign + " Zeile=235 id=1000001 Gruppe=ale Name=ALEErrorSign + let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=ale>.*\=(ALE[a-zA-Z]+Sign)' + else + " Matches output like : + " line=4 id=1 name=ALEErrorSign + " строка=1 id=1000001 имя=ALEErrorSign + " 行=1 識別子=1000001 名前=ALEWarningSign + " línea=12 id=1000001 nombre=ALEWarningSign + " riga=1 id=1000001 nome=ALEWarningSign + " Zeile=235 id=1000001 Name=ALEErrorSign + let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)' + endif + + return l:pattern +endfunction + " Given a list of lines for sign output, return a List of [line, id, group] function! ale#sign#ParseSigns(line_list) abort - " Matches output like : - " line=4 id=1 name=ALEErrorSign - " строка=1 id=1000001 имя=ALEErrorSign - " 行=1 識別子=1000001 名前=ALEWarningSign - " línea=12 id=1000001 nombre=ALEWarningSign - " riga=1 id=1000001, nome=ALEWarningSign - let l:pattern = '\v^.*\=(\d+).*\=(\d+).*\=(ALE[a-zA-Z]+Sign)' + let l:pattern =ale#sign#ParsePattern() let l:result = [] let l:is_dummy_sign_set = 0 @@ -318,8 +357,10 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort if !l:is_dummy_sign_set && (!empty(a:sign_map) || g:ale_sign_column_always) call add(l:command_list, 'sign place ' \ . g:ale_sign_offset - \ . ' line=1 name=ALEDummySign buffer=' - \ . a:buffer + \ . s:GroupCmd() + \ . s:PriorityCmd() + \ . ' line=1 name=ALEDummySign ' + \ . ' buffer=' . a:buffer \) let l:is_dummy_sign_set = 1 endif @@ -336,6 +377,8 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort if index(l:info.current_id_list, l:info.new_id) < 0 call add(l:command_list, 'sign place ' \ . (l:info.new_id) + \ . s:GroupCmd() + \ . s:PriorityCmd() \ . ' line=' . l:line_str \ . ' name=' . (l:info.new_name) \ . ' buffer=' . a:buffer @@ -350,6 +393,7 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort if l:current_id isnot l:info.new_id call add(l:command_list, 'sign unplace ' \ . l:current_id + \ . s:GroupCmd() \ . ' buffer=' . a:buffer \) endif @@ -360,6 +404,7 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort if l:is_dummy_sign_set && !g:ale_sign_column_always call add(l:command_list, 'sign unplace ' \ . g:ale_sign_offset + \ . s:GroupCmd() \ . ' buffer=' . a:buffer \) endif @@ -414,3 +459,12 @@ function! ale#sign#SetSigns(buffer, loclist) abort highlight link SignColumn ALESignColumnWithoutErrors endif endfunction + +" Remove all signs. +function! ale#sign#Clear() abort + if s:supports_sign_groups + sign unplace group=ale * + else + sign unplace * + endif +endfunction diff --git a/autoload/ale/util.vim b/autoload/ale/util.vim index e7563608..ee62af28 100644 --- a/autoload/ale/util.vim +++ b/autoload/ale/util.vim @@ -91,17 +91,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. @@ -336,15 +336,11 @@ function! ale#util#GetMatches(lines, patterns) abort endfunction function! s:LoadArgCount(function) abort - let l:Function = a:function - - redir => l:output - silent! function Function - redir END - - if !exists('l:output') + try + let l:output = execute('function a:function') + catch /E123/ return 0 - endif + endtry let l:match = matchstr(split(l:output, "\n")[0], '\v\([^)]+\)')[1:-2] let l:arg_list = filter(split(l:match, ', '), 'v:val isnot# ''...''') @@ -477,3 +473,47 @@ function! ale#util#FindItemAtCursor(buffer) abort return [l:info, l:loc] 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/autoload/asyncomplete/sources/ale.vim b/autoload/asyncomplete/sources/ale.vim new file mode 100644 index 00000000..ce793773 --- /dev/null +++ b/autoload/asyncomplete/sources/ale.vim @@ -0,0 +1,26 @@ +function! asyncomplete#sources#ale#get_source_options(...) abort + let l:default = extend({ + \ 'name': 'ale', + \ 'completor': function('asyncomplete#sources#ale#completor'), + \ 'whitelist': ['*'], + \ 'triggers': asyncomplete#sources#ale#get_triggers(), + \ }, a:0 >= 1 ? a:1 : {}) + + return extend(l:default, {'refresh_pattern': '\k\+$'}) +endfunction + +function! asyncomplete#sources#ale#get_triggers() abort + let l:triggers = ale#completion#GetAllTriggers() + let l:triggers['*'] = l:triggers['<default>'] + + return l:triggers +endfunction + +function! asyncomplete#sources#ale#completor(options, context) abort + let l:keyword = matchstr(a:context.typed, '\w\+$') + let l:startcol = a:context.col - len(l:keyword) + + call ale#completion#GetCompletions('ale-callback', { 'callback': {completions -> + \ asyncomplete#complete(a:options.name, a:context, l:startcol, completions) + \ }}) +endfunction diff --git a/doc/ale-ada.txt b/doc/ale-ada.txt index 93e3261a..2ac30c0a 100644 --- a/doc/ale-ada.txt +++ b/doc/ale-ada.txt @@ -22,4 +22,15 @@ g:ale_ada_gcc_options *g:ale_ada_gcc_options* =============================================================================== +gnatpp *ale-ada-gnatpp* + +g:ale_ada_gnatpp_options *g:ale_ada_gnatpp_options* + *b:ale_ada_gnatpp_options* + Type: |String| + Default: `''` + + This variable can be set to pass extra options to the gnatpp fixer. + + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-cs.txt b/doc/ale-cs.txt index abcc43eb..bb13863f 100644 --- a/doc/ale-cs.txt +++ b/doc/ale-cs.txt @@ -11,22 +11,21 @@ csc *ale-cs-csc* The |ale-cs-csc| linter checks for semantic errors when files are opened or saved. - + See |ale-lint-file-linters| for more information on linters which do not check for problems while you type. - The csc linter uses the mono csc compiler providing full c# 7 and newer - support to generate a temporary module target file (/t:module). The module - includes including all '*.cs' files contained in the directory tree rooted - at the path defined by the |g:ale_cs_csc_source| or |b:ale_cs_csc_source| - variabl and all sub directories. - + The csc linter uses the mono csc compiler, providing full C# 7 and newer + support, to generate a temporary module target file (/t:module). The module + includes all '*.cs' files contained in the directory tree rooted at the path + defined by the |g:ale_cs_csc_source| or |b:ale_cs_csc_source| variable and + all sub directories. + It will in future replace the |ale-cs-mcs| and |ale-cs-mcsc| linters as both - utilizer the mcsc compiler which according to mono porject ist further - developed and as of writint these lines only receives maintenance updates. - The down is that the csc compiler does not support the -sytax option any more - and therefore |ale-cs-csc| linter doese not offer any as you type syntax - checking like the |ale-cs-mcsc| linter doesn't. + utilize the mcsc compiler which, according to the mono project, is no longer + actively developed, and only receives maintenance updates. However, because + the csc compiler does not support the -syntax option, this linter does not + offer any as-you-type syntax checking, similar to the |ale-cs-mcsc| linter. The paths to search for additional assembly files can be specified using the |g:ale_cs_csc_assembly_path| or |b:ale_cs_csc_assembly_path| variables. diff --git a/doc/ale-d.txt b/doc/ale-d.txt index 55596062..72349a20 100644 --- a/doc/ale-d.txt +++ b/doc/ale-d.txt @@ -1,6 +1,15 @@ =============================================================================== ALE D Integration *ale-d-options* +=============================================================================== +dfmt *ale-d-dfmt* + +g:ale_d_dfmt_options *g:ale_d_dfmt_options* + *b:ale_d_dfmt_options* + Type: |String| + Default: `''` + +This variable can be set to pass additional options to the dfmt fixer. =============================================================================== dls *ale-d-dls* diff --git a/doc/ale-development.txt b/doc/ale-development.txt index 16b16483..faa570c1 100644 --- a/doc/ale-development.txt +++ b/doc/ale-development.txt @@ -184,13 +184,12 @@ tests: https://github.com/junegunn/vader.vim See |ale-development-linter-tests| for more information on how to write linter tests. -When you add new linters or fixers, make sure to add them into the table in -the README, and also into the |ale-support| list in the main help file. If you -forget to keep them both in sync, you should see an error like the following -in Travis CI. > - +When you add new linters or fixers, make sure to add them into the tables in +supported-tools.md and |ale-supported-languages-and-tools.txt|. If you forget to +keep them both in sync, you should see an error like the following in Travis CI. +> ======================================== - diff README.md and doc/ale.txt tables + diff supported-tools.md and doc/ale-supported-languages-and-tools.txt tables ======================================== Differences follow: diff --git a/doc/ale-elm.txt b/doc/ale-elm.txt index 823b53e1..b1510241 100644 --- a/doc/ale-elm.txt +++ b/doc/ale-elm.txt @@ -50,7 +50,7 @@ g:ale_elm_ls_use_global *g:ale_elm_ls_use_global* g:ale_elm_ls_elm_path *g:ale_elm_ls_elm_path* *b:ale_elm_ls_elm_path* Type: |String| - Default: `'elm'` + Default: `''` See |ale-integrations-local-executables| @@ -58,7 +58,7 @@ g:ale_elm_ls_elm_path *g:ale_elm_ls_elm_path* g:ale_elm_ls_elm_format_path *g:ale_elm_ls_elm_format_path* *b:ale_elm_ls_elm_format_path* Type: |String| - Default: `'elm-format'` + Default: `''` See |ale-integrations-local-executables| @@ -66,10 +66,18 @@ g:ale_elm_ls_elm_format_path *g:ale_elm_ls_elm_format_path* g:ale_elm_ls_elm_test_path *g:ale_elm_ls_elm_test_path* *b:ale_elm_ls_elm_test_path* Type: |String| - Default: `'elm-test'` + Default: `''` See |ale-integrations-local-executables| + +g:ale_elm_ls_elm_analyse_trigger *g:ale_elm_ls_elm_analyse_trigger* + *b:ale_elm_ls_elm_analyse_trigger* + Type: |String| + Default: `'change'` + + One of 'change', 'save' or 'never' + =============================================================================== elm-make *ale-elm-elm-make* diff --git a/doc/ale-haskell.txt b/doc/ale-haskell.txt index e2073e37..5dd3ec15 100644 --- a/doc/ale-haskell.txt +++ b/doc/ale-haskell.txt @@ -12,6 +12,7 @@ g:ale_haskell_brittany_executable *g:ale_haskell_brittany_executable* This variable can be changed to use a different executable for brittany. + =============================================================================== floskell *ale-haskell-floskell* @@ -22,6 +23,7 @@ g:ale_haskell_floskell_executable *g:ale_haskell_floskell_executable* This variable can be changed to use a different executable for floskell. + =============================================================================== ghc *ale-haskell-ghc* @@ -32,6 +34,7 @@ g:ale_haskell_ghc_options *g:ale_haskell_ghc_options* This variable can be changed to modify flags given to ghc. + =============================================================================== ghc-mod *ale-haskell-ghc-mod* @@ -42,6 +45,7 @@ g:ale_haskell_ghc_mod_executable *g:ale_haskell_ghc_mod_executable* This variable can be changed to use a different executable for ghc-mod. + =============================================================================== cabal-ghc *ale-haskell-cabal-ghc* @@ -53,6 +57,7 @@ g:ale_haskell_cabal_ghc_options *g:ale_haskell_cabal_ghc_options* This variable can be changed to modify flags given to ghc through cabal exec. + =============================================================================== hdevtools *ale-haskell-hdevtools* @@ -87,6 +92,18 @@ g:ale_haskell_hfmt_executable *g:ale_haskell_hfmt_executable* This variable can be changed to use a different executable for hfmt. + +=============================================================================== +hindent *ale-haskell-hindent* + +g:ale_haskell_hindent_executable *g:ale_haskell_hindent_executable* + *b:ale_haskell_hindent_executable* + Type: |String| + Default: `'hindent'` + + This variable can be changed to use a different executable for hindent. + + =============================================================================== hlint *ale-haskell-hlint* @@ -106,6 +123,7 @@ g:ale_haskell_hlint_options g:ale_haskell_hlint_options This variable can be used to pass extra options to the underlying hlint executable. + =============================================================================== stack-build *ale-haskell-stack-build* @@ -117,6 +135,7 @@ g:ale_haskell_stack_build_options *g:ale_haskell_stack_build_options* We default to using `'--fast'`. Since Stack generates binaries, your programs will be slower unless you separately rebuild them outside of ALE. + =============================================================================== stack-ghc *ale-haskell-stack-ghc* @@ -128,6 +147,7 @@ g:ale_haskell_stack_ghc_options *g:ale_haskell_stack_ghc_options* This variable can be changed to modify flags given to ghc through `stack ghc` + =============================================================================== stylish-haskell *ale-haskell-stylish-haskell* @@ -139,6 +159,7 @@ g:ale_haskell_stylish_haskell_executable This variable can be changed to use a different executable for stylish-haskell. + =============================================================================== hie *ale-haskell-hie* @@ -150,5 +171,6 @@ g:ale_haskell_hie_executable *g:ale_haskell_hie_executable* This variable can be changed to use a different executable for the haskell ide engine. i.e. `'hie-wrapper'` + =============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-html.txt b/doc/ale-html.txt index 5d6b20e2..c78dc4cd 100644 --- a/doc/ale-html.txt +++ b/doc/ale-html.txt @@ -9,6 +9,16 @@ fecs *ale-html-fecs* and both of them reads `./.fecsrc` as the default configuration file. See: |ale-javascript-fecs|. +=============================================================================== +html-beautify *ale-html-beautify* + +g:ale_html_beautify_options *g:ale_html_beautify_options* + *b:ale_html_beautify_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to html-beautify. + =============================================================================== htmlhint *ale-html-htmlhint* diff --git a/doc/ale-ink.txt b/doc/ale-ink.txt new file mode 100644 index 00000000..9412a09f --- /dev/null +++ b/doc/ale-ink.txt @@ -0,0 +1,40 @@ +=============================================================================== +ALE Ink Integration *ale-ink-options* + + +=============================================================================== +ink-language-server *ale-ink-language-server* + +Ink Language Server + (https://github.com/ephraim/ink-language-server) + +g:ale_ink_ls_executable g:ale_ink_ls_executable + b:ale_ink_ls_executable + Type: |String| + Default: `'ink-language-server'` + + Ink language server executable. + +g:ale_ink_ls_initialization_options + g:ale_ink_ls_initialization_options + b:ale_ink_ls_initialization_options + Type: |Dictionary| + Default: `{}` + + Dictionary containing configuration settings that will be passed to the + language server at startup. For certain platforms and certain story + structures, the defaults will suffice. However, many projects will need to + change these settings - see the ink-language-server website for more + information. + + An example of setting non-default options: + { + \ 'ink': { + \ 'mainStoryPath': 'init.ink', + \ 'inklecateExecutablePath': '/usr/local/bin/inklecate', + \ 'runThroughMono': v:false + \ } + \} + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nim.txt b/doc/ale-nim.txt new file mode 100644 index 00000000..8985aeb8 --- /dev/null +++ b/doc/ale-nim.txt @@ -0,0 +1,45 @@ +=============================================================================== +ALE Nim Integration *ale-nim-options* + + +=============================================================================== +nimcheck *ale-nim-nimcheck* + + ALE does not provide additional configuration options for `nimcheck` at this + point. + + +=============================================================================== +nimlsp *ale-nim-nimlsp* + +g:nim_nimlsp_nim_sources *g:nim_nimlsp_nim_sources* + + Type: |String| + Default: `''` + + Sets the path to Nim source repository as the first argument to `nimlsp` + command. + + +=============================================================================== +nimpretty *ale-nim-nimpretty* + + +g:ale_nim_nimpretty_executable *g:ale_nim_nimpretty_executable* + *b:ale_nim_nimpretty_executable* + Type: |String| + Default: `'nimpretty'` + + This variable can be changed to use a different executable for nimpretty. + + +g:ale_nim_nimpretty_options *g:ale_nim_nimpretty_options* + *b:ale_nim_nimpretty_options* + Type: |String| + Default: `'--maxLineLen:80'` + + This variable can be changed to modify flags given to nimpretty. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-nix.txt b/doc/ale-nix.txt new file mode 100644 index 00000000..5b2bd6cb --- /dev/null +++ b/doc/ale-nix.txt @@ -0,0 +1,24 @@ +=============================================================================== +ALE Nix Integration *ale-nix-options* + + +=============================================================================== +nixpkgs-fmt *ale-nix-nixpkgs-fmt* + +g:ale_nix_nixpkgsfmt_executable *g:ale_nix_nixpkgsfmt_executable* + *b:ale_nix_nixpkgsfmt_executable* + Type: |String| + Default: `'nixpkgs-fmt'` + + This variable sets executable used for nixpkgs-fmt. + +g:ale_nix_nixpkgsfmt_options *g:ale_nix_nixpkgsfmt_options* + *b:ale_nix_nixpkgsfmt_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the nixpkgs-fmt fixer. + + +=============================================================================== + vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-php.txt b/doc/ale-php.txt index d41fb50d..645decd7 100644 --- a/doc/ale-php.txt +++ b/doc/ale-php.txt @@ -157,9 +157,9 @@ g:ale_php_phpstan_level *g:ale_php_phpstan_level* Type: |String| Default: `''` - This variable controls the rule levels. 0 is the loosest and 4 is the + This variable controls the rule levels. 0 is the loosest and 7 is the strictest. If this option isn't set, the rule level will be controlled by - the configuration file. If no configuration file can be detected, `'4'` will + the configuration file. If no configuration file can be detected, `'7'` will be used instead. @@ -189,6 +189,13 @@ 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* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to psalm. + =============================================================================== php-cs-fixer *ale-php-php-cs-fixer* diff --git a/doc/ale-purescript.txt b/doc/ale-purescript.txt index 33fd2429..e809f2c9 100644 --- a/doc/ale-purescript.txt +++ b/doc/ale-purescript.txt @@ -30,4 +30,13 @@ g:ale_purescript_ls_config g:ale_purescript_ls_config \ } \} =============================================================================== +purty *ale-purescript-purty* + +g:ale_purescript_purty_executable *g:ale_purescript_purty_executable* + *b:ale_purescript_purty_executable* + Type: |String| + Default: `'purty'` + + This variable can be changed to use a different executable for purty. +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-python.txt b/doc/ale-python.txt index 9d5846d2..93f1d668 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -145,8 +145,8 @@ g:ale_python_black_use_global *g:ale_python_black_use_global* See |ale-integrations-local-executables| -g:ale_python_black_auto_pipenv *g:ale_python_black_auto_pipenv* - *b:ale_python_black_auto_pipenv* +g:ale_python_black_auto_pipenv *g:ale_python_black_auto_pipenv* + *b:ale_python_black_auto_pipenv* Type: |Number| Default: `0` @@ -263,6 +263,15 @@ to check for errors while you type. `mypy` will be run from a detected project root, per |ale-python-root|. +g:ale_python_mypy_auto_pipenv *g:ale_python_mypy_auto_pipenv* + *b:ale_python_mypy_auto_pipenv* + Type: |Number| + Default: `0` + + Detect whether the file is inside a pipenv, and set the executable to `pipenv` + if true. This is overridden by a manually-set executable. + + g:ale_python_mypy_executable *g:ale_python_mypy_executable* *b:ale_python_mypy_executable* Type: |String| @@ -272,6 +281,7 @@ g:ale_python_mypy_executable *g:ale_python_mypy_executable* Set this to `'pipenv'` to invoke `'pipenv` `run` `mypy'`. + g:ale_python_mypy_ignore_invalid_syntax *g:ale_python_mypy_ignore_invalid_syntax* *b:ale_python_mypy_ignore_invalid_syntax* @@ -292,6 +302,14 @@ g:ale_python_mypy_options *g:ale_python_mypy_options* invocation. +g:ale_python_mypy_show_notes *g:ale_python_mypy_show_notes* + *b:ale_python_mypy_show_notes* + Type: |Number| + Default: `1` + + If enabled, notes on lines will be displayed as 'I' (info) messages. + + g:ale_python_mypy_use_global *g:ale_python_mypy_use_global* *b:ale_python_mypy_use_global* Type: |Number| @@ -300,14 +318,6 @@ g:ale_python_mypy_use_global *g:ale_python_mypy_use_global* See |ale-integrations-local-executables| -g:ale_python_mypy_auto_pipenv *g:ale_python_mypy_auto_pipenv* - *b:ale_python_mypy_auto_pipenv* - Type: |Number| - Default: `0` - - Detect whether the file is inside a pipenv, and set the executable to `pipenv` - if true. This is overridden by a manually-set executable. - =============================================================================== prospector *ale-python-prospector* diff --git a/doc/ale-ruby.txt b/doc/ale-ruby.txt index e373ab8e..a27a20b2 100644 --- a/doc/ale-ruby.txt +++ b/doc/ale-ruby.txt @@ -21,6 +21,26 @@ g:ale_ruby_brakeman_options *g:ale_ruby_brakeman_options* The contents of this variable will be passed through to brakeman. +=============================================================================== +debride *ale-ruby-debride* + +g:ale_ruby_debride_executable *g:ale_ruby_debride_executable* + *b:ale_ruby_debride_executable* + Type: String + Default: `'debride'` + + Override the invoked debride binary. Set this to `'bundle'` to invoke + `'bundle` `exec` debride'. + + +g:ale_ruby_debride_options *g:ale_ruby_debride_options* + *b:ale_ruby_debride_options* + Type: |String| + Default: `''` + + This variable can be changed to modify flags given to debride. + + =============================================================================== rails_best_practices *ale-ruby-rails_best_practices* @@ -91,7 +111,7 @@ g:ale_ruby_rubocop_options *g:ale_ruby_rubocop_options* Type: |String| Default: `''` - This variable can be change to modify flags given to rubocop. + This variable can be changed to modify flags given to rubocop. =============================================================================== @@ -146,7 +166,7 @@ g:ale_ruby_sorbet_options *g:ale_ruby_sorbet_options* Type: |String| Default: `''` - This variable can be change to modify flags given to sorbet. + This variable can be changed to modify flags given to sorbet. =============================================================================== @@ -166,7 +186,7 @@ g:ale_ruby_standardrb_options *g:ale_ruby_standardrb_options* Type: |String| Default: `''` - This variable can be change to modify flags given to standardrb. + This variable can be changed to modify flags given to standardrb. =============================================================================== diff --git a/doc/ale-scala.txt b/doc/ale-scala.txt index ff43cd6c..c9638baf 100644 --- a/doc/ale-scala.txt +++ b/doc/ale-scala.txt @@ -3,6 +3,32 @@ ALE Scala Integration *ale-scala-options* =============================================================================== +metals *ale-scala-metals* + +`metals` requires either an SBT project, a Mill project, or a running Bloop +server. + + +g:ale_scala_metals_executable *g:ale_scala_metals_executable* + *b:ale_scala_metals_executable* + Type: |String| + Default: `'metals-vim'` + + Override the invoked `metals` binary. + + +g:ale_scala_metals_project_root *g:ale_scala_metals_project_root* + *b:ale_scala_metals_project_root* + Type: |String| + Default: `''` + + By default the project root is found by searching upwards for `build.sbt`, + `build.sc`, `.bloop` or `.metals`. + If the project root is elsewhere, you can override the project root + directory. + + +=============================================================================== sbtserver *ale-scala-sbtserver* `sbtserver` requires a running ^1.1.x sbt shell to connect to. It will attempt diff --git a/doc/ale-solidity.txt b/doc/ale-solidity.txt index 4b74a27a..b6e48675 100644 --- a/doc/ale-solidity.txt +++ b/doc/ale-solidity.txt @@ -3,6 +3,18 @@ ALE Solidity Integration *ale-solidity-options* =============================================================================== +solc *ale-solidity-solc* + + +g:ale_solidity_solc_options *g:ale_solidity_solc_options* + *b:ale_solidity_solc_options* + Type: |String| + Default: `''` + + This variable can be set to pass extra options to solc. + + +=============================================================================== solhint *ale-solidity-solhint* Solhint should work out-of-the-box. You can further configure it using a diff --git a/doc/ale-sql.txt b/doc/ale-sql.txt index f9bc6ac2..2807271b 100644 --- a/doc/ale-sql.txt +++ b/doc/ale-sql.txt @@ -40,4 +40,22 @@ g:ale_sql_sqlfmt_options *g:ale_sql_sqlfmt_options* =============================================================================== +sqlformat *ale-sql-sqlformat* + +g:ale_sql_sqlformat_executable *g:ale_sql_sqlformat_executable* + *b:ale_sql_sqlformat_executable* + Type: |String| + Default: `'sqlformat'` + + This variable sets executable used for sqlformat. + +g:ale_sql_sqlformat_options *g:ale_sql_sqlformat_options* + *b:ale_sql_sqlformat_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to the sqlformat fixer. + + +=============================================================================== vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl: diff --git a/doc/ale-supported-languages-and-tools.txt b/doc/ale-supported-languages-and-tools.txt index d39aaf7e..29dabab7 100644 --- a/doc/ale-supported-languages-and-tools.txt +++ b/doc/ale-supported-languages-and-tools.txt @@ -14,6 +14,7 @@ Notes: * Ada * `gcc` + * `gnatpp` * Ansible * `ansible-lint` * API Blueprint @@ -102,6 +103,7 @@ Notes: * Cython (pyrex filetype) * `cython` * D + * `dfmt` * `dls` * `dmd` * `uncrustify` @@ -122,7 +124,7 @@ Notes: * `mix`!! * Elm * `elm-format` - * `elm-lsp` + * `elm-ls` * `elm-make` * Erb * `erb` @@ -182,6 +184,7 @@ Notes: * `hdevtools` * `hfmt` * `hie` + * `hindent` * `hlint` * `stack-build`!! * `stack-ghc` @@ -191,6 +194,7 @@ Notes: * HTML * `alex`!! * `fecs` + * `html-beautify` * `HTMLHint` * `prettier` * `proselint` @@ -198,6 +202,8 @@ Notes: * `write-good` * Idris * `idris` +* Ink + * `ink-language-server` * ISPC * `ispc`!! * Java @@ -277,8 +283,11 @@ Notes: * `nasm`!! * Nim * `nim check`!! + * `nimlsp` + * `nimpretty` * nix * `nix-instantiate` + * `nixpkgs-fmt` * nroff * `alex`!! * `proselint` @@ -341,6 +350,7 @@ Notes: * `puppet-lint` * PureScript * `purescript-language-server` + * `purty` * Python * `autopep8` * `bandit` @@ -386,6 +396,7 @@ Notes: * `rpmlint` * Ruby * `brakeman` + * `debride` * `rails_best_practices`!! * `reek` * `rubocop` @@ -404,6 +415,7 @@ Notes: * `stylelint` * Scala * `fsc` + * `metals` * `sbtserver` * `scalac` * `scalafmt` @@ -418,11 +430,13 @@ Notes: * SML * `smlnj` * Solidity + * `solc` * `solhint` * `solium` * SQL * `pgformatter` * `sqlfmt` + * `sqlformat` * `sqlint` * Stylus * `stylelint` @@ -455,6 +469,7 @@ Notes: * `eslint` * `fecs` * `prettier` + * `standard` * `tslint` * `tsserver` * `typecheck` diff --git a/doc/ale-typescript.txt b/doc/ale-typescript.txt index 7dc59820..2c50d119 100644 --- a/doc/ale-typescript.txt +++ b/doc/ale-typescript.txt @@ -17,6 +17,33 @@ See |ale-javascript-prettier| for information about the available options. =============================================================================== +standard *ale-typescript-standard* + +g:ale_typescript_standard_executable *g:ale_typescript_standard_executable* + *b:ale_typescript_standard_executable* + Type: |String| + Default: `'standard'` + + See |ale-integrations-local-executables| + + +g:ale_typescript_standard_options *g:ale_typescript_standard_options* + *b:ale_typescript_standard_options* + Type: |String| + Default: `''` + + This variable can be set to pass additional options to standard. + + +g:ale_typescript_standard_use_global *g:ale_typescript_standard_use_global* + *b:ale_typescript_standard_use_global* + Type: |Number| + Default: `get(g:, 'ale_use_global_executables', 0)` + + See |ale-integrations-local-executables| + + +=============================================================================== tslint *ale-typescript-tslint* This linter isn't recommended, because TSLint can't be used for checking for diff --git a/doc/ale.txt b/doc/ale.txt index 5541236f..469fa106 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -9,7 +9,8 @@ CONTENTS *ale-contents* 1. Introduction.........................|ale-introduction| 2. Supported Languages & Tools..........|ale-support| 3. Linting..............................|ale-lint| - 3.1 Other Sources.....................|ale-lint-other-sources| + 3.1 Adding Language Servers...........|ale-lint-language-servers| + 3.2 Other Sources.....................|ale-lint-other-sources| 4. Fixing Problems......................|ale-fix| 5. Language Server Protocol Support.....|ale-lsp| 5.1 Completion........................|ale-completion| @@ -147,7 +148,48 @@ ALE offers several options for controlling which linters are run. ------------------------------------------------------------------------------- -3.1 Other Sources *ale-lint-other-sources* +3.1 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 +by defining a new linter for a filetype. New linters can be defined in |vimrc|, +in plugin files, or `ale_linters` directories in |runtimepath|. + +See |ale-linter-loading-behavior| for more information on loading linters. + +A minimal configuration for a language server linter might look so. > + + call ale#linter#Define('filetype_here', { + \ 'name': 'any_name_you_want', + \ 'lsp': 'stdio', + \ 'executable': '/path/to/executable', + \ 'command': '%e run', + \ 'project_root': '/path/to/root_of_project', + \}) +< +For language servers that use a TCP socket connection, you should define the +address to connect to instead. > + + call ale#linter#Define('filetype_here', { + \ 'name': 'any_name_you_want', + \ 'lsp': 'socket', + \ 'address': 'servername:1234', + \ 'project_root': '/path/to/root_of_project', + \}) +< + Most of the options for a language server can be replaced with a |Funcref| + for a function accepting a buffer number for dynamically computing values + such as the executable path, the project path, the server address, etc, + most of which can also be determined based on executing some other + asynchronous task. See |ale#command#Run()| for computing linter options + based on asynchronous results. + + See |ale#linter#Define()| for a detailed explanation of all of the options + for configuring linters. + + +------------------------------------------------------------------------------- +3.2 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 @@ -341,6 +383,17 @@ completion source for Deoplete is named `'ale'`, and should enabled automatically if Deoplete is enabled and configured correctly. Deoplete integration should not be combined with ALE's own implementation. + *ale-asyncomplete-integration* + +ALE additionally integrates with asyncomplete.vim for offering automatic +completion data. ALE's asyncomplete source requires registration and should +use the defaults provided by the|asyncomplete#sources#ale#get_source_options| function > + + " Use ALE's function for asyncomplete defaults + au User asyncomplete_setup call asyncomplete#register_source(asyncomplete#sources#ale#get_source_options({ + \ 'priority': 10, " Provide your own overrides here + \ })) +> ALE also offers its own completion implementation, which does not require any other plugins. Suggestions will be made while you type after completion is enabled. ALE's own completion implementation can be enabled by setting @@ -365,6 +418,10 @@ 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-completion-completeopt-bug* ALE Automatic completion implementation replaces |completeopt| before opening @@ -376,17 +433,54 @@ vimrc, and your issues should go away. > set completeopt=menu,menuone,preview,noselect,noinsert < +Or alternatively, if you want to show documentation in popups: > + + set completeopt=menu,menuone,popup,noselect,noinsert +< + *ale-symbols* + +ALE provides a set of basic completion symbols. If you want to replace those +symbols with others, you can set the variable |g:ale_completion_symbols| with +a mapping of the type of completion to the symbol or other string that you +would like to use. An example here shows the available options for symbols > + + let g:ale_completion_symbols = { + \ 'text': '', + \ 'method': '', + \ 'function': '', + \ 'constructor': '', + \ 'field': '', + \ 'variable': '', + \ 'class': '', + \ 'interface': '', + \ 'module': '', + \ 'property': '', + \ 'unit': 'unit', + \ 'value': 'val', + \ 'enum': '', + \ 'keyword': 'keyword', + \ 'snippet': '', + \ 'color': 'color', + \ 'file': '', + \ 'reference': 'ref', + \ 'folder': '', + \ 'enum member': '', + \ 'constant': '', + \ 'struct': '', + \ 'event': 'event', + \ 'operator': '', + \ 'type_parameter': 'type param', + \ '<default>': 'v' + \ } +< ------------------------------------------------------------------------------- 5.2 Go To Definition *ale-go-to-definition* 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`. @@ -396,15 +490,10 @@ 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* @@ -433,6 +522,9 @@ the mouse over a symbol in a buffer. Diagnostic information will take priority over hover information for balloons. If a line contains a problem, that problem will be displayed in a balloon instead of hover information. +Hover information can be displayed in the preview window instead by setting +|g:ale_hover_to_preview| to `1`. + For Vim 8.1+ terminals, mouse hovering is disabled by default. Enabling |balloonexpr| commands in terminals can cause scrolling issues in terminals, so ALE will not attempt to show balloons unless |g:ale_set_balloons| is set to @@ -566,7 +658,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` @@ -586,6 +678,16 @@ b:ale_completion_enabled *b:ale_completion_enabled* See |ale-completion| +g:ale_completion_tsserver_autoimport *g:ale_completion_tsserver_autoimport* + + Type: Number + Default: `0` + + When this option is set to `0`, ALE will not try to automatically import + completion results from external modules. It can be enabled by setting it + to `1`. + + g:ale_completion_excluded_words *g:ale_completion_excluded_words* *b:ale_completion_excluded_words* Type: |List| @@ -604,6 +706,47 @@ g:ale_completion_excluded_words *g:ale_completion_excluded_words* let b:ale_completion_excluded_words = ['it', 'describe'] < +g:ale_completion_symbols *g:ale_completion_symbols* + + Type: |Dictionary| + + + A mapping from completion types to symbols for completions. See + |ale-symbols| for more information. + + By default, this mapping only uses built in Vim completion kinds, but it can + be updated to use any unicode character for the completion kind. For + example: > + let g:ale_completion_symbols = { + \ 'text': '', + \ 'method': '', + \ 'function': '', + \ 'constructor': '', + \ 'field': '', + \ 'variable': '', + \ 'class': '', + \ 'interface': '', + \ 'module': '', + \ 'property': '', + \ 'unit': 'v', + \ 'value': 'v', + \ 'enum': 't', + \ 'keyword': 'v', + \ 'snippet': 'v', + \ 'color': 'v', + \ 'file': 'v', + \ 'reference': 'v', + \ 'folder': 'v', + \ 'enum_member': 'm', + \ 'constant': 'm', + \ 'struct': 't', + \ 'event': 'v', + \ 'operator': 'f', + \ 'type_parameter': 'p', + \ '<default>': 'v' + \ }) +< + g:ale_completion_max_suggestions *g:ale_completion_max_suggestions* Type: |Number| @@ -642,6 +785,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* @@ -694,7 +847,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'` @@ -734,6 +887,15 @@ g:ale_echo_msg_info_str *g:ale_echo_msg_info_str* The string used for `%severity%` for info. See |g:ale_echo_msg_format| +g:ale_echo_msg_log_str *g:ale_echo_msg_log_str* + + Type: |String| + Default: `'Log'` + + The string used for `%severity%` for log, used only for handling LSP show + message requests. See |g:ale_lsp_show_message_format| + + g:ale_echo_msg_warning_str *g:ale_echo_msg_warning_str* Type: |String| @@ -763,6 +925,21 @@ g:ale_enabled *g:ale_enabled* See |g:ale_pattern_options| for more information on that option. +g:ale_exclude_highlights *g:ale_exclude_highlights* + *b:ale_exclude_highlights* + + Type: |List| + Default: `[]` + + A list of regular expressions for matching against highlight messages to + remove. For example: > + + " Do not highlight messages matching strings like these. + let b:ale_exclude_highlights = ['line too long', 'foo.*bar'] +< + See also: |g:ale_set_highlights| + + g:ale_fixers *g:ale_fixers* *b:ale_fixers* @@ -786,7 +963,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` @@ -808,7 +985,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: `{}` @@ -871,6 +1048,16 @@ g:ale_history_log_output *g:ale_history_log_output* if you want to save on some memory usage. +g:ale_hover_to_preview *g:ale_hover_to_preview* + *b:ale_hover_to_preview* + + Type: |Number| + Default: `0` + + If set to `1`, hover messages will be displayed in the preview window, + instead of in balloons or the message line. + + g:ale_keep_list_window_open *g:ale_keep_list_window_open* *b:ale_keep_list_window_open* Type: |Number| @@ -1016,9 +1203,12 @@ g:ale_linter_aliases *g:ale_linter_aliases* { \ 'Dockerfile': 'dockerfile', \ 'csh': 'sh', + \ 'javascriptreact': ['javascript', 'jsx'], \ 'plaintex': 'tex', \ 'rmarkdown': 'r', + \ 'rmd': 'r', \ 'systemverilog': 'verilog', + \ 'typescriptreact': ['typescript', 'tsx'], \ 'verilog_systemverilog': ['verilog_systemverilog', 'verilog'], \ 'vimwiki': 'markdown', \ 'vue': ['vue', 'javascript'], @@ -1171,7 +1361,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` @@ -1181,8 +1371,49 @@ b:ale_loclist_msg_format *b:ale_loclist_msg_format* The strings for configuring `%severity%` are also used for this option. + +g:ale_lsp_show_message_format *g:ale_lsp_show_message_format* + + Type: |String| + Default: `'%severity%:%linter%: %s'` + + This variable defines the format that messages received from an LSP will + have when echoed. The following sequences of characters will be replaced. + + `%s` - replaced with the message text + `%linter%` - replaced with the name of the linter + `%severity%` - replaced with the severity of the message + + The strings for `%severity%` levels "error", "info" and "warning" are shared + with |g:ale_echo_msg_format|. Severity "log" is unique to + |g:ale_lsp_show_message_format| and it can be configured via + + |g:ale_echo_msg_log_str| - Defaults to `'Log'` + + Please note that |g:ale_lsp_show_message_format| *can not* be configured + separately for each buffer like |g:ale_echo_msg_format| can. + + +g:ale_lsp_show_message_severity *g:ale_lsp_show_message_severity* + + Type: |String| + Default: `'error'` + + This variable defines the minimum severity level an LSP message needs to be + displayed. Messages below this level are discarded; please note that + messages with `Log` severity level are always discarded. + + Possible values follow the LSP spec `MessageType` definition: + + `'error'` - Displays only errors. + `'warning'` - Displays errors and warnings. + `'information'` - Displays errors, warnings and infos + `'log'` - Same as `'information'` + `'disabled'` - Doesn't display any information at all. + + 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: {} @@ -1306,6 +1537,27 @@ g:ale_pattern_options_enabled *g:ale_pattern_options_enabled* will not set buffer variables per |g:ale_pattern_options|. +g:ale_rename_tsserver_find_in_comments *g:ale_rename_tsserver_find_in_comments* + + Type: |Number| + Default: `0` + + If enabled, this option will tell tsserver to find and replace text in + comments when calling |ALERename|. It can be enabled by settings the value + to `1`. + + +g:ale_rename_tsserver_find_in_strings *g:ale_rename_tsserver_find_in_strings* + + + Type: |Number| + Default: `0` + + If enabled, this option will tell tsserver to find and replace text in + strings when calling |ALERename|. It can be enabled by settings the value to + `1`. + + g:ale_set_balloons *g:ale_set_balloons* *b:ale_set_balloons* @@ -1374,6 +1626,8 @@ g:ale_set_highlights *g:ale_set_highlights* match highlights, whereas the line highlights when signs are enabled will run to the edge of the screen. + Highlights can be excluded with the |g:ale_exclude_highlights| option. + g:ale_set_loclist *g:ale_set_loclist* @@ -1426,8 +1680,8 @@ g:ale_set_signs *g:ale_set_signs* |ALEWarningLine| - All items with `'type': 'W'` |ALEInfoLine| - All items with `'type': 'I'` - With Neovim 0.3.2 or higher, ALE uses `numhl` option to highlight 'number' - column. It uses the following highlight groups. + With Neovim 0.3.2 or higher, ALE can use the `numhl` option to highlight the + 'number' column. It uses the following highlight groups. |ALEErrorSignLineNr| - Items with `'type': 'E'` |ALEWarningSignLineNr| - Items with `'type': 'W'` @@ -1435,6 +1689,9 @@ g:ale_set_signs *g:ale_set_signs* |ALEStyleErrorSignLineNr| - Items with `'type': 'E'` and `'sub_type': 'style'` |ALEStyleWarningSignLineNr| - Items with `'type': 'W'` and `'sub_type': 'style'` + To enable line number highlighting |g:ale_sign_highlight_linenrs| must be + set to `1` before ALE is loaded. + The markers for the highlights can be customized with the following options: |g:ale_sign_error| @@ -1449,6 +1706,16 @@ g:ale_set_signs *g:ale_set_signs* To limit the number of signs ALE will set, see |g:ale_max_signs|. +g:ale_sign_priority *g:ale_sign_priority* + + Type: |Number| + Default: `30` + + From Neovim 0.4.0 and Vim 8.1, ALE can set sign priority to all signs. The + larger this value is, the higher priority ALE signs have over other plugin + signs. See |sign-priority| for further details on how priority works. + + g:ale_shell *g:ale_shell* Type: |String| @@ -1541,6 +1808,16 @@ g:ale_sign_warning *g:ale_sign_warning* The sign for warnings in the sign gutter. +g:ale_sign_highlight_linenrs *g:ale_sign_highlight_linenrs* + + Type: |Number| + Default: `0` + + When set to `1`, this option enables highlighting problems on the 'number' + column in Vim versions that support `numhl` highlights. This option must be + configured before ALE is loaded. + + g:ale_update_tagstack *g:ale_update_tagstack* *b:ale_update_tagstack* Type: |Number| @@ -1595,6 +1872,8 @@ g:ale_virtualtext_cursor *g:ale_virtualtext_cursor* Type: |Number| Default: `0` + This option only has any effect in NeoVim. + When this option is set to `1`, a message will be shown when a cursor is near a warning or error. ALE will attempt to find the warning or error at a column nearest to the cursor when the cursor is resting on a line which @@ -1615,7 +1894,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` @@ -1634,7 +1914,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']` @@ -1648,7 +1928,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` @@ -1660,7 +1940,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` @@ -1943,6 +2223,14 @@ g:ale_languagetool_executable *g:ale_languagetool_executable* The executable to run for languagetool. +g:ale_languagetool_options *g:ale_languagetool_options* + *b:ale_languagetool_options* + Type: |String| + Default: `'--autoDetect'` + + This variable can be set to pass additional options to languagetool. + + ------------------------------------------------------------------------------- 7.3. Options for write-good *ale-write-good-options* @@ -1981,6 +2269,7 @@ documented in additional help files. ada.....................................|ale-ada-options| gcc...................................|ale-ada-gcc| + gnatpp................................|ale-ada-gnatpp| ansible.................................|ale-ansible-options| ansible-lint..........................|ale-ansible-ansible-lint| asciidoc................................|ale-asciidoc-options| @@ -2041,6 +2330,7 @@ documented in additional help files. nvcc..................................|ale-cuda-nvcc| clang-format..........................|ale-cuda-clangformat| d.......................................|ale-d-options| + dfmt..................................|ale-d-dfmt| dls...................................|ale-d-dls| uncrustify............................|ale-d-uncrustify| dart....................................|ale-dart-options| @@ -2107,6 +2397,7 @@ documented in additional help files. cabal-ghc.............................|ale-haskell-cabal-ghc| hdevtools.............................|ale-haskell-hdevtools| hfmt..................................|ale-haskell-hfmt| + hindent...............................|ale-haskell-hindent| hlint.................................|ale-haskell-hlint| stack-build...........................|ale-haskell-stack-build| stack-ghc.............................|ale-haskell-stack-ghc| @@ -2116,6 +2407,7 @@ documented in additional help files. terraform-fmt.........................|ale-hcl-terraform-fmt| html....................................|ale-html-options| fecs..................................|ale-html-fecs| + html-beautify.........................|ale-html-beautify| htmlhint..............................|ale-html-htmlhint| tidy..................................|ale-html-tidy| prettier..............................|ale-html-prettier| @@ -2123,6 +2415,8 @@ documented in additional help files. write-good............................|ale-html-write-good| idris...................................|ale-idris-options| idris.................................|ale-idris-idris| + ink.....................................|ale-ink-options| + ink-language-server...................|ale-ink-language-server| ispc....................................|ale-ispc-options| ispc..................................|ale-ispc-ispc| java....................................|ale-java-options| @@ -2178,6 +2472,12 @@ documented in additional help files. mmc...................................|ale-mercury-mmc| nasm....................................|ale-nasm-options| nasm..................................|ale-nasm-nasm| + nim.....................................|ale-nim-options| + nimcheck..............................|ale-nim-nimcheck| + nimlsp................................|ale-nim-nimlsp| + nimpretty.............................|ale-nim-nimpretty| + nix.....................................|ale-nix-options| + nixpkgs-fmt...........................|ale-nix-nixpkgs-fmt| nroff...................................|ale-nroff-options| write-good............................|ale-nroff-write-good| objc....................................|ale-objc-options| @@ -2233,6 +2533,7 @@ documented in additional help files. puppet-languageserver.................|ale-puppet-languageserver| purescript..............................|ale-purescript-options| purescript-language-server............|ale-purescript-language-server| + purty.................................|ale-purescript-purty| pyrex (cython)..........................|ale-pyrex-options| cython................................|ale-pyrex-cython| python..................................|ale-python-options| @@ -2268,6 +2569,7 @@ documented in additional help files. write-good............................|ale-restructuredtext-write-good| ruby....................................|ale-ruby-options| brakeman..............................|ale-ruby-brakeman| + debride...............................|ale-ruby-debride| rails_best_practices..................|ale-ruby-rails_best_practices| reek..................................|ale-ruby-reek| rubocop...............................|ale-ruby-rubocop| @@ -2285,6 +2587,7 @@ documented in additional help files. sasslint..............................|ale-sass-sasslint| stylelint.............................|ale-sass-stylelint| scala...................................|ale-scala-options| + metals................................|ale-scala-metals| sbtserver.............................|ale-scala-sbtserver| scalafmt..............................|ale-scala-scalafmt| scalastyle............................|ale-scala-scalastyle| @@ -2300,6 +2603,7 @@ documented in additional help files. sml.....................................|ale-sml-options| smlnj.................................|ale-sml-smlnj| solidity................................|ale-solidity-options| + solc..................................|ale-solidity-solc| solhint...............................|ale-solidity-solhint| solium................................|ale-solidity-solium| spec....................................|ale-spec-options| @@ -2307,6 +2611,7 @@ documented in additional help files. sql.....................................|ale-sql-options| pgformatter...........................|ale-sql-pgformatter| sqlfmt................................|ale-sql-sqlfmt| + sqlformat.............................|ale-sql-sqlformat| stylus..................................|ale-stylus-options| stylelint.............................|ale-stylus-stylelint| sugarss.................................|ale-sugarss-options| @@ -2334,6 +2639,7 @@ documented in additional help files. typescript..............................|ale-typescript-options| eslint................................|ale-typescript-eslint| prettier..............................|ale-typescript-prettier| + standard..............................|ale-typescript-standard| tslint................................|ale-typescript-tslint| tsserver..............................|ale-typescript-tsserver| vala....................................|ale-vala-options| @@ -2401,11 +2707,23 @@ 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|. + + 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. + ALEFix *ALEFix* Fix problems with the current buffer. See |ale-fix| for more information. @@ -2420,40 +2738,29 @@ 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. - 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_definition)` is defined for this command. - - -ALEGoToDefinitionInTab *ALEGoToDefinitionInTab* + The locations opened in different ways using the following variations. - The same as |ALEGoToDefinition|, but opens results in a new tab. + `: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. - A plug mapping `<Plug>(ale_go_to_definition_in_tab)` is defined for this - command. - - -ALEGoToDefinitionInSplit *ALEGoToDefinitionInSplit* + The default method used for navigating to a new location can be changed + by modifying |g:ale_default_navigation|. - 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* + 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 |ALEGoToDefinition|, but opens results in a new vertical split. + You should consider using the 'hidden' option in combination with this + 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_in_vsplit)` is defined for this - command. + A plug mapping `<Plug>(ale_go_to_definition)` is defined for this command. ALEGoToTypeDefinition *ALEGoToTypeDefinition* @@ -2463,6 +2770,15 @@ ALEGoToTypeDefinition *ALEGoToTypeDefinition* 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. + + `: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|. + 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|. @@ -2470,42 +2786,34 @@ ALEGoToTypeDefinition *ALEGoToTypeDefinition* command. -ALEGoToTypeDefinitionInTab *ALEGoToTypeDefinitionInTab* +ALEHover *ALEHover* - The same as |ALEGoToTypeDefinition|, but opens results in a new tab. + Print brief information about the symbol under the cursor, taken from any + available LSP linters. There may be a small non-blocking delay before + information is printed. - A plug mapping `<Plug>(ale_go_to_type_definition_in_tab)` is defined for - this command. + NOTE: In Vim 8, long messages will be shown in a preview window, as Vim 8 + does not support showing a prompt to press enter to continue for long + messages from asynchronous callbacks. + A plug mapping `<Plug>(ale_hover)` is defined for this command. -ALEGoToTypeDefinitionInSplit *ALEGoToTypeDefinitionInSplit* - The same as |ALEGoToTypeDefinition|, but opens results in a new split. +ALEOrganizeImports *ALEOrganizeImports* - A plug mapping `<Plug>(ale_go_to_type_definition_in_split)` is defined for - this command. + Organize imports using tsserver. Currently not implemented for LSPs. -ALEGoToTypeDefinitionInVSplit *ALEGoToTypeDefinitionInVSplit* +ALERename *ALERename* - The same as |ALEGoToTypeDefinition|, but opens results in a new vertical - split. + Rename a symbol using TypeScript server or Language Server. - A plug mapping `<Plug>(ale_go_to_type_definition_in_vsplit)` is defined for - this command. + The user will be prompted for a new name. -ALEHover *ALEHover* +ALERepeatSelection *ALERepeatSelection* - Print brief information about the symbol under the cursor, taken from any - available LSP linters. There may be a small non-blocking delay before - information is printed. - - NOTE: In Vim 8, long messages will be shown in a preview window, as Vim 8 - does not support showing a prompt to press enter to continue for long - messages from asynchronous callbacks. - - A plug mapping `<Plug>(ale_hover)` is defined for this command. + Repeat the last selection displayed in the preview window. ALESymbolSearch `<query>` *ALESymbolSearch* @@ -2816,7 +3124,6 @@ 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 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 6262a7c4..e1ddf7b7 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -109,6 +109,9 @@ let g:ale_set_signs = get(g:, 'ale_set_signs', has('signs')) " This flag can be set to 0 to disable setting error highlights. let g:ale_set_highlights = get(g:, 'ale_set_highlights', has('syntax')) +" This List can be configured to exclude particular highlights. +let g:ale_exclude_highlights = get(g:, 'ale_exclude_highlights', []) + " This flag can be set to 0 to disable echoing when the cursor moves. let g:ale_echo_cursor = get(g:, 'ale_echo_cursor', 1) @@ -125,6 +128,9 @@ let g:ale_close_preview_on_insert = get(g:, 'ale_close_preview_on_insert', 0) " This flag can be set to 0 to disable balloon support. let g:ale_set_balloons = get(g:, 'ale_set_balloons', has('balloon_eval') && has('gui_running')) +" Use preview window for hover messages. +let g:ale_hover_to_preview = get(g:, 'ale_hover_to_preview', 0) + " This flag can be set to 0 to disable warnings for trailing whitespace let g:ale_warn_about_trailing_whitespace = get(g:, 'ale_warn_about_trailing_whitespace', 1) " This flag can be set to 0 to disable warnings for trailing blank lines @@ -196,16 +202,23 @@ command! -bar -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEF 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>) + +" Deprecated commands we have to keep for now. +command! -bar ALEGoToDefinitionInTab :call ale#definition#GoTo({'open_in': 'tab', 'deprecated_command': 'ALEGoToDefinitionInTab'}) +command! -bar ALEGoToDefinitionInSplit :call ale#definition#GoTo({'open_in': 'split', 'deprecated_command': 'ALEGoToDefinitionInSplit'}) +command! -bar ALEGoToDefinitionInVSplit :call ale#definition#GoTo({'open_in': 'vsplit', 'deprecated_command': 'ALEGoToDefinitionInVSplit'}) " 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>) + +" Deprecated commands we have to keep for now. +command! -bar ALEGoToTypeDefinitionInTab :call ale#definition#GoToType({'open_in': 'tab', 'deprecated_command': 'ALEGoToTypeDefinitionInTab'}) +command! -bar ALEGoToTypeDefinitionInSplit :call ale#definition#GoToType({'open_in': 'split', 'deprecated_command': 'ALEGoToTypeDefinitionInSplit'}) +command! -bar ALEGoToTypeDefinitionInVSplit :call ale#definition#GoToType({'open_in': 'vsplit', 'deprecated_command': 'ALEGoToTypeDefinitionInVSplit'}) + +" 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>) @@ -221,6 +234,12 @@ command! -nargs=1 ALESymbolSearch :call ale#symbol#Search(<q-args>) command! -bar ALEComplete :call ale#completion#GetCompletions('ale-manual') +" Rename symbols using tsserver and LSP +command! -bar ALERename :call ale#rename#Execute() + +" Organize import statements using tsserver +command! -bar ALEOrganizeImports :call ale#organize_imports#Execute() + " <Plug> mappings for commands nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return> nnoremap <silent> <Plug>(ale_previous_wrap) :ALEPreviousWrap<Return> @@ -248,17 +267,21 @@ 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_type_definition) :ALEGoToTypeDefinition<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_rename) :ALERename<Return> +nnoremap <silent> <Plug>(ale_repeat_selection) :ALERepeatSelection<Return> + +" Deprecated <Plug> mappings 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_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_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> " 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 3955ed2d..ae1f4039 100644 --- a/rplugin/python3/deoplete/sources/ale.py +++ b/rplugin/python3/deoplete/sources/ale.py @@ -24,6 +24,7 @@ class Source(Base): self.rank = 1000 self.is_bytepos = True self.min_pattern_length = 1 + self.is_volatile = True # Do not forget to update s:trigger_character_map in completion.vim in # updating entries in this map. self.input_patterns = { @@ -44,21 +45,16 @@ class Source(Base): if not self.vim.call('ale#completion#CanProvideCompletions'): return None - if context.get('is_refresh'): - context['is_async'] = False + event = context.get('event') - if context['is_async']: - # Result is the same as for omnifunc, or None. + if event == 'Async': result = self.vim.call('ale#completion#GetCompletionResult') + return result or [] - if result is not None: - context['is_async'] = False - - return result - else: - context['is_async'] = True - - # Request some completion results. - self.vim.call('ale#completion#GetCompletions', 'deoplete') + if context.get('is_refresh'): + self.vim.command( + "call ale#completion#GetCompletions('ale-callback', " + \ + "{'callback': {completions -> deoplete#auto_complete() }})" + ) return [] diff --git a/supported-tools.md b/supported-tools.md index 6cb9f418..0abc6b75 100644 --- a/supported-tools.md +++ b/supported-tools.md @@ -23,6 +23,7 @@ formatting. * Ada * [gcc](https://gcc.gnu.org) + * [gnatpp](https://docs.adacore.com/gnat_ugn-docs/html/gnat_ugn/gnat_ugn/gnat_utility_programs.html#the-gnat-pretty-printer-gnatpp) :floppy_disk: * Ansible * [ansible-lint](https://github.com/willthames/ansible-lint) * API Blueprint @@ -111,6 +112,7 @@ formatting. * Cython (pyrex filetype) * [cython](http://cython.org/) * D + * [dfmt](https://github.com/dlang-community/dfmt) * [dls](https://github.com/d-language-server/dls) * [dmd](https://dlang.org/dmd-linux.html) * [uncrustify](https://github.com/uncrustify/uncrustify) @@ -125,13 +127,13 @@ formatting. * [hadolint](https://github.com/hadolint/hadolint) * Elixir * [credo](https://github.com/rrrene/credo) - * [dialyxir](https://github.com/jeremyjh/dialyxir) - * [dogma](https://github.com/lpil/dogma) + * [dialyxir](https://github.com/jeremyjh/dialyxir) :floppy_disk: + * [dogma](https://github.com/lpil/dogma) :floppy_disk: * [elixir-ls](https://github.com/JakeBecker/elixir-ls) :warning: * [mix](https://hexdocs.pm/mix/Mix.html) :warning: :floppy_disk: * Elm * [elm-format](https://github.com/avh4/elm-format) - * [elm-lsp](https://github.com/antew/elm-lsp) + * [elm-ls](https://github.com/elm-tooling/elm-language-server) * [elm-make](https://github.com/elm/compiler) * Erb * [erb](https://apidock.com/ruby/ERB) @@ -191,6 +193,7 @@ formatting. * [hdevtools](https://hackage.haskell.org/package/hdevtools) * [hfmt](https://github.com/danstiner/hfmt) * [hie](https://github.com/haskell/haskell-ide-engine) + * [hindent](https://hackage.haskell.org/package/hindent) * [hlint](https://hackage.haskell.org/package/hlint) * [stack-build](https://haskellstack.org/) :floppy_disk: * [stack-ghc](https://haskellstack.org/) @@ -200,6 +203,7 @@ formatting. * HTML * [alex](https://github.com/wooorm/alex) :floppy_disk: * [fecs](http://fecs.baidu.com/) + * [html-beautify](https://beautifier.io/) * [HTMLHint](http://htmlhint.com/) * [prettier](https://github.com/prettier/prettier) * [proselint](http://proselint.com/) @@ -207,6 +211,8 @@ formatting. * [write-good](https://github.com/btford/write-good) * Idris * [idris](http://www.idris-lang.org/) +* Ink + * [ink-language-server](https://github.com/ephread/ink-language-server) * ISPC * [ispc](https://ispc.github.io/) :floppy_disk: * Java @@ -221,7 +227,7 @@ formatting. * [eslint](http://eslint.org/) * [fecs](http://fecs.baidu.com/) * [flow](https://flowtype.org/) - * [jscs](http://jscs.info/) + * [jscs](https://jscs-dev.github.io/) * [jshint](http://jshint.com/) * [prettier](https://github.com/prettier/prettier) * [prettier-eslint](https://github.com/prettier/prettier-eslint-cli) @@ -246,7 +252,7 @@ formatting. * [lacheck](https://www.ctan.org/pkg/lacheck) * [proselint](http://proselint.com/) * [redpen](http://redpen.cc/) - * [texlab](https://texlab.netlify.com) ([Rust rewrite](https://github.com/latex-lsp/texlab/tree/rust)) + * [texlab](https://texlab.netlify.com) * [textlint](https://textlint.github.io/) * [vale](https://github.com/ValeLint/vale) * [write-good](https://github.com/btford/write-good) @@ -286,8 +292,11 @@ formatting. * [nasm](https://www.nasm.us/) :floppy_disk: * Nim * [nim check](https://nim-lang.org/docs/nimc.html) :floppy_disk: + * [nimlsp](https://github.com/PMunch/nimlsp) + * nimpretty * nix * [nix-instantiate](http://nixos.org/nix/manual/#sec-nix-instantiate) + * [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt) * nroff * [alex](https://github.com/wooorm/alex) :floppy_disk: * [proselint](http://proselint.com/) @@ -336,8 +345,8 @@ formatting. * Pony * [ponyc](https://github.com/ponylang/ponyc) * PowerShell - * [powershell](https://github.com/PowerShell/PowerShell) :floppy_disk - * [psscriptanalyzer](https://github.com/PowerShell/PSScriptAnalyzer) :floppy_disk + * [powershell](https://github.com/PowerShell/PowerShell) :floppy_disk: + * [psscriptanalyzer](https://github.com/PowerShell/PSScriptAnalyzer) :floppy_disk: * Prolog * [swipl](https://github.com/SWI-Prolog/swipl-devel) * proto @@ -350,6 +359,7 @@ formatting. * [puppet-lint](https://puppet-lint.com) * PureScript * [purescript-language-server](https://github.com/nwolverson/purescript-language-server) + * [purty](https://gitlab.com/joneshf/purty) * Python * [autopep8](https://github.com/hhatto/autopep8) * [bandit](https://github.com/PyCQA/bandit) :warning: @@ -395,6 +405,7 @@ formatting. * [rpmlint](https://github.com/rpm-software-management/rpmlint) :warning: (see `:help ale-integration-spec`) * Ruby * [brakeman](http://brakemanscanner.org/) :floppy_disk: + * [debride](https://github.com/seattlerb/debride) :floppy_disk: * [rails_best_practices](https://github.com/flyerhzm/rails_best_practices) :floppy_disk: * [reek](https://github.com/troessner/reek) * [rubocop](https://github.com/bbatsov/rubocop) @@ -413,6 +424,7 @@ formatting. * [stylelint](https://github.com/stylelint/stylelint) * Scala * [fsc](https://www.scala-lang.org/old/sites/default/files/linuxsoft_archives/docu/files/tools/fsc.html) + * [metals](https://scalameta.org/metals/) * [sbtserver](https://www.scala-sbt.org/1.x/docs/sbt-server.html) * [scalac](http://scala-lang.org) * [scalafmt](https://scalameta.org/scalafmt/) @@ -427,11 +439,13 @@ formatting. * SML * [smlnj](http://www.smlnj.org/) * Solidity + * [solc](https://solidity.readthedocs.io/) * [solhint](https://github.com/protofire/solhint) * [solium](https://github.com/duaraghav8/Solium) * SQL * [pgformatter](https://github.com/darold/pgFormatter) * [sqlfmt](https://github.com/jackc/sqlfmt) + * [sqlformat](https://github.com/andialbrecht/sqlparse) * [sqlint](https://github.com/purcell/sqlint) * Stylus * [stylelint](https://github.com/stylelint/stylelint) @@ -464,6 +478,7 @@ formatting. * [eslint](http://eslint.org/) * [fecs](http://fecs.baidu.com/) * [prettier](https://github.com/prettier/prettier) + * [standard](http://standardjs.com/) * [tslint](https://github.com/palantir/tslint) * [tsserver](https://github.com/Microsoft/TypeScript/wiki/Standalone-Server-%28tsserver%29) * typecheck diff --git a/test/command_callback/clangd_paths/compile_commands.json b/test/ada_files/testfile.adb index e69de29b..e69de29b 100644 --- a/test/command_callback/clangd_paths/compile_commands.json +++ b/test/ada_files/testfile.adb diff --git a/test/command_callback/psalm-project/vendor/bin/psalm-language-server b/test/command_callback/clangd_paths/with_build_dir/unusual_build_dir_name/compile_commands.json index e69de29b..e69de29b 100755..100644 --- a/test/command_callback/psalm-project/vendor/bin/psalm-language-server +++ b/test/command_callback/clangd_paths/with_build_dir/unusual_build_dir_name/compile_commands.json diff --git a/test/command_callback/clangd_paths/with_compile_commands/compile_commands.json b/test/command_callback/clangd_paths/with_compile_commands/compile_commands.json new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/clangd_paths/with_compile_commands/compile_commands.json diff --git a/test/command_callback/html_beautify_paths/html-beautify b/test/command_callback/html_beautify_paths/html-beautify new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/html_beautify_paths/html-beautify diff --git a/test/command_callback/html_beautify_paths/test.html b/test/command_callback/html_beautify_paths/test.html new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/html_beautify_paths/test.html diff --git a/test/command_callback/ink_paths/story/main.ink b/test/command_callback/ink_paths/story/main.ink new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/ink_paths/story/main.ink diff --git a/test/command_callback/psalm-project/vendor/bin/psalm b/test/command_callback/psalm-project/vendor/bin/psalm new file mode 100755 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/psalm-project/vendor/bin/psalm diff --git a/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/mypy.ini b/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/mypy.ini new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/mypy.ini diff --git a/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/tests/pytest.ini b/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/tests/pytest.ini new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/tests/pytest.ini diff --git a/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/tests/testsubfolder/my_tests.py b/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/tests/testsubfolder/my_tests.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_mypy_ini_and_pytest_ini/tests/testsubfolder/my_tests.py diff --git a/test/command_callback/python_paths/with_virtualenv/subdir/foo/bar.pyi b/test/command_callback/python_paths/with_virtualenv/subdir/foo/bar.pyi new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/command_callback/python_paths/with_virtualenv/subdir/foo/bar.pyi 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 fa76c66c..5ebbbd45 100644 --- a/test/command_callback/test_c_clang_tidy_command_callback.vader +++ b/test/command_callback/test_c_clang_tidy_command_callback.vader @@ -1,4 +1,7 @@ Before: + Save g:ale_c_parse_makefile + let g:ale_c_parse_makefile = 0 + call ale#assert#SetUpLinterTest('c', 'clangtidy') call ale#test#SetFilename('test.c') diff --git a/test/command_callback/test_c_clangd_command_callbacks.vader b/test/command_callback/test_c_clangd_command_callbacks.vader index dc52097d..753dc9a8 100644 --- a/test/command_callback/test_c_clangd_command_callbacks.vader +++ b/test/command_callback/test_c_clangd_command_callbacks.vader @@ -4,6 +4,11 @@ Before: Save &filetype let &filetype = 'c' + Save b:ale_c_clangd_options + Save b:ale_c_build_dir + Save b:ale_c_build_dir_names + Save b:ale_c_parse_compile_commands + After: call ale#assert#TearDownLinterTest() @@ -18,9 +23,9 @@ Execute(The project root should be detected correctly): AssertLSPProject '' - call ale#test#SetFilename('clangd_paths/dummy.c') + call ale#test#SetFilename('clangd_paths/with_compile_commands/dummy.c') - AssertLSPProject ale#path#Simplify(g:dir . '/clangd_paths') + AssertLSPProject ale#path#Simplify(g:dir . '/clangd_paths/with_compile_commands') Execute(The executable should be configurable): let g:ale_c_clangd_executable = 'foobar' @@ -32,3 +37,15 @@ Execute(The options should be configurable): AssertLinter 'clangd', ale#Escape('clangd') . ' ' . b:ale_c_clangd_options +Execute(The compile command database should be detected correctly): + call ale#test#SetFilename('clangd_paths/with_build_dir/dummy_src/dummy.c') + + let b:ale_c_clangd_options = '' + let b:ale_c_build_dir = '' + let b:ale_c_build_dir_names = ['unusual_build_dir_name'] + let b:ale_c_parse_compile_commands = 1 + + AssertLinter 'clangd', ale#Escape('clangd') + \ . ' -compile-commands-dir=' + \ . ale#Escape(ale#path#Simplify(g:dir . '/clangd_paths/with_build_dir/unusual_build_dir_name')) + diff --git a/test/command_callback/test_c_import_paths.vader b/test/command_callback/test_c_import_paths.vader index 6c616d89..e6102998 100644 --- a/test/command_callback/test_c_import_paths.vader +++ b/test/command_callback/test_c_import_paths.vader @@ -121,6 +121,16 @@ Execute(The C Clang handler should include root directories for projects with .h \ . ' -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/hpp_file_project')) \ . ' -' +Execute(The C ClangTidy handler should include 'include' directories for projects with a Makefile): + call ale#assert#SetUpLinterTest('c', 'clangtidy') + call ale#test#SetFilename('../test_c_projects/makefile_project/subdir/file.cpp') + let g:ale_c_clangtidy_options = '' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' %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') call ale#test#SetFilename('../test_c_projects/makefile_project/subdir/file.cpp') @@ -225,3 +235,14 @@ Execute(The C++ ClangTidy handler should include json folders for projects with \ ale#Escape('clang-tidy') \ . ' %s ' \ . '-p ' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/json_project/build')) + +Execute(The C++ ClangTidy handler should include 'include' directories for projects with a Makefile): + call ale#assert#SetUpLinterTest('cpp', 'clangtidy') + call ale#test#SetFilename('../test_c_projects/makefile_project/subdir/file.cpp') + let g:ale_cpp_clangtidy_options = '' + + AssertLinter 'clang-tidy', + \ ale#Escape('clang-tidy') + \ . ' %s ' + \ . '-- -I' . ale#Escape(ale#path#Simplify(g:dir . '/../test_c_projects/makefile_project/include')) + diff --git a/test/command_callback/test_clang_tidy_command_callback.vader b/test/command_callback/test_clang_tidy_command_callback.vader index 53ae311b..c2d18dea 100644 --- a/test/command_callback/test_clang_tidy_command_callback.vader +++ b/test/command_callback/test_clang_tidy_command_callback.vader @@ -1,4 +1,7 @@ Before: + Save g:ale_c_parse_makefile + let g:ale_c_parse_makefile = 0 + call ale#assert#SetUpLinterTest('cpp', 'clangtidy') call ale#test#SetFilename('test.cpp') diff --git a/test/command_callback/test_cpp_clangcheck_command_callbacks.vader b/test/command_callback/test_cpp_clangcheck_command_callbacks.vader index 7aa09eed..188141d5 100644 --- a/test/command_callback/test_cpp_clangcheck_command_callbacks.vader +++ b/test/command_callback/test_cpp_clangcheck_command_callbacks.vader @@ -7,7 +7,7 @@ After: Execute(The executable should be configurable): AssertLinter 'clang-check', \ ale#Escape('clang-check') - \ . ' -analyze %s -extra-arg -Xclang -extra-arg -analyzer-output=text' + \ . ' -analyze %s --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' let b:ale_cpp_clangcheck_executable = 'foobar' @@ -15,7 +15,7 @@ Execute(The executable should be configurable): " being generated. AssertLinter 'foobar', \ ale#Escape('foobar') - \ . ' -analyze %s -extra-arg -Xclang -extra-arg -analyzer-output=text' + \ . ' -analyze %s --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' Execute(The options should be configurable): let b:ale_cpp_clangcheck_options = '--something' @@ -23,7 +23,7 @@ Execute(The options should be configurable): AssertLinter 'clang-check', \ ale#Escape('clang-check') \ . ' -analyze %s' - \ . ' -extra-arg -Xclang -extra-arg -analyzer-output=text' + \ . ' --extra-arg=-Xclang --extra-arg=-analyzer-output=text --extra-arg=-fno-color-diagnostics' \ . ' --something' Execute(The build directory should be used when set): diff --git a/test/command_callback/test_ink_ls_command_callbacks.vader b/test/command_callback/test_ink_ls_command_callbacks.vader new file mode 100644 index 00000000..5074506c --- /dev/null +++ b/test/command_callback/test_ink_ls_command_callbacks.vader @@ -0,0 +1,22 @@ +Before: + call ale#assert#SetUpLinterTest('ink', 'ls') + set ft=ink + +After: + call ale#assert#TearDownLinterTest() + +Execute(should set correct defaults): + AssertLinter 'ink-language-server', ale#Escape('ink-language-server') . ' --stdio' + +Execute(should set correct LSP values): + call ale#test#SetFilename('ink_paths/story/main.ink') + + AssertLSPLanguage 'ink' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . '/ink_paths/story') + +Execute(should accept configuration settings): + AssertLSPConfig {} + let b:ale_ink_ls_initialization_options = {'ink': {'runThroughMono': v:true}} + AssertLSPOptions {'ink': {'runThroughMono': v:true}} diff --git a/test/command_callback/test_languagetool_command_callback.vader b/test/command_callback/test_languagetool_command_callback.vader index a79662b9..ff6b2064 100644 --- a/test/command_callback/test_languagetool_command_callback.vader +++ b/test/command_callback/test_languagetool_command_callback.vader @@ -12,4 +12,11 @@ Execute(Should be able to set a custom executable): let g:ale_languagetool_executable = 'foobar' AssertLinter 'foobar' , ale#Escape('foobar') - \ . ' --autoDetect %s' + \ . ' --autoDetect %s' + +Execute(Should be able to include custom languagetool options): + let g:ale_languagetool_options = '--language en' + + " is now 'foobar' based on above global + AssertLinter 'foobar', ale#Escape('foobar') + \ . ' --language en %s' diff --git a/test/command_callback/test_markdown_mdl_command_callback.vader b/test/command_callback/test_markdown_mdl_command_callback.vader index e029bf9b..1ce4db1a 100644 --- a/test/command_callback/test_markdown_mdl_command_callback.vader +++ b/test/command_callback/test_markdown_mdl_command_callback.vader @@ -5,15 +5,15 @@ After: call ale#assert#TearDownLinterTest() Execute(The default command should be correct): - AssertLinter 'mdl', ale#Escape('mdl') + AssertLinter 'mdl', ale#Escape('mdl') . ' -j' Execute(The executable and options should be configurable): let g:ale_markdown_mdl_executable = 'foo bar' let g:ale_markdown_mdl_options = '--wat' - AssertLinter 'foo bar', ale#Escape('foo bar') . ' --wat' + AssertLinter 'foo bar', ale#Escape('foo bar') . ' -j --wat' Execute(Setting bundle appends 'exec mdl'): let g:ale_markdown_mdl_executable = 'path to/bundle' - AssertLinter 'path to/bundle', ale#Escape('path to/bundle') . ' exec mdl' + AssertLinter 'path to/bundle', ale#Escape('path to/bundle') . ' exec mdl -j' diff --git a/test/command_callback/test_mypy_command_callback.vader b/test/command_callback/test_mypy_command_callback.vader index 8ca35207..afa9f9af 100644 --- a/test/command_callback/test_mypy_command_callback.vader +++ b/test/command_callback/test_mypy_command_callback.vader @@ -52,6 +52,15 @@ Execute(The mypy callbacks should detect virtualenv directories and switch to th \ . ' --show-column-numbers ' \ . '--shadow-file %s %t %s' +Execute(The mypy callbacks should cd to directory containing mypy.ini if found): + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_mypy_ini_and_pytest_ini/tests/testsubfolder/my_tests.py') + + AssertLinter 'mypy', + \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_mypy_ini_and_pytest_ini')) + \ . ale#Escape('mypy') + \ . ' --show-column-numbers ' + \ . '--shadow-file %s %t %s' + Execute(You should able able to use the global mypy instead): silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') let g:ale_python_mypy_use_global = 1 diff --git a/test/command_callback/test_nimlsp_command_callback.vader b/test/command_callback/test_nimlsp_command_callback.vader new file mode 100644 index 00000000..c109deef --- /dev/null +++ b/test/command_callback/test_nimlsp_command_callback.vader @@ -0,0 +1,12 @@ +Before: + call ale#assert#SetUpLinterTest('nim', 'nimlsp') + +After: + call ale#assert#TearDownLinterTest() + +Execute(It does not set nim sources by default): + AssertLinter 'nimlsp', ale#Escape('nimlsp') + +Execute(Sets nimlsp and escapes sources from g:ale_nim_nimlsp_nim_sources): + let g:ale_nim_nimlsp_nim_sources = '/path/to /Nim' + AssertLinter 'nimlsp', ale#Escape('nimlsp') . ' ' . ale#Escape('/path/to /Nim') diff --git a/test/command_callback/test_psalm_command_callbacks.vader b/test/command_callback/test_psalm_command_callbacks.vader index 33d770c2..70b5af95 100644 --- a/test/command_callback/test_psalm_command_callbacks.vader +++ b/test/command_callback/test_psalm_command_callbacks.vader @@ -9,18 +9,26 @@ After: call ale#assert#TearDownLinterTest() Execute(The default executable path should be correct): - AssertLinter 'psalm-language-server', - \ ale#Escape('psalm-language-server') + AssertLinter 'psalm', + \ ale#Escape('psalm') . ' --language-server' Execute(Vendor executables should be detected): call ale#test#SetFilename('psalm-project/test.php') AssertLinter - \ ale#path#Simplify(g:dir . '/psalm-project/vendor/bin/psalm-language-server'), + \ ale#path#Simplify(g:dir . '/psalm-project/vendor/bin/psalm'), \ ale#Escape(ale#path#Simplify( \ g:dir - \ . '/psalm-project/vendor/bin/psalm-language-server' - \ )) + \ . '/psalm-project/vendor/bin/psalm' + \ )) . ' --language-server' + +Execute(User provided options should be used): + let g:ale_psalm_langserver_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') diff --git a/test/command_callback/test_ruby_debride_command_callback.vader b/test/command_callback/test_ruby_debride_command_callback.vader new file mode 100644 index 00000000..f7628432 --- /dev/null +++ b/test/command_callback/test_ruby_debride_command_callback.vader @@ -0,0 +1,8 @@ +Before: + call ale#assert#SetUpLinterTest('ruby', 'debride') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'debride', ale#Escape('debride') . ' %s' diff --git a/test/command_callback/test_scala_metals.vader b/test/command_callback/test_scala_metals.vader new file mode 100644 index 00000000..70e14c1a --- /dev/null +++ b/test/command_callback/test_scala_metals.vader @@ -0,0 +1,20 @@ +" Author: Jeffrey Lau https://github.com/zoonfafer +" Description: Tests for the Scala Metals linter + +Before: + call ale#assert#SetUpLinterTest('scala', 'metals') + +After: + call ale#assert#TearDownLinterTest() +Execute(should set metals for sbt project with build.sbt): + call ale#test#SetFilename('../scala_fixtures/valid_sbt_project/Main.scala') + AssertLSPLanguage 'scala' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject ale#path#Simplify(g:dir . 'command_callback/../scala_fixtures/valid_sbt_project') +Execute(should not set metals for sbt project without build.sbt): + call ale#test#SetFilename('../scala_fixtures/invalid_sbt_project/Main.scala') + AssertLSPLanguage 'scala' + AssertLSPOptions {} + AssertLSPConfig {} + AssertLSPProject '' diff --git a/test/command_callback/test_solc_command_callback.vader b/test/command_callback/test_solc_command_callback.vader new file mode 100644 index 00000000..23521f6a --- /dev/null +++ b/test/command_callback/test_solc_command_callback.vader @@ -0,0 +1,13 @@ +Before: + call ale#assert#SetUpLinterTest('solidity', 'solc') + +After: + call ale#assert#TearDownLinterTest() + +Execute(The default command should be correct): + AssertLinter 'solc', 'solc %s' + +Execute(The options should be configurable): + let g:ale_solidity_solc_options = '--foobar' + + AssertLinter 'solc', 'solc --foobar %s' diff --git a/test/command_callback/test_standardts_command_callback.vader b/test/command_callback/test_standardts_command_callback.vader new file mode 100644 index 00000000..d769e712 --- /dev/null +++ b/test/command_callback/test_standardts_command_callback.vader @@ -0,0 +1,43 @@ +Before: + call ale#assert#SetUpLinterTest('typescript', 'standard') + call ale#test#SetFilename('testfile.js') + unlet! b:executable + +After: + call ale#assert#TearDownLinterTest() + +Execute(bin/cmd.js paths should be preferred): + call ale#test#SetFilename('standard-test-files/with-cmd/testfile.js') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/standard-test-files/with-cmd/node_modules/standard/bin/cmd.js' + \) + + AssertLinter b:executable, + \ (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(b:executable) + \ . ' --stdin %s' + +Execute(.bin directories should be used too): + call ale#test#SetFilename('standard-test-files/with-bin/testfile.js') + + let b:executable = ale#path#Simplify( + \ g:dir + \ . '/standard-test-files/with-bin/node_modules/.bin/standard' + \) + + AssertLinter b:executable, ale#Escape(b:executable) . ' --stdin %s' + +Execute(The global executable should be used otherwise): + AssertLinter 'standard', ale#Escape('standard') . ' --stdin %s' + +Execute(The global executable should be configurable): + let b:ale_typescript_standard_executable = 'foobar' + + AssertLinter 'foobar', ale#Escape('foobar') . ' --stdin %s' + +Execute(The options should be configurable): + let b:ale_typescript_standard_options = '--wat' + + AssertLinter 'standard', ale#Escape('standard') . ' --wat --stdin %s' diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader index 6bc0035e..3a7a31d0 100644 --- a/test/completion/test_completion_events.vader +++ b/test/completion/test_completion_events.vader @@ -47,6 +47,15 @@ Before: AssertEqual a:expect_success, g:get_completions_called endfunction + let g:handle_code_action_called = 0 + function! MockHandleCodeAction() abort + " delfunction! ale#code_action#HandleCodeAction + function! ale#code_action#HandleCodeAction(action, should_save) abort + AssertEqual v:false, a:should_save + let g:handle_code_action_called += 1 + endfunction + endfunction + After: Restore @@ -54,6 +63,7 @@ After: unlet! g:output unlet! g:fake_mode unlet! g:get_completions_called + unlet! g:handle_code_action_called unlet! b:ale_old_omnifunc unlet! b:ale_old_completeopt unlet! b:ale_completion_info @@ -61,6 +71,12 @@ After: unlet! b:ale_complete_done_time delfunction CheckCompletionCalled + delfunction ale#code_action#HandleCodeAction + delfunction MockHandleCodeAction + + if exists('*CompleteCallback') + delfunction CompleteCallback + endif " Stop any timers we left behind. " This stops the tests from failing randomly. @@ -73,6 +89,7 @@ After: endfunction runtime autoload/ale/completion.vim + runtime autoload/ale/code_action.vim runtime autoload/ale/util.vim Execute(ale#completion#GetCompletions should be called when the cursor position stays the same): @@ -217,7 +234,7 @@ Execute(ale#completion#Show() should make the correct feedkeys() call for manual AssertEqual [["\<Plug>(ale_show_completion_menu)"]], g:feedkeys_calls Execute(ale#completion#Show() should not call feedkeys() for other sources): - let b:ale_completion_info = {'source': 'deoplete'} + let b:ale_completion_info = {'source': 'other-source'} call ale#completion#Show([{'word': 'x', 'kind': 'v', 'icase': 1}]) sleep 1ms @@ -318,7 +335,31 @@ Execute(b:ale_completion_info should be set up correctly when requesting complet Execute(b:ale_completion_info should be set up correctly for other sources): let b:ale_completion_result = [] call setpos('.', [bufnr(''), 3, 14, 0]) - call ale#completion#GetCompletions('deoplete') + call ale#completion#GetCompletions('ale-callback') + + AssertEqual + \ { + \ 'request_id': 0, + \ 'conn_id': 0, + \ 'column': 14, + \ 'line_length': 14, + \ 'line': 3, + \ 'prefix': 'ab', + \ 'source': 'ale-callback', + \ }, + \ b:ale_completion_info + Assert !exists('b:ale_completion_result') + +Execute(b:ale_completion_info should be set up correctly when requesting completions via callback): + let b:ale_completion_result = [] + call setpos('.', [bufnr(''), 3, 14, 0]) + + function! CompleteCallback() abort + echo 'Called' + endfunction + + + call ale#completion#GetCompletions('ale-callback', {'callback': funcref('CompleteCallback')}) AssertEqual \ { @@ -328,7 +369,7 @@ Execute(b:ale_completion_info should be set up correctly for other sources): \ 'line_length': 14, \ 'line': 3, \ 'prefix': 'ab', - \ 'source': 'deoplete', + \ 'source': 'ale-callback', \ }, \ b:ale_completion_info Assert !exists('b:ale_completion_result') @@ -357,3 +398,50 @@ Execute(Running the normal mode <Plug> keybind should reset the settings): AssertEqual 'menu', &l:completeopt Assert !has_key(b:, 'ale_old_omnifunc') Assert !has_key(b:, 'ale_old_completeopt') + +Execute(HandleUserData should call ale#code_action#HandleCodeAction): + let b:ale_completion_info = {'source': 'ale-manual'} + call MockHandleCodeAction() + + call ale#completion#HandleUserData({}) + AssertEqual g:handle_code_action_called, 0 + + call ale#completion#HandleUserData({ + \ 'user_data': '' + \}) + AssertEqual g:handle_code_action_called, 0 + + call ale#completion#HandleUserData({ + \ 'user_data': '{}' + \}) + AssertEqual g:handle_code_action_called, 0 + + call ale#completion#HandleUserData({ + \ 'user_data': '{"codeActions": []}' + \}) + AssertEqual g:handle_code_action_called, 0 + + call ale#completion#HandleUserData({ + \ 'user_data': '{"codeActions": [{"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": []}]}' + \}) + AssertEqual g:handle_code_action_called, 2 + + let b:ale_completion_info = {'source': 'ale-callback'} + call ale#completion#HandleUserData({ + \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \}) + AssertEqual g:handle_code_action_called, 3 + +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": []}]}' + \}) + AssertEqual g:handle_code_action_called, 0 diff --git a/test/completion/test_lsp_completion_messages.vader b/test/completion/test_lsp_completion_messages.vader index 6bd241a8..4b7392f5 100644 --- a/test/completion/test_lsp_completion_messages.vader +++ b/test/completion/test_lsp_completion_messages.vader @@ -116,7 +116,13 @@ Execute(The right message should be sent for the initial tsserver request): \ string(g:Callback) " We should send the right message. AssertEqual - \ [[0, 'ts@completions', {'file': expand('%:p'), 'line': 1, 'offset': 3, 'prefix': 'fo'}]], + \ [[0, 'ts@completions', { + \ 'file': expand('%:p'), + \ 'line': 1, + \ 'offset': 3, + \ 'prefix': 'fo', + \ 'includeExternalModuleExports': g:ale_completion_tsserver_autoimport, + \ }]], \ g:message_list " We should set up the completion info correctly. AssertEqual @@ -151,7 +157,7 @@ Execute(The right message sent to the tsserver LSP when the first completion mes \ 'body': [ \ {'name': 'Baz'}, \ {'name': 'dingDong'}, - \ {'name': 'Foo'}, + \ {'name': 'Foo', 'source': '/path/to/foo.ts'}, \ {'name': 'FooBar'}, \ {'name': 'frazzle'}, \ {'name': 'FFS'}, @@ -160,8 +166,16 @@ Execute(The right message sent to the tsserver LSP when the first completion mes " We should save the names we got in the buffer, as TSServer doesn't return " details for every name. - AssertEqual - \ ['Foo', 'FooBar', 'frazzle'], + AssertEqual [{ + \ 'word': 'Foo', + \ 'source': '/path/to/foo.ts', + \ }, { + \ 'word': 'FooBar', + \ 'source': '', + \ }, { + \ 'word': 'frazzle', + \ 'source': '', + \}], \ get(b:, 'ale_tsserver_completion_names', []) " The entry details messages should have been sent. @@ -171,7 +185,14 @@ Execute(The right message sent to the tsserver LSP when the first completion mes \ 'ts@completionEntryDetails', \ { \ 'file': expand('%:p'), - \ 'entryNames': ['Foo', 'FooBar', 'frazzle'], + \ 'entryNames': [{ + \ 'name': 'Foo', + \ 'source': '/path/to/foo.ts', + \ }, { + \ 'name': 'FooBar', + \ }, { + \ 'name': 'frazzle', + \ }], \ 'offset': 1, \ 'line': 1, \ }, diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader index ef954564..1fdbbd96 100644 --- a/test/completion/test_lsp_completion_parsing.vader +++ b/test/completion/test_lsp_completion_parsing.vader @@ -17,17 +17,17 @@ Execute(Should handle Rust completion results correctly): \ {'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': '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': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Target', 'menu': 'type Target = str;', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Err', 'menu': 'type Err = ParseError;', '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}, diff --git a/test/completion/test_tsserver_completion_parsing.vader b/test/completion/test_tsserver_completion_parsing.vader index dbc4f9e2..6beb7b0a 100644 --- a/test/completion/test_tsserver_completion_parsing.vader +++ b/test/completion/test_tsserver_completion_parsing.vader @@ -6,10 +6,24 @@ Execute(TypeScript completions responses should be parsed correctly): \ ale#completion#ParseTSServerCompletions({ \ 'body': [], \}) - AssertEqual ['foo', 'bar', 'baz'], + AssertEqual + \ [ + \ { + \ 'word': 'foo', + \ 'source': '/path/to/foo.ts', + \ }, + \ { + \ 'word': 'bar', + \ 'source': '', + \ }, + \ { + \ 'word': 'baz', + \ 'source': '', + \ } + \ ], \ ale#completion#ParseTSServerCompletions({ \ 'body': [ - \ {'name': 'foo'}, + \ {'name': 'foo', 'source': '/path/to/foo.ts'}, \ {'name': 'bar'}, \ {'name': 'baz'}, \ ], @@ -22,22 +36,25 @@ Execute(TypeScript completion details responses should be parsed correctly): \ 'word': 'abc', \ 'menu': '(property) Foo.abc: number', \ 'info': '', - \ 'kind': 'f', + \ 'kind': 'v', \ 'icase': 1, + \ 'dup': g:ale_completion_tsserver_autoimport, \ }, \ { \ 'word': 'def', \ 'menu': '(property) Foo.def: number', \ 'info': 'foo bar baz', - \ 'kind': 'f', + \ 'kind': 'v', \ 'icase': 1, + \ 'dup': g:ale_completion_tsserver_autoimport, \ }, \ { \ 'word': 'ghi', \ 'menu': '(class) Foo', \ 'info': '', - \ 'kind': 'f', + \ 'kind': 'v', \ 'icase': 1, + \ 'dup': g:ale_completion_tsserver_autoimport, \ }, \ ], \ ale#completion#ParseTSServerCompletionEntryDetails({ @@ -96,23 +113,34 @@ Execute(TypeScript completion details responses should be parsed correctly): \}) Execute(Entries without details should be included in the responses): - let b:ale_tsserver_completion_names = ['xyz'] + let b:ale_tsserver_completion_names = [{ + \ 'word': 'xyz', + \ 'source': '/path/to/xyz.ts', + \ }] AssertEqual \ [ \ { \ 'word': 'abc', - \ 'menu': '(property) Foo.abc: number', + \ 'menu': 'import { def } from "./Foo"; (property) Foo.abc: number', \ 'info': '', - \ 'kind': 'f', + \ 'kind': 'v', \ 'icase': 1, + \ 'user_data': json_encode({ + \ 'codeActions': [{ + \ 'description': 'import { def } from "./Foo";', + \ 'changes': [], + \ }], + \ }), + \ 'dup': g:ale_completion_tsserver_autoimport, \ }, \ { \ 'word': 'def', \ 'menu': '(property) Foo.def: number', \ 'info': 'foo bar baz', - \ 'kind': 'f', + \ 'kind': 'v', \ 'icase': 1, + \ 'dup': g:ale_completion_tsserver_autoimport, \ }, \ { \ 'word': 'xyz', @@ -139,6 +167,10 @@ Execute(Entries without details should be included in the responses): \ {'text': ' '}, \ {'text': 'number'}, \ ], + \ 'codeActions': [{ + \ 'description': 'import { def } from "./Foo";', + \ 'changes': [], + \ }], \ }, \ { \ 'name': 'def', diff --git a/test/d_files/test.d b/test/d_files/test.d new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/d_files/test.d diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader index 9dd04213..c3dd20e4 100644 --- a/test/fix/test_ale_fix.vader +++ b/test/fix/test_ale_fix.vader @@ -40,8 +40,8 @@ Before: return map(a:lines, '''^'' . v:val') endfunction - function AddDollars(buffer, lines) abort - return map(a:lines, '''$'' . v:val') + function Capitalize(buffer, lines) abort + return map(a:lines, 'toupper(v:val)') endfunction function DoNothing(buffer, lines) abort @@ -196,7 +196,7 @@ After: unlet! b:ale_fix_on_save unlet! b:ale_quitting delfunction AddCarets - delfunction AddDollars + delfunction Capitalize delfunction DoNothing delfunction CatLine delfunction CatLineOneArg @@ -265,28 +265,28 @@ Expect(The first function should be used): ^c Execute(ALEFix should apply simple functions in a chain): - let g:ale_fixers.testft = ['AddCarets', 'AddDollars'] + let g:ale_fixers.testft = ['AddCarets', 'Capitalize'] ALEFix call ale#test#FlushJobs() Expect(Both functions should be used): - $^a - $^b - $^c + ^A + ^B + ^C Execute(ALEFix should allow 0 to be returned to skip functions): - let g:ale_fixers.testft = ['DoNothing', 'AddDollars'] + let g:ale_fixers.testft = ['DoNothing', 'Capitalize'] ALEFix call ale#test#FlushJobs() Expect(Only the second function should be applied): - $a - $b - $c + A + B + C Execute(The * fixers shouldn't be used if an empty list is set for fixers): let g:ale_fixers.testft = [] - let g:ale_fixers['*'] = ['AddDollars'] + let g:ale_fixers['*'] = ['Capitalize'] ALEFix call ale#test#FlushJobs() @@ -296,14 +296,14 @@ Expect(Nothing should be changed): c Execute(* fixers should be used if no filetype is matched): - let g:ale_fixers = {'*': ['AddDollars']} + let g:ale_fixers = {'*': ['Capitalize']} ALEFix call ale#test#FlushJobs() Expect(The file should be changed): - $a - $b - $c + A + B + C Execute(ALEFix should allow commands to be run): if has('win32') @@ -323,13 +323,13 @@ Expect(An extra line should be added): Execute(ALEFix should use fixers passed in commandline when provided): let g:ale_fixers.testft = ['RemoveLastLine'] - ALEFix AddCarets AddDollars + ALEFix AddCarets Capitalize call ale#test#FlushJobs() Expect(Only fixers passed via command line should be run): - $^a - $^b - $^c + ^A + ^B + ^C Execute(ALEFix should allow temporary files to be read): if has('win32') @@ -364,43 +364,43 @@ Expect(An extra line should be added): Execute(ALEFix should allow jobs and simple functions to be combined): if has('win32') " Just skip this test on Windows, we can't run it. - call setline(1, ['$x']) + call setline(1, ['X']) 2,3d else - let g:ale_fixers.testft = ['ReplaceWithTempFile', 'AddDollars'] + let g:ale_fixers.testft = ['ReplaceWithTempFile', 'Capitalize'] ALEFix call ale#test#FlushJobs() endif Expect(The lines from the temporary file should be modified): - $x + X Execute(ALEFix should send lines modified by functions to jobs): if has('win32') " Just skip this test on Windows, we can't run it. - call setline(1, ['$a', '$b', '$c', 'd']) + call setline(1, ['A', 'B', 'C', 'd']) else - let g:ale_fixers.testft = ['AddDollars', 'CatLine'] + let g:ale_fixers.testft = ['Capitalize', 'CatLine'] ALEFix call ale#test#FlushJobs() endif Expect(The lines should first be modified by the function, then the job): - $a - $b - $c + A + B + C d Execute(ALEFix should skip commands when jobs fail to run): let g:ale_emulate_job_failure = 1 - let g:ale_fixers.testft = ['CatLine', 'AddDollars'] + let g:ale_fixers.testft = ['CatLine', 'Capitalize'] ALEFix call ale#test#FlushJobs() Expect(Only the second function should be applied): - $a - $b - $c + A + B + C Execute(ALEFix should handle strings for selecting a single function): let g:ale_fixers.testft = 'AddCarets' @@ -459,7 +459,7 @@ Expect(There should be an extra line): d Execute(ALEFix should user buffer-local fixer settings): - let g:ale_fixers.testft = ['AddCarets', 'AddDollars'] + let g:ale_fixers.testft = ['AddCarets', 'Capitalize'] let b:ale_fixers = {'testft': ['RemoveLastLine']} ALEFix call ale#test#FlushJobs() @@ -469,7 +469,7 @@ Expect(There should be only two lines): b Execute(ALEFix should allow Lists to be used for buffer-local fixer settings): - let g:ale_fixers.testft = ['AddCarets', 'AddDollars'] + let g:ale_fixers.testft = ['AddCarets', 'Capitalize'] let b:ale_fixers = ['RemoveLastLine'] ALEFix call ale#test#FlushJobs() @@ -492,7 +492,7 @@ Execute(ALEFix should fix files on the save event): execute 'noautocmd silent file ' . fnameescape(g:test_filename) call writefile(getline(1, '$'), g:test_filename) - let g:ale_fixers.testft = ['AddDollars'] + let g:ale_fixers.testft = ['Capitalize'] " We have to set the buftype to empty so the file will be written. setlocal buftype= @@ -502,7 +502,7 @@ Execute(ALEFix should fix files on the save event): call ale#test#FlushJobs() " We should save the file. - AssertEqual ['$a', '$b', '$c'], readfile(g:test_filename) + AssertEqual ['A', 'B', 'C'], readfile(g:test_filename) Assert !&modified, 'The file was marked as ''modified''' if !has('win32') @@ -521,9 +521,9 @@ Execute(ALEFix should fix files on the save event): endif Expect(The buffer should be modified): - $a - $b - $c + A + B + C Given testft (A file with three lines): a @@ -540,7 +540,7 @@ Execute(ALEFix should run the linters with b:ale_lint_on_save = 1): execute 'noautocmd silent file ' . fnameescape(g:test_filename) call writefile(getline(1, '$'), g:test_filename) - let g:ale_fixers.testft = ['AddDollars'] + let g:ale_fixers.testft = ['Capitalize'] " We have to set the buftype to empty so the file will be written. setlocal buftype= @@ -550,7 +550,7 @@ Execute(ALEFix should run the linters with b:ale_lint_on_save = 1): call ale#test#FlushJobs() " We should save the file. - AssertEqual ['$a', '$b', '$c'], readfile(g:test_filename) + AssertEqual ['A', 'B', 'C'], readfile(g:test_filename) Assert !&modified, 'The file was marked as ''modified''' if !has('win32') @@ -569,9 +569,9 @@ Execute(ALEFix should run the linters with b:ale_lint_on_save = 1): endif Expect(The buffer should be modified): - $a - $b - $c + A + B + C Execute(ALEFix should not fix files on :wq): let g:ale_fix_on_save = 1 @@ -582,7 +582,7 @@ Execute(ALEFix should not fix files on :wq): execute 'noautocmd silent file ' . fnameescape(g:test_filename) call writefile(getline(1, '$'), g:test_filename) - let g:ale_fixers.testft = ['AddDollars'] + let g:ale_fixers.testft = ['Capitalize'] " We have to set the buftype to empty so the file will be written. setlocal buftype= diff --git a/test/fixers/test_black_fixer_callback.vader b/test/fixers/test_black_fixer_callback.vader index 25ad05db..75864479 100644 --- a/test/fixers/test_black_fixer_callback.vader +++ b/test/fixers/test_black_fixer_callback.vader @@ -36,6 +36,14 @@ Execute(The black callback should include options): \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/black')) . ' --some-option -' }, \ ale#fixers#black#Fix(bufnr('')) +Execute(The black callback should include --pyi for .pyi files): + let g:ale_python_black_change_directory = 0 + + silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.pyi') + AssertEqual + \ {'command': ale#Escape(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/env/' . b:bin_dir . '/black')) . ' --pyi -' }, + \ ale#fixers#black#Fix(bufnr('')) + Execute(Pipenv is detected when python_black_auto_pipenv is set): let g:ale_python_black_auto_pipenv = 1 let g:ale_python_black_change_directory = 0 diff --git a/test/fixers/test_dfmt_fixer_callback.vader b/test/fixers/test_dfmt_fixer_callback.vader new file mode 100644 index 00000000..5ecb56e6 --- /dev/null +++ b/test/fixers/test_dfmt_fixer_callback.vader @@ -0,0 +1,40 @@ +Before: + Save g:ale_d_dfmt_executable + Save g:ale_d_dfmt_options + + " Use an invalid global executable, so we don't match it. + let g:ale_d_dfmt_executable = 'xxxinvalid' + let g:ale_d_dfmt_options = '' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The dfmt callback should return the correct default values): + call ale#test#SetFilename('../d_files/test.d') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -i' + \ . ' %t', + \ }, + \ ale#fixers#dfmt#Fix(bufnr('')) + +Execute(The dfmt callback should include custom dfmt options): + let g:ale_d_dfmt_options = "--space-after-cast" + call ale#test#SetFilename('../d_files/test.d') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('xxxinvalid') + \ . ' -i' + \ . ' ' . g:ale_d_dfmt_options + \ . ' %t', + \ }, + \ ale#fixers#dfmt#Fix(bufnr('')) diff --git a/test/fixers/test_gnatpp_fixer_callback.vader b/test/fixers/test_gnatpp_fixer_callback.vader new file mode 100644 index 00000000..a2bf898e --- /dev/null +++ b/test/fixers/test_gnatpp_fixer_callback.vader @@ -0,0 +1,28 @@ +Before: + call ale#assert#SetUpFixerTest('ada', 'gnatpp') + +After: + " Reset fixers, variables, etc. + " + " Vader's 'Restore' command will be called here. + call ale#assert#TearDownFixerTest() + +Execute(The default command should be correct): + call ale#test#SetFilename('../ada_files/testfile.adb') + + AssertFixer + \ { + \ 'command': ale#Escape(g:ale_ada_gnatpp_executable) .' %t', + \ 'read_temporary_file': 1, + \ } + +Execute(The version check should be correct): + call ale#test#SetFilename('../ada_files/testfile.adb') + let g:ale_ada_gnatpp_options = '--no-alignment' + + AssertFixer + \ { + \ 'command': ale#Escape(g:ale_ada_gnatpp_executable) + \ . ' --no-alignment %t', + \ 'read_temporary_file': 1, + \ } diff --git a/test/fixers/test_hindent_fixer_callback.vader b/test/fixers/test_hindent_fixer_callback.vader new file mode 100644 index 00000000..2e5a8b9f --- /dev/null +++ b/test/fixers/test_hindent_fixer_callback.vader @@ -0,0 +1,18 @@ +Before: + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The hindent callback should return the correct default values): + call ale#test#SetFilename('../haskell_files/testfile.hs') + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('hindent') + \ . ' %t', + \ }, + \ ale#fixers#hindent#Fix(bufnr('')) diff --git a/test/fixers/test_html_beautify_fixer_callback.vader b/test/fixers/test_html_beautify_fixer_callback.vader new file mode 100644 index 00000000..372572e4 --- /dev/null +++ b/test/fixers/test_html_beautify_fixer_callback.vader @@ -0,0 +1,16 @@ +Before: + call ale#assert#SetUpFixerTest('html', 'html-beautify', 'beautify') + + call ale#test#SetDirectory('/testplugin/test/fixers') + silent cd .. + silent cd command_callback + +After: + Restore + + call ale#assert#TearDownFixerTest() + +Execute(The html-beautify callback should return the correct default command): + AssertEqual + \ {'command': ale#Escape('html-beautify') . ' -'}, + \ ale#fixers#html_beautify#Fix(bufnr('')) diff --git a/test/fixers/test_nimpretty_fixer_callback.vader b/test/fixers/test_nimpretty_fixer_callback.vader new file mode 100644 index 00000000..a26f649a --- /dev/null +++ b/test/fixers/test_nimpretty_fixer_callback.vader @@ -0,0 +1,23 @@ +Before: + call ale#assert#SetUpFixerTest('nim', 'nimpretty') + +After: + call ale#assert#TearDownFixerTest() + +Execute(The nimpretty callback should return the correct default values): + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('nimpretty') . ' %t --maxLineLen:80' + \ }, + \ ale#fixers#nimpretty#Fix(bufnr('')) + +Execute(The nimpretty callback should include any additional options): + let g:ale_nim_nimpretty_options = '--some-option' + + AssertEqual + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#Escape('nimpretty') . ' %t --some-option' + \ }, + \ ale#fixers#nimpretty#Fix(bufnr('')) diff --git a/test/fixers/test_nixpkgsfmt_fixer_callback.vader b/test/fixers/test_nixpkgsfmt_fixer_callback.vader new file mode 100644 index 00000000..0065f77b --- /dev/null +++ b/test/fixers/test_nixpkgsfmt_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_nix_nixpkgsfmt_executable + Save g:ale_nix_nixpkgsfmt_options + +After: + Restore + +Execute(The nixpkgs-fmt callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('nixpkgs-fmt') + \ }, + \ ale#fixers#nixpkgsfmt#Fix(bufnr('')) + +Execute(The nixpkgs-fmt executable and options should be configurable): + let g:ale_nix_nixpkgsfmt_executable = '/path/to/nixpkgs-fmt' + let g:ale_nix_nixpkgsfmt_options = '-h' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/nixpkgs-fmt') + \ . ' -h', + \ }, + \ ale#fixers#nixpkgsfmt#Fix(bufnr('')) diff --git a/test/fixers/test_purty_fixer_callback.vader b/test/fixers/test_purty_fixer_callback.vader new file mode 100644 index 00000000..e83b8c18 --- /dev/null +++ b/test/fixers/test_purty_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_purescript_purty_executable + + " Use an invalid global executable, so we don't match it. + let g:ale_purescript_purty_executable = 'my-special-purty' + + call ale#test#SetDirectory('/testplugin/test/fixers') + +After: + Restore + + call ale#test#RestoreDirectory() + +Execute(The purty callback should return the correct options): + call ale#test#SetFilename('../purescript_files/testfile.purs') + + AssertEqual + \ { + \ 'command': ale#Escape('my-special-purty') + \ . ' --write' + \ . ' %t', + \ 'read_temporary_file': 1, + \ }, + \ ale#fixers#purty#Fix(bufnr('')) diff --git a/test/fixers/test_sqlformat_fixer_callback.vader b/test/fixers/test_sqlformat_fixer_callback.vader new file mode 100644 index 00000000..4bace089 --- /dev/null +++ b/test/fixers/test_sqlformat_fixer_callback.vader @@ -0,0 +1,24 @@ +Before: + Save g:ale_sql_sqlformat_executable + Save g:ale_sql_sqlformat_options + +After: + Restore + +Execute(The sqlformat callback should return the correct default values): + AssertEqual + \ { + \ 'command': ale#Escape('sqlformat') . ' -' + \ }, + \ ale#fixers#sqlformat#Fix(bufnr('')) + +Execute(The sqlformat executable and options should be configurable): + let g:ale_sql_sqlformat_executable = '/path/to/sqlformat' + let g:ale_sql_sqlformat_options = '-a' + + AssertEqual + \ { + \ 'command': ale#Escape('/path/to/sqlformat') + \ . ' -a -' + \ }, + \ ale#fixers#sqlformat#Fix(bufnr('')) diff --git a/test/fixers/test_stylelint_fixer_callback.vader b/test/fixers/test_stylelint_fixer_callback.vader index 90a9dc1f..f677cdf7 100644 --- a/test/fixers/test_stylelint_fixer_callback.vader +++ b/test/fixers/test_stylelint_fixer_callback.vader @@ -1,17 +1,33 @@ Before: - call ale#test#SetDirectory('/testplugin/test/fixers') + call ale#assert#SetUpFixerTest('css', 'stylelint') After: - call ale#test#RestoreDirectory() + call ale#assert#TearDownFixerTest() -Execute(The executable path should be correct): +Execute(The stylelint callback should return the correct default values): call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.css') - AssertEqual + AssertFixer \ { \ 'read_temporary_file': 1, - \ 'command': (has('win32') ? 'node.exe ' : '') + \ 'command': ale#path#CdString(expand('%:p:h')) + \ . (has('win32') ? 'node.exe ' : '') \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js')) - \ . ' --fix %t', - \ }, - \ ale#fixers#stylelint#Fix(bufnr('')) + \ . ' %t' + \ . ' --fix', + \ } + +Execute(The stylelint callback should include custom stylelint options): + let g:ale_stylelint_options = '--cache' + call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.css') + + AssertFixer + \ { + \ 'read_temporary_file': 1, + \ 'command': ale#path#CdString(expand('%:p:h')) + \ . (has('win32') ? 'node.exe ' : '') + \ . ale#Escape(ale#path#Simplify(g:dir . '/../eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js')) + \ . ' %t' + \ . ' --cache' + \ . ' --fix', + \ } diff --git a/test/fixers/test_styler_fixer_callback.vader b/test/fixers/test_styler_fixer_callback.vader index 85e45c1d..79f71ba9 100644 --- a/test/fixers/test_styler_fixer_callback.vader +++ b/test/fixers/test_styler_fixer_callback.vader @@ -13,7 +13,7 @@ Execute(The styler callback should include custom styler options): \ { \ 'command': 'Rscript --vanilla -e ' \ . '"suppressPackageStartupMessages(library(styler));' - \ . 'style_file(commandArgs(TRUE), style = ' + \ . 'style_file(commandArgs(TRUE), transformers = ' \ . 'a_custom_option)"' \ . ' %t', \ 'read_temporary_file': 1, diff --git a/test/handler/test_crystal_handler.vader b/test/handler/test_crystal_handler.vader index a7b7f3ab..209632e9 100644 --- a/test/handler/test_crystal_handler.vader +++ b/test/handler/test_crystal_handler.vader @@ -16,3 +16,13 @@ Execute(The crystal handler should parse lines correctly and add the column if i \ ale_linters#crystal#crystal#Handle(255, [ \ '[{"file":"/tmp/test.cr","line":2,"column":1,"size":null,"message":"unexpected token: EOF"}]' \ ]) + +Execute(The crystal handler should not fail when a missing file is required): + AssertEqual + \ [ { 'lnum':1, 'col': 1, 'text': 'while requiring "./nonexistent.cr"' } ], + \ ale_linters#crystal#crystal#Handle(255, + \ json_encode([ + \ { "file":"/tmp/file.cr","line":1,"column":1,"size":0,"message":"while requiring \"./nonexistent.cr\"" }, + \ { "message": "can't find file './nonexistent.cr' relative to '/tmp'" }, + \ ]) + \ ) diff --git a/test/handler/test_debride_handler.vader b/test/handler/test_debride_handler.vader new file mode 100644 index 00000000..62851468 --- /dev/null +++ b/test/handler/test_debride_handler.vader @@ -0,0 +1,27 @@ +Before: + runtime ale_linters/ruby/debride.vim + +After: + call ale#linter#Reset() + +Execute(The debride linter parses output correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'text': 'Possible unused method: image_tags', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 7, + \ 'text': 'Possible unused method: not_deleted', + \ 'type': 'W', + \ } + \ ], + \ ale_linters#ruby#debride#HandleOutput(0, [ + \ 'These methods MIGHT not be called:', + \ '', + \ 'Image', + \ ' image_tags app/models/image.rb:2', + \ ' not_deleted app/models/image.rb:7' + \]) diff --git a/test/handler/test_eslint_json_handler.vader b/test/handler/test_eslint_json_handler.vader index 8e07bd80..6235794a 100644 --- a/test/handler/test_eslint_json_handler.vader +++ b/test/handler/test_eslint_json_handler.vader @@ -21,6 +21,7 @@ After: unlet! g:config_error_lines Execute(The eslint handler should parse json correctly): + call ale#test#SetFilename('foo.js') AssertEqual \ [ \ { @@ -53,6 +54,21 @@ Execute(The eslint handler should parse json correctly): \ '[{"filePath":"foo.js","messages":[{"ruleId":"no-unused-vars","severity":1,"message":"''variable'' is assigned a value but never used.","line":1,"column":7,"nodeType":"Identifier","endLine":1,"endColumn":15},{"ruleId":"semi","severity":1,"message":"Missing semicolon.","line":5,"column":15,"nodeType":"ExpressionStatement","fix":{"range":[46,46],"text":";"}},{"ruleId":"no-redeclare","severity":2,"message":"''variable'' is already defined.","line":7,"column":7,"nodeType":"Identifier","endLine":7,"endColumn":15}],"errorCount":1,"warningCount":3,"fixableErrorCount":0,"fixableWarningCount":1,"source":"const variable = {\n a: 3\n};\n\nconsole.log(1)\n\nclass variable {\n}\n"}]' \ ]) +Execute(The eslint handler should suppress deprecation warnings): + call ale#test#SetFilename('foo.js') + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 9, + \ 'text': 'Parsing error: Unexpected token Controller', + \ 'type': 'E', + \ } + \ ], + \ ale#handlers#eslint#HandleJSON(bufnr(''), [ + \ '[{"filePath":"foo.js","messages":[{"ruleId":null,"fatal":true,"severity":2 ,"message":"Parsing error: Unexpected token Controller","line":1,"column":9}],"errorCount":1,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount": 0,"source":"i:mport Controller from \"@ember/controller\";\nimport listViewControllerMixin from \"elearning/mixins/list-view-controller\";\nimport { inject as service } from \"@ember/service\";\n\nexport default Controller.extend(listViewControllerMixin(), {\n modelName: \"notification\",\n intl: service(),\n\n flatpickrLocale: computed(\"intl.locale\", function() {\n return this.intl.locale.firstObject.split(\"-\")[0];\n })\n});\n"}]', '(node:616989) [ESLINT_LEGACY_OBJECT_REST_SPREAD] DeprecationWarning: The ''parserOptions.ecmaFeatures.experimentalObjectRestSpread'' option is deprecated. Use ''parser Options.ecmaVersion'' instead. (found in "node_modules/eslint-plugin-ember/lib/config/base.js")]' + \ ]) + Execute(The eslint handler should print a message about a missing configuration file): let g:config_error_lines = [ \ '', @@ -262,7 +278,7 @@ Execute(Suppressing missing configs shouldn't suppress module import errors): \ ale#handlers#eslint#HandleJSON(bufnr(''), g:config_error_lines[:]) Execute(The eslint handler should hint about using typescript-eslint-parser): - silent! noautocmd file foo.ts + call ale#test#SetFilename('foo.ts') AssertEqual \ [ @@ -326,7 +342,7 @@ Execute(Failing to connect to eslint_d should be handled correctly): \ ]) Execute(Disabling warnings about trailing spaces should work): - silent! noautocmd file foo.ts + call ale#test#SetFilename('foo.js') AssertEqual \ [ diff --git a/test/handler/test_mdl_handler.vader b/test/handler/test_mdl_handler.vader new file mode 100644 index 00000000..d01b52af --- /dev/null +++ b/test/handler/test_mdl_handler.vader @@ -0,0 +1,25 @@ +Before: + runtime ale_linters/markdown/mdl.vim + +After: + call ale#linter#Reset() + +Execute(The mdl handler should parse output correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'code': 'MD002/first-header-h1', + \ 'text': 'First header should be a top level header', + \ 'type': 'W' + \ }, + \ { + \ 'lnum': 18, + \ 'code': 'MD033/no-inline-html', + \ 'text': 'Inline HTML', + \ 'type': 'W' + \ } + \ ], + \ ale_linters#markdown#mdl#Handle(0, [ + \ '[{"filename":"README.md","line":1,"rule":"MD002","aliases":["first-header-h1"],"description":"First header should be a top level header"},{"filename":"README.md","line":18,"rule":"MD033","aliases":["no-inline-html"],"description":"Inline HTML"}]' + \ ]) diff --git a/test/handler/test_mypy_handler.vader b/test/handler/test_mypy_handler.vader index 6e96f3f3..039155a2 100644 --- a/test/handler/test_mypy_handler.vader +++ b/test/handler/test_mypy_handler.vader @@ -1,6 +1,8 @@ Before: Save g:ale_python_mypy_ignore_invalid_syntax + Save g:ale_python_mypy_show_notes + unlet! g:ale_python_mypy_show_notes unlet! g:ale_python_mypy_ignore_invalid_syntax runtime ale_linters/python/mypy.vim @@ -16,6 +18,8 @@ After: Execute(The mypy handler should parse lines correctly): call ale#test#SetFilename('__init__.py') + let g:ale_python_mypy_show_notes = 0 + AssertEqual \ [ \ { @@ -69,6 +73,31 @@ Execute(The mypy handler should parse lines correctly): \ '__init__.py:72:1: warning: Some warning', \ ]) +Execute(The mypy handler should show notes if enabled): + call ale#test#SetFilename('__init__.py') + + AssertEqual + \ [ + \ { + \ 'lnum': 72, + \ 'col': 1, + \ 'filename': ale#path#Simplify(g:dir . '/__init__.py'), + \ 'type': 'I', + \ 'text': 'A note', + \ }, + \ ], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ '__init__.py:72:1: note: A note', + \ ]) + + let g:ale_python_mypy_show_notes = 0 + + AssertEqual + \ [], + \ ale_linters#python#mypy#Handle(bufnr(''), [ + \ '__init__.py:72:1: note: A note', + \ ]) + Execute(The mypy handler should handle Windows names with spaces): " This test works on Unix, where this is seen as a single filename silent file C:\\something\\with\ spaces.py diff --git a/test/handler/test_nim_handler.vader b/test/handler/test_nim_handler.vader index e484000e..b6784d84 100644 --- a/test/handler/test_nim_handler.vader +++ b/test/handler/test_nim_handler.vader @@ -21,6 +21,7 @@ Execute(Parsing nim errors should work): \ 'col': 2, \ 'text': 'identifier expected, but found ''a.barfoo''', \ 'type': 'E', + \ 'end_col': 9, \ }, \ { \ 'lnum': 2, @@ -28,6 +29,7 @@ Execute(Parsing nim errors should work): \ 'text': '''NotUsed'' is declared but not used', \ 'code': 'XDeclaredButNotUsed', \ 'type': 'W', + \ 'end_col': 11, \ }, \ { \ 'lnum': 12, @@ -35,6 +37,35 @@ Execute(Parsing nim errors should work): \ 'text': 'with : character', \ 'type': 'E', \ }, + \ { + \ 'lnum': 1, + \ 'col': 8, + \ 'text': 'imported and not used: ''strutils''', + \ 'code': 'UnusedImport', + \ 'type': 'W', + \ 'end_col': 15, + \ }, + \ { + \ 'lnum': 12, + \ 'col': 9, + \ 'text': 'undeclared identifier: ''total''', + \ 'type': 'E', + \ 'end_col': 13, + \ }, + \ { + \ 'lnum': 14, + \ 'col': 1, + \ 'text': '''sum'' cannot be assigned to', + \ 'type': 'E', + \ 'end_col': 3, + \ }, + \ { + \ 'lnum': 15, + \ 'col': 1, + \ 'text': 'redefinition of ''getName''; previous declaration here: /nested/folder/foobar.nim(14, 6)', + \ 'type': 'E', + \ 'end_col': 7, + \ }, \ ], \ ale_linters#nim#nimcheck#Handle(bufnr(''), [ \ 'Line with wrong( format)', @@ -42,4 +73,8 @@ Execute(Parsing nim errors should work): \ 'foobar.nim(12, 2) Error: identifier expected, but found ''a.barfoo''', \ '/nested/folder/foobar.nim(2, 5) Hint: ''NotUsed'' is declared but not used [XDeclaredButNotUsed]', \ 'foobar.nim(12, 2) Error: with : character', + \ 'foobar.nim(1, 8) Warning: imported and not used: ''strutils'' [UnusedImport]', + \ 'foobar.nim(12, 9) Error: undeclared identifier: ''total''', + \ 'foobar.nim(14, 1) Error: ''sum'' cannot be assigned to', + \ 'foobar.nim(15, 1) Error: redefinition of ''getName''; previous declaration here: /nested/folder/foobar.nim(14, 6)', \ ]) diff --git a/test/handler/test_powershell_handler.vader b/test/handler/test_powershell_handler.vader index 77c3dc65..77c3dc65 100755..100644 --- a/test/handler/test_powershell_handler.vader +++ b/test/handler/test_powershell_handler.vader diff --git a/test/handler/test_shell_handler.vader b/test/handler/test_shell_handler.vader index 2465f179..c61cf37d 100644 --- a/test/handler/test_shell_handler.vader +++ b/test/handler/test_shell_handler.vader @@ -40,3 +40,138 @@ Execute(The shell handler should parse lines correctly): \ 'qfm:22: :11: :33: :44:', \ 'foo.sh: syntax error at line 9: `done'' unexpected', \ ]) + +Execute(The shell handler should parse Simplified Chinese lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': '未预期的符号“done”附近有语法错误', + \ }, + \ { + \ 'lnum': 90, + \ 'text': '寻找匹配的“"”时遇到了未预期的文件结束符', + \ }, + \ { + \ 'lnum': 111, + \ 'text': '语法错误: 未预期的文件结尾', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ '/tmp/nvimWL5sOL/2/a.sh:行0: 未预期的符号“done”附近有语法错误', + \ '/tmp/nvimWL5sOL/2/a.sh:行90: 寻找匹配的“"”时遇到了未预期的文件结束符', + \ '/tmp/nvimWL5sOL/2/a.sh:行111: 语法错误: 未预期的文件结尾', + \ '/tmp/nvimWL5sOL/2/a.sh:行22: :11: :33: :44:', + \ ]) + +Execute(The shell handler should parse Traditional Chinese lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': '未預期的字組「(」附近有語法錯誤', + \ }, + \ { + \ 'lnum': 90, + \ 'text': '尋找匹配的「"」時遇到了未預期的檔案結束符', + \ }, + \ { + \ 'lnum': 111, + \ 'text': '語法錯誤: 未預期的檔案結尾', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ '/tmp/nvimWL5sOL/2/a.sh: 列 0: 未預期的字組「(」附近有語法錯誤', + \ '/tmp/nvimWL5sOL/2/a.sh: 列 90: 尋找匹配的「"」時遇到了未預期的檔案結束符', + \ '/tmp/nvimWL5sOL/2/a.sh: 列 111: 語法錯誤: 未預期的檔案結尾', + \ '/tmp/nvimWL5sOL/2/a.sh: 列 22: :11: :33: :44:', + \ ]) + +Execute(The shell handler should parse Japanese lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': "予期しないトークン `(' 周辺に構文エラーがあります", + \ }, + \ { + \ 'lnum': 90, + \ 'text': "予期しないトークン `done' 周辺に構文エラーがあります", + \ }, + \ { + \ 'lnum': 111, + \ 'text': "対応する `\"' を探索中に予期しないファイル終了 (EOF) です", + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ "/tmp/nvimWL5sOL/2/a.sh: 行 0: 予期しないトークン `(' 周辺に構文エラーがあります", + \ "/tmp/nvimWL5sOL/2/a.sh: 行 90: 予期しないトークン `done' 周辺に構文エラーがあります", + \ "/tmp/nvimWL5sOL/2/a.sh: 行 111: 対応する `\"' を探索中に予期しないファイル終了 (EOF) です", + \ "/tmp/nvimWL5sOL/2/a.sh: 行 22: :11: :33: :44:", + \ ]) + +Execute(The shell handler should parse Greek lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': 'συντακτικό σφάλμα κοντά στο μη αναμενόμενο σύμβολο «done»', + \ }, + \ { + \ 'lnum': 90, + \ 'text': 'syntax error: μη αναμενόμενο τέλος αρχείου', + \ }, + \ { + \ 'lnum': 111, + \ 'text': 'μη αναμενόμενο EOF κατά την αναζήτηση «"»', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ '/tmp/nvimWL5sOL/2/a.sh: γραμμή 0: συντακτικό σφάλμα κοντά στο μη αναμενόμενο σύμβολο «done»', + \ '/tmp/nvimWL5sOL/2/a.sh: γραμμή 90: syntax error: μη αναμενόμενο τέλος αρχείου', + \ '/tmp/nvimWL5sOL/2/a.sh: γραμμή 111: μη αναμενόμενο EOF κατά την αναζήτηση «"»', + \ "/tmp/nvimWL5sOL/2/a.sh: γραμμή 22: :11: :33: :44:", + \ ]) + +Execute(The shell handler should parse Russian lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 0, + \ 'text': 'синтаксическая ошибка рядом с неожиданным маркером «done»', + \ }, + \ { + \ 'lnum': 90, + \ 'text': 'синтаксическая ошибка: неожиданный конец файла', + \ }, + \ { + \ 'lnum': 111, + \ 'text': 'неожиданный конец файла во время поиска «"»', + \ }, + \ { + \ 'lnum': 22, + \ 'text': ':11: :33: :44:', + \ }, + \ ], + \ ale_linters#sh#shell#Handle(347, [ + \ '/tmp/nvimWL5sOL/2/a.sh: строка 0: синтаксическая ошибка рядом с неожиданным маркером «done»', + \ '/tmp/nvimWL5sOL/2/a.sh: строка 90: синтаксическая ошибка: неожиданный конец файла', + \ '/tmp/nvimWL5sOL/2/a.sh: строка 111: неожиданный конец файла во время поиска «"»', + \ '/tmp/nvimWL5sOL/2/a.sh: строка 22: :11: :33: :44:', + \ ]) diff --git a/test/handler/test_solc_handler.vader b/test/handler/test_solc_handler.vader new file mode 100644 index 00000000..8c197507 --- /dev/null +++ b/test/handler/test_solc_handler.vader @@ -0,0 +1,30 @@ +Before: + runtime ale_linters/solidity/solc.vim + +After: + call ale#linter#Reset() + +Execute(Check solc output parsing): + AssertEqual + \ [ + \ { + \ 'lnum': 40, + \ 'col': 48, + \ 'text': 'This declaration shadows an existing declaration.', + \ 'type': 'W', + \ }, + \ { + \ 'lnum': 23, + \ 'col': 16, + \ 'text': 'Member "getSinleSignature" not found or not visible after argument-dependent lookup in type(contract OneToN).', + \ 'type': 'E', + \ }, + \ ], + \ ale_linters#solidity#solc#Handle(bufnr(''), [ + \ 'raiden_contracts/data/source/raiden/Token.sol:40:48: Warning: This declaration shadows an existing declaration.', + \ ' function decimals() external view returns (uint8 decimals);', + \ ' ^------------^', + \ '/home/karl/raiden-contracts/raiden_contracts/data/source/test/OneToNInternalsTest.sol:23:16: Error: Member "getSinleSignature" not found or not visible after argument-dependent lookup in type(contract OneToN).', + \ ' return OneToN.getSinleSignature(signatures, i);', + \ ' ^----------------------^', + \ ]) diff --git a/test/handler/test_terraform_handler.vader b/test/handler/test_terraform_handler.vader index 976ce12a..976ce12a 100755..100644 --- a/test/handler/test_terraform_handler.vader +++ b/test/handler/test_terraform_handler.vader diff --git a/test/handler/test_tflint_handler.vader b/test/handler/test_tflint_handler.vader index 099d0926..6b8173af 100644 --- a/test/handler/test_tflint_handler.vader +++ b/test/handler/test_tflint_handler.vader @@ -4,7 +4,7 @@ Before: After: call ale#linter#Reset() -Execute(The tflint handler should parse items correctly): +Execute(The tflint handler should parse items correctly for pre 0.11): AssertEqual \ [ \ { @@ -29,3 +29,71 @@ Execute(The tflint handler should parse items correctly): \ ale_linters#terraform#tflint#Handle(123, [ \ '[ { "detector": "aws_db_instance_readable_password", "type": "WARNING", "message": "be warned, traveller", "line": 12, "file": "github.com/wata727/example-module/aws_db_instance.tf", "link": "https://github.com/wata727/tflint/blob/master/docs/aws_db_instance_readable_password.md" }, { "detector": "aws_elasticache_cluster_invalid_type", "type": "ERROR", "message": "error message", "line": 9, "file": "github.com/wata727/example-module/aws_elasticache_cluster.tf", "link": "https://github.com/wata727/tflint/blob/master/docs/aws_elasticache_cluster_invalid_type.md" }, { "detector": "aws_instance_not_specified_iam_profile", "type": "NOTICE", "message": "just so ya know", "line": 5, "file": "github.com/wata727/example-module/aws_instance.tf", "link": "https://github.com/wata727/tflint/blob/master/docs/aws_instance_not_specified_iam_profile.md" } ]' \ ]) + +Execute(The tflint handler should parse items correctly): + AssertEqual + \ [ + \ { + \ 'filename': 'github.com/wata727/example-module/aws_instance.tf', + \ 'lnum': 1, + \ 'col': 30, + \ 'end_lnum': 2, + \ 'end_col': 1, + \ 'text': 'A block definition must have block content delimited by "{" and "}", starting on the same line as the block header.', + \ 'code': 'Invalid block definition', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_instance.tf', + \ 'lnum': 2, + \ 'col': 3, + \ 'end_lnum': 2, + \ 'end_col': 6, + \ 'text': 'An argument named "ami" is not expected here.', + \ 'code': 'Unsupported argument', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_instance.tf', + \ 'lnum': 3, + \ 'col': 3, + \ 'end_lnum': 1, + \ 'end_col': 6, + \ 'text': 'An argument named "instance_type" is not expected here.', + \ 'code': 'Unsupported argument', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_db_instance.tf', + \ 'lnum': 12, + \ 'col': 11, + \ 'end_lnum': 12, + \ 'end_col': 21, + \ 'text': 'be warned, traveller', + \ 'code': 'aws_db_instance_readable_password', + \ 'type': 'W', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_elasticache_cluster.tf', + \ 'lnum': 9, + \ 'col': 29, + \ 'end_lnum': 9, + \ 'end_col': 29, + \ 'text': 'error message', + \ 'code': 'aws_elasticache_cluster_invalid_type', + \ 'type': 'E', + \ }, + \ { + \ 'filename': 'github.com/wata727/example-module/aws_instance.tf', + \ 'lnum': 5, + \ 'col': 15, + \ 'end_lnum': 5, + \ 'end_col': 25, + \ 'text': 'just so ya know', + \ 'code': 'aws_instance_not_specified_iam_profile', + \ 'type': 'I', + \ }, + \ ], + \ ale_linters#terraform#tflint#Handle(123, [ + \ '{"issues":[{"rule":{"name":"aws_db_instance_readable_password","severity":"WARNING","link":"https://github.com/wata727/tflint/blob/master/docs/aws_db_instance_readable_password.md"},"message":"be warned, traveller","range":{"filename":"github.com/wata727/example-module/aws_db_instance.tf","start":{"line":12,"column":11},"end":{"line":12,"column":21},"callers":[]}},{"rule":{"name":"aws_elasticache_cluster_invalid_type","severity":"ERROR","link":"https://github.com/wata727/tflint/blob/master/docs/aws_elasticache_cluster_invalid_type.md"},"message":"error message","range":{"filename":"github.com/wata727/example-module/aws_elasticache_cluster.tf","start":{"line":9,"column":29},"end":{"line":9,"column":29},"callers":[]}},{"rule":{"name":"aws_instance_not_specified_iam_profile","severity":"NOTICE","link":"https://github.com/wata727/tflint/blob/master/docs/aws_instance_not_specified_iam_profile.md"},"message":"just so ya know","range":{"filename":"github.com/wata727/example-module/aws_instance.tf","start":{"line":5,"column":15},"end":{"line":5,"column":25},"callers":[]}}],"errors":[{"message":"github.com/wata727/example-module/aws_instance.tf:1,30-2,1: Invalid block definition; A block definition must have block content delimited by \"{\" and \"}\", starting on the same line as the block header."},{"message":"github.com/wata727/example-module/aws_instance.tf:2,3-6: Unsupported argument; An argument named \"ami\" is not expected here."},{"message":"github.com/wata727/example-module/aws_instance.tf:3,3-16: Unsupported argument; An argument named \"instance_type\" is not expected here."}]}' + \ ]) diff --git a/test/handler/test_vlog_handler.vader b/test/handler/test_vlog_handler.vader index a70665db..daf3cdcf 100644 --- a/test/handler/test_vlog_handler.vader +++ b/test/handler/test_vlog_handler.vader @@ -4,7 +4,7 @@ Before: After: call ale#linter#Reset() -Execute(The vlog handler should parse lines correctly): +Execute(The vlog handler should parse old-style lines correctly): AssertEqual \ [ \ { @@ -22,3 +22,22 @@ Execute(The vlog handler should parse lines correctly): \ '** Warning: add.v(7): (vlog-2623) Undefined variable: C.', \ '** Error: file.v(1): (vlog-13294) Identifier must be declared with a port mode: C.', \ ]) + +Execute(The vlog handler should parse new-style lines correctly): + AssertEqual + \ [ + \ { + \ 'lnum': 7, + \ 'type': 'W', + \ 'text': '(vlog-2623) Undefined variable: C.' + \ }, + \ { + \ 'lnum': 1, + \ 'type': 'E', + \ 'text': '(vlog-13294) Identifier must be declared with a port mode: C.' + \ }, + \ ], + \ ale_linters#verilog#vlog#Handle(bufnr(''), [ + \ '** Warning: (vlog-2623) add.v(7): Undefined variable: C.', + \ '** Error: (vlog-13294) file.v(1): Identifier must be declared with a port mode: C.', + \ ]) diff --git a/test/lsp/test_handling_window_requests.vader b/test/lsp/test_handling_window_requests.vader new file mode 100644 index 00000000..551d5975 --- /dev/null +++ b/test/lsp/test_handling_window_requests.vader @@ -0,0 +1,94 @@ +Before: + let g:expr_list = [] + let g:linter_name = 'some_linter' + let g:format = '%severity%:%linter%: %s' + " Get the default value to restore it + let g:default_severity = g:ale_lsp_show_message_severity + let g:ale_lsp_show_message_severity = 'information' + + function! ale#util#ShowMessage(expr) abort + call add(g:expr_list, a:expr) + endfunction + +After: + unlet! g:expr_list + unlet! g:linter_name + unlet! g:format + let g:ale_lsp_show_message_severity = g:default_severity + unlet! g:default_severity + +Execute(ale#lsp_window#HandleShowMessage() should only show errors when severity is set to "error"): + let g:ale_lsp_show_message_severity = 'error' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual ['Error:some_linter: an error'], g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should only show errors and warnings when severity is set to "warning"): + let g:ale_lsp_show_message_severity = 'warning' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual ['Error:some_linter: an error', 'Warning:some_linter: a warning'], g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should only show errors, warnings and infos when severity is set to "information"): + let g:ale_lsp_show_message_severity = 'information' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [ + \ 'Error:some_linter: an error', + \ 'Warning:some_linter: a warning', + \ 'Info:some_linter: an info'], + \ g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should only show errors, warnings and infos when severity is set to "info"): + let g:ale_lsp_show_message_severity = 'info' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [ + \ 'Error:some_linter: an error', + \ 'Warning:some_linter: a warning', + \ 'Info:some_linter: an info'], + \ g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should show all messages is severity is set to "log"): + let g:ale_lsp_show_message_severity = 'log' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [ + \ 'Error:some_linter: an error', + \ 'Warning:some_linter: a warning', + \ 'Info:some_linter: an info', + \ 'Log:some_linter: a log'], + \ g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should not show anything if severity is configured as disabled): + let g:ale_lsp_show_message_severity = 'disabled' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [], g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should use "warning" when severity is set to an invalid value): + let g:ale_lsp_show_message_severity = 'foo' + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'}) + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'}) + AssertEqual [ + \ 'Error:some_linter: an error', + \ 'Warning:some_linter: a warning'], + \ g:expr_list + +Execute(ale#lsp_window#HandleShowMessage() should escape quotes on messages): + call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':"this is an 'info'"}) + AssertEqual ['Info:some_linter: this is an ''info'''], g:expr_list diff --git a/test/lsp/test_lsp_client_messages.vader b/test/lsp/test_lsp_client_messages.vader index 90a20832..bc91bf68 100644 --- a/test/lsp/test_lsp_client_messages.vader +++ b/test/lsp/test_lsp_client_messages.vader @@ -275,9 +275,10 @@ Execute(ale#lsp#tsserver_message#Completions() should return correct messages): \ 'line': 347, \ 'offset': 12, \ 'prefix': 'abc', + \ 'includeExternalModuleExports': 1, \ } \ ], - \ ale#lsp#tsserver_message#Completions(bufnr(''), 347, 12, 'abc') + \ ale#lsp#tsserver_message#Completions(bufnr(''), 347, 12, 'abc', 1) Execute(ale#lsp#tsserver_message#CompletionEntryDetails() should return correct messages): AssertEqual diff --git a/test/lsp/test_other_initialize_message_handling.vader b/test/lsp/test_other_initialize_message_handling.vader index 0372765d..6473e283 100644 --- a/test/lsp/test_other_initialize_message_handling.vader +++ b/test/lsp/test_other_initialize_message_handling.vader @@ -17,6 +17,7 @@ Before: \ 'init_queue': [], \ 'capabilities': { \ 'hover': 0, + \ 'rename': 0, \ 'references': 0, \ 'completion': 0, \ 'completion_trigger_characters': [], @@ -100,6 +101,7 @@ Execute(Capabilities should bet set up correctly): \ 'hover': 1, \ 'definition': 1, \ 'symbol_search': 1, + \ 'rename': 1, \ }, \ b:conn.capabilities AssertEqual [[1, 'initialized', {}]], g:message_list @@ -110,7 +112,7 @@ Execute(Disabled capabilities should be recognised correctly): \ 'id': 1, \ 'result': { \ 'capabilities': { - \ 'renameProvider': v:true, + \ 'renameProvider': v:false, \ 'executeCommandProvider': { \ 'commands': [], \ }, @@ -143,6 +145,7 @@ Execute(Disabled capabilities should be recognised correctly): \ 'hover': 0, \ 'definition': 0, \ 'symbol_search': 0, + \ 'rename': 0, \ }, \ b:conn.capabilities AssertEqual [[1, 'initialized', {}]], g:message_list diff --git a/test/nim-test-files/with-git/src/source.nim b/test/nim-test-files/with-git/src/source.nim new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/test/nim-test-files/with-git/src/source.nim diff --git a/test/python/test_deoplete_source.py b/test/python/test_deoplete_source.py index 9e56a10d..8304fa25 100644 --- a/test/python/test_deoplete_source.py +++ b/test/python/test_deoplete_source.py @@ -8,15 +8,20 @@ ale_module = imp.load_source( class VimMock(object): - def __init__(self, call_list, call_results): + def __init__(self, call_list, call_results, commands): self.__call_list = call_list self.__call_results = call_results + self.__commands = commands + def call(self, function, *args): self.__call_list.append((function, args)) return self.__call_results.get(function, 0) + def command(self, command): + self.__commands.append(command) + class DeopleteSourceTest(unittest.TestCase): def setUp(self): @@ -24,8 +29,10 @@ class DeopleteSourceTest(unittest.TestCase): self.call_list = [] self.call_results = {'ale#completion#CanProvideCompletions': 1} + self.commands = [] self.source = ale_module.Source('vim') - self.source.vim = VimMock(self.call_list, self.call_results) + self.source.vim = VimMock( + self.call_list, self.call_results, self.commands) def test_attributes(self): """ @@ -48,6 +55,7 @@ class DeopleteSourceTest(unittest.TestCase): 'cpp': r'(\.|::|->)\w*$', }, 'is_bytepos': True, + 'is_volatile': True, 'mark': '[L]', 'min_pattern_length': 1, 'name': 'ale', @@ -64,70 +72,28 @@ class DeopleteSourceTest(unittest.TestCase): ]) def test_request_completion_results(self): - context = {'is_async': False} + context = {'event': 'TextChangedI', 'is_refresh': True} self.assertEqual(self.source.gather_candidates(context), []) - self.assertEqual(context, {'is_async': True}) self.assertEqual(self.call_list, [ ('ale#completion#CanProvideCompletions', ()), - ('ale#completion#GetCompletions', ('deoplete',)), + ]) + self.assertEqual(self.commands, [ + "call ale#completion#GetCompletions('ale-callback', " + \ + "{'callback': {completions -> deoplete#auto_complete() }})" ]) def test_request_completion_results_from_buffer_without_providers(self): self.call_results['ale#completion#CanProvideCompletions'] = 0 - context = {'is_async': False} + context = {'event': 'TextChangedI', 'is_refresh': True} self.assertIsNone(self.source.gather_candidates(context), []) - self.assertEqual(context, {'is_async': False}) - self.assertEqual(self.call_list, [ - ('ale#completion#CanProvideCompletions', ()), - ]) - - def test_refresh_completion_results(self): - context = {'is_async': False} - - self.assertEqual(self.source.gather_candidates(context), []) - self.assertEqual(context, {'is_async': True}) - self.assertEqual(self.call_list, [ - ('ale#completion#CanProvideCompletions', ()), - ('ale#completion#GetCompletions', ('deoplete',)), - ]) - - context = {'is_async': True, 'is_refresh': True} - - self.assertEqual(self.source.gather_candidates(context), []) - self.assertEqual(context, {'is_async': True, 'is_refresh': True}) self.assertEqual(self.call_list, [ ('ale#completion#CanProvideCompletions', ()), - ('ale#completion#GetCompletions', ('deoplete',)), - ('ale#completion#CanProvideCompletions', ()), - ('ale#completion#GetCompletions', ('deoplete',)), ]) - def test_poll_no_result(self): - context = {'is_async': True} - self.call_results['ale#completion#GetCompletionResult'] = None - - self.assertEqual(self.source.gather_candidates(context), []) - self.assertEqual(context, {'is_async': True}) - self.assertEqual(self.call_list, [ - ('ale#completion#CanProvideCompletions', ()), - ('ale#completion#GetCompletionResult', ()), - ]) - - def test_poll_empty_result_ready(self): - context = {'is_async': True} - self.call_results['ale#completion#GetCompletionResult'] = [] - - self.assertEqual(self.source.gather_candidates(context), []) - self.assertEqual(context, {'is_async': False}) - self.assertEqual(self.call_list, [ - ('ale#completion#CanProvideCompletions', ()), - ('ale#completion#GetCompletionResult', ()), - ]) - - def test_poll_non_empty_result_ready(self): - context = {'is_async': True} + def test_async_event(self): + context = {'event': 'Async', 'is_refresh': True} self.call_results['ale#completion#GetCompletionResult'] = [ { 'word': 'foobar', @@ -147,7 +113,7 @@ class DeopleteSourceTest(unittest.TestCase): 'info': '', }, ]) - self.assertEqual(context, {'is_async': False}) + self.assertEqual(self.call_list, [ ('ale#completion#CanProvideCompletions', ()), ('ale#completion#GetCompletionResult', ()), diff --git a/test/script/check-supported-tools-tables b/test/script/check-supported-tools-tables index 65270029..f4305707 100755 --- a/test/script/check-supported-tools-tables +++ b/test/script/check-supported-tools-tables @@ -3,8 +3,9 @@ set -e set -u -# This script compares the table of supported tools in both the README file -# and the doc/ale.txt file, so we can complain if they don't match up. +# This script compares the table of supported tools in both supported-tools.md +# (for GitHub) and doc/ale-supported-languages-and-tools.txt (for vim), so we +# can complain if they don't match up. doc_file="$(mktemp -t doc.XXXXXXXX)" doc_sorted_file="$(mktemp -t doc-sorted.XXXXXXXX)" diff --git a/test/script/check-toc b/test/script/check-toc index f3a57ed2..87a61262 100755 --- a/test/script/check-toc +++ b/test/script/check-toc @@ -35,7 +35,7 @@ sed -n "$toc_start_line,$toc_end_line"p doc/ale.txt \ > "$toc_file" # Get all of the doc files in a natural sorted order. -doc_files="$(/bin/ls -1v doc | grep ^ale- | sed 's/^/doc\//' | paste -sd ' ' -)" +doc_files="$(/usr/bin/env ls -1v doc | grep ^ale- | sed 's/^/doc\//' | paste -sd ' ' -)" # shellcheck disable=SC2086 grep -h '\*ale-.*-options\|^[a-z].*\*ale-.*\*$' $doc_files \ diff --git a/test/script/custom-checks b/test/script/custom-checks index 20dbfb80..ca9069e4 100755 --- a/test/script/custom-checks +++ b/test/script/custom-checks @@ -36,12 +36,12 @@ tag_regex='[gb]\?:\?\(ale\|ALE\)[a-zA-Z_\-]\+' # Grep for tags and references, and complain if we find a reference without # a tag for the reference. Only our tags will be included. diff -u \ - <(grep --exclude=tags -roh "\*$tag_regex\*" doc | sort -u | sed 's/*//g') \ + <(grep --exclude=tags -roh "\\*$tag_regex\\*" doc | sort -u | sed 's/*//g') \ <(grep --exclude=tags -roh "|$tag_regex|" doc | sort -u | sed 's/|//g') \ | grep '^+[^+]' && exit_code=1 echo '========================================' -echo 'diff README.md and doc/ale.txt tables' +echo 'diff supported-tools.md and doc/ale-supported-languages-and-tools.txt tables' echo '========================================' echo 'Differences follow:' echo diff --git a/test/sign/test_linting_sets_signs.vader b/test/sign/test_linting_sets_signs.vader index a8d5761f..1d1f9802 100644 --- a/test/sign/test_linting_sets_signs.vader +++ b/test/sign/test_linting_sets_signs.vader @@ -21,7 +21,7 @@ Before: let g:ale_set_highlights = 0 let g:ale_echo_cursor = 0 - sign unplace * + call ale#sign#Clear() function! TestCallback(buffer, output) return [ @@ -32,16 +32,20 @@ Before: function! CollectSigns() redir => l:output - silent exec 'sign place' + if has('nvim-0.4.2') || has('patch-8.1.614') + silent exec 'sign place group=ale' + else + silent exec 'sign place' + endif redir END let l:actual_sign_list = [] for l:line in split(l:output, "\n") - let l:match = matchlist(l:line, '\v^.*\=(\d+).*\=\d+.*\=(ALE[a-zA-Z]+Sign)') + let l:match = matchlist(l:line, ale#sign#ParsePattern()) if len(l:match) > 0 - call add(l:actual_sign_list, [l:match[1], l:match[2]]) + call add(l:actual_sign_list, [l:match[1], l:match[3]]) endif endfor @@ -60,7 +64,7 @@ After: delfunction CollectSigns unlet! g:ale_run_synchronously_callbacks - sign unplace * + call ale#sign#Clear() call ale#linter#Reset() Execute(The signs should be updated after linting is done): diff --git a/test/sign/test_sign_column_highlighting.vader b/test/sign/test_sign_column_highlighting.vader index 0b506fa7..7ea5eb0f 100644 --- a/test/sign/test_sign_column_highlighting.vader +++ b/test/sign/test_sign_column_highlighting.vader @@ -30,7 +30,7 @@ After: delfunction SetHighlight unlet! g:sign_highlight - sign unplace * + call ale#sign#Clear() Execute(The SignColumn highlight shouldn't be changed if the option is off): let g:ale_change_sign_column_color = 0 diff --git a/test/sign/test_sign_limits.vader b/test/sign/test_sign_limits.vader index b8868aeb..d7a4e2f5 100644 --- a/test/sign/test_sign_limits.vader +++ b/test/sign/test_sign_limits.vader @@ -30,7 +30,7 @@ After: delfunction SetNProblems - sign unplace * + call ale#sign#Clear() Execute(There should be no limit on signs with negative numbers): AssertEqual range(1, 42), SetNProblems(42) diff --git a/test/sign/test_sign_parsing.vader b/test/sign/test_sign_parsing.vader index 07848afb..c0967f43 100644 --- a/test/sign/test_sign_parsing.vader +++ b/test/sign/test_sign_parsing.vader @@ -1,35 +1,88 @@ Execute (Parsing English signs should work): - AssertEqual - \ [0, [[9, 1000001, 'ALEWarningSign']]], - \ ale#sign#ParseSigns([ - \ 'Signs for app.js:', - \ ' line=9 id=1000001 name=ALEWarningSign', - \ ]) + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[9, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([ + \ 'Signs for app.js:', + \ ' line=9 id=1000001 group=ale name=ALEWarningSign', + \ ]) + else + AssertEqual + \ [0, [[9, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([ + \ 'Signs for app.js:', + \ ' line=9 id=1000001 name=ALEWarningSign', + \ ]) + endif Execute (Parsing Russian signs should work): - AssertEqual - \ [0, [[1, 1000001, 'ALEErrorSign']]], - \ ale#sign#ParseSigns([' строка=1 id=1000001 имя=ALEErrorSign']) + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([' строка=1 id=1000001 группа=ale имя=ALEErrorSign']) + else + AssertEqual + \ [0, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([' строка=1 id=1000001 имя=ALEErrorSign']) + endif Execute (Parsing Japanese signs should work): - AssertEqual - \ [0, [[1, 1000001, 'ALEWarningSign']]], - \ ale#sign#ParseSigns([' 行=1 識別子=1000001 名前=ALEWarningSign']) + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' 行=1 識別子=1000001 グループ=ale 名前=ALEWarningSign']) + else + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' 行=1 識別子=1000001 名前=ALEWarningSign']) + endif Execute (Parsing Spanish signs should work): - AssertEqual - \ [0, [[12, 1000001, 'ALEWarningSign']]], - \ ale#sign#ParseSigns([' línea=12 id=1000001 nombre=ALEWarningSign']) + 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']) + else + AssertEqual + \ [0, [[12, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' línea=12 id=1000001 nombre=ALEWarningSign']) + endif Execute (Parsing Italian signs should work): - AssertEqual - \ [0, [[1, 1000001, 'ALEWarningSign']]], - \ ale#sign#ParseSigns([' riga=1 id=1000001, nome=ALEWarningSign']) - \ + 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']) + else + AssertEqual + \ [0, [[1, 1000001, 'ALEWarningSign']]], + \ ale#sign#ParseSigns([' riga=1 id=1000001, nome=ALEWarningSign']) + endif + +Execute (Parsing German signs should work): + 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']) + else + AssertEqual + \ [0, [[235, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([' Zeile=235 id=1000001 Name=ALEErrorSign']) + endif + Execute (The sign parser should indicate if the dummy sign is set): - AssertEqual - \ [1, [[1, 1000001, 'ALEErrorSign']]], - \ ale#sign#ParseSigns([ - \ ' строка=1 id=1000001 имя=ALEErrorSign', - \ ' line=1 id=1000000 name=ALEDummySign', - \ ]) + if has('nvim-0.4.2') || has('patch-8.1.614') + AssertEqual + \ [1, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([ + \ ' строка=1 id=1000001 group=ale имя=ALEErrorSign', + \ ' line=1 id=1000000 group=ale name=ALEDummySign', + \ ]) + else + AssertEqual + \ [1, [[1, 1000001, 'ALEErrorSign']]], + \ ale#sign#ParseSigns([ + \ ' строка=1 id=1000001 имя=ALEErrorSign', + \ ' line=1 id=1000000 name=ALEDummySign', + \ ]) + endif diff --git a/test/sign/test_sign_placement.vader b/test/sign/test_sign_placement.vader index f0b3ba2f..d8d05b28 100644 --- a/test/sign/test_sign_placement.vader +++ b/test/sign/test_sign_placement.vader @@ -17,7 +17,7 @@ Before: let g:ale_echo_cursor = 0 call ale#linter#Reset() - sign unplace * + call ale#sign#Clear() function! GenerateResults(buffer, output) return [ @@ -68,12 +68,16 @@ Before: function! ParseSigns() redir => l:output - silent sign place + if has('nvim-0.4.2') || has('patch-8.1.614') + silent sign place group=ale + else + silent sign place + endif redir END return map( \ split(l:output, '\n')[2:], - \ 'matchlist(v:val, ''^.*=\(\d\+\).*=\(\d\+\).*=\(.*\)$'')[1:3]', + \ 'matchlist(v:val, ''' . ale#sign#ParsePattern() . ''')[1:3]', \) endfunction @@ -92,7 +96,7 @@ After: delfunction GenerateResults delfunction ParseSigns call ale#linter#Reset() - sign unplace * + call ale#sign#Clear() Execute(ale#sign#GetSignName should return the right sign names): AssertEqual 'ALEErrorSign', ale#sign#GetSignName([{'type': 'E'}]) @@ -148,9 +152,15 @@ Execute(The current signs should be set for running a job): \ ParseSigns() Execute(Loclist items with sign_id values should be kept): - exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('') - exec 'sign place 1000348 line=15 name=ALEErrorSign buffer=' . bufnr('') - exec 'sign place 1000349 line=16 name=ALEWarningSign buffer=' . bufnr('') + 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('') + else + exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000348 line=15 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000349 line=16 name=ALEWarningSign buffer=' . bufnr('') + endif let g:loclist = [ \ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'E', 'text': 'a', 'sign_id': 1000348}, @@ -287,10 +297,17 @@ 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. - exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('') - exec 'sign place 1000348 line=3 name=ALEWarningSign buffer=' . bufnr('') - exec 'sign place 1000349 line=10 name=ALEErrorSign buffer=' . bufnr('') - exec 'sign place 1000350 line=10 name=ALEWarningSign buffer=' . bufnr('') + 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('') + exec 'sign place 1000350 group=ale line=10 name=ALEWarningSign buffer=' . bufnr('') + else + exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000348 line=3 name=ALEWarningSign buffer=' . bufnr('') + exec 'sign place 1000349 line=10 name=ALEErrorSign buffer=' . bufnr('') + exec 'sign place 1000350 line=10 name=ALEWarningSign buffer=' . bufnr('') + endif call ale#sign#SetSigns(bufnr(''), []) AssertEqual [], ParseSigns() diff --git a/test/test_ale_has.vader b/test/test_ale_has.vader index b2df01fe..b8a99103 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.6.0') AssertEqual 1, ale#Has('ale-2.5.0') AssertEqual 1, ale#Has('ale-2.4.0') AssertEqual 1, ale#Has('ALE-2.2.1') diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index decd49e9..2bc8c635 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -1,50 +1,114 @@ Before: Save g:ale_buffer_info Save g:ale_cache_executable_check_failures + Save g:ale_change_sign_column_color + Save g:ale_command_wrapper Save g:ale_completion_delay Save g:ale_completion_enabled Save g:ale_completion_max_suggestions + Save g:ale_echo_cursor + Save g:ale_echo_msg_error_str + Save g:ale_echo_msg_format + Save g:ale_echo_msg_info_str + Save g:ale_echo_msg_warning_str + Save g:ale_fix_on_save Save g:ale_fixers + Save g:ale_history_enabled Save g:ale_history_log_output + Save g:ale_keep_list_window_open + Save g:ale_lint_delay + Save g:ale_lint_on_enter + Save g:ale_lint_on_filetype_changed Save g:ale_lint_on_insert_leave + Save g:ale_lint_on_save Save g:ale_lint_on_text_changed Save g:ale_linters + Save g:ale_linters_explicit + Save g:ale_list_vertical + Save g:ale_list_window_size + Save g:ale_loclist_msg_format Save g:ale_lsp_error_messages + Save g:ale_lsp_root + Save g:ale_max_buffer_history_size + Save g:ale_max_signs Save g:ale_maximum_file_size + Save g:ale_open_list Save g:ale_pattern_options Save g:ale_pattern_options_enabled Save g:ale_set_balloons + Save g:ale_set_highlights + Save g:ale_set_loclist + Save g:ale_set_quickfix + Save g:ale_set_signs + Save g:ale_sign_column_always Save g:ale_sign_error Save g:ale_sign_info + Save g:ale_sign_offset Save g:ale_sign_style_error Save g:ale_sign_style_warning Save g:ale_sign_warning + Save g:ale_sign_highlight_linenrs Save g:ale_statusline_format Save g:ale_type_map + Save g:ale_use_global_executables + Save g:ale_virtualtext_cursor + Save g:ale_warn_about_trailing_blank_lines Save g:ale_warn_about_trailing_whitespace unlet! b:ale_history let g:ale_buffer_info = {} let g:ale_cache_executable_check_failures = 0 + let g:ale_change_sign_column_color = 0 + let g:ale_command_wrapper = '' let g:ale_completion_delay = 100 let g:ale_completion_enabled = 0 let g:ale_completion_max_suggestions = 50 + let g:ale_echo_cursor = 1 + let g:ale_echo_msg_error_str = 'Error' + let g:ale_echo_msg_format = '%code: %%s' + let g:ale_echo_msg_info_str = 'Info' + let g:ale_echo_msg_warning_str = 'Warning' + let g:ale_fix_on_save = 0 + let g:ale_history_enabled = 1 let g:ale_history_log_output = 1 + let g:ale_keep_list_window_open = 0 + let g:ale_lint_delay = 200 + let g:ale_lint_on_enter = 1 + let g:ale_lint_on_filetype_changed = 1 let g:ale_lint_on_insert_leave = 1 + let g:ale_lint_on_save = 1 let g:ale_lint_on_text_changed = 'normal' + let g:ale_linters_explicit = 0 + let g:ale_list_vertical = 0 + let g:ale_list_window_size = 10 + let g:ale_loclist_msg_format = '%code: %%s' let g:ale_lsp_error_messages = {} + let g:ale_lsp_root = {} + let g:ale_max_buffer_history_size = 20 + let g:ale_max_signs = -1 let g:ale_maximum_file_size = 0 + let g:ale_open_list = 0 let g:ale_pattern_options = {} let g:ale_pattern_options_enabled = 0 let g:ale_set_balloons = 0 + let g:ale_set_highlights = 1 + let g:ale_set_loclist = 1 + let g:ale_set_quickfix = 0 + let g:ale_set_signs = 1 + let g:ale_sign_column_always = 0 let g:ale_sign_error = '>>' let g:ale_sign_info = '--' + let g:ale_sign_offset = 1000000 let g:ale_sign_style_error = '>>' let g:ale_sign_style_warning = '--' let g:ale_sign_warning = '--' + let g:ale_sign_highlight_linenrs = 0 let g:ale_statusline_format = ['%d error(s)', '%d warning(s)', 'OK'] let g:ale_type_map = {} + let g:ale_use_global_executables = v:null + let g:ale_virtualtext_cursor = 0 + let g:ale_warn_about_trailing_blank_lines = 1 let g:ale_warn_about_trailing_whitespace = 1 let g:testlinter1 = {'name': 'testlinter1', 'executable': 'testlinter1', 'command': 'testlinter1', 'callback': 'testCB1', 'output_stream': 'stdout'} @@ -116,6 +180,7 @@ Before: \ 'let g:ale_sign_style_error = ''>>''', \ 'let g:ale_sign_style_warning = ''--''', \ 'let g:ale_sign_warning = ''--''', + \ 'let g:ale_sign_highlight_linenrs = 0', \ 'let g:ale_statusline_format = [''%d error(s)'', ''%d warning(s)'', ''OK'']', \ 'let g:ale_type_map = {}', \ 'let g:ale_use_global_executables = v:null', @@ -164,6 +229,8 @@ After: unlet! b:ale_testft2_testlinter2_foo unlet! g:ale_testft2_testlinter2_bar unlet! g:info_test_file + unlet! g:ale_testft_build_dir_names + unlet! g:ale_testft_testlinter2_option delfunction CheckInfo call ale#test#RestoreDirectory() @@ -632,3 +699,52 @@ Execute (LSP errors for other linters shouldn't appear): \ + g:globals_lines \ + g:command_header \) + +Given testft.testft2 (Empty buffer with two filetypes): +Execute (ALEInfo should include linter global options): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft2', g:testlinter2) + + " eg: like g:c_build_dir_names + let g:ale_testft_build_dir_names = ['build', 'bin'] + + call add(g:variables_lines, 'let g:ale_testft_build_dir_names = [''build'', ''bin'']') + + call CheckInfo( + \ [ + \ ' Current Filetype: testft.testft2', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) + +Given testft (Empty buffer with two filetypes): +Execute (ALEInfo should include linter global options for enabled linters): + call ale#linter#Define('testft', g:testlinter1) + call ale#linter#Define('testft', g:testlinter2) + + let g:ale_linters = {'testft': ['testlinter1']} + + " should not appear, since not enabled + let g:ale_testft_testlinter2_option = 'test' + + let g:globals_lines[index(g:globals_lines, 'let g:ale_linters = {}')] + \ = 'let g:ale_linters = {''testft'': [''testlinter1'']}' + + call CheckInfo( + \ [ + \ ' Current Filetype: testft', + \ 'Available Linters: [''testlinter1'', ''testlinter2'']', + \ ' Enabled Linters: [''testlinter1'']', + \ ] + \ + g:fixer_lines + \ + g:variables_lines + \ + g:globals_lines + \ + g:command_header + \) + + diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader index d0bca329..1debcee6 100644 --- a/test/test_ale_toggle.vader +++ b/test/test_ale_toggle.vader @@ -36,6 +36,7 @@ Before: \ 'ALEEvents', \ 'ALEHighlightBufferGroup', \] + let g:has_nvim_highlight = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace') function! ToggleTestCallback(buffer, output) return [{ @@ -81,7 +82,7 @@ Before: \ 'read_buffer': 0, \}) - sign unplace * + call ale#sign#Clear() After: Restore @@ -91,6 +92,7 @@ After: unlet! g:expected_groups unlet! b:ale_enabled unlet! g:output + unlet! g:has_nvim_highlight call ale#linter#Reset() @@ -103,7 +105,7 @@ After: delfunction ParseAuGroups call setloclist(0, []) - sign unplace * + call ale#sign#Clear() call clearmatches() Given foobar (Some imaginary filetype): @@ -120,9 +122,14 @@ Execute(ALEToggle should reset everything and then run again): " First check that everything is there... AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) - AssertEqual - \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], - \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + + " Only check the legacy matches if not using the new NeoVIM API. + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + AssertEqual g:expected_groups, ParseAuGroups() AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist @@ -133,7 +140,11 @@ Execute(ALEToggle should reset everything and then run again): Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' AssertEqual [], ale#test#GetLoclistWithoutModule(), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' - AssertEqual [], getmatches(), 'The highlights were not cleared' + + if !g:has_nvim_highlight + AssertEqual [], getmatches(), 'The highlights were not cleared' + endif + AssertEqual g:expected_groups, ParseAuGroups() " Toggle ALE on, everything should be set up and run again. @@ -142,9 +153,13 @@ Execute(ALEToggle should reset everything and then run again): AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) - AssertEqual - \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], - \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + AssertEqual g:expected_groups, ParseAuGroups() AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist @@ -228,9 +243,13 @@ Execute(ALEReset should reset everything for a buffer): " First check that everything is there... AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) - AssertEqual - \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], - \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist " Now Toggle ALE off. @@ -241,7 +260,10 @@ Execute(ALEReset should reset everything for a buffer): Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' AssertEqual [], ale#test#GetLoclistWithoutModule(), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' - AssertEqual [], getmatches(), 'The highlights were not cleared' + + if !g:has_nvim_highlight + AssertEqual [], getmatches(), 'The highlights were not cleared' + endif AssertEqual 1, g:ale_enabled @@ -254,9 +276,13 @@ Execute(ALEToggleBuffer should reset everything and then run again): " First check that everything is there... AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) - AssertEqual - \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], - \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist " Now Toggle ALE off. @@ -266,7 +292,10 @@ Execute(ALEToggleBuffer should reset everything and then run again): Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' AssertEqual [], ale#test#GetLoclistWithoutModule(), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' - AssertEqual [], getmatches(), 'The highlights were not cleared' + + if !g:has_nvim_highlight + AssertEqual [], getmatches(), 'The highlights were not cleared' + endif " Toggle ALE on, everything should be set up and run again. ALEToggleBuffer @@ -274,9 +303,13 @@ Execute(ALEToggleBuffer should reset everything and then run again): AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) - AssertEqual - \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], - \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + AssertEqual g:expected_groups, ParseAuGroups() AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist @@ -325,9 +358,13 @@ Execute(ALEResetBuffer should reset everything for a buffer): " First check that everything is there... AssertEqual g:expected_loclist, ale#test#GetLoclistWithoutModule() AssertEqual [0, [[2, 1000001, 'ALEErrorSign']]], ale#sign#FindCurrentSigns(bufnr('%')) - AssertEqual - \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], - \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + + if !g:has_nvim_highlight + AssertEqual + \ [{'group': 'ALEError', 'pos1': [2, 3, 1]}], + \ map(getmatches(), '{''group'': v:val.group, ''pos1'': v:val.pos1}') + endif + AssertEqual [{'lnum': 2, 'bufnr': bufnr(''), 'col': 3, 'linter_name': 'testlinter', 'vcol': 0, 'nr': -1, 'type': 'E', 'text': 'foo bar', 'sign_id': 1000001}], g:ale_buffer_info[bufnr('')].loclist " Now Toggle ALE off. @@ -338,7 +375,10 @@ Execute(ALEResetBuffer should reset everything for a buffer): Assert !has_key(g:ale_buffer_info, bufnr('')), 'The g:ale_buffer_info Dictionary was not removed' AssertEqual [], ale#test#GetLoclistWithoutModule(), 'The loclist was not cleared' AssertEqual [0, []], ale#sign#FindCurrentSigns(bufnr('%')), 'The signs were not cleared' - AssertEqual [], getmatches(), 'The highlights were not cleared' + + if !g:has_nvim_highlight + AssertEqual [], getmatches(), 'The highlights were not cleared' + endif AssertEqual 1, g:ale_enabled AssertEqual 1, get(b:, 'ale_enabled', 1) diff --git a/test/test_autocmd_commands.vader b/test/test_autocmd_commands.vader index 241e7d3e..355b4c77 100644 --- a/test/test_autocmd_commands.vader +++ b/test/test_autocmd_commands.vader @@ -188,6 +188,10 @@ Execute (ALECleanupGroup should include the right commands): \], CheckAutocmd('ALECleanupGroup') endif +Execute(ALECompletionActions should always be set up): + AssertEqual [ + \ 'CompleteDone * call ale#completion#HandleUserData(v:completed_item)', + \], CheckAutocmd('ALECompletionActions') Execute(Enabling completion should set up autocmd events correctly): let g:ale_completion_enabled = 0 diff --git a/test/test_c_flag_parsing.vader b/test/test_c_flag_parsing.vader index 045554a3..8ae6f9dc 100644 --- a/test/test_c_flag_parsing.vader +++ b/test/test_c_flag_parsing.vader @@ -14,7 +14,7 @@ Execute(The CFlags parser should be able to parse include directives): call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') AssertEqual - \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir'), + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -c file.c']) AssertEqual @@ -25,14 +25,14 @@ Execute(ParseCFlags should ignore -c and -o): call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') AssertEqual - \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir'), + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')), \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -c file.c -o a.out']) Execute(The CFlags parser should be able to parse macro directives): call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') AssertEqual - \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) \ . ' -DTEST=1', \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -DTEST=1 -c file.c']) @@ -40,7 +40,7 @@ Execute(The CFlags parser should be able to parse macro directives with spaces): call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') AssertEqual - \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) \ . ' -DTEST=$(( 2 * 4 ))', \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -DTEST=$(( 2 * 4 )) -c file.c']) @@ -48,14 +48,14 @@ Execute(The CFlags parser should be able to parse shell directives with spaces): call ale#test#SetFilename('test_c_projects/makefile_project/subdir/file.c') AssertEqual - \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlagsFromMakeOutput(bufnr(''), ['gcc -Isubdir -DTEST=`date +%s` -c file.c']) Execute(ParseCFlags should be able to parse flags with relative paths): AssertEqual - \ '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') + \ '-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'), @@ -67,8 +67,8 @@ Execute(ParseCFlags should be able to parse flags with relative paths): Execute(ParseCFlags should be able to parse -Dgoal): AssertEqual \ '-Dgoal=9' - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') + \ . ' ' . '-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'), @@ -77,16 +77,30 @@ Execute(ParseCFlags should be able to parse -Dgoal): \ . ' -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): AssertEqual \ '-Dgoal=9' - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/"dir with spaces"') - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') + \ . ' ' . '-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( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Isubdir ' + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' \ . '-I"dir with spaces"' . ' -I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) @@ -94,28 +108,29 @@ Execute(ParseCFlags should handle paths with spaces in double quotes): Execute(ParseCFlags should handle paths with spaces in single quotes): AssertEqual \ '-Dgoal=9' - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') - \ . ' ' . '-I' . ale#path#Simplify(g:dir. "/test_c_projects/makefile_project/'dir with spaces'") - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') + \ . ' ' . '-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( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Isubdir ' - \ . "-I'dir with spaces'" . ' -I'. ale#path#Simplify('kernel/include') + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ . '-I''dir with spaces''' . ' -I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) Execute(ParseCFlags should handle paths with minuses): AssertEqual \ '-Dgoal=9' - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/dir-with-dash') - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') + \ . ' ' . '-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')) \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Isubdir ' - \ . ' -Idir-with-dash' + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ . '-I''dir with spaces''' . ' -Idir-with-dash' \ . ' -I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) @@ -123,14 +138,17 @@ Execute(ParseCFlags should handle paths with minuses): Execute(ParseCFlags should handle -D with minuses): AssertEqual \ '-Dgoal=9' - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) \ . ' -Dmacro-with-dash' - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include') + \ . ' ' . '-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')) \ . ' -DTEST=`date +%s`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 -Isubdir ' + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' \ . '-Dmacro-with-dash ' + \ . '-I''dir with spaces''' . ' -Idir-with-dash' \ . ' -I'. ale#path#Simplify('kernel/include') \ . ' -DTEST=`date +%s` -c file.c' \ ) @@ -138,11 +156,16 @@ Execute(ParseCFlags should handle -D with minuses): Execute(ParseCFlags should handle flags at the end of the line): AssertEqual \ '-Dgoal=9' - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir') - \ . ' ' . '-I' . ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/kernel/include'), + \ . ' ' . '-I' . ' ' . ale#Escape(ale#path#Simplify(g:dir. '/test_c_projects/makefile_project/subdir')) + \ . ' -Dmacro-with-dash' + \ . ' ' . '-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 -Isubdir ' + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla -Isubdir ' + \ . '-Dmacro-with-dash ' + \ . '-I''dir with spaces''' . ' -Idir-with-dash' \ . ' -I'. ale#path#Simplify('kernel/include') \ ) @@ -156,14 +179,14 @@ Execute(ParseCompileCommandsFlags should parse some basic flags): silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) AssertEqual - \ '-I/usr/include/xmms2', + \ '-I ' . ale#path#Simplify('/usr/include/xmms2'), \ ale#c#ParseCompileCommandsFlags(bufnr(''), { "xmms2-mpris.c": [ \ { - \ 'directory': '/foo/bar/xmms2-mpris', - \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2' + \ '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 ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', - \ 'file': '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', + \ . ' -c ' . ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), + \ 'file': ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c'), \ }, \ ] }, {}) @@ -183,14 +206,14 @@ Execute(ParseCompileCommandsFlags should fall back to files in the same director silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.c')) AssertEqual - \ '-I/usr/include/xmms2', + \ '-I ' . ale#path#Simplify('/usr/include/xmms2'), \ ale#c#ParseCompileCommandsFlags(bufnr(''), {}, { "src": [ \ { - \ 'directory': '/foo/bar/xmms2-mpris', - \ 'command': '/usr/bin/cc -I' . '/usr/include/xmms2' + \ '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 ' . '/foo/bar/xmms2-mpris/src/xmms2-mpris.c', - \ 'file': (has('win32') ? 'C:' : '') . '/foo/bar/xmms2-mpris/src/xmms2-other.c', + \ . ' -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'), \ }, \ ] }) @@ -198,7 +221,7 @@ Execute(ParseCompileCommandsFlags should take commands from matching .c files fo silent noautocmd execute 'file! ' . fnameescape(ale#path#Simplify('/foo/bar/xmms2-mpris/src/xmms2-mpris.h')) AssertEqual - \ '-I/usr/include/xmms2', + \ '-I /usr/include/xmms2', \ ale#c#ParseCompileCommandsFlags( \ bufnr(''), \ { @@ -220,7 +243,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.hpp')) AssertEqual - \ '-I/usr/include/xmms2', + \ '-I /usr/include/xmms2', \ ale#c#ParseCompileCommandsFlags( \ bufnr(''), \ { @@ -242,7 +265,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 /usr/include/xmms2', \ ale#c#ParseCompileCommandsFlags( \ bufnr(''), \ { @@ -282,13 +305,64 @@ Execute(ParseCompileCommandsFlags should not take commands from .c files for .h \ }, \ ) +Execute(ParseCFlags 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 ' + \ ) + Execute(ParseCFlags should handle parenthesis and quotes): AssertEqual - \ '-Dgoal=9 -Dtest1="('' '')" file1.o -Dtest2=''(` `)'' file2.o -Dtest3=`(" ")` file3.o', + \ '-Dgoal=9 -Dtest1="('' '')" -Dtest2=''(` `)'' -Dtest3=`(" ")`', \ ale#c#ParseCFlags( \ ale#path#Simplify(g:dir. '/test_c_projects/makefile_project'), - \ 'gcc -Dgoal=9 ' + \ 'gcc -Dgoal=9 -Tlinkerfile.ld blabla ' \ . '-Dtest1="('' '')" file1.o ' \ . '-Dtest2=''(` `)'' file2.o ' \ . '-Dtest3=`(" ")` file3.o ' \ ) + +Execute(CFlags we want to pass): + 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')) + \ . ' -Dmacro=value -D macro2 -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', + \ 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' + \ . ' -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(CFlags we dont want to pass): + 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' + \ ) diff --git a/test/test_code_action.vader b/test/test_code_action.vader new file mode 100644 index 00000000..b47f24ff --- /dev/null +++ b/test/test_code_action.vader @@ -0,0 +1,352 @@ +Before: + runtime autoload/ale/code_action.vim + runtime autoload/ale/util.vim + + let g:file1 = tempname() + let g:file2 = tempname() + let g:test = {} + + let g:test.create_change = {line, offset, end_line, end_offset, value -> + \{ + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': line, + \ 'offset': offset, + \ }, + \ 'end': { + \ 'line': end_line, + \ 'offset': end_offset, + \ }, + \ 'newText': value, + \ }], + \ }] + \}} + + function! WriteFileAndEdit() abort + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + execute 'edit ' . g:file1 + endfunction! + +After: + " Close the extra buffers if we opened it. + if bufnr(g:file1) != -1 + execute ':bp! | :bd! ' . bufnr(g:file1) + endif + if bufnr(g:file2) != -1 + execute ':bp! | :bd! ' . bufnr(g:file2) + endif + + if filereadable(g:file1) + call delete(g:file1) + endif + if filereadable(g:file2) + call delete(g:file2) + endif + + unlet g:file1 + unlet g:file2 + unlet g:test + delfunction WriteFileAndEdit + + runtime autoload/ale/code_action.vim + runtime autoload/ale/util.vim + + +Execute(It should modify and save multiple files): + call writefile([ + \ 'class Name {', + \ ' value: string', + \ '}', + \ '', + \ 'class B {', + \ ' constructor(readonly a: Name) {}', + \ '}' + \], g:file1, 'S') + call writefile([ + \ 'import A from "A"', + \ 'import {', + \ ' B,', + \ ' C,', + \ '} from "module"', + \ 'import D from "D"', + \], g:file2, 'S') + + call ale#code_action#HandleCodeAction({ + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 1, + \ 'offset': 7, + \ }, + \ 'end': { + \ 'line': 1, + \ 'offset': 11, + \ }, + \ 'newText': 'Value', + \ }, { + \ 'start': { + \ 'line': 6, + \ 'offset': 27, + \ }, + \ 'end': { + \ 'line': 6, + \ 'offset': 31, + \ }, + \ 'newText': 'Value', + \ }], + \ }, { + \ 'fileName': g:file2, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 2, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 6, + \ 'offset': 1, + \ }, + \ 'newText': "import {A, B} from 'module'\n\n", + \ }] + \ }], + \}, v:true) + + AssertEqual [ + \ 'class Value {', + \ ' value: string', + \ '}', + \ '', + \ 'class B {', + \ ' constructor(readonly a: Value) {}', + \ '}', + \ '', + \], readfile(g:file1, 'b') + + AssertEqual [ + \ 'import A from "A"', + \ 'import {A, B} from ''module''', + \ '', + \ 'import D from "D"', + \ '', + \], readfile(g:file2, 'b') + + +Execute(Beginning of file can be modified): + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + + call ale#code_action#HandleCodeAction({ + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'newText': "type A: string\ntype B: number\n", + \ }], + \ }] + \}, v:true) + + AssertEqual [ + \ 'type A: string', + \ 'type B: number', + \] + g:test.text + [''], readfile(g:file1, 'b') + + +Execute(End of file can be modified): + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + + call ale#code_action#HandleCodeAction({ + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 4, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 4, + \ 'offset': 1, + \ }, + \ 'newText': "type A: string\ntype B: number\n", + \ }], + \ }] + \}, v:true) + + AssertEqual g:test.text + [ + \ 'type A: string', + \ 'type B: number', + \ '', + \], readfile(g:file1, 'b') + + +Execute(Current buffer contents will be reloaded): + let g:test.text = [ + \ 'class Name {', + \ ' value: string', + \ '}', + \] + call writefile(g:test.text, g:file1, 'S') + + execute 'edit ' . g:file1 + let g:test.buffer = bufnr(g:file1) + + call ale#code_action#HandleCodeAction({ + \ 'changes': [{ + \ 'fileName': g:file1, + \ 'textChanges': [{ + \ 'start': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'end': { + \ 'line': 1, + \ 'offset': 1, + \ }, + \ 'newText': "type A: string\ntype B: number\n", + \ }], + \ }] + \}, v:true) + + AssertEqual [ + \ 'type A: string', + \ 'type B: number', + \] + g:test.text + [''], readfile(g:file1, 'b') + + AssertEqual [ + \ 'type A: string', + \ 'type B: number', + \] + g:test.text, getbufline(g:test.buffer, 1, '$') + + +# Tests for cursor repositioning. In comments `=` designates change range, and +# `C` cursor position + +# C === +Execute(Cursor will not move when it is before text change): + call WriteFileAndEdit() + 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, v:true) + AssertEqual [1, 1], getpos('.')[1:2] + + call setpos('.', [0, 2, 2, 0]) + call ale#code_action#HandleCodeAction(g:test.changes, v:true) + AssertEqual [2, 2], getpos('.')[1:2] + +# ====C==== +Execute(Cursor column will move to the change end when cursor between start/end): + let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2') + + for r in range(3, 8) + call WriteFileAndEdit() + call setpos('.', [0, 2, r, 0]) + AssertEqual ' value: string', getline('.') + call ale#code_action#HandleCodeAction(g:test.changes, v:true) + AssertEqual ' value2: string', getline('.') + AssertEqual [2, 9], getpos('.')[1:2] + endfor + + +# ====C +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'), v:true) + AssertEqual ' val: string', getline('.') + AssertEqual [2, 6], getpos('.')[1:2] + + +# ==== C +Execute(Cursor column will move forward when new text is longer): + 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, 'longValue'), v:true) + AssertEqual ' longValue: string', getline('.') + AssertEqual [2, 12], getpos('.')[1:2] + +# ========= +# = +# C +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"), v:true) + AssertEqual '}', getline('.') + AssertEqual [4, 1], getpos('.')[1:2] + + +# ========= +# =C +Execute(Cursor line and column will move when change on lines above and just before cursor column): + 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"), v:true) + AssertEqual '123 value: string', getline('.') + AssertEqual [3, 5], getpos('.')[1:2] + +# ========= +# ======C== +# = +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"), v:true) + AssertEqual '}', getline('.') + AssertEqual [2, 1], getpos('.')[1:2] + +# C == +# === +Execute(Cursor will not move when changes happening on lines >= cursor, but after cursor): + 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"), 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, '$') diff --git a/test/test_eslint_executable_detection.vader b/test/test_eslint_executable_detection.vader index 64383dcb..c2071131 100644 --- a/test/test_eslint_executable_detection.vader +++ b/test/test_eslint_executable_detection.vader @@ -57,13 +57,25 @@ Execute(eslint.js executables should be run with node on Windows): " We have to execute the file with node. if has('win32') AssertEqual - \ ale#Escape('node.exe') . ' ' + \ 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#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')) + \ 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') + + " cd "..." not present, since project root not found (from node_modules) + AssertEqual + \ ale#Escape(ale#handlers#eslint#GetExecutable(bufnr(''))) + \ . ' -f json --stdin --stdin-filename %s', + \ ale#handlers#eslint#GetCommand(bufnr('')) diff --git a/test/test_filetype_linter_defaults.vader b/test/test_filetype_linter_defaults.vader index af028041..9c40cb23 100644 --- a/test/test_filetype_linter_defaults.vader +++ b/test/test_filetype_linter_defaults.vader @@ -66,3 +66,7 @@ Execute(The defaults for the verilog filetype should be correct): let g:ale_linters_explicit = 1 AssertEqual [], GetLinterNames('verilog') + +Execute(Default aliases for React should be defined): + AssertEqual ['javascript', 'jsx'], ale#linter#ResolveFiletype('javascriptreact') + AssertEqual ['typescript', 'tsx'], ale#linter#ResolveFiletype('typescriptreact') diff --git a/test/test_find_references.vader b/test/test_find_references.vader index 1a147849..9949362a 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 @@ -63,6 +66,8 @@ Before: endfunction After: + Restore + if g:conn_id isnot v:null call ale#lsp#RemoveConnectionWithID(g:conn_id) endif @@ -152,6 +157,20 @@ 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:ale_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() + Execute(The preview window should not be opened for empty tsserver responses): call ale#references#SetMap({3: {}}) call ale#references#HandleTSServerResponse(1, { @@ -195,7 +214,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 +224,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 +362,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 +373,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_go_to_definition.vader b/test/test_go_to_definition.vader index 3479d7b5..a517bd54 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) diff --git a/test/test_highlight_placement.vader b/test/test_highlight_placement.vader index 87ac6073..dab73073 100644 --- a/test/test_highlight_placement.vader +++ b/test/test_highlight_placement.vader @@ -7,6 +7,10 @@ Before: Save g:ale_set_loclist Save g:ale_set_quickfix Save g:ale_set_signs + Save g:ale_exclude_highlights + Save b:ale_exclude_highlights + + runtime autoload/ale/highlight.vim let g:ale_run_synchronously = 1 unlet! g:ale_run_synchronously_callbacks @@ -18,6 +22,8 @@ Before: let g:ale_set_quickfix = 0 let g:ale_set_loclist = 0 let g:ale_echo_cursor = 0 + let g:ale_exclude_highlights = [] + let b:ale_exclude_highlights = [] function! GenerateResults(buffer, output) return [ @@ -42,16 +48,54 @@ Before: \] endfunction + let g:has_nvim_highlight = exists('*nvim_buf_add_highlight') && exists('*nvim_buf_clear_namespace') + let g:nvim_highlight_matches = {} + + function! ale#highlight#nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end) abort + if a:line_end != -1 + throw 'nvim api behavior not supported' + endif + + let l:matches = get(g:nvim_highlight_matches, a:buffer, []) + call filter( + \ l:matches, + \ {_, val -> val.pos1[0] < (a:line_start + 1) }, + \) + endfunction + + function! ale#highlight#nvim_buf_add_highlight(buffer, ns_id, hl_group, line, col_start, col_end) abort + if a:col_end == -1 + throw 'nvim api behavior not supported' + endif + + let l:matches = get(g:nvim_highlight_matches, a:buffer, []) + let g:nvim_highlight_matches[a:buffer] = l:matches + + let l:new_match = { + \ 'group': a:hl_group, + \ 'priority': 10, + \ 'pos1': [a:line + 1, a:col_start + 1, a:col_end - a:col_start], + \} + + call add(l:matches, l:new_match) + " sort by line number to emulate getmatches faithfully + call sort(l:matches, {m1, m2 -> m1.pos1[0] - m2.pos1[0]}) + endfunction + " We don't care what the IDs are, just that we have some matches. " The IDs are generated. function! GetMatchesWithoutIDs() abort - let l:list = getmatches() + if g:has_nvim_highlight + return get(g:nvim_highlight_matches, bufnr(''), []) + else + let l:list = getmatches() - for l:item in l:list - call remove(l:item, 'id') - endfor + for l:item in l:list + call remove(l:item, 'id') + endfor - return l:list + return l:list + endif endfunction call ale#linter#Define('testft', { @@ -68,13 +112,17 @@ After: unlet! g:ale_run_synchronously_callbacks unlet! g:items unlet! b:ale_enabled + unlet! g:has_nvim_highlight + unlet! g:nvim_highlight_matches delfunction GenerateResults call ale#linter#Reset() call clearmatches() - sign unplace * + call ale#sign#Clear() highlight clear SomeOtherGroup + runtime autoload/ale/highlight.vim + Given testft(A Javscript file with warnings/errors): foo bar @@ -102,9 +150,12 @@ Execute(Highlights set by ALE should be removed when buffer cleanup is done): \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 3, 'col': 2}, \]) - AssertEqual - \ [{'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}], - \ GetMatchesWithoutIDs() + if !g:has_nvim_highlight + " This check doesn't work with the new API, for some reason. + AssertEqual + \ [{'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}], + \ GetMatchesWithoutIDs() + endif call ale#engine#Cleanup(bufnr('%')) @@ -145,31 +196,53 @@ Execute(Only ALE highlights should be restored when buffers are restored): call matchaddpos('SomeOtherGroup', [[1, 1, 1]]) " We should have both highlights. - AssertEqual - \ [ - \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, - \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, - \ ], - \ GetMatchesWithoutIDs() + if g:has_nvim_highlight + " When the newer NeoVim API is used, we don't have to worry about + " other highlights, namespacing is available. + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ ], + \ GetMatchesWithoutIDs() + else + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ ], + \ sort(GetMatchesWithoutIDs(), {m1, m2 -> m1.group < m2.group ? -1 : 1}) + endif call ale#highlight#BufferHidden(bufnr('%')) " We should remove our highlight, but not the other one. - AssertEqual - \ [ - \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]} - \ ], - \ GetMatchesWithoutIDs() + if g:has_nvim_highlight + AssertEqual [], GetMatchesWithoutIDs() + else + AssertEqual + \ [ + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]} + \ ], + \ GetMatchesWithoutIDs() + endif call ale#highlight#UpdateHighlights() " Our highlight should apper again. - AssertEqual - \ [ - \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, - \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, - \ ], - \ GetMatchesWithoutIDs() + if g:has_nvim_highlight + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ ], + \ GetMatchesWithoutIDs() + else + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 2, 1]}, + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ ], + \ sort(GetMatchesWithoutIDs(), {m1, m2 -> m1.group < m2.group ? -1 : 1}) + endif Execute(Higlight end columns should set an appropriate size): call ale#highlight#SetHighlights(bufnr('%'), [ @@ -216,32 +289,67 @@ Execute(Highlighting should support errors spanning many lines): call ale#highlight#SetHighlights(bufnr(''), g:items) - " We should set 2 highlights for the item, as we can only add 8 at a time. - AssertEqual - \ [ - \ { - \ 'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1073741824], - \ 'pos2': [2], 'pos3': [3], 'pos4': [4], 'pos5': [5], 'pos6': [6], - \ 'pos7': [7], 'pos8': [8], - \ }, - \ { - \ 'group': 'ALEError', 'priority': 10, - \ 'pos1': [9], 'pos2': [10, 1, 3] - \ }, - \ ], - \ GetMatchesWithoutIDs() + if g:has_nvim_highlight + " The newer NeoVim highlight API produces different output. + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [2, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [3, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [4, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [5, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [6, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [7, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [8, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [9, 1, 1073741824]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [10, 1, 3]}, + \ ], + \ GetMatchesWithoutIDs() + else + " We should set 2 highlights for the item, as we can only add 8 at a time. + AssertEqual + \ [ + \ { + \ 'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1073741824], + \ 'pos2': [2], 'pos3': [3], 'pos4': [4], 'pos5': [5], 'pos6': [6], + \ 'pos7': [7], 'pos8': [8], + \ }, + \ { + \ 'group': 'ALEError', 'priority': 10, + \ 'pos1': [9], 'pos2': [10, 1, 3] + \ }, + \ ], + \ GetMatchesWithoutIDs() + endif Execute(Highlights should always be cleared when the buffer highlight list is empty): - " Add our highlights and something else. - call matchaddpos('ALEError', [[1, 1, 1]]) - call matchaddpos('SomeOtherGroup', [[1, 1, 1]]) + if g:has_nvim_highlight + " The newer API uses namespacing. We'll emulate it here. + call ale#highlight#nvim_buf_add_highlight( + \ bufnr(''), + \ 1, + \ 'ALEError', + \ 0, + \ 0, + \ 1, + \) + + AssertEqual + \ [{'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}], + \ GetMatchesWithoutIDs() + else + " Add our highlights and something else. + call matchaddpos('ALEError', [[1, 1, 1]]) + call matchaddpos('SomeOtherGroup', [[1, 1, 1]]) + + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ ], + \ GetMatchesWithoutIDs() + endif - AssertEqual - \ [ - \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, - \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, - \ ], - \ GetMatchesWithoutIDs() " Set the List we use for holding highlights for buffers. let b:ale_highlight_items = [] @@ -251,9 +359,28 @@ Execute(Highlights should always be cleared when the buffer highlight list is em call ale#highlight#UpdateHighlights() " Check that we remove our highlights. + if g:has_nvim_highlight + AssertEqual [], GetMatchesWithoutIDs() + else + AssertEqual + \ [{'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}], + \ GetMatchesWithoutIDs() + endif + +Execute(Highlights should be hidden when excluded): + let b:ale_exclude_highlights = ['ig.*ore', 'nope'] + + call ale#highlight#SetHighlights(bufnr('%'), [ + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 1, 'col': 1, 'text': 'hello'}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 2, 'col': 1, 'text': 'ignore'}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 3, 'col': 1, 'text': 'nope'}, + \ {'bufnr': bufnr('%'), 'type': 'E', 'lnum': 4, 'col': 1, 'text': 'world'}, + \]) + AssertEqual \ [ - \ {'group': 'SomeOtherGroup', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEError', 'priority': 10, 'pos1': [4, 1, 1]}, \ ], \ GetMatchesWithoutIDs() @@ -291,16 +418,30 @@ Execute(Line highlights should be set when signs are disabled): \ {'bufnr': bufnr(''), 'type': 'I', 'lnum': 3, 'col': 1}, \]) - AssertEqual - \ [ - \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, - \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [2, 1, 1]}, - \ {'group': 'ALEInfo', 'priority': 10, 'pos1': [3, 1, 1]}, - \ {'group': 'aleerrorline', 'priority': 10, 'pos1': [1]}, - \ {'group': 'ALEWarningLine', 'priority': 10, 'pos1': [2]}, - \ {'group': 'ALEInfoLine', 'priority': 10, 'pos1': [3]}, - \ ], - \ GetMatchesWithoutIDs() + if g:has_nvim_highlight + " The output is different with the newer NeoVIM highlight API. + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEErrorLine', 'priority': 10, 'pos1': [1, 1, 1073741824]}, + \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [2, 1, 1]}, + \ {'group': 'ALEWarningLine', 'priority': 10, 'pos1': [2, 1, 1073741824]}, + \ {'group': 'ALEInfo', 'priority': 10, 'pos1': [3, 1, 1]}, + \ {'group': 'ALEInfoLine', 'priority': 10, 'pos1': [3, 1, 1073741824]} + \ ], + \ GetMatchesWithoutIDs() + else + AssertEqual + \ [ + \ {'group': 'ALEError', 'priority': 10, 'pos1': [1, 1, 1]}, + \ {'group': 'ALEWarning', 'priority': 10, 'pos1': [2, 1, 1]}, + \ {'group': 'ALEInfo', 'priority': 10, 'pos1': [3, 1, 1]}, + \ {'group': 'aleerrorline', 'priority': 10, 'pos1': [1]}, + \ {'group': 'ALEWarningLine', 'priority': 10, 'pos1': [2]}, + \ {'group': 'ALEInfoLine', 'priority': 10, 'pos1': [3]}, + \ ], + \ GetMatchesWithoutIDs() + endif " All of the highlights should be removed. call ale#highlight#RemoveHighlights() diff --git a/test/test_nimlsp_project_root.vader b/test/test_nimlsp_project_root.vader new file mode 100644 index 00000000..e7027575 --- /dev/null +++ b/test/test_nimlsp_project_root.vader @@ -0,0 +1,19 @@ +Before: + runtime ale_linters/nim/nimlsp.vim + call ale#test#SetDirectory('/testplugin/test') + +After: + if isdirectory(g:dir . '/.git') + call delete(g:dir . '/.git', 'd') + endif + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + +Execute(Detect root of nim project with .git/ correctly): + call ale#test#SetFilename('nim-test-files/with-git/src/source.nim') + call mkdir(g:dir . '/.git') + AssertEqual + \ ale#path#Simplify(g:dir), + \ ale_linters#nim#nimlsp#GetProjectRoot(bufnr('')) diff --git a/test/test_organize_imports.vader b/test/test_organize_imports.vader new file mode 100644 index 00000000..c51ff1c0 --- /dev/null +++ b/test/test_organize_imports.vader @@ -0,0 +1,172 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:old_filename = expand('%:p') + let g:Callback = '' + let g:expr_list = [] + let g:message_list = [] + let g:handle_code_action_called = 0 + let g:code_actions = [] + let g:options = {} + let g:capability_checked = '' + let g:conn_id = v:null + let g:InitCallback = v:null + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/organize_imports.vim + runtime autoload/ale/code_action.vim + + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) + call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) + + if a:linter.lsp is# 'tsserver' + call ale#lsp#MarkConnectionAsTsserver(g:conn_id) + endif + + let l:details = { + \ 'command': 'foobar', + \ 'buffer': a:buffer, + \ 'connection_id': g:conn_id, + \ 'project_root': '/foo/bar', + \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + 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 + +After: + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + call ale#references#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + unlet! g:InitCallback + unlet! g:old_filename + unlet! g:conn_id + unlet! g:Callback + unlet! g:message_list + unlet! g:expr_list + unlet! b:ale_linters + unlet! g:options + unlet! g:code_actions + unlet! g:handle_code_action_called + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/organize_imports.vim + runtime autoload/ale/code_action.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#organize_imports#HandleTSServerResponse(1, {'command': 'foo'}) + AssertEqual g:handle_code_action_called, 0 + +Execute(Failed organizeImports responses should be handled correctly): + call ale#organize_imports#HandleTSServerResponse( + \ 1, + \ {'command': 'organizeImports', 'request_seq': 3} + \) + AssertEqual g:handle_code_action_called, 0 + +Execute(Code actions from tsserver should be handled): + call ale#organize_imports#HandleTSServerResponse(1, { + \ 'command': 'organizeImports', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': [], + \}) + AssertEqual g:handle_code_action_called, 1 + AssertEqual [{ + \ 'description': 'Organize Imports', + \ 'changes': [], + \}], g:code_actions + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(tsserver organize imports requests should be sent): + call ale#linter#Reset() + runtime ale_linters/typescript/tsserver.vim + + ALEOrganizeImports + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual + \ 'function(''ale#organize_imports#HandleTSServerResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@organizeImports', { + \ 'scope': { + \ 'type': 'file', + \ 'args': { + \ 'file': expand('%:p'), + \ }, + \ }, + \ }] + \ ], + \ g:message_list + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Should result in error message): + call ale#linter#Reset() + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + + ALEOrganizeImports + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual [ + \ 'echom ''OrganizeImports currently only works with tsserver''', + \], g:expr_list diff --git a/test/test_rename.vader b/test/test_rename.vader new file mode 100644 index 00000000..3600df59 --- /dev/null +++ b/test/test_rename.vader @@ -0,0 +1,395 @@ +Before: + call ale#test#SetDirectory('/testplugin/test') + call ale#test#SetFilename('dummy.txt') + + let g:old_filename = expand('%:p') + let g:Callback = '' + let g:expr_list = [] + let g:message_list = [] + let g:handle_code_action_called = 0 + let g:code_actions = [] + let g:options = {} + let g:capability_checked = '' + let g:conn_id = v:null + let g:InitCallback = v:null + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/rename.vim + runtime autoload/ale/code_action.vim + + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {}) + call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer) + + if a:linter.lsp is# 'tsserver' + call ale#lsp#MarkConnectionAsTsserver(g:conn_id) + endif + + let l:details = { + \ 'command': 'foobar', + \ 'buffer': a:buffer, + \ 'connection_id': g:conn_id, + \ 'project_root': '/foo/bar', + \} + + let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)} + endfunction + + function! ale#lsp#HasCapability(conn_id, capability) abort + let g:capability_checked = a:capability + + return 1 + endfunction + + function! ale#lsp#RegisterCallback(conn_id, callback) abort + let g:Callback = a:callback + endfunction + + function! ale#lsp#Send(conn_id, message) abort + call add(g:message_list, a:message) + + return 42 + endfunction + + function! ale#util#Execute(expr) abort + call add(g:expr_list, a:expr) + endfunction + + 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 + + function! ale#util#Input(message, value) abort + return 'a-new-name' + endfunction + + call ale#rename#SetMap({ + \ 3: { + \ 'old_name': 'oldName', + \ 'new_name': 'aNewName', + \ }, + \}) + +After: + if g:conn_id isnot v:null + call ale#lsp#RemoveConnectionWithID(g:conn_id) + endif + + call ale#rename#SetMap({}) + call ale#test#RestoreDirectory() + call ale#linter#Reset() + + unlet! g:capability_checked + unlet! g:InitCallback + unlet! g:old_filename + unlet! g:conn_id + unlet! g:Callback + unlet! g:message_list + unlet! g:expr_list + unlet! b:ale_linters + unlet! g:options + unlet! g:code_actions + unlet! g:handle_code_action_called + + runtime autoload/ale/lsp_linter.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/util.vim + runtime autoload/ale/rename.vim + runtime autoload/ale/code_action.vim + +Execute(Other messages for the tsserver handler should be ignored): + call ale#rename#HandleTSServerResponse(1, {'command': 'foo'}) + AssertEqual g:handle_code_action_called, 0 + +Execute(Failed rename responses should be handled correctly): + call ale#rename#SetMap({3: {'old_name': 'oldName', 'new_name': 'a-test'}}) + call ale#rename#HandleTSServerResponse( + \ 1, + \ {'command': 'rename', 'request_seq': 3} + \) + AssertEqual g:handle_code_action_called, 0 + +Given typescript(Some typescript file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Code actions from tsserver should be handled): + call ale#rename#HandleTSServerResponse(1, { + \ 'command': 'rename', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'locs': [ + \ { + \ 'file': '/foo/bar/file1.ts', + \ 'locs': [ + \ { + \ 'start': { + \ 'line': 1, + \ 'offset': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'offset': 4, + \ }, + \ }, + \ ], + \ }, + \ { + \ 'file': '/foo/bar/file2.ts', + \ 'locs': [ + \ { + \ 'start': { + \ 'line': 10, + \ 'offset': 20, + \ }, + \ 'end': { + \ 'line': 30, + \ 'offset': 40, + \ }, + \ }, + \ ], + \ }, + \ ] + \ }, + \}) + + AssertEqual + \ [ + \ { + \ 'description': 'rename', + \ 'changes': [ + \ { + \ 'fileName': '/foo/bar/file1.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 1, + \ 'offset': 2, + \ }, + \ 'end': { + \ 'line': 3, + \ 'offset': 4, + \ }, + \ 'newText': 'aNewName', + \ }, + \ ], + \ }, + \ { + \ 'fileName': '/foo/bar/file2.ts', + \ 'textChanges': [ + \ { + \ 'start': { + \ 'line': 10, + \ 'offset': 20, + \ }, + \ 'end': { + \ 'line': 30, + \ 'offset': 40, + \ }, + \ 'newText': 'aNewName', + \ }, + \ ], + \ }, + \ ], + \ } + \ ], + \ g:code_actions + +Execute(HandleTSServerResponse does nothing when no data in rename_map): + call ale#rename#HandleTSServerResponse(1, { + \ 'command': 'rename', + \ 'request_seq': -9, + \ 'success': v:true, + \ 'body': {} + \}) + + AssertEqual g:handle_code_action_called, 0 + +Execute(Prints a tsserver error message when unsuccessful): + call ale#rename#HandleTSServerResponse(1, { + \ 'command': 'rename', + \ 'request_seq': 3, + \ 'success': v:false, + \ 'message': 'This symbol cannot be renamed', + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''Error renaming "oldName" to: "aNewName". ' . + \ 'Reason: This symbol cannot be renamed'''], g:expr_list + +Execute(Does nothing when no changes): + call ale#rename#HandleTSServerResponse(1, { + \ 'command': 'rename', + \ 'request_seq': 3, + \ 'success': v:true, + \ 'body': { + \ 'locs': [] + \ } + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''Error renaming "oldName" to: "aNewName"'''], g:expr_list + +Execute(tsserver rename requests should be sent): + call ale#rename#SetMap({}) + call ale#linter#Reset() + + runtime ale_linters/typescript/tsserver.vim + call setpos('.', [bufnr(''), 2, 5, 0]) + + ALERename + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'rename', g:capability_checked + AssertEqual + \ 'function(''ale#rename#HandleTSServerResponse'')', + \ string(g:Callback) + AssertEqual + \ [ + \ ale#lsp#tsserver_message#Change(bufnr('')), + \ [0, 'ts@rename', { + \ 'file': expand('%:p'), + \ 'line': 2, + \ 'offset': 5, + \ 'arguments': { + \ 'findInComments': g:ale_rename_tsserver_find_in_comments, + \ 'findInStrings': g:ale_rename_tsserver_find_in_strings, + \ }, + \ }] + \ ], + \ g:message_list + AssertEqual {'42': {'old_name': 'somelongerline', 'new_name': 'a-new-name'}}, + \ ale#rename#GetMap() + +Given python(Some Python file): + foo + somelongerline + bazxyzxyzxyz + +Execute(Code actions from LSP should be handled): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': { + \ 'changes': { + \ 'file:///foo/bar/file1.ts': [ + \ { + \ '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, + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No rename result received from server'''], g:expr_list + +Execute(LSP should perform no action when no changes): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': {}, + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No changes received from server'''], g:expr_list + +Execute(LSP should perform no action when changes is empty): + call ale#rename#HandleLSPResponse(1, { + \ 'id': 3, + \ 'result': { + \ 'changes': [], + \ }, + \}) + + AssertEqual g:handle_code_action_called, 0 + AssertEqual ['echom ''No changes received from server'''], g:expr_list + +Execute(LSP rename requests should be sent): + call ale#rename#SetMap({}) + runtime ale_linters/python/pyls.vim + let b:ale_linters = ['pyls'] + call setpos('.', [bufnr(''), 1, 5, 0]) + + ALERename + + " We shouldn't register the callback yet. + AssertEqual '''''', string(g:Callback) + + AssertEqual type(function('type')), type(g:InitCallback) + call g:InitCallback() + + AssertEqual 'rename', g:capability_checked + AssertEqual + \ 'function(''ale#rename#HandleLSPResponse'')', + \ string(g:Callback) + + AssertEqual + \ [ + \ [1, 'textDocument/didChange', { + \ 'textDocument': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'version': g:ale_lsp_next_version_id - 1, + \ }, + \ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}] + \ }], + \ [0, 'textDocument/rename', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'line': 0, 'character': 2}, + \ 'newName': 'a-new-name', + \ }], + \ ], + \ g:message_list + + AssertEqual {'42': {'old_name': 'foo', 'new_name': 'a-new-name'}}, + \ ale#rename#GetMap() diff --git a/test/test_results_not_cleared_when_opening_loclist.vader b/test/test_results_not_cleared_when_opening_loclist.vader index 4a53d356..5621eb35 100644 --- a/test/test_results_not_cleared_when_opening_loclist.vader +++ b/test/test_results_not_cleared_when_opening_loclist.vader @@ -8,7 +8,7 @@ After: call setloclist(0, []) call clearmatches() - sign unplace * + call ale#sign#Clear() Given foobar (Some file): abc diff --git a/test/test_setting_problems_found_in_previous_buffers.vader b/test/test_setting_problems_found_in_previous_buffers.vader index 36eeb4ca..a5c8e0d3 100644 --- a/test/test_setting_problems_found_in_previous_buffers.vader +++ b/test/test_setting_problems_found_in_previous_buffers.vader @@ -38,7 +38,7 @@ After: " Items and markers, etc. call setloclist(0, []) call clearmatches() - sign unplace * + call ale#sign#Clear() Given foobar(A file with some lines): foo |