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:
w0rp
2020-08-28 14:02:05 +01:00
parent b8c0ac2e61
commit 34e409ea21
9 changed files with 276 additions and 62 deletions

View File

@@ -1,8 +1,10 @@
Before:
Save g:ale_enabled
Save g:ale_set_lists_synchronously
Save g:ale_buffer_info
Save &shell
let g:ale_enabled = 1
let g:ale_buffer_info = {}
let g:ale_set_lists_synchronously = 1

View File

@@ -0,0 +1,134 @@
Before:
Save g:ale_enabled
Save g:ale_run_synchronously
Save g:ale_set_lists_synchronously
Save g:ale_buffer_info
let g:ale_enabled = 1
let g:ale_buffer_info = {}
let g:ale_run_synchronously = 1
let g:ale_set_lists_synchronously = 1
function! TestCallback(buffer, output)
" Windows adds extra spaces to the text from echo.
return [{
\ 'lnum': 2,
\ 'col': 3,
\ 'text': 'testlinter1',
\}]
endfunction
function! TestCallback2(buffer, output)
" Windows adds extra spaces to the text from echo.
return [{
\ 'lnum': 1,
\ 'col': 3,
\ 'text': 'testlinter2',
\}]
endfunction
function! TestCallback3(buffer, output)
" Windows adds extra spaces to the text from echo.
return [{
\ 'lnum': 3,
\ 'col': 3,
\ 'text': 'testlinter3',
\}]
endfunction
" These two linters computer their lint_file values after running commands.
call ale#linter#Define('foobar', {
\ 'name': 'testlinter1',
\ 'callback': 'TestCallback',
\ 'executable': has('win32') ? 'cmd' : 'echo',
\ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''',
\ 'lint_file': {b -> ale#command#Run(b, 'echo', {-> 1})},
\})
call ale#linter#Define('foobar', {
\ 'name': 'testlinter2',
\ 'callback': 'TestCallback2',
\ 'executable': has('win32') ? 'cmd' : 'echo',
\ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''',
\ 'lint_file': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 1})})},
\})
" This one directly computes the result.
call ale#linter#Define('foobar', {
\ 'name': 'testlinter3',
\ 'callback': 'TestCallback3',
\ 'executable': has('win32') ? 'cmd' : 'echo',
\ 'command': has('win32') ? 'echo foo bar' : '/bin/sh -c ''echo foo bar''',
\ 'lint_file': {b -> 1},
\})
let g:filename = tempname()
call writefile([], g:filename)
call ale#test#SetFilename(g:filename)
After:
delfunction TestCallback
call ale#engine#Cleanup(bufnr(''))
Restore
call ale#linter#Reset()
" Items and markers, etc.
call setloclist(0, [])
call clearmatches()
call ale#sign#Clear()
if filereadable(g:filename)
call delete(g:filename)
endif
unlet g:filename
Given foobar(A file with some lines):
foo
bar
baz
Execute(lint_file results where the result is eventually computed should be run):
call ale#Queue(0, 'lint_file')
call ale#test#FlushJobs()
AssertEqual
\ [
\ {
\ 'bufnr': bufnr('%'),
\ 'lnum': 1,
\ 'vcol': 0,
\ 'col': 3,
\ 'text': 'testlinter2',
\ 'type': 'E',
\ 'nr': -1,
\ 'pattern': '',
\ 'valid': 1,
\ },
\ {
\ 'bufnr': bufnr('%'),
\ 'lnum': 2,
\ 'vcol': 0,
\ 'col': 3,
\ 'text': 'testlinter1',
\ 'type': 'E',
\ 'nr': -1,
\ 'pattern': '',
\ 'valid': 1,
\ },
\ {
\ 'bufnr': bufnr('%'),
\ 'lnum': 3,
\ 'vcol': 0,
\ 'col': 3,
\ 'text': 'testlinter3',
\ 'type': 'E',
\ 'nr': -1,
\ 'pattern': '',
\ 'valid': 1,
\ },
\ ],
\ ale#test#GetLoclistWithoutModule()
Execute(Linters where lint_file eventually evaluates to 1 shouldn't be run if we don't want to run them):
call ale#Queue(0, '')
call ale#test#FlushJobs()
AssertEqual [], ale#test#GetLoclistWithoutModule()

View File

@@ -12,7 +12,7 @@ Before:
call ale#linter#Define('foobar', {
\ 'name': 'lint_file_linter',
\ 'callback': 'LintFileCallback',
\ 'executable': 'echo',
\ 'executable': has('win32') ? 'cmd' : 'echo',
\ 'command': {b -> ale#command#Run(b, 'echo', {-> ale#command#Run(b, 'echo', {-> 'foo'})})},
\ 'read_buffer': 0,
\})
@@ -28,7 +28,7 @@ After:
Given foobar (Some imaginary filetype):
Execute(It should be possible to compute an executable to check based on the result of commands):
AssertLinter 'echo', 'foo'
AssertLinter has('win32') ? 'cmd' : 'echo', 'foo'
ALELint
call ale#test#FlushJobs()
@@ -40,7 +40,7 @@ Execute(It should be possible to compute an executable to check based on the res
Execute(It handle the deferred command failing):
let g:ale_emulate_job_failure = 1
AssertLinter 'echo', 0
AssertLinter has('win32') ? 'cmd' : 'echo', 0
ALELint
call ale#test#FlushJobs()

View File

@@ -174,7 +174,7 @@ Execute(PreProcess should process the lint_file option correctly):
\}
AssertThrows call ale#linter#PreProcess('testft', g:linter)
AssertEqual '`lint_file` must be `0` or `1`', g:vader_exception
AssertEqual '`lint_file` must be `0`, `1`, or a Function', g:vader_exception
let g:linter.lint_file = 0
@@ -185,14 +185,17 @@ Execute(PreProcess should process the lint_file option correctly):
let g:linter.lint_file = 1
AssertEqual 1, ale#linter#PreProcess('testft', g:linter).lint_file
" The default for read_buffer should change to 0 when lint_file is 1.
AssertEqual 0, ale#linter#PreProcess('testft', g:linter).read_buffer
" The default for read_buffer should still be 1
AssertEqual 1, ale#linter#PreProcess('testft', g:linter).read_buffer
let g:linter.read_buffer = 1
" We shouldn't be able to set both options to 1 at the same time.
AssertThrows call ale#linter#PreProcess('testft', g:linter)
AssertEqual 'Only one of `lint_file` or `read_buffer` can be `1`', g:vader_exception
" We should be able to set `read_buffer` and `lint_file` at the same time.
AssertEqual 1, ale#linter#PreProcess('testft', g:linter).read_buffer
let g:linter.lint_file = function('type')
Assert type(ale#linter#PreProcess('testft', g:linter).lint_file) is v:t_func
Execute(PreProcess should set a default value for lint_file):
let g:linter = {

View File

@@ -23,22 +23,22 @@ Execute(React Native apps using CocoaPods should take precedence over the defaul
call ale#test#SetFilename('swiftlint-test-files/react-native/testfile.swift')
AssertEqual
\ ale#path#Simplify(g:dir . '/swiftlint-test-files/react-native/ios/Pods/SwiftLint/swiftlint'),
\ ale_linters#swift#swiftlint#GetExecutable(bufnr(''))
\ tolower(ale#path#Simplify(g:dir . '/swiftlint-test-files/react-native/ios/Pods/SwiftLint/swiftlint')),
\ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr('')))
Execute(CocoaPods installation should take precedence over the default executable):
call ale#test#SetFilename('swiftlint-test-files/cocoapods/testfile.swift')
AssertEqual
\ ale#path#Simplify(g:dir . '/swiftlint-test-files/cocoapods/Pods/SwiftLint/swiftlint'),
\ ale_linters#swift#swiftlint#GetExecutable(bufnr(''))
\ tolower(ale#path#Simplify(g:dir . '/swiftlint-test-files/cocoapods/Pods/SwiftLint/swiftlint')),
\ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr('')))
Execute(Top level CocoaPods installation should take precedence over React Native installation):
call ale#test#SetFilename('swiftlint-test-files/cocoapods-and-react-native/testfile.swift')
AssertEqual
\ ale#path#Simplify(g:dir . '/swiftlint-test-files/cocoapods-and-react-native/Pods/SwiftLint/swiftlint'),
\ ale_linters#swift#swiftlint#GetExecutable(bufnr(''))
\ tolower(ale#path#Simplify(g:dir . '/swiftlint-test-files/cocoapods-and-react-native/Pods/SwiftLint/swiftlint')),
\ tolower(ale_linters#swift#swiftlint#GetExecutable(bufnr('')))
Execute(use-global should override other versions):
let g:ale_swift_swiftlint_use_global = 1