mirror of
1
0
Fork 0
ultimate-vim/sources_non_forked/ale/autoload/ale/handlers/eslint.vim

281 lines
8.7 KiB
VimL

" Author: w0rp <devw0rp@gmail.com>
" Description: Functions for working with eslint, for checking or fixing files.
let s:executables = [
\ '.yarn/sdks/eslint/bin/eslint.js',
\ 'node_modules/.bin/eslint_d',
\ 'node_modules/eslint/bin/eslint.js',
\ 'node_modules/.bin/eslint',
\]
let s:sep = has('win32') ? '\' : '/'
call ale#Set('javascript_eslint_options', '')
call ale#Set('javascript_eslint_executable', 'eslint')
call ale#Set('javascript_eslint_use_global', get(g:, 'ale_use_global_executables', 0))
call ale#Set('javascript_eslint_suppress_eslintignore', 0)
call ale#Set('javascript_eslint_suppress_missing_config', 0)
function! ale#handlers#eslint#FindConfig(buffer) abort
for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
for l:basename in [
\ '.eslintrc.js',
\ '.eslintrc.yaml',
\ '.eslintrc.yml',
\ '.eslintrc.json',
\ '.eslintrc',
\]
let l:config = ale#path#Simplify(join([l:path, l:basename], s:sep))
if filereadable(l:config)
return l:config
endif
endfor
endfor
return ale#path#FindNearestFile(a:buffer, 'package.json')
endfunction
function! ale#handlers#eslint#GetExecutable(buffer) abort
return ale#path#FindExecutable(a:buffer, 'javascript_eslint', s:executables)
endfunction
" 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
" https://github.com/dense-analysis/ale/issues/2787
"
" 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.
let l:executable = ale#path#FindNearestExecutable(a:buffer, s:executables)
if !empty(l:executable)
let l:modules_index = strridx(l:executable, 'node_modules')
let l:modules_root = l:modules_index > -1 ? l:executable[0:l:modules_index - 2] : ''
let l:sdks_index = strridx(l:executable, ale#path#Simplify('.yarn/sdks'))
let l:sdks_root = l:sdks_index > -1 ? l:executable[0:l:sdks_index - 2] : ''
else
let l:modules_dir = ale#path#FindNearestDirectory(a:buffer, 'node_modules')
let l:modules_root = !empty(l:modules_dir) ? fnamemodify(l:modules_dir, ':h:h') : ''
let l:sdks_dir = ale#path#FindNearestDirectory(a:buffer, ale#path#Simplify('.yarn/sdks'))
let l:sdks_root = !empty(l:sdks_dir) ? fnamemodify(l:sdks_dir, ':h:h:h') : ''
endif
return strlen(l:modules_root) > strlen(l:sdks_root) ? l:modules_root : l:sdks_root
endfunction
function! ale#handlers#eslint#GetCommand(buffer) abort
let l:executable = ale#handlers#eslint#GetExecutable(a:buffer)
let l:options = ale#Var(a:buffer, 'javascript_eslint_options')
return ale#node#Executable(a:buffer, l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' -f json --stdin --stdin-filename %s'
endfunction
function! s:AddHintsForTypeScriptParsingErrors(output) abort
for l:item in a:output
let l:item.text = substitute(
\ l:item.text,
\ '^\(Parsing error\)',
\ '\1 (You may need configure typescript-eslint-parser)',
\ '',
\)
endfor
endfunction
function! s:CheckForBadConfig(buffer, lines) abort
let l:config_error_pattern = '\v^ESLint couldn''t find a configuration file'
\ . '|^Cannot read config file'
\ . '|^.*Configuration for rule .* is invalid'
\ . '|^ImportDeclaration should appear'
" Look for a message in the first few lines which indicates that
" a configuration file couldn't be found.
for l:line in a:lines[:10]
let l:match = matchlist(l:line, l:config_error_pattern)
if len(l:match) > 0
" Don't show the missing config error if we've disabled it.
if ale#Var(a:buffer, 'javascript_eslint_suppress_missing_config')
\&& l:match[0] is# 'ESLint couldn''t find a configuration file'
return 0
endif
return 1
endif
endfor
return 0
endfunction
function! s:parseJSON(buffer, lines) abort
let l:parsed = []
for l:line in a:lines
try
let l:parsed = extend(l:parsed, json_decode(l:line))
catch
endtry
endfor
if type(l:parsed) != v:t_list || empty(l:parsed)
return []
endif
let l:errors = l:parsed[0]['messages']
if empty(l:errors)
return []
endif
let l:output = []
for l:error in l:errors
let l:obj = ({
\ 'lnum': get(l:error, 'line', 0),
\ 'text': get(l:error, 'message', ''),
\ 'type': 'E',
\})
if get(l:error, 'severity', 0) is# 1
let l:obj.type = 'W'
endif
if has_key(l:error, 'ruleId')
let l:code = l:error['ruleId']
" Sometimes ESLint returns null here
if !empty(l:code)
let l:obj.code = l:code
endif
endif
if has_key(l:error, 'column')
let l:obj.col = l:error['column']
endif
if has_key(l:error, 'endColumn')
let l:obj.end_col = l:error['endColumn'] - 1
endif
if has_key(l:error, 'endLine')
let l:obj.end_lnum = l:error['endLine']
endif
call add(l:output, l:obj)
endfor
return l:output
endfunction
let s:col_end_patterns = [
\ '\vParsing error: Unexpected token (.+) ?',
\ '\v''(.+)'' is not defined.',
\ '\v%(Unexpected|Redundant use of) [''`](.+)[''`]',
\ '\vUnexpected (console) statement',
\]
function! s:parseLines(buffer, lines) abort
" Matches patterns line the following:
"
" /path/to/some-filename.js:47:14: Missing trailing comma. [Warning/comma-dangle]
" /path/to/some-filename.js:56:41: Missing semicolon. [Error/semi]
let l:pattern = '^.*:\(\d\+\):\(\d\+\): \(.\+\) \[\(.\+\)\]$'
" This second pattern matches lines like the following:
"
" /path/to/some-filename.js:13:3: Parsing error: Unexpected token
let l:parsing_pattern = '^.*:\(\d\+\):\(\d\+\): \(.\+\)$'
let l:output = []
for l:match in ale#util#GetMatches(a:lines, [l:pattern, l:parsing_pattern])
let l:text = l:match[3]
let l:obj = {
\ 'lnum': l:match[1] + 0,
\ 'col': l:match[2] + 0,
\ 'text': l:text,
\ 'type': 'E',
\}
" Take the error type from the output if available.
let l:split_code = split(l:match[4], '/')
if get(l:split_code, 0, '') is# 'Warning'
let l:obj.type = 'W'
endif
" The code can be something like 'Error/foo/bar', or just 'Error'
if !empty(get(l:split_code, 1))
let l:obj.code = join(l:split_code[1:], '/')
endif
for l:col_match in ale#util#GetMatches(l:text, s:col_end_patterns)
let l:obj.end_col = l:obj.col + len(l:col_match[1]) - 1
endfor
call add(l:output, l:obj)
endfor
return l:output
endfunction
function! s:FilterResult(buffer, obj) abort
if ale#Var(a:buffer, 'javascript_eslint_suppress_eslintignore')
if a:obj.text =~# '^File ignored'
return 0
endif
endif
if has_key(a:obj, 'code') && a:obj.code is# 'no-trailing-spaces'
\&& !ale#Var(a:buffer, 'warn_about_trailing_whitespace')
return 0
endif
return 1
endfunction
function! s:HandleESLintOutput(buffer, lines, type) abort
if s:CheckForBadConfig(a:buffer, a:lines)
return [{
\ 'lnum': 1,
\ 'text': 'eslint configuration error (type :ALEDetail for more information)',
\ 'detail': join(a:lines, "\n"),
\}]
endif
if a:lines == ['Could not connect']
return [{
\ 'lnum': 1,
\ 'text': 'Could not connect to eslint_d. Try updating eslint_d or killing it.',
\}]
endif
if a:type is# 'json'
let l:output = s:parseJSON(a:buffer, a:lines)
else
let l:output = s:parseLines(a:buffer, a:lines)
endif
call filter(l:output, {idx, obj -> s:FilterResult(a:buffer, obj)})
if expand('#' . a:buffer . ':t') =~? '\.tsx\?$'
call s:AddHintsForTypeScriptParsingErrors(l:output)
endif
return l:output
endfunction
function! ale#handlers#eslint#HandleJSON(buffer, lines) abort
return s:HandleESLintOutput(a:buffer, a:lines, 'json')
endfunction
function! ale#handlers#eslint#Handle(buffer, lines) abort
return s:HandleESLintOutput(a:buffer, a:lines, 'lines')
endfunction