280 lines
8.7 KiB
VimL
280 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
|