mirror of
https://github.com/dense-analysis/ale.git
synced 2025-12-07 05:04:28 +08:00
Close #3285 - lint_file is now dynamic
`lint_file` can now be computed dynamically with a callback function, which can return a deferred result, as per `ale#command#Run`. This allows linters to dynamically switch between checking files on disk, or checking code on the fly. Some tests have been fixed on Windows.
This commit is contained in:
@@ -417,7 +417,7 @@ function! s:RunJob(command, options) abort
|
||||
let l:buffer = a:options.buffer
|
||||
let l:linter = a:options.linter
|
||||
let l:output_stream = a:options.output_stream
|
||||
let l:read_buffer = a:options.read_buffer
|
||||
let l:read_buffer = a:options.read_buffer && !a:options.lint_file
|
||||
let l:info = g:ale_buffer_info[l:buffer]
|
||||
|
||||
let l:Callback = function('s:HandleExit', [{
|
||||
@@ -508,10 +508,15 @@ function! s:AddProblemsFromOtherBuffers(buffer, linters) abort
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:RunIfExecutable(buffer, linter, executable) abort
|
||||
function! s:RunIfExecutable(buffer, linter, lint_file, executable) abort
|
||||
if ale#command#IsDeferred(a:executable)
|
||||
let a:executable.result_callback = {
|
||||
\ executable -> s:RunIfExecutable(a:buffer, a:linter, executable)
|
||||
\ executable -> s:RunIfExecutable(
|
||||
\ a:buffer,
|
||||
\ a:linter,
|
||||
\ a:lint_file,
|
||||
\ executable
|
||||
\ )
|
||||
\}
|
||||
|
||||
return 1
|
||||
@@ -519,7 +524,7 @@ function! s:RunIfExecutable(buffer, linter, executable) abort
|
||||
|
||||
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'
|
||||
let l:job_type = a:lint_file ? 'file_linter' : 'linter'
|
||||
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
|
||||
|
||||
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
|
||||
@@ -529,6 +534,7 @@ function! s:RunIfExecutable(buffer, linter, executable) abort
|
||||
\ 'linter': a:linter,
|
||||
\ 'output_stream': get(a:linter, 'output_stream', 'stdout'),
|
||||
\ 'read_buffer': a:linter.read_buffer,
|
||||
\ 'lint_file': a:lint_file,
|
||||
\}
|
||||
|
||||
return s:RunJob(l:command, l:options)
|
||||
@@ -540,33 +546,62 @@ endfunction
|
||||
" Run a linter for a buffer.
|
||||
"
|
||||
" Returns 1 if the linter was successfully run.
|
||||
function! s:RunLinter(buffer, linter) abort
|
||||
function! s:RunLinter(buffer, linter, lint_file) abort
|
||||
if !empty(a:linter.lsp)
|
||||
return ale#lsp_linter#CheckWithLSP(a:buffer, a:linter)
|
||||
else
|
||||
let l:executable = ale#linter#GetExecutable(a:buffer, a:linter)
|
||||
|
||||
return s:RunIfExecutable(a:buffer, a:linter, l:executable)
|
||||
return s:RunIfExecutable(a:buffer, a:linter, a:lint_file, l:executable)
|
||||
endif
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort
|
||||
" Initialise the buffer information if needed.
|
||||
let l:new_buffer = ale#engine#InitBufferInfo(a:buffer)
|
||||
call s:StopCurrentJobs(a:buffer, a:should_lint_file)
|
||||
call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters)
|
||||
function! s:GetLintFileValues(slots, Callback) abort
|
||||
let l:deferred_list = []
|
||||
let l:new_slots = []
|
||||
|
||||
" We can only clear the results if we aren't checking the buffer.
|
||||
let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer)
|
||||
for [l:lint_file, l:linter] in a:slots
|
||||
while ale#command#IsDeferred(l:lint_file) && has_key(l:lint_file, 'value')
|
||||
" If we've already computed the return value, use it.
|
||||
let l:lint_file = l:lint_file.value
|
||||
endwhile
|
||||
|
||||
silent doautocmd <nomodeline> User ALELintPre
|
||||
if ale#command#IsDeferred(l:lint_file)
|
||||
" If we are going to return the result later, wait for it.
|
||||
call add(l:deferred_list, l:lint_file)
|
||||
else
|
||||
" If we have the value now, coerce it to 0 or 1.
|
||||
let l:lint_file = l:lint_file is 1
|
||||
endif
|
||||
|
||||
for l:linter in a:linters
|
||||
call add(l:new_slots, [l:lint_file, l:linter])
|
||||
endfor
|
||||
|
||||
if !empty(l:deferred_list)
|
||||
for l:deferred in l:deferred_list
|
||||
let l:deferred.result_callback =
|
||||
\ {-> s:GetLintFileValues(l:new_slots, a:Callback)}
|
||||
endfor
|
||||
else
|
||||
call a:Callback(l:new_slots)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:RunLinters(
|
||||
\ buffer,
|
||||
\ slots,
|
||||
\ should_lint_file,
|
||||
\ new_buffer,
|
||||
\ can_clear_results
|
||||
\) abort
|
||||
let l:can_clear_results = a:can_clear_results
|
||||
|
||||
for [l:lint_file, l:linter] in a:slots
|
||||
" Only run lint_file linters if we should.
|
||||
if !l:linter.lint_file || a:should_lint_file
|
||||
if s:RunLinter(a:buffer, l:linter)
|
||||
if !l:lint_file || a:should_lint_file
|
||||
if s:RunLinter(a:buffer, l:linter, l:lint_file)
|
||||
" If a single linter ran, we shouldn't clear everything.
|
||||
let l:can_clear_results = 0
|
||||
endif
|
||||
@@ -581,11 +616,49 @@ function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort
|
||||
" disabled, or ALE itself is disabled.
|
||||
if l:can_clear_results
|
||||
call ale#engine#SetResults(a:buffer, [])
|
||||
elseif l:new_buffer
|
||||
call s:AddProblemsFromOtherBuffers(a:buffer, a:linters)
|
||||
elseif a:new_buffer
|
||||
call s:AddProblemsFromOtherBuffers(
|
||||
\ a:buffer,
|
||||
\ map(copy(a:slots), 'v:val[1]')
|
||||
\)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort
|
||||
" Initialise the buffer information if needed.
|
||||
let l:new_buffer = ale#engine#InitBufferInfo(a:buffer)
|
||||
call s:StopCurrentJobs(a:buffer, a:should_lint_file)
|
||||
call s:RemoveProblemsForDisabledLinters(a:buffer, a:linters)
|
||||
|
||||
" We can only clear the results if we aren't checking the buffer.
|
||||
let l:can_clear_results = !ale#engine#IsCheckingBuffer(a:buffer)
|
||||
|
||||
silent doautocmd <nomodeline> User ALELintPre
|
||||
|
||||
" Handle `lint_file` callbacks first.
|
||||
let l:linter_slots = []
|
||||
|
||||
for l:linter in a:linters
|
||||
let l:LintFile = l:linter.lint_file
|
||||
|
||||
if type(l:LintFile) is v:t_func
|
||||
let l:LintFile = l:LintFile(a:buffer)
|
||||
endif
|
||||
|
||||
call add(l:linter_slots, [l:LintFile, l:linter])
|
||||
endfor
|
||||
|
||||
call s:GetLintFileValues(l:linter_slots, {
|
||||
\ new_slots -> s:RunLinters(
|
||||
\ a:buffer,
|
||||
\ new_slots,
|
||||
\ a:should_lint_file,
|
||||
\ l:new_buffer,
|
||||
\ l:can_clear_results,
|
||||
\ )
|
||||
\})
|
||||
endfunction
|
||||
|
||||
" Clean up a buffer.
|
||||
"
|
||||
" This function will stop all current jobs for the buffer,
|
||||
|
||||
@@ -211,21 +211,17 @@ function! ale#linter#PreProcess(filetype, linter) abort
|
||||
" file on disk.
|
||||
let l:obj.lint_file = get(a:linter, 'lint_file', 0)
|
||||
|
||||
if !s:IsBoolean(l:obj.lint_file)
|
||||
throw '`lint_file` must be `0` or `1`'
|
||||
if !s:IsBoolean(l:obj.lint_file) && type(l:obj.lint_file) isnot v:t_func
|
||||
throw '`lint_file` must be `0`, `1`, or a Function'
|
||||
endif
|
||||
|
||||
" An option indicating that the buffer should be read.
|
||||
let l:obj.read_buffer = get(a:linter, 'read_buffer', !l:obj.lint_file)
|
||||
let l:obj.read_buffer = get(a:linter, 'read_buffer', 1)
|
||||
|
||||
if !s:IsBoolean(l:obj.read_buffer)
|
||||
throw '`read_buffer` must be `0` or `1`'
|
||||
endif
|
||||
|
||||
if l:obj.lint_file && l:obj.read_buffer
|
||||
throw 'Only one of `lint_file` or `read_buffer` can be `1`'
|
||||
endif
|
||||
|
||||
let l:obj.aliases = get(a:linter, 'aliases', [])
|
||||
|
||||
if type(l:obj.aliases) isnot v:t_list
|
||||
|
||||
Reference in New Issue
Block a user