Prefer ale_root setting for project roots

This commit is contained in:
Andrew Wray
2025-06-25 20:50:15 +01:00
parent 9e49019a26
commit 0b0f8d91bc
10 changed files with 80 additions and 52 deletions

View File

@@ -36,7 +36,7 @@ function! ale_linters#python#pylsp#GetCwd(buffer) abort
\ 'name': 'pylsp', \ 'name': 'pylsp',
\ 'project_root': function('ale#python#FindProjectRoot'), \ 'project_root': function('ale#python#FindProjectRoot'),
\} \}
let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, l:fake_linter) let l:root = ale#linter#GetRoot(a:buffer, l:fake_linter)
return !empty(l:root) ? l:root : v:null return !empty(l:root) ? l:root : v:null
endfunction endfunction

View File

@@ -13,7 +13,7 @@ function! ale_linters#python#pyright#GetCwd(buffer) abort
\ 'name': 'pyright', \ 'name': 'pyright',
\ 'project_root': function('ale#python#FindProjectRoot'), \ 'project_root': function('ale#python#FindProjectRoot'),
\} \}
let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, l:fake_linter) let l:root = ale#linter#GetRoot(a:buffer, l:fake_linter)
return !empty(l:root) ? l:root : v:null return !empty(l:root) ? l:root : v:null
endfunction endfunction

View File

@@ -216,7 +216,7 @@ endfunction
function! ale#assert#LSPProject(expected_root) abort function! ale#assert#LSPProject(expected_root) abort
let l:buffer = bufnr('') let l:buffer = bufnr('')
let l:linter = s:GetLinter() let l:linter = s:GetLinter()
let l:root = ale#lsp_linter#FindProjectRoot(l:buffer, l:linter) let l:root = ale#linter#GetRoot(l:buffer, l:linter)
AssertEqual a:expected_root, l:root AssertEqual a:expected_root, l:root
endfunction endfunction

View File

@@ -447,3 +447,32 @@ function! ale#linter#GetAddress(buffer, linter) abort
return type(l:Address) is v:t_func ? l:Address(a:buffer) : l:Address return type(l:Address) is v:t_func ? l:Address(a:buffer) : l:Address
endfunction endfunction
" Get the project root for a linter.
" If |b:ale_root| or |g:ale_root| is set to either a String or a Dict mapping
" linter names to roots or callbacks, return that value immediately. When no
" value is available, fall back to the linter-specific configuration.
function! ale#linter#GetRoot(buffer, linter) abort
let l:buffer_ale_root = getbufvar(a:buffer, 'ale_root', {})
if type(l:buffer_ale_root) is v:t_string
return l:buffer_ale_root
endif
if has_key(l:buffer_ale_root, a:linter.name)
let l:Root = l:buffer_ale_root[a:linter.name]
return type(l:Root) is v:t_func ? l:Root(a:buffer) : l:Root
endif
if has_key(g:ale_root, a:linter.name)
let l:Root = g:ale_root[a:linter.name]
return type(l:Root) is v:t_func ? l:Root(a:buffer) : l:Root
endif
if has_key(a:linter, 'project_root')
let l:Root = a:linter.project_root
return type(l:Root) is v:t_func ? l:Root(a:buffer) : l:Root
endif
return ''
endfunction

View File

@@ -296,44 +296,6 @@ function! ale#lsp_linter#GetConfig(buffer, linter) abort
return {} return {}
endfunction endfunction
function! ale#lsp_linter#FindProjectRoot(buffer, linter) abort
let l:buffer_ale_root = getbufvar(a:buffer, 'ale_root', {})
if type(l:buffer_ale_root) is v:t_string
return l:buffer_ale_root
endif
" Try to get a buffer-local setting for the root
if has_key(l:buffer_ale_root, a:linter.name)
let l:Root = l:buffer_ale_root[a:linter.name]
if type(l:Root) is v:t_func
return l:Root(a:buffer)
else
return l:Root
endif
endif
" Try to get a global setting for the root
if has_key(g:ale_root, a:linter.name)
let l:Root = g:ale_root[a:linter.name]
if type(l:Root) is v:t_func
return l:Root(a:buffer)
else
return l:Root
endif
endif
" Fall back to the linter-specific configuration
if has_key(a:linter, 'project_root')
let l:Root = a:linter.project_root
return type(l:Root) is v:t_func ? l:Root(a:buffer) : l:Root
endif
return ale#util#GetFunction(a:linter.project_root_callback)(a:buffer)
endfunction
" This function is accessible so tests can call it. " This function is accessible so tests can call it.
function! ale#lsp_linter#OnInit(linter, details, Callback) abort function! ale#lsp_linter#OnInit(linter, details, Callback) abort
@@ -504,7 +466,7 @@ endfunction
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let l:command = '' let l:command = ''
let l:address = '' let l:address = ''
let l:root = ale#lsp_linter#FindProjectRoot(a:buffer, a:linter) let l:root = ale#linter#GetRoot(a:buffer, a:linter)
if empty(l:root) && a:linter.lsp isnot# 'tsserver' if empty(l:root) && a:linter.lsp isnot# 'tsserver'
" If there's no project root, then we can't check files with LSP, " If there's no project root, then we can't check files with LSP,

View File

@@ -61,6 +61,12 @@ endfunction
" through paths, including the current directory, until no __init__.py files " through paths, including the current directory, until no __init__.py files
" is found. " is found.
function! ale#python#FindProjectRoot(buffer) abort function! ale#python#FindProjectRoot(buffer) abort
let l:root = ale#linter#GetRoot(a:buffer, {'name': 'python'})
if !empty(l:root)
return l:root
endif
let l:ini_root = ale#python#FindProjectRootIni(a:buffer) let l:ini_root = ale#python#FindProjectRootIni(a:buffer)
if !empty(l:ini_root) if !empty(l:ini_root)

View File

@@ -55,6 +55,9 @@ For some linters, ALE will search for a Python project root by looking at the
files in directories on or above where a file being checked is. ALE applies files in directories on or above where a file being checked is. ALE applies
the following methods, in order: the following methods, in order:
If |g:ale_root| or |b:ale_root| provides a value, that value is used as the
project root instead and the searching described below is skipped.
1. Find the first directory containing a common Python configuration file. 1. Find the first directory containing a common Python configuration file.
2. If no configuration file can be found, use the first directory which does 2. If no configuration file can be found, use the first directory which does
not contain a readable file named `__init__.py`. not contain a readable file named `__init__.py`.

View File

@@ -2297,17 +2297,18 @@ g:ale_root
Type: |Dictionary| or |String| Type: |Dictionary| or |String|
Default: `{}` Default: `{}`
This option is used to determine the project root for a linter. If the value This option is used to determine the project root for a linter. When set to a
is a |Dictionary|, it maps a linter to either a |String| containing the |String| it will be used for all linters. When set to a |Dictionary|, the
project root or a |Funcref| to call to look up the root. The |Funcref| is keys are linter names and the values are either |Strings| containing project
provided the buffer number as its argument. roots or |Funcref|s which are passed the buffer number.
The buffer-specific variable may additionally be a string containing the The buffer-specific variable may additionally be a |String| containing the
project root itself. project root itself.
If neither variable yields a result, a linter-specific function is invoked to If a value can be found from either variable, ALE uses it directly and skips
detect a project root. If this, too, yields no result, and the linter is an searching for a project root. If no value is found, a linter-specific
LSP linter, it will not run. function is invoked to detect a project root. If this, too, yields no result
and the linter is an LSP linter, it will not run.
*ale-options.save_hidden* *ale-options.save_hidden*
*g:ale_save_hidden* *g:ale_save_hidden*

View File

@@ -407,7 +407,7 @@ Execute(PreProcess should allow the `project_root` to be set as a String):
\ 'project_root': '/foo/bar', \ 'project_root': '/foo/bar',
\}) \})
AssertEqual '/foo/bar', ale#lsp_linter#FindProjectRoot(0, g:linter) AssertEqual '/foo/bar', ale#linter#GetRoot(0, g:linter)
Execute(PreProcess should `project_root` be set as a Function): Execute(PreProcess should `project_root` be set as a Function):
let g:linter = ale#linter#PreProcess('testft', { let g:linter = ale#linter#PreProcess('testft', {
@@ -418,7 +418,7 @@ Execute(PreProcess should `project_root` be set as a Function):
\ 'project_root': {-> '/foo/bar'}, \ 'project_root': {-> '/foo/bar'},
\}) \})
AssertEqual '/foo/bar', ale#lsp_linter#FindProjectRoot(0, g:linter) AssertEqual '/foo/bar', ale#linter#GetRoot(0, g:linter)
Execute(PreProcess should complain when `project_root` is invalid): Execute(PreProcess should complain when `project_root` is invalid):
AssertThrows call ale#linter#PreProcess('testft', { AssertThrows call ale#linter#PreProcess('testft', {

View File

@@ -0,0 +1,27 @@
Before:
Save g:ale_root
Save b:ale_root
call ale#test#SetDirectory('/testplugin/test')
After:
Restore
call ale#test#RestoreDirectory()
Execute(The global setting is used as the project root):
let g:ale_root = '/foo/python'
call ale#test#SetFilename('test-files/python/no_virtualenv/subdir/foo/bar.py')
AssertEqual '/foo/python', ale#python#FindProjectRoot(bufnr(''))
Execute(The buffer setting overrides the global setting):
let g:ale_root = '/foo/python'
let b:ale_root = '/bar/python'
call ale#test#SetFilename('test-files/python/no_virtualenv/subdir/foo/bar.py')
AssertEqual '/bar/python', ale#python#FindProjectRoot(bufnr(''))
Execute(Fallback to searching when no setting is used):
unlet! g:ale_root
unlet! b:ale_root
call ale#test#SetFilename('test-files/python/no_virtualenv/subdir/foo/bar.py')
AssertEqual \
\ ale#path#Simplify(g:dir . '/../test-files/python/no_virtualenv/subdir'),
\ ale#python#FindProjectRoot(bufnr(''))