diff --git a/ale_linters/python/pylint.vim b/ale_linters/python/pylint.vim index b16d5355..44eea246 100644 --- a/ale_linters/python/pylint.vim +++ b/ale_linters/python/pylint.vim @@ -17,7 +17,7 @@ function! ale_linters#python#pylint#GetExecutable(buffer) abort return ale#python#FindExecutable(a:buffer, 'python_pylint', ['pylint']) endfunction -function! ale_linters#python#pylint#GetCommand(buffer) abort +function! ale_linters#python#pylint#GetCommand(buffer, version) abort let l:cd_string = '' if ale#Var(a:buffer, 'python_pylint_change_directory') @@ -38,17 +38,23 @@ function! ale_linters#python#pylint#GetCommand(buffer) abort return l:cd_string \ . ale#Escape(l:executable) . l:exec_args - \ . ' ' . ale#Var(a:buffer, 'python_pylint_options') + \ . ale#Pad(ale#Var(a:buffer, 'python_pylint_options')) \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n' + \ . (ale#semver#GTE(a:version, [2, 4, 0]) ? ' --from-stdin' : '') \ . ' %s' endfunction function! ale_linters#python#pylint#Handle(buffer, lines) abort + let l:output = ale#python#HandleTraceback(a:lines, 10) + + if !empty(l:output) + return l:output + endif + " Matches patterns like the following: " " test.py:4:4: W0101 (unreachable) Unreachable code let l:pattern = '\v^[a-zA-Z]?:?[^:]+:(\d+):(\d+): ([[:alnum:]]+) \(([^(]*)\) (.*)$' - let l:output = [] for l:match in ale#util#GetMatches(a:lines, l:pattern) "let l:failed = append(0, l:match) @@ -71,13 +77,19 @@ function! ale_linters#python#pylint#Handle(buffer, lines) abort let l:code_out = l:match[4] endif - call add(l:output, { + let l:item = { \ 'lnum': l:match[1] + 0, \ 'col': l:match[2] + 1, \ 'text': l:match[5], \ 'code': l:code_out, - \ 'type': l:code[:0] is# 'E' ? 'E' : 'W', - \}) + \ 'type': 'W', + \} + + if l:code[:0] is# 'E' + let l:item.type = 'E' + endif + + call add(l:output, l:item) endfor return l:output @@ -86,7 +98,17 @@ endfunction call ale#linter#Define('python', { \ 'name': 'pylint', \ 'executable': function('ale_linters#python#pylint#GetExecutable'), -\ 'command': function('ale_linters#python#pylint#GetCommand'), +\ 'lint_file': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'python_pylint_executable'), +\ '%e --version', +\ {buffer, version -> !ale#semver#GTE(version, [2, 4, 0])}, +\ )}, +\ 'command': {buffer -> ale#semver#RunWithVersionCheck( +\ buffer, +\ ale#Var(buffer, 'python_pylint_executable'), +\ '%e --version', +\ function('ale_linters#python#pylint#GetCommand'), +\ )}, \ 'callback': 'ale_linters#python#pylint#Handle', -\ 'lint_file': 1, \}) diff --git a/ale_linters/vim/vint.vim b/ale_linters/vim/vint.vim index c7461bc8..f7054ffb 100644 --- a/ale_linters/vim/vint.vim +++ b/ale_linters/vim/vint.vim @@ -13,12 +13,17 @@ function! ale_linters#vim#vint#GetCommand(buffer, version) abort let l:warning_flag = ale#Var(a:buffer, 'vim_vint_show_style_issues') ? '-s' : '-w' + " Use the --stdin-display-name argument if supported, temp file otherwise. + let l:stdin_or_temp = ale#semver#GTE(a:version, [0, 4, 0]) + \ ? ' --stdin-display-name %s -' + \ : ' %t' + return '%e' \ . ' ' . l:warning_flag \ . (l:can_use_no_color_flag ? ' --no-color' : '') \ . s:enable_neovim \ . ' ' . s:format - \ . ' %t' + \ . l:stdin_or_temp endfunction let s:word_regex_list = [ diff --git a/autoload/ale.vim b/autoload/ale.vim index 5ec22f57..3f59a6a4 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -100,13 +100,7 @@ function! s:Lint(buffer, should_lint_file, timer_id) abort " Use the filetype from the buffer let l:filetype = getbufvar(a:buffer, '&filetype') let l:linters = ale#linter#Get(l:filetype) - - " Apply ignore lists for linters only if needed. - let l:ignore_config = ale#Var(a:buffer, 'linters_ignore') - let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') - let l:linters = !empty(l:ignore_config) || l:disable_lsp - \ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config, l:disable_lsp) - \ : l:linters + let l:linters = ale#linter#RemoveIgnored(a:buffer, l:filetype, l:linters) " Tell other sources that they can start checking the buffer now. let g:ale_want_results_buffer = a:buffer @@ -163,7 +157,7 @@ function! ale#Queue(delay, ...) abort endif endfunction -let s:current_ale_version = [2, 7, 0] +let s:current_ale_version = [3, 0, 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/completion.vim b/autoload/ale/completion.vim index bdade95c..ecd93600 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -503,17 +503,19 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort \ || g:ale_completion_autoimport, \ 'info': join(l:documentationParts, ''), \} + " This flag is used to tell if this completion came from ALE or not. + let l:user_data = {'_ale_completion_item': 1} if has_key(l:suggestion, 'codeActions') - let l:result.user_data = json_encode({ - \ 'codeActions': l:suggestion.codeActions, - \ }) + let l:user_data.code_actions = l:suggestion.codeActions endif + let l:result.user_data = json_encode(l:user_data) + " Include this item if we'll accept any items, " or if we only want items with additional edits, and this has them. if !get(l:info, 'additional_edits_only', 0) - \|| has_key(l:result, 'user_data') + \|| has_key(l:user_data, 'code_actions') call add(l:results, l:result) endif endfor @@ -534,6 +536,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort \ 'icase': 1, \ 'menu': '', \ 'info': '', + \ 'user_data': json_encode({'_ale_completion_item': 1}), \}) endfor endif @@ -610,6 +613,8 @@ function! ale#completion#ParseLSPCompletions(response) abort \ 'menu': get(l:item, 'detail', ''), \ 'info': (type(l:doc) is v:t_string ? l:doc : ''), \} + " This flag is used to tell if this completion came from ALE or not. + let l:user_data = {'_ale_completion_item': 1} if has_key(l:item, 'additionalTextEdits') let l:text_changes = [] @@ -629,24 +634,24 @@ function! ale#completion#ParseLSPCompletions(response) abort endfor if !empty(l:text_changes) - let l:result.user_data = json_encode({ - \ 'codeActions': [{ - \ 'description': 'completion', - \ 'changes': [ - \ { - \ 'fileName': expand('#' . l:buffer . ':p'), - \ 'textChanges': l:text_changes, - \ } - \ ], - \ }], - \}) + let l:user_data.code_actions = [{ + \ 'description': 'completion', + \ 'changes': [ + \ { + \ 'fileName': expand('#' . l:buffer . ':p'), + \ 'textChanges': l:text_changes, + \ }, + \ ], + \}] endif endif + let l:result.user_data = json_encode(l:user_data) + " Include this item if we'll accept any items, " or if we only want items with additional edits, and this has them. if !get(l:info, 'additional_edits_only', 0) - \|| has_key(l:result, 'user_data') + \|| has_key(l:user_data, 'code_actions') call add(l:results, l:result) endif endfor @@ -983,30 +988,29 @@ function! ale#completion#Queue() abort endfunction function! ale#completion#HandleUserData(completed_item) abort - let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - - if l:source isnot# 'ale-automatic' - \&& l:source isnot# 'ale-manual' - \&& l:source isnot# 'ale-callback' - \&& l:source isnot# 'ale-import' - 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) + let l:user_data = !empty(l:user_data_json) + \ ? json_decode(l:user_data_json) + \ : v:null if type(l:user_data) isnot v:t_dict + \|| get(l:user_data, '_ale_completion_item', 0) isnot 1 return endif - for l:code_action in get(l:user_data, 'codeActions', []) - call ale#code_action#HandleCodeAction(l:code_action, v:false) - endfor + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if l:source is# 'ale-automatic' + \|| l:source is# 'ale-manual' + \|| l:source is# 'ale-callback' + \|| l:source is# 'ale-import' + \|| l:source is# 'ale-omnifunc' + for l:code_action in get(l:user_data, 'code_actions', []) + call ale#code_action#HandleCodeAction(l:code_action, v:false) + endfor + endif + + silent doautocmd User ALECompletePost endfunction function! ale#completion#Done() abort diff --git a/autoload/ale/debugging.vim b/autoload/ale/debugging.vim index 4e134f8c..5e6d5906 100644 --- a/autoload/ale/debugging.vim +++ b/autoload/ale/debugging.vim @@ -8,6 +8,7 @@ let s:global_variable_list = [ \ 'ale_completion_delay', \ 'ale_completion_enabled', \ 'ale_completion_max_suggestions', +\ 'ale_disable_lsp', \ 'ale_echo_cursor', \ 'ale_echo_msg_error_str', \ 'ale_echo_msg_format', @@ -28,6 +29,7 @@ let s:global_variable_list = [ \ 'ale_linter_aliases', \ 'ale_linters', \ 'ale_linters_explicit', +\ 'ale_linters_ignore', \ 'ale_list_vertical', \ 'ale_list_window_size', \ 'ale_loclist_msg_format', @@ -196,6 +198,7 @@ function! s:EchoLSPErrorMessages(all_linter_names) abort endfunction function! ale#debugging#Info() abort + let l:buffer = bufnr('') let l:filetype = &filetype " We get the list of enabled linters for free by the above function. @@ -222,10 +225,20 @@ function! ale#debugging#Info() abort let l:fixers = uniq(sort(l:fixers[0] + l:fixers[1])) let l:fixers_string = join(map(copy(l:fixers), '"\n " . v:val'), '') + let l:non_ignored_names = map( + \ copy(ale#linter#RemoveIgnored(l:buffer, l:filetype, l:enabled_linters)), + \ 'v:val[''name'']', + \) + let l:ignored_names = filter( + \ copy(l:enabled_names), + \ 'index(l:non_ignored_names, v:val) < 0' + \) + call s:Echo(' Current Filetype: ' . l:filetype) call s:Echo('Available Linters: ' . string(l:all_names)) call s:EchoLinterAliases(l:all_linters) call s:Echo(' Enabled Linters: ' . string(l:enabled_names)) + call s:Echo(' Ignored Linters: ' . string(l:ignored_names)) call s:Echo(' Suggested Fixers: ' . l:fixers_string) call s:Echo(' Linter Variables:') call s:Echo('') diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index ae0354b8..3cafa25c 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -444,7 +444,7 @@ function! s:RunJob(command, options) abort return 1 endfunction -function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort +function! s:StopCurrentJobs(buffer, clear_lint_file_jobs, linter_slots) abort let l:info = get(g:ale_buffer_info, a:buffer, {}) call ale#command#StopJobs(a:buffer, 'linter') @@ -453,11 +453,25 @@ function! s:StopCurrentJobs(buffer, clear_lint_file_jobs) abort call ale#command#StopJobs(a:buffer, 'file_linter') let l:info.active_linter_list = [] else + let l:lint_file_map = {} + + " Use a previously computed map of `lint_file` values to find + " linters that are used for linting files. + for [l:lint_file, l:linter] in a:linter_slots + if l:lint_file is 1 + let l:lint_file_map[l:linter.name] = 1 + endif + endfor + " Keep jobs for linting files when we're only linting buffers. - call filter(l:info.active_linter_list, 'get(v:val, ''lint_file'')') + call filter(l:info.active_linter_list, 'get(l:lint_file_map, v:val.name)') endif endfunction +function! ale#engine#Stop(buffer) abort + call s:StopCurrentJobs(a:buffer, 1, []) +endfunction + function! s:RemoveProblemsForDisabledLinters(buffer, linters) abort " Figure out which linters are still enabled, and remove " problems for linters which are no longer enabled. @@ -558,6 +572,22 @@ function! s:RunLinter(buffer, linter, lint_file) abort return 0 endfunction +function! s:GetLintFileSlots(buffer, linters) abort + let l:linter_slots = [] + + for l:linter in a:linters + let l:LintFile = l:linter.lint_file + + if type(l:LintFile) is v:t_func + let l:LintFile = l:LintFile(a:buffer) + endif + + call add(l:linter_slots, [l:LintFile, l:linter]) + endfor + + return l:linter_slots +endfunction + function! s:GetLintFileValues(slots, Callback) abort let l:deferred_list = [] let l:new_slots = [] @@ -591,12 +621,18 @@ endfunction function! s:RunLinters( \ buffer, +\ linters, \ slots, \ should_lint_file, \ new_buffer, -\ can_clear_results \) abort - let l:can_clear_results = a:can_clear_results + call s:StopCurrentJobs(a:buffer, a:should_lint_file, a:slots) + call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters) + + " We can only clear the results if we aren't checking the buffer. + let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer) + + silent doautocmd User ALELintPre for [l:lint_file, l:linter] in a:slots " Only run lint_file linters if we should. @@ -627,36 +663,19 @@ endfunction function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort " Initialise the buffer information if needed. let l:new_buffer = ale#engine#InitBufferInfo(a:buffer) - call s:StopCurrentJobs(a:buffer, a:should_lint_file) - call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters) - " We can only clear the results if we aren't checking the buffer. - let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer) - - silent doautocmd User ALELintPre - - " Handle `lint_file` callbacks first. - let l:linter_slots = [] - - for l:linter in a:linters - let l:LintFile = l:linter.lint_file - - if type(l:LintFile) is v:t_func - let l:LintFile = l:LintFile(a:buffer) - endif - - call add(l:linter_slots, [l:LintFile, l:linter]) - endfor - - call s:GetLintFileValues(l:linter_slots, { - \ new_slots -> s:RunLinters( - \ a:buffer, - \ new_slots, - \ a:should_lint_file, - \ l:new_buffer, - \ l:can_clear_results, - \ ) - \}) + call s:GetLintFileValues( + \ s:GetLintFileSlots(a:buffer, a:linters), + \ { + \ slots -> s:RunLinters( + \ a:buffer, + \ a:linters, + \ slots, + \ a:should_lint_file, + \ l:new_buffer, + \ ) + \ } + \) endfunction " Clean up a buffer. diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim index 8b841b13..c3338fc5 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -75,7 +75,10 @@ function! ale#fix#ApplyFixes(buffer, output) abort if l:data.lines_before != l:lines call remove(g:ale_fix_buffer_data, a:buffer) - execute 'echoerr ''The file was changed before fixing finished''' + + if !l:data.ignore_file_changed_errors + execute 'echoerr ''The file was changed before fixing finished''' + endif return endif @@ -329,6 +332,7 @@ function! ale#fix#InitBufferData(buffer, fixing_flag) abort \ 'lines_before': getbufline(a:buffer, 1, '$'), \ 'done': 0, \ 'should_save': a:fixing_flag is# 'save_file', + \ 'ignore_file_changed_errors': a:fixing_flag is# '!', \ 'temporary_directory_list': [], \} endfunction @@ -337,19 +341,23 @@ endfunction " " Returns 0 if no fixes can be applied, and 1 if fixing can be done. function! ale#fix#Fix(buffer, fixing_flag, ...) abort - if a:fixing_flag isnot# '' && a:fixing_flag isnot# 'save_file' - throw "fixing_flag must be either '' or 'save_file'" + if a:fixing_flag isnot# '' + \&& a:fixing_flag isnot# '!' + \&& a:fixing_flag isnot# 'save_file' + throw "fixing_flag must be '', '!', or 'save_file'" endif try let l:callback_list = s:GetCallbacks(a:buffer, a:fixing_flag, a:000) catch /E700\|BADNAME/ - let l:function_name = join(split(split(v:exception, ':')[3])) - let l:echo_message = printf( - \ 'There is no fixer named `%s`. Check :ALEFixSuggest', - \ l:function_name, - \) - execute 'echom l:echo_message' + if a:fixing_flag isnot# '!' + let l:function_name = join(split(split(v:exception, ':')[3])) + let l:echo_message = printf( + \ 'There is no fixer named `%s`. Check :ALEFixSuggest', + \ l:function_name, + \) + execute 'echom l:echo_message' + endif return 0 endtry diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index b483fc19..645c25f9 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -394,6 +394,16 @@ function! ale#linter#Get(original_filetypes) abort return reverse(l:combined_linters) endfunction +function! ale#linter#RemoveIgnored(buffer, filetype, linters) abort + " Apply ignore lists for linters only if needed. + let l:ignore_config = ale#Var(a:buffer, 'linters_ignore') + let l:disable_lsp = ale#Var(a:buffer, 'disable_lsp') + + return !empty(l:ignore_config) || l:disable_lsp + \ ? ale#engine#ignore#Exclude(a:filetype, a:linters, l:ignore_config, l:disable_lsp) + \ : a:linters +endfunction + " Given a buffer and linter, get the executable String for the linter. function! ale#linter#GetExecutable(buffer, linter) abort let l:Executable = a:linter.executable diff --git a/doc/ale.txt b/doc/ale.txt index 2192755b..0337cdbd 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -147,6 +147,8 @@ ALE offers several options for controlling which linters are run. * Disabling only a subset of linters. - |g:ale_linters_ignore| * Disabling LSP linters and `tsserver`. - |g:ale_disable_lsp| +You can stop ALE any currently running linters with the |ALELintStop| command. +Any existing problems will be kept. ------------------------------------------------------------------------------- 3.1 Linting On Other Machines *ale-lint-other-machines* @@ -177,8 +179,11 @@ script like so. > #!/usr/bin/env bash - exec docker run --rm -v "$(pwd):/data" cytopia/pylint "$@" + exec docker run -i --rm -v "$(pwd):/data" cytopia/pylint "$@" < + +You will run to run Docker commands with `-i` in order to read from stdin. + With the above script in mind, you might configure ALE to lint your Python project with `pylint` by providing the path to the script to execute, and mappings which describe how to between the two file systems in your @@ -532,6 +537,9 @@ potential completion result which includes additional text to insert into the current buffer, which ALE will assume is code for an import line. This command can be useful when your code already contains something you need to import. +You can execute other commands whenever ALE inserts some completion text with +the |ALECompletePost| event. + When working with TypeScript files, ALE can remove warnings from your completions by setting the |g:ale_completion_tsserver_remove_warnings| variable to 1. @@ -2980,6 +2988,10 @@ ALEFix *ALEFix* Fix problems with the current buffer. See |ale-fix| for more information. + If the command is run with a bang (`:ALEFix!`), all warnings will be + suppressed, including warnings about no fixers being defined, and warnings + about not being able to apply fixes to a file because it has been changed. + A plug mapping `(ale_fix)` is defined for this command. @@ -3119,6 +3131,13 @@ ALELint *ALELint* A plug mapping `(ale_lint)` is defined for this command. +ALELintStop *ALELintStop* + + Stop any currently running jobs for checking the current buffer. + + Any problems from previous linter results will continue to be shown. + + ALEPrevious *ALEPrevious* ALEPreviousWrap *ALEPreviousWrap* ALENext *ALENext* @@ -3984,6 +4003,23 @@ g:ale_want_results_buffer *g:ale_want_results_buffer* figure out which buffer other sources should lint. +ALECompletePost *ALECompletePost-autocmd* + *ALECompletePost* + + This |User| autocmd is triggered after ALE inserts an item on + |CompleteDone|. This event can be used to run commands after a buffer + is changed by ALE as the result of completion. For example, |ALEFix| can + be configured to run automatically when completion is done: > + + augroup FixAfterComplete + autocmd! + " Run ALEFix when completion items are added. + autocmd User ALECompletePost ALEFix! + " If ALE starts fixing a file, stop linters running for now. + autocmd User ALEFixPre ALELintStop + augroup END +< + ALELintPre *ALELintPre-autocmd* *ALELintPre* ALELintPost *ALELintPost-autocmd* diff --git a/plugin/ale.vim b/plugin/ale.vim index 9cebb71f..18d867ee 100644 --- a/plugin/ale.vim +++ b/plugin/ale.vim @@ -195,6 +195,8 @@ command! -bar ALEStopAllLSPs :call ale#lsp#reset#StopAllLSPs() " A command for linting manually. command! -bar ALELint :call ale#Queue(0, 'lint_file') +" Stop current jobs when linting. +command! -bar ALELintStop :call ale#engine#Stop(bufnr('')) " Define a command to get information about current filetype. command! -bar ALEInfo :call ale#debugging#Info() @@ -204,7 +206,7 @@ command! -bar ALEInfoToClipboard :call ale#debugging#InfoToClipboard() command! -bar -nargs=1 ALEInfoToFile :call ale#debugging#InfoToFile() " Fix problems in files. -command! -bar -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEFix :call ale#fix#Fix(bufnr(''), '', ) +command! -bar -bang -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEFix :call ale#fix#Fix(bufnr(''), '', ) " Suggest registered functions to use for fixing problems. command! -bar ALEFixSuggest :call ale#fix#registry#Suggest(&filetype) diff --git a/rplugin/python3/deoplete/sources/ale.py b/rplugin/python3/deoplete/sources/ale.py index ae1f4039..82d9bbf2 100644 --- a/rplugin/python3/deoplete/sources/ale.py +++ b/rplugin/python3/deoplete/sources/ale.py @@ -49,12 +49,13 @@ class Source(Base): if event == 'Async': result = self.vim.call('ale#completion#GetCompletionResult') + return result or [] if context.get('is_refresh'): self.vim.command( - "call ale#completion#GetCompletions('ale-callback', " + \ - "{'callback': {completions -> deoplete#auto_complete() }})" + "call ale#completion#GetCompletions('ale-callback', " + + "{'callback': {completions -> deoplete#auto_complete() }})" ) return [] diff --git a/test/command_callback/test_pylint_command_callback.vader b/test/command_callback/test_pylint_command_callback.vader index 755dd292..15f004b6 100644 --- a/test/command_callback/test_pylint_command_callback.vader +++ b/test/command_callback/test_pylint_command_callback.vader @@ -8,6 +8,8 @@ Before: let b:bin_dir = has('win32') ? 'Scripts' : 'bin' let b:command_tail = ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + GivenCommandOutput ['pylint 2.3.0'] + After: unlet! b:bin_dir unlet! b:executable @@ -17,26 +19,33 @@ After: Execute(The pylint callbacks should return the correct default values): AssertLinter 'pylint', - \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) - \ . ale#Escape('pylint') . ' ' . b:command_tail + \ ale#path#CdString(expand('%:p:h')) + \ . ale#Escape('pylint') . b:command_tail + +Execute(Pylint should run with the --from-stdin in new enough versions): + GivenCommandOutput ['pylint 2.4.0'] + + AssertLinter 'pylint', + \ ale#path#CdString(expand('%:p:h')) + \ . ale#Escape('pylint') . b:command_tail[:-3] . '--from-stdin %s' Execute(The option for disabling changing directories should work): let g:ale_python_pylint_change_directory = 0 - AssertLinter 'pylint', ale#Escape('pylint') . ' ' . b:command_tail + AssertLinter 'pylint', ale#Escape('pylint') . b:command_tail Execute(The pylint executable should be configurable, and escaped properly): let g:ale_python_pylint_executable = 'executable with spaces' AssertLinter 'executable with spaces', - \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) - \ . ale#Escape('executable with spaces') . ' ' . b:command_tail + \ ale#path#CdString(expand('%:p:h')) + \ . ale#Escape('executable with spaces') . b:command_tail Execute(The pylint command callback should let you set options): let g:ale_python_pylint_options = '--some-option' AssertLinter 'pylint', - \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) + \ ale#path#CdString(expand('%:p:h')) \ . ale#Escape('pylint') . ' --some-option' . b:command_tail Execute(The pylint callbacks shouldn't detect virtualenv directories where they don't exist): @@ -44,7 +53,7 @@ Execute(The pylint callbacks shouldn't detect virtualenv directories where they AssertLinter 'pylint', \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/no_virtualenv/subdir')) - \ . ale#Escape('pylint') . ' ' . b:command_tail + \ . ale#Escape('pylint') . b:command_tail Execute(The pylint callbacks should detect virtualenv directories): silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') @@ -55,7 +64,7 @@ Execute(The pylint callbacks should detect virtualenv directories): AssertLinter b:executable, \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) - \ . ale#Escape(b:executable) . ' ' . b:command_tail + \ . ale#Escape(b:executable) . b:command_tail Execute(You should able able to use the global pylint instead): silent execute 'file ' . fnameescape(g:dir . '/python_paths/with_virtualenv/subdir/foo/bar.py') @@ -63,7 +72,7 @@ Execute(You should able able to use the global pylint instead): AssertLinter 'pylint', \ ale#path#CdString(ale#path#Simplify(g:dir . '/python_paths/with_virtualenv/subdir')) - \ . ale#Escape('pylint') . ' ' . b:command_tail + \ . ale#Escape('pylint') . b:command_tail Execute(Setting executable to 'pipenv' appends 'run pylint'): let g:ale_python_pylint_executable = 'path/to/pipenv' @@ -71,7 +80,7 @@ Execute(Setting executable to 'pipenv' appends 'run pylint'): AssertLinter 'path/to/pipenv', \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('path/to/pipenv') . ' run pylint' - \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' Execute(Pipenv is detected when python_pylint_auto_pipenv is set): let g:ale_python_pylint_auto_pipenv = 1 @@ -80,4 +89,4 @@ Execute(Pipenv is detected when python_pylint_auto_pipenv is set): AssertLinter 'pipenv', \ ale#path#CdString(expand('#' . bufnr('') . ':p:h')) \ . ale#Escape('pipenv') . ' run pylint' - \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' + \ . ' --output-format text --msg-template="{path}:{line}:{column}: {msg_id} ({symbol}) {msg}" --reports n %s' diff --git a/test/command_callback/test_vint_command_callback.vader b/test/command_callback/test_vint_command_callback.vader index 4ce277e8..4a224d01 100644 --- a/test/command_callback/test_vint_command_callback.vader +++ b/test/command_callback/test_vint_command_callback.vader @@ -1,17 +1,17 @@ Before: call ale#assert#SetUpLinterTest('vim', 'vint') - let b:command_tail = (has('nvim') ? ' --enable-neovim' : '') - \ . ' -f "{file_path}:{line_number}:{column_number}: {severity}: {policy_name} - {description} (see {reference})" %t' + let b:common_flags = (has('nvim') ? ' --enable-neovim' : '') + \ . ' -f "{file_path}:{line_number}:{column_number}: {severity}: {policy_name} - {description} (see {reference})"' After: - unlet! b:bin_dir - unlet! b:executable + unlet! b:common_flags + call ale#assert#TearDownLinterTest() Execute(The default command should be correct): AssertLinter 'vint', [ \ ale#Escape('vint') .' --version', - \ ale#Escape('vint') .' -s --no-color' . b:command_tail, + \ ale#Escape('vint') .' -s --no-color' . b:common_flags . ' %t', \] Execute(The executable should be configurable): @@ -19,5 +19,16 @@ Execute(The executable should be configurable): AssertLinter 'foobar', [ \ ale#Escape('foobar') .' --version', - \ ale#Escape('foobar') .' -s --no-color' . b:command_tail, + \ ale#Escape('foobar') .' -s --no-color' . b:common_flags . ' %t', \] + +Execute(The --no-color flag should not be used for older Vint versions): + GivenCommandOutput ['v0.3.5'] + + AssertLinter 'vint', ale#Escape('vint') .' -s' . b:common_flags . ' %t' + +Execute(--stdin-display-name should be used in newer versions): + GivenCommandOutput ['v0.4.0'] + + AssertLinter 'vint', ale#Escape('vint') .' -s --no-color' . b:common_flags + \ . ' --stdin-display-name %s -' diff --git a/test/completion/test_complete_events.vader b/test/completion/test_complete_events.vader new file mode 100644 index 00000000..cee15985 --- /dev/null +++ b/test/completion/test_complete_events.vader @@ -0,0 +1,35 @@ +Before: + let g:complete_post_triggered = 0 + + augroup VaderTest + autocmd! + autocmd User ALECompletePost let g:complete_post_triggered = 1 + augroup END + +After: + unlet! b:ale_completion_info + unlet! g:complete_post_triggered + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + +Execute(ALECompletePost should not be triggered when completion is cancelled): + call ale#completion#HandleUserData({}) + + Assert !g:complete_post_triggered + +Execute(ALECompletePost should not be triggered when tools other than ALE insert completions): + call ale#completion#HandleUserData({'user_data': ''}) + call ale#completion#HandleUserData({'user_data': '{}'}) + + Assert !g:complete_post_triggered + +Execute(ALECompletePost should be triggered when ALE inserts completions): + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \}) + + Assert g:complete_post_triggered diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader index 87bd10ad..f678e773 100644 --- a/test/completion/test_completion_events.vader +++ b/test/completion/test_completion_events.vader @@ -407,41 +407,75 @@ Execute(HandleUserData should call ale#code_action#HandleCodeAction): AssertEqual g:handle_code_action_called, 0 call ale#completion#HandleUserData({ - \ 'user_data': '' + \ 'user_data': '' \}) AssertEqual g:handle_code_action_called, 0 call ale#completion#HandleUserData({ - \ 'user_data': '{}' + \ 'user_data': json_encode({}), \}) AssertEqual g:handle_code_action_called, 0 call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": []}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [], + \ }), \}) AssertEqual g:handle_code_action_called, 0 call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), \}) AssertEqual g:handle_code_action_called, 1 let b:ale_completion_info = {'source': 'ale-automatic'} call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), \}) AssertEqual g:handle_code_action_called, 2 let b:ale_completion_info = {'source': 'ale-callback'} call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), \}) AssertEqual g:handle_code_action_called, 3 + let b:ale_completion_info = {'source': 'ale-omnifunc'} + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), + \}) + AssertEqual g:handle_code_action_called, 4 + Execute(ale#code_action#HandleCodeAction should not be called when when source is not ALE): call MockHandleCodeAction() let b:ale_completion_info = {'source': 'syntastic'} call ale#completion#HandleUserData({ - \ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}' + \ 'user_data': json_encode({ + \ '_ale_completion_item': 1, + \ 'code_actions': [ + \ {'description': '', 'changes': []}, + \ ], + \ }), \}) AssertEqual g:handle_code_action_called, 0 diff --git a/test/completion/test_lsp_completion_parsing.vader b/test/completion/test_lsp_completion_parsing.vader index b8e71320..d989aefe 100644 --- a/test/completion/test_lsp_completion_parsing.vader +++ b/test/completion/test_lsp_completion_parsing.vader @@ -12,34 +12,34 @@ After: Execute(Should handle Rust completion results correctly): AssertEqual \ [ - \ {'word': 'new', 'menu': 'pub fn new() -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'with_capacity', 'menu': 'pub fn with_capacity(capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf8', 'menu': 'pub fn from_utf8(vec: Vec) -> Result', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf8_lossy', 'menu': 'pub fn from_utf8_lossy<''a>(v: &''a [u8]) -> Cow<''a, str>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf16', 'menu': 'pub fn from_utf16(v: &[u16]) -> Result', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf16_lossy', 'menu': 'pub fn from_utf16_lossy(v: &[u16]) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_raw_parts', 'menu': 'pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf8_unchecked', 'menu': 'pub unsafe fn from_utf8_unchecked(bytes: Vec) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Searcher', 'menu': 'type Searcher = <&''b str as Pattern<''a>>::Searcher;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'default', 'menu': 'fn default() -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = String;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Target', 'menu': 'type Target = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Err', 'menu': 'type Err = ParseError;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'from_str', 'menu': 'fn from_str(s: &str) -> Result', '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) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from', 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'new', 'menu': 'pub fn new() -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'with_capacity', 'menu': 'pub fn with_capacity(capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8', 'menu': 'pub fn from_utf8(vec: Vec) -> Result', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8_lossy', 'menu': 'pub fn from_utf8_lossy<''a>(v: &''a [u8]) -> Cow<''a, str>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf16', 'menu': 'pub fn from_utf16(v: &[u16]) -> Result', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf16_lossy', 'menu': 'pub fn from_utf16_lossy(v: &[u16]) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_raw_parts', 'menu': 'pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8_unchecked', 'menu': 'pub unsafe fn from_utf8_unchecked(bytes: Vec) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Searcher', 'menu': 'type Searcher = <&''b str as Pattern<''a>>::Searcher;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'default', 'menu': 'fn default() -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = String;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Target', 'menu': 'type Target = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Err', 'menu': 'type Err = ParseError;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_str', 'menu': 'fn from_str(s: &str) -> Result', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: &''a str) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: Box) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \], \ ale#completion#ParseLSPCompletions({ \ "jsonrpc":"2.0", @@ -195,7 +195,7 @@ Execute(Should handle Python completion results correctly): AssertEqual \ [ - \ {'word': 'what', 'menu': 'example-python-project.bar.Bar', 'info': "what()\n\n", 'kind': 'f', 'icase': 1}, + \ {'word': 'what', 'menu': 'example-python-project.bar.Bar', 'info': "what()\n\n", 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ "jsonrpc":"2.0", @@ -399,7 +399,7 @@ Execute(Should handle Python completion results correctly): \ } \ }) -Execute(Should handle Python completion results correctly): +Execute(Should handle extra Python completion results correctly): let b:ale_completion_info = { \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', \ 'prefix': 'mig', @@ -407,8 +407,8 @@ Execute(Should handle Python completion results correctly): AssertEqual \ [ - \ {'word': 'migrations', 'menu': 'xxx', 'info': 'migrations', 'kind': 'f', 'icase': 1}, - \ {'word': 'MigEngine', 'menu': 'xxx', 'info': 'mig engine', 'kind': 'f', 'icase': 1}, + \ {'word': 'migrations', 'menu': 'xxx', 'info': 'migrations', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'MigEngine', 'menu': 'xxx', 'info': 'mig engine', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'jsonrpc': '2.0', @@ -441,7 +441,7 @@ Execute(Should handle Python completion results correctly): Execute(Should handle missing keys): AssertEqual \ [ - \ {'word': 'x', 'menu': '', 'info': '', 'kind': 'v', 'icase': 1}, + \ {'word': 'x', 'menu': '', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'jsonrpc': '2.0', @@ -459,7 +459,7 @@ Execute(Should handle missing keys): Execute(Should handle documentation in the markdown format): AssertEqual \ [ - \ {'word': 'migrations', 'menu': 'xxx', 'info': 'Markdown documentation', 'kind': 'f', 'icase': 1}, + \ {'word': 'migrations', 'menu': 'xxx', 'info': 'Markdown documentation', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'jsonrpc': '2.0', @@ -483,7 +483,7 @@ Execute(Should handle documentation in the markdown format): Execute(Should handle completion messages with textEdit objects): AssertEqual \ [ - \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1}, + \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'id': 226, @@ -514,7 +514,7 @@ Execute(Should handle completion messages with textEdit objects): Execute(Should handle completion messages with the deprecated insertText attribute): AssertEqual \ [ - \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1}, + \ {'word': 'next_callback', 'menu': 'PlayTimeCallback', 'info': '', 'kind': 'v', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \ ], \ ale#completion#ParseLSPCompletions({ \ 'id': 226, @@ -547,7 +547,8 @@ Execute(Should handle completion messages with additionalTextEdits when ale_comp \ 'kind': 'v', \ 'icase': 1, \ 'user_data': json_encode({ - \ 'codeActions': [ + \ '_ale_completion_item': 1, + \ 'code_actions': [ \ { \ 'description': 'completion', \ 'changes': [ @@ -658,6 +659,7 @@ Execute(Should still handle completion messages with empty additionalTextEdits w \ 'info': '', \ 'kind': 'v', \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), \ } \ ], \ ale#completion#ParseLSPCompletions({ diff --git a/test/completion/test_tsserver_completion_parsing.vader b/test/completion/test_tsserver_completion_parsing.vader index aaaaae95..231c0f95 100644 --- a/test/completion/test_tsserver_completion_parsing.vader +++ b/test/completion/test_tsserver_completion_parsing.vader @@ -90,6 +90,7 @@ Execute(TypeScript completion details responses should be parsed correctly): \ 'info': '', \ 'kind': 'v', \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), \ 'dup': g:ale_completion_autoimport, \ }, \ { @@ -98,6 +99,7 @@ Execute(TypeScript completion details responses should be parsed correctly): \ 'info': 'foo bar baz', \ 'kind': 'v', \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), \ 'dup': g:ale_completion_autoimport, \ }, \ { @@ -106,6 +108,7 @@ Execute(TypeScript completion details responses should be parsed correctly): \ 'info': '', \ 'kind': 'v', \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), \ 'dup': g:ale_completion_autoimport, \ }, \ ], @@ -179,7 +182,8 @@ Execute(Entries without details should be included in the responses): \ 'kind': 'v', \ 'icase': 1, \ 'user_data': json_encode({ - \ 'codeActions': [{ + \ '_ale_completion_item': 1, + \ 'code_actions': [{ \ 'description': 'import { def } from "./Foo";', \ 'changes': [], \ }], @@ -192,6 +196,7 @@ Execute(Entries without details should be included in the responses): \ 'info': 'foo bar baz', \ 'kind': 'v', \ 'icase': 1, + \ 'user_data': json_encode({'_ale_completion_item': 1}), \ 'dup': g:ale_completion_autoimport, \ }, \ { @@ -199,6 +204,7 @@ Execute(Entries without details should be included in the responses): \ 'menu': '', \ 'info': '', \ 'kind': 'v', + \ 'user_data': json_encode({'_ale_completion_item': 1}), \ 'icase': 1, \ }, \ ], @@ -260,7 +266,8 @@ Execute(Default imports should be handled correctly): \ 'kind': 't', \ 'icase': 1, \ 'user_data': json_encode({ - \ 'codeActions': [{ + \ '_ale_completion_item': 1, + \ 'code_actions': [{ \ 'description': 'Import default ''abcd'' from module "./foo"', \ 'changes': [], \ }], diff --git a/test/fix/test_ale_fix.vader b/test/fix/test_ale_fix.vader index a51a5a53..128e3a14 100644 --- a/test/fix/test_ale_fix.vader +++ b/test/fix/test_ale_fix.vader @@ -196,6 +196,10 @@ After: " Clear the messages between tests. echomsg '' + if !exists('g:ale_command_wrapper') + let g:ale_command_wrapper = '' + endif + Given testft (A file with three lines): a b @@ -206,6 +210,13 @@ Execute(ALEFix should complain when there are no functions to call): call ale#test#FlushJobs() AssertEqual 'No fixers have been defined. Try :ALEFixSuggest', GetLastMessage() +Execute(ALEFix should not complain when the command is run with a bang): + echom 'none' + + ALEFix! + call ale#test#FlushJobs() + AssertEqual 'none', GetLastMessage() + Execute(ALEFix should apply simple functions): let g:ale_fixers.testft = ['AddCarets'] ALEFix @@ -715,6 +726,19 @@ Execute(ale#fix#InitBufferData() should set up the correct data): \ 'done': 0, \ 'lines_before': ['a', 'b', 'c'], \ 'should_save': 1, + \ 'ignore_file_changed_errors': 0, + \ }, + \}, g:ale_fix_buffer_data + + call ale#fix#InitBufferData(bufnr(''), '!') + + AssertEqual { + \ bufnr(''): { + \ 'temporary_directory_list': [], + \ 'done': 0, + \ 'lines_before': ['a', 'b', 'c'], + \ 'should_save': 0, + \ 'ignore_file_changed_errors': 1, \ }, \}, g:ale_fix_buffer_data diff --git a/test/test_ale_has.vader b/test/test_ale_has.vader index 633c7186..6e73641d 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-3.0.0') AssertEqual 1, ale#Has('ale-2.7.0') AssertEqual 1, ale#Has('ale-2.6.0') AssertEqual 1, ale#Has('ale-2.5.0') diff --git a/test/test_ale_info.vader b/test/test_ale_info.vader index 2bc8c635..895ed2a7 100644 --- a/test/test_ale_info.vader +++ b/test/test_ale_info.vader @@ -6,6 +6,7 @@ Before: Save g:ale_completion_delay Save g:ale_completion_enabled Save g:ale_completion_max_suggestions + Save g:ale_disable_lsp Save g:ale_echo_cursor Save g:ale_echo_msg_error_str Save g:ale_echo_msg_format @@ -24,6 +25,7 @@ Before: Save g:ale_lint_on_text_changed Save g:ale_linters Save g:ale_linters_explicit + Save g:ale_linters_ignore Save g:ale_list_vertical Save g:ale_list_window_size Save g:ale_loclist_msg_format @@ -64,6 +66,7 @@ Before: let g:ale_completion_delay = 100 let g:ale_completion_enabled = 0 let g:ale_completion_max_suggestions = 50 + let g:ale_disable_lsp = 0 let g:ale_echo_cursor = 1 let g:ale_echo_msg_error_str = 'Error' let g:ale_echo_msg_format = '%code: %%s' @@ -80,6 +83,7 @@ Before: let g:ale_lint_on_save = 1 let g:ale_lint_on_text_changed = 'normal' let g:ale_linters_explicit = 0 + let g:ale_linters_ignore = {'python': ['pyright']} let g:ale_list_vertical = 0 let g:ale_list_window_size = 10 let g:ale_loclist_msg_format = '%code: %%s' @@ -138,6 +142,7 @@ Before: \ 'let g:ale_completion_delay = 100', \ 'let g:ale_completion_enabled = 0', \ 'let g:ale_completion_max_suggestions = 50', + \ 'let g:ale_disable_lsp = 0', \ 'let g:ale_echo_cursor = 1', \ 'let g:ale_echo_msg_error_str = ''Error''', \ 'let g:ale_echo_msg_format = ''%code: %%s''', @@ -158,6 +163,7 @@ Before: \ 'let g:ale_linter_aliases = {}', \ 'let g:ale_linters = {}', \ 'let g:ale_linters_explicit = 0', + \ 'let g:ale_linters_ignore = {''python'': [''pyright'']}', \ 'let g:ale_list_vertical = 0', \ 'let g:ale_list_window_size = 10', \ 'let g:ale_loclist_msg_format = ''%code: %%s''', @@ -243,6 +249,7 @@ Execute (ALEInfo with no linters should return the right output): \ ' Current Filetype: nolintersft', \ 'Available Linters: []', \ ' Enabled Linters: []', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -265,6 +272,7 @@ Execute (ALEInfo should return buffer-local global ALE settings): \ ' Current Filetype: ', \ 'Available Linters: []', \ ' Enabled Linters: []', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -279,6 +287,7 @@ Execute (ALEInfo with no filetype should return the right output): \ ' Current Filetype: ', \ 'Available Linters: []', \ ' Enabled Linters: []', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -295,6 +304,7 @@ Execute (ALEInfo with a single linter should return the right output): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -312,6 +322,7 @@ Execute (ALEInfo with two linters should return the right output): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -333,6 +344,7 @@ Execute (ALEInfo should calculate enabled linters correctly): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -350,6 +362,7 @@ Execute (ALEInfo should only return linters for current filetype): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -367,6 +380,7 @@ Execute (ALEInfo with compound filetypes should return linters for both of them) \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -389,6 +403,7 @@ Execute (ALEInfo should return appropriately named global variables): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + [ @@ -420,6 +435,7 @@ Execute (ALEInfoToFile should write to a file correctly): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + [ @@ -447,6 +463,7 @@ Execute (ALEInfo should buffer-local linter variables): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + [ @@ -478,6 +495,7 @@ Execute (ALEInfo should output linter aliases): \ '''testlinter1'' -> [''testftalias1'', ''testftalias2'']', \ '''testlinter2'' -> [''testftalias3'', ''testftalias4'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + [ @@ -505,6 +523,7 @@ Execute (ALEInfo should return command history): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -532,6 +551,7 @@ Execute (ALEInfo command history should print exit codes correctly): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -580,6 +600,7 @@ Execute (ALEInfo command history should print command output if logging is on): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -618,6 +639,7 @@ Execute (ALEInfo should include executable checks in the history): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -647,6 +669,7 @@ Execute (The option for caching failing executable checks should work): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -669,6 +692,7 @@ Execute (LSP errors for a linter should be outputted): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -693,6 +717,7 @@ Execute (LSP errors for other linters shouldn't appear): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -715,6 +740,7 @@ Execute (ALEInfo should include linter global options): \ ' Current Filetype: testft.testft2', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'', ''testlinter2'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines @@ -740,6 +766,7 @@ Execute (ALEInfo should include linter global options for enabled linters): \ ' Current Filetype: testft', \ 'Available Linters: [''testlinter1'', ''testlinter2'']', \ ' Enabled Linters: [''testlinter1'']', + \ ' Ignored Linters: []', \ ] \ + g:fixer_lines \ + g:variables_lines diff --git a/test/test_ale_lint_stop_command.vader b/test/test_ale_lint_stop_command.vader new file mode 100644 index 00000000..c50db5a8 --- /dev/null +++ b/test/test_ale_lint_stop_command.vader @@ -0,0 +1,27 @@ +Before: + Save g:ale_buffer_info + + let g:ale_buffer_info = {} + + call ale#linter#PreventLoading('testft') + call ale#linter#Define('testft', { + \ 'name': 'testlinter', + \ 'callback': {-> []}, + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': 'sleep 9001', + \}) + +After: + Restore + + call ale#linter#Reset() + +Given testft (An empty file): +Execute(ALELintStop should stop ALE from linting): + ALELint + + Assert ale#engine#IsCheckingBuffer(bufnr('')), 'ALE did not start checking the buffer' + + ALELintStop + + Assert !ale#engine#IsCheckingBuffer(bufnr('')), 'ALELintStop didn''t work' diff --git a/test/test_computed_lint_file_values.vader b/test/test_computed_lint_file_values.vader index 399e96fe..ed0d4c0c 100644 --- a/test/test_computed_lint_file_values.vader +++ b/test/test_computed_lint_file_values.vader @@ -132,3 +132,19 @@ Execute(Linters where lint_file eventually evaluates to 1 shouldn't be run if we call ale#test#FlushJobs() AssertEqual [], ale#test#GetLoclistWithoutModule() + +Execute(Keeping computed lint_file jobs running should work): + AssertEqual 'testlinter2', ale#linter#Get('foobar')[1].name + + call ale#engine#InitBufferInfo(bufnr('')) + + call ale#engine#MarkLinterActive( + \ g:ale_buffer_info[bufnr('')], + \ ale#linter#Get('foobar')[1] + \) + call ale#engine#RunLinters(bufnr(''), ale#linter#Get('foobar'), 0) + + Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list), + \ 'The active linter list was empty' + Assert ale#engine#IsCheckingBuffer(bufnr('')), + \ 'The IsCheckingBuffer function returned 0'