#2132 - Implement deferred executable string handling for linters
This commit is contained in:
parent
bf196ba17c
commit
926ad47a49
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
32
test/test_deferred_executable_string.vader
Normal file
32
test/test_deferred_executable_string.vader
Normal 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''')
|
@ -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',
|
||||
|
Loading…
Reference in New Issue
Block a user