diff options
32 files changed, 1324 insertions, 255 deletions
diff --git a/ale_linters/asciidoc/languagetool.vim b/ale_linters/asciidoc/languagetool.vim index f16df6c0..8e8de7f3 100644 --- a/ale_linters/asciidoc/languagetool.vim +++ b/ale_linters/asciidoc/languagetool.vim @@ -2,4 +2,4 @@ " Description: languagetool for asciidoc files, copied from markdown. -call ale#handlers#languagetool#DefineLinter('asciidoctor') +call ale#handlers#languagetool#DefineLinter('asciidoc') diff --git a/ale_linters/handlebars/embertemplatelint.vim b/ale_linters/handlebars/embertemplatelint.vim index 31d65b70..bd4d1d31 100644 --- a/ale_linters/handlebars/embertemplatelint.vim +++ b/ale_linters/handlebars/embertemplatelint.vim @@ -19,11 +19,11 @@ endfunction function! ale_linters#handlebars#embertemplatelint#GetCommandWithVersionCheck(buffer) abort return ale#semver#RunWithVersionCheck( - \ a:buffer, - \ ale_linters#handlebars#embertemplatelint#GetExecutable(a:buffer), - \ '%e --version', - \ function('ale_linters#handlebars#embertemplatelint#GetCommand'), - \ ) + \ a:buffer, + \ ale_linters#handlebars#embertemplatelint#GetExecutable(a:buffer), + \ '%e --version', + \ function('ale_linters#handlebars#embertemplatelint#GetCommand'), + \) endfunction function! ale_linters#handlebars#embertemplatelint#Handle(buffer, lines) abort @@ -52,7 +52,8 @@ function! ale_linters#handlebars#embertemplatelint#Handle(buffer, lines) abort endfunction call ale#linter#Define('handlebars', { -\ 'name': 'ember-template-lint', +\ 'name': 'embertemplatelint', +\ 'aliases': ['ember-template-lint'], \ 'executable': function('ale_linters#handlebars#embertemplatelint#GetExecutable'), \ 'command': function('ale_linters#handlebars#embertemplatelint#GetCommandWithVersionCheck'), \ 'callback': 'ale_linters#handlebars#embertemplatelint#Handle', diff --git a/ale_linters/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 c2cfd74a..ecd93600 100644 --- a/autoload/ale/completion.vim +++ b/autoload/ale/completion.vim @@ -188,7 +188,13 @@ function! ale#completion#GetTriggerCharacter(filetype, prefix) abort return '' endfunction -function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort +function! ale#completion#Filter( +\ buffer, +\ filetype, +\ suggestions, +\ prefix, +\ exact_prefix_match, +\) abort let l:excluded_words = ale#Var(a:buffer, 'completion_excluded_words') if empty(a:prefix) @@ -215,10 +221,17 @@ function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort " Dictionaries is accepted here. let l:word = type(l:item) is v:t_string ? l:item : l:item.word - " Add suggestions if the suggestion starts with a - " case-insensitive match for the prefix. - if l:word[: len(a:prefix) - 1] is? a:prefix - call add(l:filtered_suggestions, l:item) + if a:exact_prefix_match + " Add suggestions if the word is an exact match. + if l:word is# a:prefix + call add(l:filtered_suggestions, l:item) + endif + else + " Add suggestions if the suggestion starts with a + " case-insensitive match for the prefix. + if l:word[: len(a:prefix) - 1] is? a:prefix + call add(l:filtered_suggestions, l:item) + endif endif endfor endif @@ -241,21 +254,17 @@ function! ale#completion#Filter(buffer, filetype, suggestions, prefix) abort return l:filtered_suggestions endfunction -function! s:ReplaceCompletionOptions() abort - let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - - if l:source is# 'ale-automatic' || l:source is# 'ale-manual' - " Remember the old omnifunc value, if there is one. - " If we don't store an old one, we'll just never reset the option. - " This will stop some random exceptions from appearing. - if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc) - let b:ale_old_omnifunc = &l:omnifunc - endif - - let &l:omnifunc = 'ale#completion#AutomaticOmniFunc' +function! s:ReplaceCompletionOptions(source) abort + " Remember the old omnifunc value, if there is one. + " If we don't store an old one, we'll just never reset the option. + " This will stop some random exceptions from appearing. + if !exists('b:ale_old_omnifunc') && !empty(&l:omnifunc) + let b:ale_old_omnifunc = &l:omnifunc endif - if l:source is# 'ale-automatic' + let &l:omnifunc = 'ale#completion#AutomaticOmniFunc' + + if a:source is# 'ale-automatic' if !exists('b:ale_old_completeopt') let b:ale_old_completeopt = &l:completeopt endif @@ -318,7 +327,11 @@ function! ale#completion#AutomaticOmniFunc(findstart, base) abort else let l:result = ale#completion#GetCompletionResult() - call s:ReplaceCompletionOptions() + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if l:source is# 'ale-automatic' || l:source is# 'ale-manual' + call s:ReplaceCompletionOptions(l:source) + endif return l:result isnot v:null ? l:result : [] endif @@ -331,31 +344,53 @@ function! s:OpenCompletionMenu(...) abort endfunction function! ale#completion#Show(result) abort - if ale#util#Mode() isnot# 'i' + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') + + if ale#util#Mode() isnot# 'i' && l:source isnot# 'ale-import' return endif - " Set the list in the buffer, temporarily replace omnifunc with our - " function, and then start omni-completion. + " Set the list in the buffer. let b:ale_completion_result = a:result " Don't try to open the completion menu if there's nothing to show. if empty(b:ale_completion_result) + if l:source is# 'ale-import' + " If we ran completion from :ALEImport, + " tell the user that nothing is going to happen. + call s:message('No possible imports found.') + endif + return endif " Replace completion options shortly before opening the menu. - call s:ReplaceCompletionOptions() - - let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - if l:source is# 'ale-automatic' || l:source is# 'ale-manual' + call s:ReplaceCompletionOptions(l:source) + call timer_start(0, function('s:OpenCompletionMenu')) endif if l:source is# 'ale-callback' call b:CompleteCallback(b:ale_completion_result) endif + + if l:source is# 'ale-import' + call ale#completion#HandleUserData(b:ale_completion_result[0]) + + let l:text_changed = '' . g:ale_lint_on_text_changed + + " Check the buffer again right away, if linting is enabled. + if g:ale_enabled + \&& ( + \ l:text_changed is# '1' + \ || l:text_changed is# 'always' + \ || l:text_changed is# 'normal' + \ || l:text_changed is# 'insert' + \) + call ale#Queue(0, '') + endif + endif endfunction function! ale#completion#GetAllTriggers() abort @@ -386,14 +421,18 @@ endfunction function! s:CompletionStillValid(request_id) abort let [l:line, l:column] = getpos('.')[1:2] - return ale#util#Mode() is# 'i' - \&& has_key(b:, 'ale_completion_info') + return has_key(b:, 'ale_completion_info') + \&& ( + \ ale#util#Mode() is# 'i' + \ || b:ale_completion_info.source is# 'ale-import' + \) \&& b:ale_completion_info.request_id == a:request_id \&& b:ale_completion_info.line == l:line \&& ( \ b:ale_completion_info.column == l:column \ || b:ale_completion_info.source is# 'ale-omnifunc' \ || b:ale_completion_info.source is# 'ale-callback' + \ || b:ale_completion_info.source is# 'ale-import' \) endfunction @@ -418,6 +457,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort let l:buffer = bufnr('') let l:results = [] let l:names_with_details = [] + let l:info = get(b:, 'ale_completion_info', {}) for l:suggestion in a:response.body let l:displayParts = [] @@ -459,17 +499,25 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort \ 'kind': ale#completion#GetCompletionSymbols(l:suggestion.kind), \ 'icase': 1, \ 'menu': join(l:displayParts, ''), - \ 'dup': g:ale_completion_autoimport, + \ 'dup': get(l:info, 'additional_edits_only', 0) + \ || g:ale_completion_autoimport, \ 'info': join(l:documentationParts, ''), \} + " This flag is used to tell if this completion came from ALE or not. + let l:user_data = {'_ale_completion_item': 1} if has_key(l:suggestion, 'codeActions') - let l:result.user_data = json_encode({ - \ 'codeActions': l:suggestion.codeActions, - \ }) + let l:user_data.code_actions = l:suggestion.codeActions endif - call add(l:results, l:result) + let l:result.user_data = json_encode(l:user_data) + + " Include this item if we'll accept any items, + " or if we only want items with additional edits, and this has them. + if !get(l:info, 'additional_edits_only', 0) + \|| has_key(l:user_data, 'code_actions') + call add(l:results, l:result) + endif endfor let l:names = getbufvar(l:buffer, 'ale_tsserver_completion_names', []) @@ -488,6 +536,7 @@ function! ale#completion#ParseTSServerCompletionEntryDetails(response) abort \ 'icase': 1, \ 'menu': '', \ 'info': '', + \ 'user_data': json_encode({'_ale_completion_item': 1}), \}) endfor endif @@ -544,7 +593,10 @@ function! ale#completion#ParseLSPCompletions(response) abort " Don't use LSP items with additional text edits when autoimport for " completions is turned off. if !empty(get(l:item, 'additionalTextEdits')) - \&& !g:ale_completion_autoimport + \&& !( + \ get(l:info, 'additional_edits_only', 0) + \ || g:ale_completion_autoimport + \) continue endif @@ -561,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 = [] @@ -580,25 +634,36 @@ 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 - call add(l:results, l:result) + let l:result.user_data = json_encode(l:user_data) + + " Include this item if we'll accept any items, + " or if we only want items with additional edits, and this has them. + if !get(l:info, 'additional_edits_only', 0) + \|| has_key(l:user_data, 'code_actions') + call add(l:results, l:result) + endif endfor if has_key(l:info, 'prefix') - let l:results = ale#completion#Filter(l:buffer, &filetype, l:results, l:info.prefix) + let l:results = ale#completion#Filter( + \ l:buffer, + \ &filetype, + \ l:results, + \ l:info.prefix, + \ get(l:info, 'additional_edits_only', 0), + \) endif return l:results[: g:ale_completion_max_suggestions - 1] @@ -622,13 +687,18 @@ function! ale#completion#HandleTSServerResponse(conn_id, response) abort \ &filetype, \ ale#completion#ParseTSServerCompletions(a:response), \ b:ale_completion_info.prefix, + \ get(b:ale_completion_info, 'additional_edits_only', 0), \)[: g:ale_completion_max_suggestions - 1] " We need to remember some names for tsserver, as it doesn't send " details back for everything we send. call setbufvar(l:buffer, 'ale_tsserver_completion_names', l:names) - if !empty(l:names) + if empty(l:names) + " Response with no results now and skip making a redundant request + " for nothing. + call ale#completion#Show([]) + else let l:identifiers = [] for l:name in l:names @@ -702,7 +772,8 @@ 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_autoimport, + \ get(b:ale_completion_info, 'additional_edits_only', 0) + \ || g:ale_completion_autoimport, \) else " Send a message saying the buffer has changed first, otherwise @@ -761,9 +832,19 @@ function! ale#completion#GetCompletions(...) abort let b:CompleteCallback = l:CompleteCallback endif - let [l:line, l:column] = getpos('.')[1:2] + if has_key(l:options, 'line') && has_key(l:options, 'column') + " Use a provided line and column, if given. + let l:line = l:options.line + let l:column = l:options.column + else + let [l:line, l:column] = getpos('.')[1:2] + endif - let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) + if has_key(l:options, 'prefix') + let l:prefix = l:options.prefix + else + let l:prefix = ale#completion#GetPrefix(&filetype, l:line, l:column) + endif if l:source is# 'ale-automatic' && empty(l:prefix) return 0 @@ -782,6 +863,11 @@ function! ale#completion#GetCompletions(...) abort \} unlet! b:ale_completion_result + if has_key(l:options, 'additional_edits_only') + let b:ale_completion_info.additional_edits_only = + \ l:options.additional_edits_only + endif + let l:buffer = bufnr('') let l:Callback = function('s:OnReady') @@ -798,6 +884,37 @@ function! ale#completion#GetCompletions(...) abort return l:started endfunction +function! s:message(message) abort + call ale#util#Execute('echom ' . string(a:message)) +endfunction + +" This function implements the :ALEImport command. +function! ale#completion#Import() abort + let l:word = expand('<cword>') + + if empty(l:word) + call s:message('Nothing to complete at cursor!') + + return + endif + + let [l:line, l:column] = getpos('.')[1:2] + let l:column = searchpos('\V' . escape(l:word, '/\'), 'bn', l:line)[1] + + if l:column isnot 0 + let l:started = ale#completion#GetCompletions('ale-import', { + \ 'line': l:line, + \ 'column': l:column, + \ 'prefix': l:word, + \ 'additional_edits_only': 1, + \}) + + if !l:started + call s:message('No completion providers are available.') + endif + endif +endfunction + function! ale#completion#OmniFunc(findstart, base) abort if a:findstart let l:started = ale#completion#GetCompletions('ale-omnifunc') @@ -871,29 +988,29 @@ function! ale#completion#Queue() abort endfunction function! ale#completion#HandleUserData(completed_item) abort - let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - - if l:source isnot# 'ale-automatic' - \&& l:source isnot# 'ale-manual' - \&& l:source isnot# 'ale-callback' - return - endif - let l:user_data_json = get(a:completed_item, 'user_data', '') + let l:user_data = !empty(l:user_data_json) + \ ? json_decode(l:user_data_json) + \ : v:null - if empty(l:user_data_json) + if type(l:user_data) isnot v:t_dict + \|| get(l:user_data, '_ale_completion_item', 0) isnot 1 return endif - let l:user_data = json_decode(l:user_data_json) + let l:source = get(get(b:, 'ale_completion_info', {}), 'source', '') - if type(l:user_data) isnot v:t_dict - return + if l:source is# 'ale-automatic' + \|| l:source is# 'ale-manual' + \|| l:source is# 'ale-callback' + \|| l:source is# 'ale-import' + \|| l:source is# 'ale-omnifunc' + for l:code_action in get(l:user_data, 'code_actions', []) + call ale#code_action#HandleCodeAction(l:code_action, v:false) + endfor endif - for l:code_action in get(l:user_data, 'codeActions', []) - call ale#code_action#HandleCodeAction(l:code_action, v:false) - endfor + silent doautocmd <nomodeline> 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 <nomodeline> 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 <nomodeline> User ALELintPre - " Handle `lint_file` callbacks first. - let l:linter_slots = [] - - for l:linter in a:linters - let l:LintFile = l:linter.lint_file - - if type(l:LintFile) is v:t_func - let l:LintFile = l:LintFile(a:buffer) - endif - - call add(l:linter_slots, [l:LintFile, l:linter]) - endfor - - call s:GetLintFileValues(l:linter_slots, { - \ new_slots -> s:RunLinters( - \ a:buffer, - \ new_slots, - \ a:should_lint_file, - \ l:new_buffer, - \ l:can_clear_results, - \ ) - \}) + 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/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index db640654..dcd76e8f 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -34,7 +34,11 @@ endfunction function! s:HandleLSPDiagnostics(conn_id, response) abort let l:linter_name = s:lsp_linter_map[a:conn_id] let l:filename = ale#path#FromURI(a:response.params.uri) - let l:buffer = bufnr('^' . l:filename . '$') + let l:escaped_name = escape( + \ fnameescape(l:filename), + \ has('win32') ? '^' : '^,}]' + \) + let l:buffer = bufnr('^' . l:escaped_name . '$') let l:info = get(g:ale_buffer_info, l:buffer, {}) if empty(l:info) @@ -52,7 +56,11 @@ endfunction function! s:HandleTSServerDiagnostics(response, error_type) abort let l:linter_name = 'tsserver' - let l:buffer = bufnr('^' . a:response.body.file . '$') + let l:escaped_name = escape( + \ fnameescape(a:response.body.file), + \ has('win32') ? '^' : '^,}]' + \) + let l:buffer = bufnr('^' . l:escaped_name . '$') let l:info = get(g:ale_buffer_info, l:buffer, {}) if empty(l:info) diff --git a/doc/ale-elixir.txt b/doc/ale-elixir.txt index 5864f728..de9daacf 100644 --- a/doc/ale-elixir.txt +++ b/doc/ale-elixir.txt @@ -6,7 +6,7 @@ ALE Elixir Integration *ale-elixir-options* mix *ale-elixir-mix* -The `mix` linter is disabled by default, as it can bee too expensive to run. +The `mix` linter is disabled by default, as it can be too expensive to run. See `:help g:ale_linters` diff --git a/doc/ale-handlebars.txt b/doc/ale-handlebars.txt index 5daec5b3..4a5a3870 100644 --- a/doc/ale-handlebars.txt +++ b/doc/ale-handlebars.txt @@ -14,7 +14,8 @@ ember-template-lint *ale-handlebars-embertemplatelint* g:ale_handlebars_embertemplatelint_executable *g:ale_handlebars_embertemplatelint_executable* - Type: |String| *b:ale_handlebars_embertemplatelint_executable* + *b:ale_handlebars_embertemplatelint_executable* + Type: |String| Default: `'ember-template-lint'` See |ale-integrations-local-executables| @@ -22,7 +23,8 @@ g:ale_handlebars_embertemplatelint_executable g:ale_handlebars_embertemplatelint_use_global *g:ale_handlebars_embertemplatelint_use_global* - Type: |Number| *b:ale_handlebars_embertemplatelint_use_global* + *b:ale_handlebars_embertemplatelint_use_global* + Type: |Number| Default: `get(g:, 'ale_use_global_executables', 0)` See |ale-integrations-local-executables| diff --git a/doc/ale.txt b/doc/ale.txt index 472c4852..0a348d5b 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 @@ -526,6 +531,15 @@ completion information with Deoplete, consult Deoplete's documentation. ALE by can support automatic imports from external modules. This behavior can be enabled by setting the |g:ale_completion_autoimport| variable to `1`. +You can manually request imports for symbols at the cursor with the +|ALEImport| command. The word at the cursor must be an exact match for some +potential completion result which includes additional text to insert into the +current buffer, which ALE will assume is code for an import line. This command +can be useful when your code already contains something you need to import. + +You can execute other commands whenever ALE inserts some completion text with +the |ALECompletePost| event. + When working with TypeScript files, ALE can remove warnings from your completions by setting the |g:ale_completion_tsserver_remove_warnings| variable to 1. @@ -2971,6 +2985,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 `<Plug>(ale_fix)` is defined for this command. @@ -3053,6 +3071,23 @@ ALEHover *ALEHover* A plug mapping `<Plug>(ale_hover)` is defined for this command. +ALEImport *ALEImport* + + Try to import a symbol using `tsserver` or a Language Server. + + ALE will look for completions for the word at the cursor which contain + additional text edits that possible insert lines to import the symbol. The + first match with additional text edits will be used, and may add other code + to the current buffer other than import lines. + + If linting is enabled, and |g:ale_lint_on_text_changed| is set to ever check + buffers when text is changed, the buffer will be checked again after changes + are made. + + A Plug mapping `<Plug>(ale_import)` is defined for this command. This + mapping should only be bound for normal mode. + + ALEOrganizeImports *ALEOrganizeImports* Organize imports using tsserver. Currently not implemented for LSPs. @@ -3060,9 +3095,10 @@ ALEOrganizeImports *ALEOrganizeImports* ALERename *ALERename* - Rename a symbol using TypeScript server or Language Server. + Rename a symbol using `tsserver` or a Language Server. - The user will be prompted for a new name. + The symbol where the cursor is resting will be the symbol renamed, and a + prompt will open to request a new name. ALERepeatSelection *ALERepeatSelection* @@ -3092,6 +3128,13 @@ ALELint *ALELint* A plug mapping `<Plug>(ale_lint)` is defined for this command. +ALELintStop *ALELintStop* + + Stop any currently running jobs for checking the current buffer. + + Any problems from previous linter results will continue to be shown. + + ALEPrevious *ALEPrevious* ALEPreviousWrap *ALEPreviousWrap* ALENext *ALENext* @@ -3957,6 +4000,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 6a947ea7..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(<f-args>) " Fix problems in files. -command! -bar -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEFix :call ale#fix#Fix(bufnr(''), '', <f-args>) +command! -bar -bang -nargs=* -complete=customlist,ale#fix#registry#CompleteFixers ALEFix :call ale#fix#Fix(bufnr(''), '<bang>', <f-args>) " Suggest registered functions to use for fixing problems. command! -bar ALEFixSuggest :call ale#fix#registry#Suggest(&filetype) @@ -229,8 +231,12 @@ command! -bar ALEDocumentation :call ale#hover#ShowDocumentationAtCursor() " Search for appearances of a symbol, such as a type name or function name. command! -nargs=1 ALESymbolSearch :call ale#symbol#Search(<q-args>) +" Complete text with tsserver and LSP command! -bar ALEComplete :call ale#completion#GetCompletions('ale-manual') +" Try to find completions for the current symbol that add additional text. +command! -bar ALEImport :call ale#completion#Import() + " Rename symbols using tsserver and LSP command! -bar ALERename :call ale#rename#Execute() @@ -275,6 +281,7 @@ nnoremap <silent> <Plug>(ale_find_references) :ALEFindReferences<Return> nnoremap <silent> <Plug>(ale_hover) :ALEHover<Return> nnoremap <silent> <Plug>(ale_documentation) :ALEDocumentation<Return> inoremap <silent> <Plug>(ale_complete) <C-\><C-O>:ALEComplete<Return> +nnoremap <silent> <Plug>(ale_import) :ALEImport<Return> nnoremap <silent> <Plug>(ale_rename) :ALERename<Return> nnoremap <silent> <Plug>(ale_repeat_selection) :ALERepeatSelection<Return> 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_embertemplatelint_command_callbacks.vader b/test/command_callback/test_embertemplatelint_command_callbacks.vader new file mode 100644 index 00000000..97687d29 --- /dev/null +++ b/test/command_callback/test_embertemplatelint_command_callbacks.vader @@ -0,0 +1,17 @@ +Before: + call ale#assert#SetUpLinterTest('handlebars', 'embertemplatelint') + + GivenCommandOutput ['1.6.0'] + +After: + call ale#assert#TearDownLinterTest() + +Execute(ember-template-lint executables runs the right command): + AssertLinter 'ember-template-lint', + \ ale#Escape('ember-template-lint') . ' --json --filename %s' + +Execute(old ember-template-lint executables runs the right command): + GivenCommandOutput [] + + AssertLinter 'ember-template-lint', + \ ale#Escape('ember-template-lint') . ' --json %t' diff --git a/test/command_callback/test_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_ale_import_command.vader b/test/completion/test_ale_import_command.vader new file mode 100644 index 00000000..2ba9b8d7 --- /dev/null +++ b/test/completion/test_ale_import_command.vader @@ -0,0 +1,562 @@ +Before: + Save g:ale_enabled + Save b:ale_enabled + Save g:ale_lint_on_text_changed + Save g:ale_completion_enabled + Save g:ale_completion_autoimport + Save g:ale_completion_max_suggestions + Save g:ale_linters + Save b:ale_linters + + let g:ale_enabled = 0 + let b:ale_enabled = 0 + let g:ale_lint_on_text_changed = 'always' + let g:ale_completion_enabled = 0 + let g:ale_completion_autoimport = 0 + let g:ale_completion_max_suggestions = 50 + let g:ale_linters = {'typescript': ['tsserver'], 'python': ['pyre']} + unlet! b:ale_linters + + let g:server_started_value = 1 + let g:request_id = 0 + let g:LastCallback = v:null + let g:LastHandleCallback = v:null + let g:sent_message_list = [] + let g:code_action_list = [] + let g:execute_list = [] + let g:ale_queue_call_list = [] + + runtime autoload/ale.vim + runtime autoload/ale/util.vim + runtime autoload/ale/code_action.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + + function! ale#util#Execute(expr) abort + call add(g:execute_list, a:expr) + endfunction + + function! ale#Queue(...) abort + call add(g:ale_queue_call_list, a:000) + endfunction + + function! ale#lsp#RegisterCallback(id, Callback) abort + let g:LastHandleCallback = a:Callback + endfunction + + function! ale#lsp#NotifyForChanges(id, buffer) abort + endfunction + + function! ale#lsp#HasCapability(id, capability) abort + return 1 + endfunction + + function! ale#lsp#Send(id, message) abort + let g:request_id += 1 + + call add(g:sent_message_list, a:message) + + return g:request_id + endfunction + + function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort + let g:LastCallback = a:Callback + + return g:server_started_value + endfunction + + function! ale#code_action#HandleCodeAction(code_action, should_save) abort + Assert !a:should_save + + call add(g:code_action_list, a:code_action) + endfunction + + function GetLastMessage() + return get(g:execute_list, -1, '') + endfunction + + function CheckLintStates(conn_id, message) + " Check that we request more linter results after adding completions. + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_enabled = 0 + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_enabled = 1 + let g:ale_lint_on_text_changed = 1 + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'normal' + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'insert' + + let g:ale_queue_call_list = [] + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [[0, '']], g:ale_queue_call_list + + let g:ale_queue_call_list = [] + let g:ale_lint_on_text_changed = 'never' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = '0' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 0 + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + + let g:ale_lint_on_text_changed = 'xxx' + + call g:LastHandleCallback(a:conn_id, a:message) + AssertEqual [], g:ale_queue_call_list + endfunction + +After: + call ale#linter#Reset() + + Restore + + delfunction GetLastMessage + delfunction CheckLintStates + + unlet! g:LastCallback + unlet! g:LastHandleCallback + unlet! g:request_id + unlet! g:server_started_value + unlet! g:sent_message_list + unlet! g:code_action_list + unlet! g:ale_queue_call_list + unlet! g:execute_list + unlet! g:received_message + unlet! b:ale_old_omnifunc + unlet! b:ale_old_completeopt + unlet! b:ale_completion_info + unlet! b:ale_completion_result + unlet! b:ale_complete_done_time + + runtime autoload/ale.vim + runtime autoload/ale/util.vim + runtime autoload/ale/code_action.vim + runtime autoload/ale/lsp.vim + runtime autoload/ale/lsp_linter.vim + +Given typescript(Some example TypeScript code): + let xyz = 123 + let foo = missingword + + let abc = 456 + +Execute(ALEImport should complain when there's no word at the cursor): + call setpos('.', [bufnr(''), 3, 1, 0]) + ALEImport + + AssertEqual 'echom ''Nothing to complete at cursor!''', GetLastMessage() + +Execute(ALEImport should tell the user if no LSP is available): + let g:server_started_value = 0 + + call setpos('.', [bufnr(''), 2, 16, 0]) + ALEImport + + AssertEqual + \ 'echom ''No completion providers are available.''', + \ GetLastMessage() + +Execute(ALEImport should request imports correctly for tsserver): + call setpos('.', [bufnr(''), 2, 16, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 11, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 11, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + call g:LastHandleCallback(347, { + \ 'request_seq': 1, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'missingwordIgnoreMe'}, + \ {'name': 'missingword'}, + \ ], + \}) + + AssertEqual + \ [ + \ [0, 'ts@completions', { + \ 'file': expand('%:p'), + \ 'includeExternalModuleExports': 1, + \ 'offset': 11, + \ 'line': 2, + \ 'prefix': 'missingword', + \ }], + \ [0, 'ts@completionEntryDetails', { + \ 'file': expand('%:p'), + \ 'entryNames': [{'name': 'missingword'}], + \ 'offset': 11, + \ 'line': 2, + \ }] + \ ], + \ g:sent_message_list + AssertEqual 2, b:ale_completion_info.request_id + + let g:ale_enabled = 1 + let g:received_message = { + \ 'request_seq': 2, + \ 'command': 'completionEntryDetails', + \ 'body': [ + \ { + \ 'name': 'missingword', + \ 'kind': 'className', + \ 'displayParts': [], + \ 'codeActions': [{ + \ 'description': 'import { missingword } from "./Something";', + \ 'changes': [], + \ }], + \ }, + \ ], + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual + \ [ + \ { + \ 'description': 'import { missingword } from "./Something";', + \ 'changes': [], + \ }, + \ ], + \ g:code_action_list + + call CheckLintStates(347, g:received_message) + +Execute(ALEImport should tell the user when no completions were found from tsserver): + call setpos('.', [bufnr(''), 2, 16, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 11, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 11, + \ 'line': 2, + \ 'line_length': 21, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + call g:LastHandleCallback(347, { + \ 'request_seq': 1, + \ 'command': 'completions', + \ 'body': [ + \ {'name': 'missingwordIgnoreMe'}, + \ ], + \}) + + AssertEqual 'echom ''No possible imports found.''', GetLastMessage() + +Given python(Some example Python code): + xyz = 123 + foo = missingword + + abc = 456 + +Execute(ALEImport should request imports correctly for language servers): + call setpos('.', [bufnr(''), 2, 12, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 7, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 7, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + AssertEqual + \ [ + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'character': 6, 'line': 1} + \ }], + \ ], + \ g:sent_message_list + AssertEqual 1, b:ale_completion_info.request_id + + let g:ale_enabled = 1 + let g:received_message = { + \ 'id': 1, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'Some other word we should ignore', + \ 'filterText': 'missingwordIgnoreMe', + \ 'insertText': 'missingwordIgnoreMe', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingwordIgnoreMe', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingwordIgnoreMe', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'Some word without text edits', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ }, + \ { + \ 'detail': 'The word we should use', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingword', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'The other word we should not use', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something_else import missingword', + \ }, + \ ], + \ }, + \ ], + \ }, + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual + \ [ + \ { + \ 'description': 'completion', + \ 'changes': [ + \ { + \ 'fileName': expand('%:p'), + \ 'textChanges': [ + \ { + \ 'start': {'line': 2, 'offset': 2}, + \ 'end': {'line': 3, 'offset': 2}, + \ 'newText': 'from something import missingword', + \ }, + \ ], + \ }, + \ ], + \ }, + \ ], + \ g:code_action_list + + call CheckLintStates(347, g:received_message) + +Execute(ALEImport should tell the user when no completions were found from a language server): + call setpos('.', [bufnr(''), 2, 12, 0]) + + ALEImport + + AssertEqual + \ { + \ 'conn_id': 0, + \ 'request_id': 0, + \ 'source': 'ale-import', + \ 'column': 7, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ }, + \ b:ale_completion_info + Assert g:LastCallback isnot v:null + + call g:LastCallback(ale#linter#Get(&filetype)[0], { + \ 'connection_id': 347, + \ 'buffer': bufnr(''), + \}) + + AssertEqual + \ { + \ 'conn_id': 347, + \ 'request_id': 1, + \ 'source': 'ale-import', + \ 'column': 7, + \ 'line': 2, + \ 'line_length': 17, + \ 'prefix': 'missingword', + \ 'additional_edits_only': 1, + \ 'completion_filter': 'ale#completion#python#CompletionItemFilter', + \ }, + \ b:ale_completion_info + Assert g:LastHandleCallback isnot v:null + + AssertEqual + \ [ + \ [0, 'textDocument/completion', { + \ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))}, + \ 'position': {'character': 6, 'line': 1} + \ }], + \ ], + \ g:sent_message_list + AssertEqual 1, b:ale_completion_info.request_id + + let g:received_message = { + \ 'id': 1, + \ 'jsonrpc': '2.0', + \ 'result': { + \ 'isIncomplete': v:false, + \ 'items': [ + \ { + \ 'detail': 'Some other word we should ignore', + \ 'filterText': 'missingwordIgnoreMe', + \ 'insertText': 'missingwordIgnoreMe', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingwordIgnoreMe', + \ 'sortText': '3ee19999missingword', + \ 'additionalTextEdits': [ + \ { + \ 'range': { + \ 'start': {'line': 1, 'character': 1}, + \ 'end': {'line': 2, 'character': 1}, + \ }, + \ 'newText': 'from something import missingwordIgnoreMe', + \ }, + \ ], + \ }, + \ { + \ 'detail': 'Some word without text edits', + \ 'filterText': 'missingword', + \ 'insertText': 'missingword', + \ 'insertTextFormat': 1, + \ 'kind': 6, + \ 'label': ' missingword', + \ 'sortText': '3ee19999missingword', + \ }, + \ ], + \ }, + \} + call g:LastHandleCallback(347, g:received_message) + + AssertEqual 'echom ''No possible imports found.''', GetLastMessage() diff --git a/test/completion/test_complete_events.vader b/test/completion/test_complete_events.vader new file mode 100644 index 00000000..cee15985 --- /dev/null +++ b/test/completion/test_complete_events.vader @@ -0,0 +1,35 @@ +Before: + let g:complete_post_triggered = 0 + + augroup VaderTest + autocmd! + autocmd User ALECompletePost let g:complete_post_triggered = 1 + augroup END + +After: + unlet! b:ale_completion_info + unlet! g:complete_post_triggered + + augroup VaderTest + autocmd! + augroup END + + augroup! VaderTest + +Execute(ALECompletePost should not be triggered when completion is cancelled): + call ale#completion#HandleUserData({}) + + Assert !g:complete_post_triggered + +Execute(ALECompletePost should not be triggered when tools other than ALE insert completions): + call ale#completion#HandleUserData({'user_data': ''}) + call ale#completion#HandleUserData({'user_data': '{}'}) + + Assert !g:complete_post_triggered + +Execute(ALECompletePost should be triggered when ALE inserts completions): + call ale#completion#HandleUserData({ + \ 'user_data': json_encode({'_ale_completion_item': 1}), + \}) + + Assert g:complete_post_triggered diff --git a/test/completion/test_completion_events.vader b/test/completion/test_completion_events.vader index 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_completion_filtering.vader b/test/completion/test_completion_filtering.vader index c5f14266..172203a4 100644 --- a/test/completion/test_completion_filtering.vader +++ b/test/completion/test_completion_filtering.vader @@ -12,13 +12,24 @@ After: Execute(Prefix filtering should work for Lists of strings): AssertEqual \ ['FooBar', 'foo'], - \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], 'foo') + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], 'foo', 0) AssertEqual \ ['FooBar', 'FongBar', 'baz', 'foo'], - \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.') + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.', 0) AssertEqual \ ['FooBar', 'FongBar', 'baz', 'foo'], - \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '') + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '', 0) + +Execute(Exact filtering should work): + AssertEqual + \ ['foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], 'foo', 1) + AssertEqual + \ ['FooBar', 'FongBar', 'baz', 'foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'baz', 'foo'], '.', 1) + AssertEqual + \ ['Foo'], + \ ale#completion#Filter(bufnr(''), '', ['FooBar', 'FongBar', 'Foo', 'foo'], 'Foo', 1) Execute(Prefix filtering should work for completion items): AssertEqual @@ -32,7 +43,8 @@ Execute(Prefix filtering should work for completion items): \ {'word': 'baz'}, \ {'word': 'foo'}, \ ], - \ 'foo' + \ 'foo', + \ 0, \ ) AssertEqual @@ -51,7 +63,8 @@ Execute(Prefix filtering should work for completion items): \ {'word': 'baz'}, \ {'word': 'foo'}, \ ], - \ '.' + \ '.', + \ 0, \ ) Execute(Excluding words from completion results should work): @@ -66,7 +79,8 @@ Execute(Excluding words from completion results should work): \ {'word': 'Italian'}, \ {'word': 'it'}, \ ], - \ 'it' + \ 'it', + \ 0, \ ) AssertEqual @@ -78,7 +92,8 @@ Execute(Excluding words from completion results should work): \ {'word': 'describe'}, \ {'word': 'Deutsch'}, \ ], - \ 'de' + \ 'de', + \ 0, \ ) AssertEqual @@ -90,7 +105,8 @@ Execute(Excluding words from completion results should work): \ {'word': 'describe'}, \ {'word': 'Deutsch'}, \ ], - \ '.' + \ '.', + \ 0, \ ) Execute(Excluding words from completion results should work with lists of Strings): @@ -98,29 +114,29 @@ Execute(Excluding words from completion results should work with lists of String AssertEqual \ ['Italian'], - \ ale#completion#Filter(bufnr(''), '', ['Italian', 'it'], 'it') + \ ale#completion#Filter(bufnr(''), '', ['Italian', 'it'], 'it', 0) AssertEqual \ ['Deutsch'], - \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], 'de') + \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], 'de', 0) AssertEqual \ ['Deutsch'], - \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], '.') + \ ale#completion#Filter(bufnr(''), '', ['describe', 'Deutsch'], '.', 0) AssertEqual \ ['Deutsch'], - \ ale#completion#Filter(bufnr(''), '', ['Deutsch'], '') + \ ale#completion#Filter(bufnr(''), '', ['Deutsch'], '', 0) Execute(Filtering shouldn't modify the original list): let b:ale_completion_excluded_words = ['it', 'describe'] let b:suggestions = [{'word': 'describe'}] - AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, '.') + AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, '.', 0) AssertEqual b:suggestions, [{'word': 'describe'}] - AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, 'de') + AssertEqual [], ale#completion#Filter(bufnr(''), '', b:suggestions, 'de', 0) AssertEqual b:suggestions, [{'word': 'describe'}] Execute(Filtering should respect filetype triggers): let b:suggestions = [{'word': 'describe'}] - AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), '', b:suggestions, '.') - AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '.') - AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '::') + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), '', b:suggestions, '.', 0) + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '.', 0) + AssertEqual b:suggestions, ale#completion#Filter(bufnr(''), 'rust', b:suggestions, '::', 0) diff --git a/test/completion/test_lsp_completion_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<u8>) -> Result<String, FromUtf8Error>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf8_lossy', 'menu': 'pub fn from_utf8_lossy<''a>(v: &''a [u8]) -> Cow<''a, str>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf16', 'menu': 'pub fn from_utf16(v: &[u16]) -> Result<String, FromUtf16Error>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf16_lossy', 'menu': 'pub fn from_utf16_lossy(v: &[u16]) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_raw_parts', 'menu': 'pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_utf8_unchecked', 'menu': 'pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a str>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = Cow<''a, str>>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Searcher', 'menu': 'type Searcher = <&''b str as Pattern<''a>>::Searcher;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'default', 'menu': 'fn default() -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = String;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Target', 'menu': 'type Target = str;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'Err', 'menu': 'type Err = ParseError;', 'info': '', 'kind': 't', 'icase': 1}, - \ {'word': 'from_str', 'menu': 'fn from_str(s: &str) -> Result<String, ParseError>', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from', 'menu': 'fn from(s: &''a str) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from', 'menu': 'fn from(s: Box<str>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, - \ {'word': 'from', 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1}, + \ {'word': 'new', 'menu': 'pub fn new() -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'with_capacity', 'menu': 'pub fn with_capacity(capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8', 'menu': 'pub fn from_utf8(vec: Vec<u8>) -> Result<String, FromUtf8Error>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8_lossy', 'menu': 'pub fn from_utf8_lossy<''a>(v: &''a [u8]) -> Cow<''a, str>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf16', 'menu': 'pub fn from_utf16(v: &[u16]) -> Result<String, FromUtf16Error>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf16_lossy', 'menu': 'pub fn from_utf16_lossy(v: &[u16]) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_raw_parts', 'menu': 'pub unsafe fn from_raw_parts(buf: *mut u8, length: usize, capacity: usize) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_utf8_unchecked', 'menu': 'pub unsafe fn from_utf8_unchecked(bytes: Vec<u8>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a char>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = &''a str>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = String>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_iter', 'menu': 'fn from_iter<I: IntoIterator<Item = Cow<''a, str>>>(iter: I) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Searcher', 'menu': 'type Searcher = <&''b str as Pattern<''a>>::Searcher;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'default', 'menu': 'fn default() -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = String;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Output', 'menu': 'type Output = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Target', 'menu': 'type Target = str;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'Err', 'menu': 'type Err = ParseError;', 'info': '', 'kind': 't', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from_str', 'menu': 'fn from_str(s: &str) -> Result<String, ParseError>', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: &''a str) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: Box<str>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, + \ {'word': 'from', 'menu': 'fn from(s: Cow<''a, str>) -> String', 'info': '', 'kind': 'f', 'icase': 1, 'user_data': json_encode({'_ale_completion_item': 1})}, \], \ ale#completion#ParseLSPCompletions({ \ "jsonrpc":"2.0", @@ -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/lsp/test_engine_lsp_response_handling.vader b/test/lsp/test_engine_lsp_response_handling.vader index 9abfa087..8261f1da 100644 --- a/test/lsp/test_engine_lsp_response_handling.vader +++ b/test/lsp/test_engine_lsp_response_handling.vader @@ -44,9 +44,21 @@ After: Given foobar(An empty file): Execute(tsserver syntax error responses should be handled correctly): runtime ale_linters/typescript/tsserver.vim - call ale#test#SetFilename('filename.ts') + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.ts') + endif + call ale#engine#InitBufferInfo(bufnr('')) + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t') + endif + " When we get syntax errors and no semantic errors, we should keep the " syntax errors. call ale#lsp_linter#HandleLSPResponse(1, { @@ -54,7 +66,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ 'type': 'event', \ 'event': 'syntaxDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ { \ 'start': { @@ -76,7 +88,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ 'type': 'event', \ 'event': 'semanticDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ ], \ }, @@ -104,7 +116,7 @@ Execute(tsserver syntax error responses should be handled correctly): \ 'type': 'event', \ 'event': 'syntaxDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ ], \ }, @@ -146,9 +158,21 @@ Execute(tsserver syntax error responses should be handled correctly): Execute(tsserver semantic error responses should be handled correctly): runtime ale_linters/typescript/tsserver.vim - call ale#test#SetFilename('filename.ts') + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.ts') + endif + call ale#engine#InitBufferInfo(bufnr('')) + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t') + endif + " When we get syntax errors and no semantic errors, we should keep the " syntax errors. call ale#lsp_linter#HandleLSPResponse(1, { @@ -156,7 +180,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ 'type': 'event', \ 'event': 'syntaxDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ ], \ }, @@ -166,7 +190,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ 'type': 'event', \ 'event': 'semanticDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ { \ 'start': { @@ -206,7 +230,7 @@ Execute(tsserver semantic error responses should be handled correctly): \ 'type': 'event', \ 'event': 'semanticDiag', \ 'body': { - \ 'file': g:dir . '/filename.ts', + \ 'file': expand('%:p'), \ 'diagnostics':[ \ ], \ }, @@ -270,15 +294,27 @@ Execute(tsserver errors should mark tsserver no longer active): Execute(LSP diagnostics responses should be handled correctly): let b:ale_linters = ['eclipselsp'] runtime ale_linters/java/eclipselsp.vim - call ale#test#SetFilename('filename.java') + + if has('win32') + call ale#test#SetFilename('filename,[]^$.ts') + else + call ale#test#SetFilename('filename*?,{}[]^$.java') + endif + call ale#engine#InitBufferInfo(bufnr('')) call ale#lsp_linter#SetLSPLinterMap({'1': 'eclipselsp'}) + if has('win32') + AssertEqual 'filename,[]^$.ts', expand('%:p:t') + else + AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t') + endif + call ale#lsp_linter#HandleLSPResponse(1, { \ 'jsonrpc':'2.0', \ 'method':'textDocument/publishDiagnostics', \ 'params': { - \ 'uri':'file://' . g:dir . '/filename.java', + \ 'uri': ale#path#ToURI(expand('%:p')), \ 'diagnostics': [ \ { \ 'range': { diff --git a/test/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' diff --git a/test/test_embertemplatelint_executable_detection.vader b/test/test_embertemplatelint_executable_detection.vader deleted file mode 100644 index bd0f5dd9..00000000 --- a/test/test_embertemplatelint_executable_detection.vader +++ /dev/null @@ -1,22 +0,0 @@ -Before: - call ale#test#SetDirectory('/testplugin/test') - - runtime ale_linters/handlebars/embertemplatelint.vim - -After: - call ale#test#RestoreDirectory() - call ale#linter#Reset() - -Execute(ember-template-lint executables runs the right command): - call ale#test#SetFilename('ember-template-lint-test-files/app/template.hbs') - - AssertEqual - \ ale_linters#handlebars#embertemplatelint#GetCommand(bufnr(''), [2, 0, 0]), - \ '%e --json --filename %s' - -Execute(old ember-template-lint executables runs the right command): - call ale#test#SetFilename('ember-template-lint-test-files/app/template.hbs') - - AssertEqual - \ ale_linters#handlebars#embertemplatelint#GetCommand(bufnr(''), [1, 5, 0]), - \ '%e --json %t' |