eslint: Use cwd from executable location to fix nested projects (#3222)
* Split FindNearestExecutable from FindExecutable The path searching in ale#node#FindExecutable() will be useful for eslint. Refactor it into a separate function so it can be used without regard for the state of the _use_global and _executable variables. Signed-off-by: Kevin Locke <kevin@kevinlocke.name> * eslint: Set project root from local executable Using the nearest directory with node_modules does not work correctly for nested projects where the eslint dependencies are in the outer project. For example: https://github.com/dense-analysis/ale/issues/3143#issuecomment-652452362 Adopt the behavior of SublimeLinter, which runs from project_root determined by the presence of the eslint executable in node_modules/.bin (or eslint in dependencies/devDependencies of package.json, which we can add later as necessary). See [NodeLinter#find_local_executable]. [NodeLinter#find_local_executable]: https://github.com/SublimeLinter/SublimeLinter/blob/056e6f6/lint/base_linter/node_linter.py#L109 Signed-off-by: Kevin Locke <kevin@kevinlocke.name>
This commit is contained in:
parent
fd399c527f
commit
106c27644b
4 changed files with 50 additions and 9 deletions
|
@ -1,6 +1,11 @@
|
||||||
" Author: w0rp <devw0rp@gmail.com>
|
" Author: w0rp <devw0rp@gmail.com>
|
||||||
" Description: Functions for working with eslint, for checking or fixing files.
|
" Description: Functions for working with eslint, for checking or fixing files.
|
||||||
|
|
||||||
|
let s:executables = [
|
||||||
|
\ 'node_modules/.bin/eslint_d',
|
||||||
|
\ 'node_modules/eslint/bin/eslint.js',
|
||||||
|
\ 'node_modules/.bin/eslint',
|
||||||
|
\]
|
||||||
let s:sep = has('win32') ? '\' : '/'
|
let s:sep = has('win32') ? '\' : '/'
|
||||||
|
|
||||||
call ale#Set('javascript_eslint_options', '')
|
call ale#Set('javascript_eslint_options', '')
|
||||||
|
@ -30,11 +35,7 @@ function! ale#handlers#eslint#FindConfig(buffer) abort
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
function! ale#handlers#eslint#GetExecutable(buffer) abort
|
function! ale#handlers#eslint#GetExecutable(buffer) abort
|
||||||
return ale#node#FindExecutable(a:buffer, 'javascript_eslint', [
|
return ale#node#FindExecutable(a:buffer, 'javascript_eslint', s:executables)
|
||||||
\ 'node_modules/.bin/eslint_d',
|
|
||||||
\ 'node_modules/eslint/bin/eslint.js',
|
|
||||||
\ 'node_modules/.bin/eslint',
|
|
||||||
\])
|
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" Given a buffer, return a command prefix string which changes directory
|
" Given a buffer, return a command prefix string which changes directory
|
||||||
|
@ -44,10 +45,19 @@ function! ale#handlers#eslint#GetCdString(buffer) abort
|
||||||
" By default, the project root is simply the CWD of the running process.
|
" 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
|
" https://github.com/eslint/rfcs/blob/master/designs/2018-simplified-package-loading/README.md
|
||||||
" https://github.com/dense-analysis/ale/issues/2787
|
" https://github.com/dense-analysis/ale/issues/2787
|
||||||
" Identify project root from presence of node_modules dir.
|
"
|
||||||
|
" If eslint is installed in a directory which contains the buffer, assume
|
||||||
|
" it is the ESLint project root. Otherwise, use nearest node_modules.
|
||||||
" Note: If node_modules not present yet, can't load local deps anyway.
|
" Note: If node_modules not present yet, can't load local deps anyway.
|
||||||
let l:modules_dir = ale#path#FindNearestDirectory(a:buffer, 'node_modules')
|
let l:executable = ale#node#FindNearestExecutable(a:buffer, s:executables)
|
||||||
let l:project_dir = !empty(l:modules_dir) ? fnamemodify(l:modules_dir, ':h:h') : ''
|
|
||||||
|
if !empty(l:executable)
|
||||||
|
let l:nmi = strridx(l:executable, 'node_modules')
|
||||||
|
let l:project_dir = l:executable[0:l:nmi - 2]
|
||||||
|
else
|
||||||
|
let l:modules_dir = ale#path#FindNearestDirectory(a:buffer, 'node_modules')
|
||||||
|
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) ? ale#path#CdString(l:project_dir) : ''
|
||||||
endfunction
|
endfunction
|
||||||
|
|
|
@ -12,6 +12,18 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort
|
||||||
return ale#Var(a:buffer, a:base_var_name . '_executable')
|
return ale#Var(a:buffer, a:base_var_name . '_executable')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
let l:nearest = ale#node#FindNearestExecutable(a:buffer, a:path_list)
|
||||||
|
|
||||||
|
if !empty(l:nearest)
|
||||||
|
return l:nearest
|
||||||
|
endif
|
||||||
|
|
||||||
|
return ale#Var(a:buffer, a:base_var_name . '_executable')
|
||||||
|
endfunction
|
||||||
|
|
||||||
|
" Given a buffer number, a base variable name, and a list of paths to search
|
||||||
|
" for in ancestor directories, detect the executable path for a Node program.
|
||||||
|
function! ale#node#FindNearestExecutable(buffer, path_list) abort
|
||||||
for l:path in a:path_list
|
for l:path in a:path_list
|
||||||
let l:executable = ale#path#FindNearestFile(a:buffer, l:path)
|
let l:executable = ale#path#FindNearestFile(a:buffer, l:path)
|
||||||
|
|
||||||
|
@ -20,7 +32,7 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort
|
||||||
endif
|
endif
|
||||||
endfor
|
endfor
|
||||||
|
|
||||||
return ale#Var(a:buffer, a:base_var_name . '_executable')
|
return ''
|
||||||
endfunction
|
endfunction
|
||||||
|
|
||||||
" Create a executable string which executes a Node.js script command with a
|
" Create a executable string which executes a Node.js script command with a
|
||||||
|
|
0
test/eslint-test-files/react-app/subdir-with-package-json/node_modules/.gitkeep
generated
vendored
Normal file
0
test/eslint-test-files/react-app/subdir-with-package-json/node_modules/.gitkeep
generated
vendored
Normal file
|
@ -70,6 +70,25 @@ Execute(eslint.js executables should be run with node on Windows):
|
||||||
\ ale#handlers#eslint#GetCommand(bufnr(''))
|
\ ale#handlers#eslint#GetCommand(bufnr(''))
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
Execute(eslint.js should be run from containing project with eslint):
|
||||||
|
call ale#test#SetFilename('eslint-test-files/react-app/subdir-with-package-json/testfile.js')
|
||||||
|
|
||||||
|
" We have to execute the file with node.
|
||||||
|
if has('win32')
|
||||||
|
AssertEqual
|
||||||
|
\ ale#path#CdString(ale#path#Simplify(g:dir . '/eslint-test-files/react-app'))
|
||||||
|
\ . ale#Escape('node.exe') . ' '
|
||||||
|
\ . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js'))
|
||||||
|
\ . ' -f json --stdin --stdin-filename %s',
|
||||||
|
\ ale#handlers#eslint#GetCommand(bufnr(''))
|
||||||
|
else
|
||||||
|
AssertEqual
|
||||||
|
\ ale#path#CdString(ale#path#Simplify(g:dir . '/eslint-test-files/react-app'))
|
||||||
|
\ . ale#Escape(ale#path#Simplify(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js'))
|
||||||
|
\ . ' -f json --stdin --stdin-filename %s',
|
||||||
|
\ ale#handlers#eslint#GetCommand(bufnr(''))
|
||||||
|
endif
|
||||||
|
|
||||||
Execute(eslint.js executables can be run outside project dir):
|
Execute(eslint.js executables can be run outside project dir):
|
||||||
" Set filename above eslint-test-files (which contains node_modules)
|
" Set filename above eslint-test-files (which contains node_modules)
|
||||||
call ale#test#SetFilename('testfile.js')
|
call ale#test#SetFilename('testfile.js')
|
||||||
|
|
Reference in a new issue