#2132 - Implement deferred executable string handling for linters

This commit is contained in:
w0rp 2019-02-12 18:05:33 +00:00
parent bf196ba17c
commit 926ad47a49
No known key found for this signature in database
GPG Key ID: 0FC1ECAA8C81CD83
7 changed files with 116 additions and 31 deletions

View File

@ -26,6 +26,11 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
let l:linter = s:GetLinter()
let l:executable = ale#linter#GetExecutable(l:buffer, l:linter)
while ale#command#IsDeferred(l:executable)
call ale#test#FlushJobs()
let l:executable = l:executable.value
endwhile
if has_key(l:linter, 'command_chain')
let l:callbacks = map(copy(l:linter.command_chain), 'v:val.callback')
@ -125,6 +130,18 @@ function! ale#assert#LSPAddress(expected_address) abort
AssertEqual a:expected_address, l:address
endfunction
function! ale#assert#SetUpLinterTestCommands() abort
command! -nargs=+ WithChainResults :call ale#assert#WithChainResults(<args>)
command! -nargs=+ AssertLinter :call ale#assert#Linter(<args>)
command! -nargs=0 AssertLinterNotExecuted :call ale#assert#LinterNotExecuted()
command! -nargs=+ AssertLSPOptions :call ale#assert#LSPOptions(<args>)
command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig(<args>)
command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage(<args>)
command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject(<args>)
command! -nargs=+ AssertLSPProjectFull :call ale#assert#LSPProjectFull(<args>)
command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress(<args>)
endfunction
" A dummy function for making sure this module is loaded.
function! ale#assert#SetUpLinterTest(filetype, name) abort
" Set up a marker so ALE doesn't create real random temporary filenames.
@ -159,15 +176,7 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort
call ale#test#SetDirectory('/testplugin/test/command_callback')
endif
command! -nargs=+ WithChainResults :call ale#assert#WithChainResults(<args>)
command! -nargs=+ AssertLinter :call ale#assert#Linter(<args>)
command! -nargs=0 AssertLinterNotExecuted :call ale#assert#LinterNotExecuted()
command! -nargs=+ AssertLSPOptions :call ale#assert#LSPOptions(<args>)
command! -nargs=+ AssertLSPConfig :call ale#assert#LSPConfig(<args>)
command! -nargs=+ AssertLSPLanguage :call ale#assert#LSPLanguage(<args>)
command! -nargs=+ AssertLSPProject :call ale#assert#LSPProject(<args>)
command! -nargs=+ AssertLSPProjectFull :call ale#assert#LSPProjectFull(<args>)
command! -nargs=+ AssertLSPAddress :call ale#assert#LSPAddress(<args>)
call ale#assert#SetUpLinterTestCommands()
endfunction
function! ale#assert#TearDownLinterTest() abort

View File

@ -236,26 +236,35 @@ function! s:ExitCallback(buffer, line_list, Callback, data) abort
" If the callback starts any new jobs, use the same job type for them.
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
let l:result = a:Callback(a:buffer, a:line_list, {
let l:value = a:Callback(a:buffer, a:line_list, {
\ 'exit_code': a:data.exit_code,
\ 'temporary_file': a:data.temporary_file,
\})
if get(a:data, 'result_callback', v:null) isnot v:null
call call(a:data.result_callback, [l:result])
let l:result = a:data.result
let l:result.value = l:value
if get(l:result, 'result_callback', v:null) isnot v:null
call call(l:result.result_callback, [l:value])
endif
endfunction
function! ale#command#Run(buffer, command, Callback, options) abort
let l:output_stream = get(a:options, 'output_stream', 'stdout')
function! ale#command#Run(buffer, command, Callback, ...) abort
let l:options = get(a:000, 0, {})
if len(a:000) > 1
throw 'Too many arguments!'
endif
let l:output_stream = get(l:options, 'output_stream', 'stdout')
let l:line_list = []
let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand(
\ a:buffer,
\ get(a:options, 'executable', ''),
\ get(l:options, 'executable', ''),
\ a:command,
\ get(a:options, 'read_buffer', 0),
\ get(a:options, 'input', v:null),
\ get(l:options, 'read_buffer', 0),
\ get(l:options, 'input', v:null),
\)
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
let l:job_options = {
@ -267,8 +276,8 @@ function! ale#command#Run(buffer, command, Callback, options) abort
\ 'job_id': job_id,
\ 'exit_code': exit_code,
\ 'temporary_file': l:temporary_file,
\ 'log_output': get(a:options, 'log_output', 1),
\ 'result_callback': get(l:result, 'result_callback', v:null),
\ 'log_output': get(l:options, 'log_output', 1),
\ 'result': l:result,
\ }
\ )},
\ 'mode': 'nl',
@ -344,3 +353,7 @@ function! ale#command#Run(buffer, command, Callback, options) abort
return l:result
endfunction
function! ale#command#IsDeferred(value) abort
return type(a:value) is v:t_dict && has_key(a:value, '_deferred_job_id')
endfunction

View File

@ -590,6 +590,26 @@ function! s:AddProblemsFromOtherBuffers(buffer, linters) abort
endif
endfunction
function! s:RunIfExecutable(buffer, linter, executable) abort
if ale#command#IsDeferred(a:executable)
let a:executable.result_callback = {
\ executable -> s:RunIfExecutable(a:buffer, a:linter, executable)
\}
return 1
endif
if ale#engine#IsExecutable(a:buffer, a:executable)
" Use different job types for file or linter jobs.
let l:job_type = a:linter.lint_file ? 'file_linter' : 'linter'
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
return s:InvokeChain(a:buffer, a:executable, a:linter, 0, [])
endif
return 0
endfunction
" Run a linter for a buffer.
"
" Returns 1 if the linter was successfully run.
@ -599,13 +619,7 @@ function! s:RunLinter(buffer, linter) abort
else
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
" Use different job types for file or linter jobs.
let l:job_type = a:linter.lint_file ? 'file_linter' : 'linter'
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
if ale#engine#IsExecutable(a:buffer, l:executable)
return s:InvokeChain(a:buffer, l:executable, a:linter, 0, [])
endif
return s:RunIfExecutable(a:buffer, a:linter, l:executable)
endif
return 0

View File

@ -120,7 +120,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
let l:obj.executable = a:linter.executable
if type(l:obj.executable) isnot v:t_string
throw '`executable` must be a string if defined'
\&& type(l:obj.executable) isnot v:t_func
throw '`executable` must be a String or Function if defined'
endif
else
throw 'Either `executable` or `executable_callback` must be defined'
@ -476,9 +477,13 @@ endfunction
" Given a buffer and linter, get the executable String for the linter.
function! ale#linter#GetExecutable(buffer, linter) abort
return has_key(a:linter, 'executable_callback')
\ ? ale#util#GetFunction(a:linter.executable_callback)(a:buffer)
let l:Executable = has_key(a:linter, 'executable_callback')
\ ? function(a:linter.executable_callback)
\ : a:linter.executable
return type(l:Executable) is v:t_func
\ ? l:Executable(a:buffer)
\ : l:Executable
endfunction
" Given a buffer and linter, get the command String for the linter.

View File

@ -2744,7 +2744,11 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
Human-readable |String| error code.
`executable` A |String| naming the executable itself which
will be run. This value will be used to check if the
will be run, or a |Funcref| for a function to call
for computing the executable, accepting a buffer
number.
This value will be used to check if the
program requested is installed or not.
Either this or the `executable_callback` argument

View File

@ -0,0 +1,32 @@
Before:
Save g:ale_run_synchronously
let g:ale_run_synchronously = 1
call ale#linter#Reset()
call ale#assert#SetUpLinterTestCommands()
call ale#linter#Define('foobar', {
\ 'name': 'lint_file_linter',
\ 'callback': 'LintFileCallback',
\ 'executable': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 'foo'})})},
\ 'command': 'echo',
\ '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):
let b:ale_history = []
AssertLinter 'foo', 'echo'
ALELint
call ale#test#FlushJobs()
AssertEqual
\ [{'status': 0, 'job_id': 'executable', 'command': 'foo'}],
\ filter(copy(b:ale_history), 'v:val.job_id is# ''executable''')

View File

@ -48,7 +48,7 @@ Execute (PreProcess should throw when executable is not a string):
\ 'executable': 123,
\ 'command': 'echo',
\})
AssertEqual '`executable` must be a string if defined', g:vader_exception
AssertEqual '`executable` must be a String or Function if defined', g:vader_exception
Execute (PreProcess should throw when executable_callback is not a callback):
AssertThrows call ale#linter#PreProcess('testft', {
@ -59,6 +59,14 @@ Execute (PreProcess should throw when executable_callback is not a callback):
\})
AssertEqual '`executable_callback` must be a callback if defined', g:vader_exception
Execute (PreProcess should allow executable to be a callback):
call ale#linter#PreProcess('testft', {
\ 'name': 'foo',
\ 'callback': 'SomeFunction',
\ 'executable': function('type'),
\ 'command': 'echo',
\})
Execute (PreProcess should throw when there is no command):
AssertThrows call ale#linter#PreProcess('testft', {
\ 'name': 'foo',