From ffa45fa3fb44ade28c64aa8f0a21acd71c903a2a Mon Sep 17 00:00:00 2001 From: w0rp Date: Thu, 21 Feb 2019 21:24:41 +0000 Subject: [PATCH] #2132 - Implement deferred command handling for linters --- autoload/ale/assert.vim | 7 +++- autoload/ale/command.vim | 4 ++ autoload/ale/engine.vim | 12 +++++- autoload/ale/fix.vim | 2 +- autoload/ale/linter.vim | 11 +++-- doc/ale.txt | 5 ++- test/test_deferred_command_string.vader | 46 +++++++++++++++++++++ test/test_deferred_executable_string.vader | 16 ++++++- test/test_linter_defintion_processing.vader | 10 ++++- 9 files changed, 102 insertions(+), 11 deletions(-) create mode 100644 test/test_deferred_command_string.vader diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim index ef2dab9e..1b004649 100644 --- a/autoload/ale/assert.vim +++ b/autoload/ale/assert.vim @@ -59,13 +59,18 @@ function! ale#assert#Linter(expected_executable, expected_command) abort endif else let l:command = ale#linter#GetCommand(l:buffer, l:linter) + + while ale#command#IsDeferred(l:command) + call ale#test#FlushJobs() + let l:command = l:command.value + endwhile endif if type(l:command) is v:t_string " Replace %e with the escaped executable, so tests keep passing after " linters are changed to use %e. let l:command = substitute(l:command, '%e', '\=ale#Escape(l:executable)', 'g') - else + elseif type(l:command) is v:t_list call map(l:command, 'substitute(v:val, ''%e'', ''\=ale#Escape(l:executable)'', ''g'')') endif diff --git a/autoload/ale/command.vim b/autoload/ale/command.vim index 7a66dc77..33ce577c 100644 --- a/autoload/ale/command.vim +++ b/autoload/ale/command.vim @@ -320,6 +320,10 @@ function! ale#command#Run(buffer, command, Callback, ...) abort call ale#history#Add(a:buffer, l:status, l:job_id, l:command) endif + if !l:job_id + return 0 + endif + " We'll return this Dictionary. A `result_callback` can be assigned to it " later for capturing the result of a:Callback. " diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index b4035422..340f6913 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -422,8 +422,16 @@ endfunction " Run a job. " -" Returns 1 when the job was started successfully. +" Returns 1 when a job was started successfully. function! s:RunJob(command, options) abort + if ale#command#IsDeferred(a:command) + let a:command.result_callback = { + \ command -> s:RunJob(command, a:options) + \} + + return 1 + endif + let l:command = a:command if empty(l:command) @@ -451,7 +459,7 @@ function! s:RunJob(command, options) abort \}) " Only proceed if the job is being run. - if !l:result._deferred_job_id + if empty(l:result) return 0 endif diff --git a/autoload/ale/fix.vim b/autoload/ale/fix.vim index 7c428f52..48874a8a 100644 --- a/autoload/ale/fix.vim +++ b/autoload/ale/fix.vim @@ -179,7 +179,7 @@ function! s:RunJob(options) abort \ 'log_output': 0, \}) - return l:result._deferred_job_id != 0 + return !empty(l:result) endfunction function! s:RunFixer(options) abort diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index 86ee506c..8c9f83ad 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -177,7 +177,8 @@ function! ale#linter#PreProcess(filetype, linter) abort let l:obj.command = a:linter.command if type(l:obj.command) isnot v:t_string - throw '`command` must be a string if defined' + \&& type(l:obj.command) isnot v:t_func + throw '`command` must be a String or Function if defined' endif else throw 'Either `command`, `executable_callback`, `command_chain` ' @@ -489,9 +490,13 @@ endfunction " Given a buffer and linter, get the command String for the linter. " The command_chain key is not supported. function! ale#linter#GetCommand(buffer, linter) abort - return has_key(a:linter, 'command_callback') - \ ? ale#util#GetFunction(a:linter.command_callback)(a:buffer) + let l:Command = has_key(a:linter, 'command_callback') + \ ? function(a:linter.command_callback) \ : a:linter.command + + return type(l:Command) is v:t_func + \ ? l:Command(a:buffer) + \ : l:Command endfunction " Given a buffer and linter, get the address for connecting to the server. diff --git a/doc/ale.txt b/doc/ale.txt index 22639251..083b0507 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -3122,7 +3122,10 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()* used in place of `executable` when more complicated processing is needed. - `command` A |String| for an executable to run asynchronously. + `command` A |String| for a command to run asynchronously, or a + |Funcref| for a function to call for computing the + command, accepting a buffer number. + This command will be fed the lines from the buffer to check, and will produce the lines of output given to the `callback`. diff --git a/test/test_deferred_command_string.vader b/test/test_deferred_command_string.vader new file mode 100644 index 00000000..4d0c8977 --- /dev/null +++ b/test/test_deferred_command_string.vader @@ -0,0 +1,46 @@ +Before: + Save g:ale_run_synchronously + Save g:ale_emulate_job_failure + Save g:ale_buffer_info + + let g:ale_run_synchronously = 1 + let g:ale_buffer_info = {} + let b:ale_history = [] + + call ale#linter#Reset() + call ale#assert#SetUpLinterTestCommands() + call ale#linter#Define('foobar', { + \ 'name': 'lint_file_linter', + \ 'callback': 'LintFileCallback', + \ 'executable': 'echo', + \ 'command': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 'foo'})})}, + \ 'read_buffer': 0, + \}) + +After: + Restore + + call ale#assert#TearDownLinterTest() + +Given foobar (Some imaginary filetype): +Execute(It should be possible to compute an executable to check based on the result of commands): + AssertLinter 'echo', 'foo' + + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ 1, + \ len(filter(copy(b:ale_history), 'string(v:val.command) =~# ''foo''')) + +Execute(It handle the deferred command failing): + let g:ale_emulate_job_failure = 1 + + AssertLinter 'echo', 0 + + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ 0, + \ len(filter(copy(b:ale_history), 'string(v:val.command) =~# ''foo''')) diff --git a/test/test_deferred_executable_string.vader b/test/test_deferred_executable_string.vader index ad2e752b..3bdc5251 100644 --- a/test/test_deferred_executable_string.vader +++ b/test/test_deferred_executable_string.vader @@ -1,9 +1,11 @@ Before: Save g:ale_run_synchronously + Save g:ale_emulate_job_failure Save g:ale_buffer_info let g:ale_run_synchronously = 1 let g:ale_buffer_info = {} + let b:ale_history = [] call ale#linter#Reset() call ale#assert#SetUpLinterTestCommands() @@ -22,8 +24,6 @@ After: Given foobar (Some imaginary filetype): Execute(It should be possible to compute an executable to check based on the result of commands): - let b:ale_history = [] - AssertLinter 'foo', 'echo' ALELint @@ -32,3 +32,15 @@ Execute(It should be possible to compute an executable to check based on the res AssertEqual \ [{'status': 0, 'job_id': 'executable', 'command': 'foo'}], \ filter(copy(b:ale_history), 'v:val.job_id is# ''executable''') + +Execute(It handle the deferred command failing): + let g:ale_emulate_job_failure = 1 + + AssertLinter 0, 'echo' + + ALELint + call ale#test#FlushJobs() + + AssertEqual + \ [], + \ filter(copy(b:ale_history), 'v:val.job_id is# ''executable''') diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader index 321c6212..0f54cf0e 100644 --- a/test/test_linter_defintion_processing.vader +++ b/test/test_linter_defintion_processing.vader @@ -82,7 +82,15 @@ Execute (PreProcess should throw when command is not a string): \ 'executable': 'echo', \ 'command': [], \}) - AssertEqual '`command` must be a string if defined', g:vader_exception + AssertEqual '`command` must be a String or Function if defined', g:vader_exception + +Execute (PreProcess should allow command to be a callback): + call ale#linter#PreProcess('testft', { + \ 'name': 'foo', + \ 'callback': 'SomeFunction', + \ 'executable': 'echo', + \ 'command': function('type'), + \}) Execute (PreProcess should throw when command_callback is not a callback): AssertThrows call ale#linter#PreProcess('testft', {