summaryrefslogtreecommitdiff
path: root/autoload/ale/handlers/eslint.vim
blob: eda033e4a83c9d7efca2526c0e912b4cc1187f6d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
" Author: w0rp <devw0rp@gmail.com>
" Description: Functions for working with eslint, for checking or fixing files.

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#node#FindExecutable(a:buffer, 'javascript_eslint', [
    \   'node_modules/.bin/eslint_d',
    \   'node_modules/eslint/bin/eslint.js',
    \   'node_modules/.bin/eslint',
    \])
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 unix --stdin --stdin-filename %s'
endfunction

let s:col_end_patterns = [
\   '\vParsing error: Unexpected token (.+) ?',
\   '\v''(.+)'' is not defined.',
\   '\v%(Unexpected|Redundant use of) [''`](.+)[''`]',
\   '\vUnexpected (console) statement',
\]

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! ale#handlers#eslint#Handle(buffer, lines) 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

    " 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]

        if ale#Var(a:buffer, 'javascript_eslint_suppress_eslintignore')
            if l:text is# 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.'
                continue
            endif
        endif

        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

    if expand('#' . a:buffer . ':t') =~? '\.tsx\?$'
        call s:AddHintsForTypeScriptParsingErrors(l:output)
    endif

    return l:output
endfunction