Close #2281 - Separate cwd commands from commands

Working directories are now set seperately from the commands so they
can later be swapped out when running linters over projects is
supported, and also better support filename mapping for running linters
on other machines in future.
This commit is contained in:
w0rp
2021-03-01 20:11:10 +00:00
parent 48fab99a0a
commit 9fe7b1fe6a
117 changed files with 1142 additions and 1111 deletions

View File

@@ -23,19 +23,23 @@ function! ale#ant#FindExecutable(buffer) abort
return ''
endfunction
" Given a buffer number, build a command to print the classpath of the root
" project. Returns an empty string if cannot build the command.
" Given a buffer number, get a working directory and command to print the
" classpath of the root project.
"
" Returns an empty string for the command if Ant is not detected.
function! ale#ant#BuildClasspathCommand(buffer) abort
let l:executable = ale#ant#FindExecutable(a:buffer)
let l:project_root = ale#ant#FindProjectRoot(a:buffer)
if !empty(l:executable) && !empty(l:project_root)
return ale#path#CdString(l:project_root)
\ . ale#Escape(l:executable)
\ . ' classpath'
\ . ' -S'
\ . ' -q'
if !empty(l:executable)
let l:project_root = ale#ant#FindProjectRoot(a:buffer)
if !empty(l:project_root)
return [
\ l:project_root,
\ ale#Escape(l:executable) .' classpath -S -q'
\]
endif
endif
return ''
return ['', '']
endfunction

View File

@@ -52,6 +52,36 @@ function! s:ProcessDeferredCommands(initial_result) abort
return l:command
endfunction
function! s:ProcessDeferredCwds(initial_command, initial_cwd) abort
let l:result = a:initial_command
let l:last_cwd = v:null
let l:command_index = 0
let l:cwd_list = []
while ale#command#IsDeferred(l:result)
call add(l:cwd_list, l:result.cwd)
if get(g:, 'ale_run_synchronously_emulate_commands')
" Don't run commands, but simulate the results.
let l:Callback = g:ale_run_synchronously_callbacks[0]
let l:output = get(s:command_output, l:command_index, [])
call l:Callback(0, l:output)
unlet g:ale_run_synchronously_callbacks
let l:command_index += 1
else
" Run the commands in the shell, synchronously.
call ale#test#FlushJobs()
endif
let l:result = l:result.value
endwhile
call add(l:cwd_list, a:initial_cwd is v:null ? l:last_cwd : a:initial_cwd)
return l:cwd_list
endfunction
" Load the currently loaded linter for a test case, and check that the command
" matches the given string.
function! ale#assert#Linter(expected_executable, expected_command) abort
@@ -85,6 +115,38 @@ function! ale#assert#Linter(expected_executable, expected_command) abort
\ [l:executable, l:command]
endfunction
function! ale#assert#LinterCwd(expected_cwd) abort
let l:buffer = bufnr('')
let l:linter = s:GetLinter()
let l:initial_cwd = ale#linter#GetCwd(l:buffer, l:linter)
call ale#command#SetCwd(l:buffer, l:initial_cwd)
let l:cwd = s:ProcessDeferredCwds(
\ ale#linter#GetCommand(l:buffer, l:linter),
\ l:initial_cwd,
\)
call ale#command#ResetCwd(l:buffer)
if type(a:expected_cwd) isnot v:t_list
let l:cwd = l:cwd[-1]
endif
AssertEqual a:expected_cwd, l:cwd
endfunction
function! ale#assert#FixerCwd(expected_cwd) abort
let l:buffer = bufnr('')
let l:cwd = s:ProcessDeferredCwds(s:FixerFunction(l:buffer), v:null)
if type(a:expected_cwd) isnot v:t_list
let l:cwd = l:cwd[-1]
endif
AssertEqual a:expected_cwd, l:cwd
endfunction
function! ale#assert#Fixer(expected_result) abort
let l:buffer = bufnr('')
let l:result = s:ProcessDeferredCommands(s:FixerFunction(l:buffer))
@@ -153,6 +215,7 @@ endfunction
function! ale#assert#SetUpLinterTestCommands() abort
command! -nargs=+ GivenCommandOutput :call ale#assert#GivenCommandOutput(<args>)
command! -nargs=+ AssertLinterCwd :call ale#assert#LinterCwd(<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>)
@@ -164,10 +227,35 @@ endfunction
function! ale#assert#SetUpFixerTestCommands() abort
command! -nargs=+ GivenCommandOutput :call ale#assert#GivenCommandOutput(<args>)
command! -nargs=+ AssertFixerCwd :call ale#assert#FixerCwd(<args>)
command! -nargs=+ AssertFixer :call ale#assert#Fixer(<args>)
command! -nargs=0 AssertFixerNotExecuted :call ale#assert#FixerNotExecuted()
endfunction
function! ale#assert#ResetVariables(filetype, name, ...) abort
" If the suffix of the option names format is different, an additional
" argument can be used for that instead.
if a:0 > 1
throw 'Too many arguments'
endif
let l:option_suffix = get(a:000, 0, a:name)
let l:prefix = 'ale_' . a:filetype . '_'
\ . substitute(l:option_suffix, '-', '_', 'g')
let l:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix'
" Save and clear linter variables.
" We'll load the runtime file to reset them to defaults.
for l:key in filter(keys(g:), l:filter_expr)
execute 'Save g:' . l:key
unlet g:[l:key]
endfor
for l:key in filter(keys(b:), l:filter_expr)
unlet b:[l:key]
endfor
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.
@@ -177,31 +265,18 @@ function! ale#assert#SetUpLinterTest(filetype, name) abort
call ale#linter#Reset()
call ale#linter#PreventLoading(a:filetype)
let l:prefix = 'ale_' . a:filetype . '_' . a:name
let b:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix'
Save g:ale_lsp_root
let g:ale_lsp_root = {}
Save b:ale_lsp_root
unlet! b:ale_lsp_root
call ale#assert#ResetVariables(a:filetype, a:name)
Save g:ale_c_build_dir
unlet! g:ale_c_build_dir
" Save and clear linter variables.
" We'll load the runtime file to reset them to defaults.
for l:key in filter(keys(g:), b:filter_expr)
execute 'Save g:' . l:key
unlet g:[l:key]
endfor
unlet! b:ale_c_build_dir
for l:key in filter(keys(b:), b:filter_expr)
unlet b:[l:key]
endfor
execute 'runtime ale_linters/' . a:filetype . '/' . a:name . '.vim'
if !exists('g:dir')
@@ -226,6 +301,10 @@ function! ale#assert#TearDownLinterTest() abort
delcommand GivenCommandOutput
endif
if exists(':AssertLinterCwd')
delcommand AssertLinterCwd
endif
if exists(':AssertLinter')
delcommand AssertLinter
endif
@@ -281,18 +360,7 @@ function! ale#assert#SetUpFixerTest(filetype, name, ...) abort
let s:FixerFunction = function(l:function_name)
let l:option_suffix = get(a:000, 0, a:name)
let l:prefix = 'ale_' . a:filetype . '_'
\ . substitute(l:option_suffix, '-', '_', 'g')
let b:filter_expr = 'v:val[: len(l:prefix) - 1] is# l:prefix'
for l:key in filter(keys(g:), b:filter_expr)
execute 'Save g:' . l:key
unlet g:[l:key]
endfor
for l:key in filter(keys(b:), b:filter_expr)
unlet b:[l:key]
endfor
call ale#assert#ResetVariables(a:filetype, a:name, l:option_suffix)
execute 'runtime autoload/ale/fixers/' . substitute(a:name, '-', '_', 'g') . '.vim'
@@ -329,6 +397,10 @@ function! ale#assert#TearDownFixerTest() abort
delcommand GivenCommandOutput
endif
if exists(':AssertFixerCwd')
delcommand AssertFixerCwd
endif
if exists(':AssertFixer')
delcommand AssertFixer
endif

View File

@@ -513,16 +513,18 @@ function! ale#c#GetMakeCommand(buffer) abort
if !empty(l:path)
let l:always_make = ale#Var(a:buffer, 'c_always_make')
return ale#path#CdString(fnamemodify(l:path, ':h'))
\ . 'make -n' . (l:always_make ? ' --always-make' : '')
return [
\ fnamemodify(l:path, ':h'),
\ 'make -n' . (l:always_make ? ' --always-make' : ''),
\]
endif
endif
return ''
return ['', '']
endfunction
function! ale#c#RunMakeCommand(buffer, Callback) abort
let l:command = ale#c#GetMakeCommand(a:buffer)
let [l:cwd, l:command] = ale#c#GetMakeCommand(a:buffer)
if empty(l:command)
return a:Callback(a:buffer, [])
@@ -532,6 +534,7 @@ function! ale#c#RunMakeCommand(buffer, Callback) abort
\ a:buffer,
\ l:command,
\ {b, output -> a:Callback(a:buffer, output)},
\ {'cwd': l:cwd},
\)
endfunction

View File

@@ -7,6 +7,9 @@ if !exists('s:buffer_data')
let s:buffer_data = {}
endif
" The regular expression used for formatting filenames with modifiers.
let s:path_format_regex = '\v\%s(%(:h|:t|:r|:e)*)'
" Used to get the data in tests.
function! ale#command#GetData() abort
return deepcopy(s:buffer_data)
@@ -26,6 +29,19 @@ function! ale#command#InitData(buffer) abort
endif
endfunction
" Set the cwd for commands that are about to run.
" Used internally.
function! ale#command#SetCwd(buffer, cwd) abort
call ale#command#InitData(a:buffer)
let s:buffer_data[a:buffer].cwd = a:cwd
endfunction
function! ale#command#ResetCwd(buffer) abort
if has_key(s:buffer_data, a:buffer)
let s:buffer_data[a:buffer].cwd = v:null
endif
endfunction
function! ale#command#ManageFile(buffer, file) abort
call ale#command#InitData(a:buffer)
call add(s:buffer_data[a:buffer].file_list, a:file)
@@ -151,6 +167,24 @@ function! s:FormatFilename(filename, mappings, modifiers) abort
return ale#Escape(l:filename)
endfunction
" Produce a command prefix to check to a particular directory for a command.
" %s format markers with filename-modifiers can be used as the directory, and
" will be returned verbatim for formatting in paths relative to files.
function! ale#command#CdString(directory) abort
let l:match = matchstrpos(a:directory, s:path_format_regex)
" Do not escape the directory here if it's a valid format string.
" This allows us to use sequences like %s:h, %s:h:h, etc.
let l:directory = l:match[1:] == [0, len(a:directory)]
\ ? a:directory
\ : ale#Escape(a:directory)
if has('win32')
return 'cd /d ' . l:directory . ' && '
endif
return 'cd ' . l:directory . ' && '
endfunction
" Given a command string, replace every...
" %s -> with the current filename
" %t -> with the name of an unused file in a temporary directory
@@ -161,11 +195,16 @@ function! ale#command#FormatCommand(
\ command,
\ pipe_file_if_needed,
\ input,
\ cwd,
\ mappings,
\) abort
let l:temporary_file = ''
let l:command = a:command
if !empty(a:cwd)
let l:command = ale#command#CdString(a:cwd) . l:command
endif
" First replace all uses of %%, used for literal percent characters,
" with an ugly string.
let l:command = substitute(l:command, '%%', '<<PERCENTS>>', 'g')
@@ -181,7 +220,7 @@ function! ale#command#FormatCommand(
let l:filename = fnamemodify(bufname(a:buffer), ':p')
let l:command = substitute(
\ l:command,
\ '\v\%s(%(:h|:t|:r|:e)*)',
\ s:path_format_regex,
\ '\=s:FormatFilename(l:filename, a:mappings, submatch(1))',
\ 'g'
\)
@@ -279,9 +318,16 @@ function! s:ExitCallback(buffer, line_list, Callback, data) abort
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
" Set the default cwd for this buffer in this call stack.
call ale#command#SetCwd(a:buffer, l:result.cwd)
try
if get(l:result, 'result_callback', v:null) isnot v:null
call call(l:result.result_callback, [l:value])
endif
finally
call ale#command#ResetCwd(a:buffer)
endtry
endfunction
function! ale#command#Run(buffer, command, Callback, ...) abort
@@ -293,6 +339,13 @@ function! ale#command#Run(buffer, command, Callback, ...) abort
let l:output_stream = get(l:options, 'output_stream', 'stdout')
let l:line_list = []
let l:cwd = get(l:options, 'cwd', v:null)
if l:cwd is v:null
" Default the working directory to whatever it was for the last
" command run in the chain.
let l:cwd = get(get(s:buffer_data, a:buffer, {}), 'cwd', v:null)
endif
let [l:temporary_file, l:command, l:file_created] = ale#command#FormatCommand(
\ a:buffer,
@@ -300,6 +353,7 @@ function! ale#command#Run(buffer, command, Callback, ...) abort
\ a:command,
\ get(l:options, 'read_buffer', 0),
\ get(l:options, 'input', v:null),
\ l:cwd,
\ get(l:options, 'filename_mappings', []),
\)
let l:command = ale#job#PrepareCommand(a:buffer, l:command)
@@ -366,10 +420,14 @@ function! ale#command#Run(buffer, command, Callback, ...) abort
" The `_deferred_job_id` is used for both checking the type of object, and
" for checking the job ID and status.
"
" The cwd is kept and used as the default value for the next command in
" the chain.
"
" The original command here is used in tests.
let l:result = {
\ '_deferred_job_id': l:job_id,
\ 'executable': get(l:options, 'executable', ''),
\ 'cwd': l:cwd,
\ 'command': a:command,
\}

View File

@@ -413,6 +413,7 @@ function! s:RunJob(command, options) abort
return 0
endif
let l:cwd = a:options.cwd
let l:executable = a:options.executable
let l:buffer = a:options.buffer
let l:linter = a:options.linter
@@ -425,6 +426,7 @@ function! s:RunJob(command, options) abort
\ 'executable': l:executable,
\}])
let l:result = ale#command#Run(l:buffer, l:command, l:Callback, {
\ 'cwd': l:cwd,
\ 'output_stream': l:output_stream,
\ 'executable': l:executable,
\ 'read_buffer': l:read_buffer,
@@ -541,8 +543,22 @@ function! s:RunIfExecutable(buffer, linter, lint_file, executable) abort
let l:job_type = a:lint_file ? 'file_linter' : 'linter'
call setbufvar(a:buffer, 'ale_job_type', l:job_type)
" Get the cwd for the linter and set it before we call GetCommand.
" This will ensure that ale#command#Run uses it by default.
let l:cwd = ale#linter#GetCwd(a:buffer, a:linter)
if l:cwd isnot v:null
call ale#command#SetCwd(a:buffer, l:cwd)
endif
let l:command = ale#linter#GetCommand(a:buffer, a:linter)
if l:cwd isnot v:null
call ale#command#ResetCwd(a:buffer)
endif
let l:options = {
\ 'cwd': l:cwd,
\ 'executable': a:executable,
\ 'buffer': a:buffer,
\ 'linter': a:linter,

View File

@@ -172,6 +172,7 @@ function! s:RunJob(result, options) abort
let l:read_temporary_file = get(a:result, 'read_temporary_file', 0)
let l:read_buffer = get(a:result, 'read_buffer', 1)
let l:output_stream = get(a:result, 'output_stream', 'stdout')
let l:cwd = get(a:result, 'cwd', v:null)
if l:read_temporary_file
let l:output_stream = 'none'
@@ -190,6 +191,7 @@ function! s:RunJob(result, options) abort
\ 'read_buffer': l:read_buffer,
\ 'input': l:input,
\ 'log_output': 0,
\ 'cwd': l:cwd,
\ 'filename_mappings': ale#GetFilenameMappings(l:buffer, l:fixer_name),
\})

View File

@@ -19,7 +19,9 @@ function! ale#fixers#autoimport#Fix(buffer) abort
endif
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#Escape(l:executable) . (!empty(l:options) ? ' ' . l:options : '') . ' -',
\ 'cwd': '%s:h',
\ 'command': ale#Escape(l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' -',
\}
endfunction

View File

@@ -17,25 +17,25 @@ function! ale#fixers#black#GetExecutable(buffer) abort
endfunction
function! ale#fixers#black#Fix(buffer) abort
let l:cd_string = ale#Var(a:buffer, 'python_black_change_directory')
\ ? ale#path#BufferCdString(a:buffer)
\ : ''
let l:executable = ale#fixers#black#GetExecutable(a:buffer)
let l:exec_args = l:executable =~? 'pipenv$'
\ ? ' run black'
\ : ''
let l:options = ale#Var(a:buffer, 'python_black_options')
if expand('#' . a:buffer . ':e') is? 'pyi'
let l:options .= '--pyi'
endif
return {
\ 'command': l:cd_string . ale#Escape(l:executable) . l:exec_args
let l:result = {
\ 'command': ale#Escape(l:executable) . l:exec_args
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' -',
\}
if ale#Var(a:buffer, 'python_black_change_directory')
let l:result.cwd = '%s:h'
endif
return l:result
endfunction

View File

@@ -53,8 +53,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort
" Use --fix-to-stdout with eslint_d
if l:executable =~# 'eslint_d$' && ale#semver#GTE(a:version, [3, 19, 0])
return {
\ 'command': ale#handlers#eslint#GetCdString(a:buffer)
\ . ale#node#Executable(a:buffer, l:executable)
\ 'cwd': ale#handlers#eslint#GetCwd(a:buffer),
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . ale#Pad(l:options)
\ . ' --stdin-filename %s --stdin --fix-to-stdout',
\ 'process_with': 'ale#fixers#eslint#ProcessEslintDOutput',
@@ -64,8 +64,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort
" 4.9.0 is the first version with --fix-dry-run
if ale#semver#GTE(a:version, [4, 9, 0])
return {
\ 'command': ale#handlers#eslint#GetCdString(a:buffer)
\ . ale#node#Executable(a:buffer, l:executable)
\ 'cwd': ale#handlers#eslint#GetCwd(a:buffer),
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . ale#Pad(l:options)
\ . ' --stdin-filename %s --stdin --fix-dry-run --format=json',
\ 'process_with': 'ale#fixers#eslint#ProcessFixDryRunOutput',
@@ -73,8 +73,8 @@ function! ale#fixers#eslint#ApplyFixForVersion(buffer, version) abort
endif
return {
\ 'command': ale#handlers#eslint#GetCdString(a:buffer)
\ . ale#node#Executable(a:buffer, l:executable)
\ 'cwd': ale#handlers#eslint#GetCwd(a:buffer),
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . ale#Pad(l:options)
\ . (!empty(l:config) ? ' -c ' . ale#Escape(l:config) : '')
\ . ' --fix %t',

View File

@@ -17,9 +17,7 @@ endfunction
function! ale#fixers#isort#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'python_isort_options')
let l:executable = ale#fixers#isort#GetExecutable(a:buffer)
let l:exec_args = l:executable =~? 'pipenv$'
\ ? ' run isort'
\ : ''
@@ -29,8 +27,8 @@ function! ale#fixers#isort#Fix(buffer) abort
endif
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#Escape(l:executable) . l:exec_args
\ 'cwd': '%s:h',
\ 'command': ale#Escape(l:executable) . l:exec_args
\ . (!empty(l:options) ? ' ' . l:options : '') . ' -',
\}
endfunction

View File

@@ -34,19 +34,11 @@ function! ale#fixers#prettier#ProcessPrettierDOutput(buffer, output) abort
return a:output
endfunction
function! ale#fixers#prettier#GetProjectRoot(buffer) abort
function! ale#fixers#prettier#GetCwd(buffer) abort
let l:config = ale#path#FindNearestFile(a:buffer, '.prettierignore')
if !empty(l:config)
return fnamemodify(l:config, ':h')
endif
" Fall back to the directory of the buffer
return fnamemodify(bufname(a:buffer), ':p:h')
endfunction
function! ale#fixers#prettier#CdProjectRoot(buffer) abort
return ale#path#CdString(ale#fixers#prettier#GetProjectRoot(a:buffer))
return !empty(l:config) ? fnamemodify(l:config, ':h') : '%s:h'
endfunction
function! ale#fixers#prettier#ApplyFixForVersion(buffer, version) abort
@@ -103,8 +95,8 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version) abort
" Special error handling needed for prettier_d
if l:executable =~# 'prettier_d$'
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#Escape(l:executable)
\ 'cwd': '%s:h',
\ 'command':ale#Escape(l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --stdin-filepath %s --stdin',
\ 'process_with': 'ale#fixers#prettier#ProcessPrettierDOutput',
@@ -114,8 +106,8 @@ function! ale#fixers#prettier#ApplyFixForVersion(buffer, version) abort
" 1.4.0 is the first version with --stdin-filepath
if ale#semver#GTE(a:version, [1, 4, 0])
return {
\ 'command': ale#fixers#prettier#CdProjectRoot(a:buffer)
\ . ale#Escape(l:executable)
\ 'cwd': ale#fixers#prettier#GetCwd(a:buffer),
\ 'command': ale#Escape(l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --stdin-filepath %s --stdin',
\}

View File

@@ -37,8 +37,8 @@ function! ale#fixers#prettier_eslint#ApplyFixForVersion(buffer, version) abort
" 4.4.0 is the first version with --stdin-filepath
if ale#semver#GTE(a:version, [4, 4, 0])
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#Escape(l:executable)
\ 'cwd': '%s:h',
\ 'command': ale#Escape(l:executable)
\ . l:eslint_config_option
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --stdin-filepath %s --stdin',

View File

@@ -17,8 +17,8 @@ function! ale#fixers#stylelint#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'stylelint_options')
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#node#Executable(a:buffer, l:executable)
\ 'cwd': '%s:h',
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . ' %t'
\ . ale#Pad(l:options)
\ . ' --fix',

View File

@@ -7,7 +7,6 @@ call ale#Set('yaml_yamlfix_use_global', get(g:, 'ale_use_global_executables', 0)
function! ale#fixers#yamlfix#Fix(buffer) abort
let l:options = ale#Var(a:buffer, 'yaml_yamlfix_options')
let l:executable = ale#python#FindExecutable(
\ a:buffer,
\ 'yaml_yamlfix',
@@ -19,7 +18,8 @@ function! ale#fixers#yamlfix#Fix(buffer) abort
endif
return {
\ 'command': ale#path#BufferCdString(a:buffer)
\ . ale#Escape(l:executable) . (!empty(l:options) ? ' ' . l:options : '') . ' -',
\ 'cwd': '%s:h',
\ 'command': ale#Escape(l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '') . ' -',
\}
endfunction

View File

@@ -50,18 +50,25 @@ function! ale#gradle#FindExecutable(buffer) abort
return ''
endfunction
" Given a buffer number, build a command to print the classpath of the root
" project. Returns an empty string if cannot build the command.
" Given a buffer number, get a working directory and command to print the
" classpath of the root project.
"
" Returns an empty string for the command if Gradle is not detected.
function! ale#gradle#BuildClasspathCommand(buffer) abort
let l:executable = ale#gradle#FindExecutable(a:buffer)
let l:project_root = ale#gradle#FindProjectRoot(a:buffer)
if !empty(l:executable) && !empty(l:project_root)
return ale#path#CdString(l:project_root)
\ . ale#Escape(l:executable)
\ . ' -I ' . ale#Escape(s:init_path)
\ . ' -q printClasspath'
if !empty(l:executable)
let l:project_root = ale#gradle#FindProjectRoot(a:buffer)
if !empty(l:project_root)
return [
\ l:project_root,
\ ale#Escape(l:executable)
\ . ' -I ' . ale#Escape(s:init_path)
\ . ' -q printClasspath'
\]
endif
endif
return ''
return ['', '']
endfunction

View File

@@ -1,10 +1,9 @@
" Description: Handle errors for cppcheck.
function! ale#handlers#cppcheck#GetCdCommand(buffer) abort
function! ale#handlers#cppcheck#GetCwd(buffer) abort
let [l:dir, l:json_path] = ale#c#FindCompileCommands(a:buffer)
let l:cd_command = !empty(l:dir) ? ale#path#CdString(l:dir) : ''
return l:cd_command
return !empty(l:dir) ? l:dir : ''
endfunction
function! ale#handlers#cppcheck#GetBufferPathIncludeOptions(buffer) abort

View File

@@ -39,9 +39,8 @@ function! ale#handlers#eslint#GetExecutable(buffer) abort
return ale#node#FindExecutable(a:buffer, 'javascript_eslint', s:executables)
endfunction
" Given a buffer, return a command prefix string which changes directory
" as necessary for running ESLint.
function! ale#handlers#eslint#GetCdString(buffer) abort
" Given a buffer, return an appropriate working directory for ESLint.
function! ale#handlers#eslint#GetCwd(buffer) abort
" ESLint 6 loads plugins/configs/parsers from the project root
" By default, the project root is simply the CWD of the running process.
" https://github.com/eslint/rfcs/blob/master/designs/2018-simplified-package-loading/README.md
@@ -60,7 +59,7 @@ function! ale#handlers#eslint#GetCdString(buffer) abort
let l:project_dir = !empty(l:modules_dir) ? fnamemodify(l:modules_dir, ':h:h') : ''
endif
return !empty(l:project_dir) ? ale#path#CdString(l:project_dir) : ''
return !empty(l:project_dir) ? l:project_dir : ''
endfunction
function! ale#handlers#eslint#GetCommand(buffer) abort
@@ -68,8 +67,7 @@ function! ale#handlers#eslint#GetCommand(buffer) abort
let l:options = ale#Var(a:buffer, 'javascript_eslint_options')
return ale#handlers#eslint#GetCdString(a:buffer)
\ . ale#node#Executable(a:buffer, l:executable)
return ale#node#Executable(a:buffer, l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' -f json --stdin --stdin-filename %s'
endfunction

View File

@@ -40,21 +40,21 @@ function! ale#handlers#shellcheck#GetDialectArgument(buffer) abort
return ''
endfunction
function! ale#handlers#shellcheck#GetCwd(buffer) abort
return ale#Var(a:buffer, 'sh_shellcheck_change_directory') ? '%s:h' : ''
endfunction
function! ale#handlers#shellcheck#GetCommand(buffer, version) abort
let l:options = ale#Var(a:buffer, 'sh_shellcheck_options')
let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions')
let l:dialect = ale#Var(a:buffer, 'sh_shellcheck_dialect')
let l:external_option = ale#semver#GTE(a:version, [0, 4, 0]) ? ' -x' : ''
let l:cd_string = ale#Var(a:buffer, 'sh_shellcheck_change_directory')
\ ? ale#path#BufferCdString(a:buffer)
\ : ''
if l:dialect is# 'auto'
let l:dialect = ale#handlers#shellcheck#GetDialectArgument(a:buffer)
endif
return l:cd_string
\ . '%e'
return '%e'
\ . (!empty(l:dialect) ? ' -s ' . l:dialect : '')
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '')
@@ -111,6 +111,7 @@ function! ale#handlers#shellcheck#DefineLinter(filetype) abort
call ale#linter#Define(a:filetype, {
\ 'name': 'shellcheck',
\ 'executable': {buffer -> ale#Var(buffer, 'sh_shellcheck_executable')},
\ 'cwd': function('ale#handlers#shellcheck#GetCwd'),
\ 'command': {buffer -> ale#semver#RunWithVersionCheck(
\ buffer,
\ ale#Var(buffer, 'sh_shellcheck_executable'),

View File

@@ -151,17 +151,30 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
let l:obj.address = a:linter.address
if has_key(a:linter, 'cwd')
throw '`cwd` makes no sense for socket LSP connections'
endif
else
throw '`address` must be defined for getting the LSP address'
endif
if has_key(a:linter, 'cwd')
let l:obj.cwd = a:linter.cwd
if type(l:obj.cwd) isnot v:t_string
\&& type(l:obj.cwd) isnot v:t_func
throw '`cwd` must be a String or Function if defined'
endif
endif
if l:needs_lsp_details
" Default to using the filetype as the language.
let l:obj.language = get(a:linter, 'language', a:filetype)
if type(l:obj.language) isnot v:t_string
\&& type(l:obj.language) isnot v:t_func
throw '`language` must be a String or Funcref if defined'
throw '`language` must be a String or Function if defined'
endif
if has_key(a:linter, 'project_root')
@@ -415,6 +428,12 @@ function! ale#linter#GetExecutable(buffer, linter) abort
\ : l:Executable
endfunction
function! ale#linter#GetCwd(buffer, linter) abort
let l:Cwd = get(a:linter, 'cwd', v:null)
return type(l:Cwd) is v:t_func ? l:Cwd(a:buffer) : l:Cwd
endfunction
" Given a buffer and linter, get the command String for the linter.
function! ale#linter#GetCommand(buffer, linter) abort
let l:Command = a:linter.command

View File

@@ -290,6 +290,7 @@ function! s:StartLSP(options, address, executable, command) abort
\ a:command,
\ 0,
\ v:false,
\ v:null,
\ [],
\)[1]
let l:command = ale#job#PrepareCommand(l:buffer, l:command)

View File

@@ -17,7 +17,6 @@ function! ale#maven#FindProjectRoot(buffer) abort
return ''
endfunction
" Given a buffer number, find the path to the executable.
" First search on the path for 'mvnw' (mvnw.cmd on Windows), if nothing is found,
" try the global command. Returns an empty string if cannot find the executable.
@@ -36,16 +35,23 @@ function! ale#maven#FindExecutable(buffer) abort
return ''
endfunction
" Given a buffer number, build a command to print the classpath of the root
" project. Returns an empty string if cannot build the command.
" Given a buffer number, get a working directory and command to print the
" classpath of the root project.
"
" Returns an empty string for the command if Maven is not detected.
function! ale#maven#BuildClasspathCommand(buffer) abort
let l:executable = ale#maven#FindExecutable(a:buffer)
let l:project_root = ale#maven#FindProjectRoot(a:buffer)
if !empty(l:executable) && !empty(l:project_root)
return ale#path#CdString(l:project_root)
\ . l:executable . ' dependency:build-classpath'
if !empty(l:executable)
let l:project_root = ale#maven#FindProjectRoot(a:buffer)
if !empty(l:project_root)
return [
\ l:project_root,
\ ale#Escape(l:executable) . ' dependency:build-classpath'
\]
endif
endif
return ''
return ['', '']
endfunction

View File

@@ -77,26 +77,6 @@ function! ale#path#ResolveLocalPath(buffer, search_string, global_fallback) abor
return l:path
endfunction
" Output 'cd <directory> && '
" This function can be used changing the directory for a linter command.
function! ale#path#CdString(directory) abort
if has('win32')
return 'cd /d ' . ale#Escape(a:directory) . ' && '
endif
return 'cd ' . ale#Escape(a:directory) . ' && '
endfunction
" Output 'cd <buffer_filename_directory> && '
" This function can be used changing the directory for a linter command.
function! ale#path#BufferCdString(buffer) abort
if has('win32')
return 'cd /d %s:h && '
endif
return 'cd %s:h && '
endfunction
" Return 1 if a path is an absolute path.
function! ale#path#IsAbsolute(filename) abort
if has('win32') && a:filename[:0] is# '\'