summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorw0rp <devw0rp@gmail.com>2019-02-12 18:05:33 +0000
committerw0rp <devw0rp@gmail.com>2019-02-12 18:05:33 +0000
commit926ad47a49d74c910c610d4a9cedd5c4838d679d (patch)
tree9f86511f8854e9965978092fcb233a34e3516440
parentbf196ba17c9e261e4e3a9dba64260c6d0b2c8af9 (diff)
downloadale-926ad47a49d74c910c610d4a9cedd5c4838d679d.zip
#2132 - Implement deferred executable string handling for linters
-rw-r--r--autoload/ale/assert.vim27
-rw-r--r--autoload/ale/command.vim33
-rw-r--r--autoload/ale/engine.vim28
-rw-r--r--autoload/ale/linter.vim11
-rw-r--r--doc/ale.txt6
-rw-r--r--test/test_deferred_executable_string.vader32
-rw-r--r--test/test_linter_defintion_processing.vader10
7 files changed, 116 insertions, 31 deletions
diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim
index 7139f05c..ef2dab9e 100644
--- a/autoload/ale/assert.vim
+++ b/autoload/ale/assert.vim
@@ -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
diff --git a/autoload/ale/command.vim b/autoload/ale/command.vim
index 89734685..7a66dc77 100644
--- a/autoload/ale/command.vim
+++ b/autoload/ale/command.vim
@@ -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
diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim
index 16653dba..c469bf5e 100644
--- a/autoload/ale/engine.vim
+++ b/autoload/ale/engine.vim
@@ -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
diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim
index 0b61bad1..86ee506c 100644
--- a/autoload/ale/linter.vim
+++ b/autoload/ale/linter.vim
@@ -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.
diff --git a/doc/ale.txt b/doc/ale.txt
index 16da5c23..0fb059f9 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -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
diff --git a/test/test_deferred_executable_string.vader b/test/test_deferred_executable_string.vader
new file mode 100644
index 00000000..65582f1f
--- /dev/null
+++ b/test/test_deferred_executable_string.vader
@@ -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''')
diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader
index d967761d..321c6212 100644
--- a/test/test_linter_defintion_processing.vader
+++ b/test/test_linter_defintion_processing.vader
@@ -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',