diff --git a/ale_linters/python/pylsp.vim b/ale_linters/python/pylsp.vim index 75ec38842a..d6867154e5 100644 --- a/ale_linters/python/pylsp.vim +++ b/ale_linters/python/pylsp.vim @@ -36,7 +36,7 @@ function! ale_linters#python#pylsp#GetCwd(buffer) abort \ 'name': 'pylsp', \ '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 endfunction diff --git a/ale_linters/python/pyright.vim b/ale_linters/python/pyright.vim index 95443a1394..88aafb5879 100644 --- a/ale_linters/python/pyright.vim +++ b/ale_linters/python/pyright.vim @@ -13,7 +13,7 @@ function! ale_linters#python#pyright#GetCwd(buffer) abort \ 'name': 'pyright', \ '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 endfunction diff --git a/autoload/ale/assert.vim b/autoload/ale/assert.vim index c5157dbae7..f3ad34a025 100644 --- a/autoload/ale/assert.vim +++ b/autoload/ale/assert.vim @@ -216,7 +216,7 @@ endfunction function! ale#assert#LSPProject(expected_root) abort let l:buffer = bufnr('') 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 endfunction diff --git a/autoload/ale/linter.vim b/autoload/ale/linter.vim index 65c5dd0dd3..497b8c0eaf 100644 --- a/autoload/ale/linter.vim +++ b/autoload/ale/linter.vim @@ -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 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 diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 3b3c403c66..0322287c3e 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -296,44 +296,6 @@ function! ale#lsp_linter#GetConfig(buffer, linter) abort return {} 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. function! ale#lsp_linter#OnInit(linter, details, Callback) abort @@ -504,7 +466,7 @@ endfunction function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort let l:command = '' 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 there's no project root, then we can't check files with LSP, diff --git a/autoload/ale/python.vim b/autoload/ale/python.vim index 81ab6eb78c..92cf2d2a49 100644 --- a/autoload/ale/python.vim +++ b/autoload/ale/python.vim @@ -61,6 +61,12 @@ endfunction " through paths, including the current directory, until no __init__.py files " is found. 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) if !empty(l:ini_root) diff --git a/doc/ale-python.txt b/doc/ale-python.txt index 3a4cedf765..82e765818b 100644 --- a/doc/ale-python.txt +++ b/doc/ale-python.txt @@ -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 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. 2. If no configuration file can be found, use the first directory which does not contain a readable file named `__init__.py`. diff --git a/doc/ale.txt b/doc/ale.txt index e508f769c7..44b3b93025 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -2297,17 +2297,18 @@ g:ale_root Type: |Dictionary| or |String| Default: `{}` - This option is used to determine the project root for a linter. If the value - is a |Dictionary|, it maps a linter to either a |String| containing the - project root or a |Funcref| to call to look up the root. The |Funcref| is - provided the buffer number as its argument. + This option is used to determine the project root for a linter. When set to a + |String| it will be used for all linters. When set to a |Dictionary|, the + keys are linter names and the values are either |Strings| containing project + 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. - If neither variable yields a result, a linter-specific 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. + If a value can be found from either variable, ALE uses it directly and skips + searching for a project root. If no value is found, a linter-specific + 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* *g:ale_save_hidden* diff --git a/test/test_linter_defintion_processing.vader b/test/test_linter_defintion_processing.vader index 4c096a5e05..df9cbf6751 100644 --- a/test/test_linter_defintion_processing.vader +++ b/test/test_linter_defintion_processing.vader @@ -407,7 +407,7 @@ Execute(PreProcess should allow the `project_root` to be set as a String): \ '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): 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'}, \}) - 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): AssertThrows call ale#linter#PreProcess('testft', { diff --git a/test/test_python_root_option.vader b/test/test_python_root_option.vader new file mode 100644 index 0000000000..f8ab88fc2c --- /dev/null +++ b/test/test_python_root_option.vader @@ -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(''))