#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:linter = s:GetLinter()
|
||||||
let l:executable = ale#linter#GetExecutable(l:buffer, l:linter)
|
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')
|
if has_key(l:linter, 'command_chain')
|
||||||
let l:callbacks = map(copy(l:linter.command_chain), 'v:val.callback')
|
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
|
AssertEqual a:expected_address, l:address
|
||||||
endfunction
|
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.
|
" A dummy function for making sure this module is loaded.
|
||||||
function! ale#assert#SetUpLinterTest(filetype, name) abort
|
function! ale#assert#SetUpLinterTest(filetype, name) abort
|
||||||
" Set up a marker so ALE doesn't create real random temporary filenames.
|
" 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')
|
call ale#test#SetDirectory('/testplugin/test/command_callback')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
command! -nargs=+ WithChainResults :call ale#assert#WithChainResults(<args>)
|
call ale#assert#SetUpLinterTestCommands()
|
||||||
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
|
endfunction
|
||||||
|
|
||||||
function! ale#assert#TearDownLinterTest() abort
|
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.
|
" If the callback starts any new jobs, use the same job type for them.
|
||||||
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
|
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,
|
\ 'exit_code': a:data.exit_code,
|
||||||
\ 'temporary_file': a:data.temporary_file,
|
\ 'temporary_file': a:data.temporary_file,
|
||||||
\})
|
\})
|
||||||
|
|
||||||
if get(a:data, 'result_callback', v:null) isnot v:null
|
let l:result = a:data.result
|
||||||
call call(a:data.result_callback, [l: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
|
endif
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! ale#command#Run(buffer, command, Callback, options) abort
|
function! ale#command#Run(buffer, command, Callback, ...) abort
|
||||||
let l:output_stream = get(a:options, 'output_stream', 'stdout')
|
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:line_list = []
|
||||||
|
|
||||||
let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand(
|
let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand(
|
||||||
\ a:buffer,
|
\ a:buffer,
|
||||||
\ get(a:options, 'executable', ''),
|
\ get(l:options, 'executable', ''),
|
||||||
\ a:command,
|
\ a:command,
|
||||||
\ get(a:options, 'read_buffer', 0),
|
\ get(l:options, 'read_buffer', 0),
|
||||||
\ get(a:options, 'input', v:null),
|
\ get(l:options, 'input', v:null),
|
||||||
\)
|
\)
|
||||||
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
|
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
|
||||||
let l:job_options = {
|
let l:job_options = {
|
||||||
@ -267,8 +276,8 @@ function! ale#command#Run(buffer, command, Callback, options) abort
|
|||||||
\ 'job_id': job_id,
|
\ 'job_id': job_id,
|
||||||
\ 'exit_code': exit_code,
|
\ 'exit_code': exit_code,
|
||||||
\ 'temporary_file': l:temporary_file,
|
\ 'temporary_file': l:temporary_file,
|
||||||
\ 'log_output': get(a:options, 'log_output', 1),
|
\ 'log_output': get(l:options, 'log_output', 1),
|
||||||
\ 'result_callback': get(l:result, 'result_callback', v:null),
|
\ 'result': l:result,
|
||||||
\ }
|
\ }
|
||||||
\ )},
|
\ )},
|
||||||
\ 'mode': 'nl',
|
\ 'mode': 'nl',
|
||||||
@ -344,3 +353,7 @@ function! ale#command#Run(buffer, command, Callback, options) abort
|
|||||||
|
|
||||||
return l:result
|
return l:result
|
||||||
endfunction
|
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
|
endif
|
||||||
endfunction
|
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.
|
" Run a linter for a buffer.
|
||||||
"
|
"
|
||||||
" Returns 1 if the linter was successfully run.
|
" Returns 1 if the linter was successfully run.
|
||||||
@ -599,13 +619,7 @@ function! s:RunLinter(buffer, linter) abort
|
|||||||
else
|
else
|
||||||
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
||||||
|
|
||||||
" Use different job types for file or linter jobs.
|
return s:RunIfExecutable(a:buffer, a:linter, l:executable)
|
||||||
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
|
|
||||||
endif
|
endif
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
@ -120,7 +120,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
|||||||
let l:obj.executable = a:linter.executable
|
let l:obj.executable = a:linter.executable
|
||||||
|
|
||||||
if type(l:obj.executable) isnot v:t_string
|
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
|
endif
|
||||||
else
|
else
|
||||||
throw 'Either `executable` or `executable_callback` must be defined'
|
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.
|
" Given a buffer and linter, get the executable String for the linter.
|
||||||
function! ale#linter#GetExecutable(buffer, linter) abort
|
function! ale#linter#GetExecutable(buffer, linter) abort
|
||||||
return has_key(a:linter, 'executable_callback')
|
let l:Executable = has_key(a:linter, 'executable_callback')
|
||||||
\ ? ale#util#GetFunction(a:linter.executable_callback)(a:buffer)
|
\ ? function(a:linter.executable_callback)
|
||||||
\ : a:linter.executable
|
\ : a:linter.executable
|
||||||
|
|
||||||
|
return type(l:Executable) is v:t_func
|
||||||
|
\ ? l:Executable(a:buffer)
|
||||||
|
\ : l:Executable
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" Given a buffer and linter, get the command String for the linter.
|
" 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.
|
Human-readable |String| error code.
|
||||||
|
|
||||||
`executable` A |String| naming the executable itself which
|
`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.
|
program requested is installed or not.
|
||||||
|
|
||||||
Either this or the `executable_callback` argument
|
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,
|
\ 'executable': 123,
|
||||||
\ 'command': 'echo',
|
\ '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):
|
Execute (PreProcess should throw when executable_callback is not a callback):
|
||||||
AssertThrows call ale#linter#PreProcess('testft', {
|
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
|
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):
|
Execute (PreProcess should throw when there is no command):
|
||||||
AssertThrows call ale#linter#PreProcess('testft', {
|
AssertThrows call ale#linter#PreProcess('testft', {
|
||||||
\ 'name': 'foo',
|
\ 'name': 'foo',
|
||||||
|
Loading…
Reference in New Issue
Block a user