aboutsummaryrefslogtreecommitdiff
path: root/dotfiles/.vim/autoload/neomake/makers/ft/rust.vim
blob: 71ddec189c94cbe6c2056a993798068efe0c274a (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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
function! neomake#makers#ft#rust#EnabledMakers() abort
    return ['cargo']
endfunction

function! neomake#makers#ft#rust#rustc() abort
    return {
        \ 'errorformat':
            \ '%-Gerror: aborting due to previous error,'.
            \ '%-Gerror: aborting due to %\\d%\\+ previous errors,'.
            \ '%-Gerror: Could not compile `%s`.,'.
            \ '%Eerror[E%n]: %m,'.
            \ '%Eerror: %m,'.
            \ '%Wwarning: %m,'.
            \ '%Inote: %m,'.
            \ '%-Z\ %#-->\ %f:%l:%c,'.
            \ '%G\ %#\= %*[^:]: %m,'.
            \ '%G\ %#|\ %#%\\^%\\+ %m,'.
            \ '%I%>help:\ %#%m,'.
            \ '%Z\ %#%m,'.
            \ '%-G%.%#',
        \ }
endfunction

function! s:get_cargo_workspace_root() abort
    if !exists('b:_neomake_cargo_workspace')
        let cmd = 'cargo metadata --no-deps --format-version 1'
        let [cd_error, cd_back_cmd] = neomake#utils#temp_cd(expand('%:h'))
        if !empty(cd_error)
            call neomake#log#debug(printf(
                        \ 's:get_cargo_workspace_root: failed to cd to buffer directory: %s.',
                        \ cd_error))
        endif
        let output = system(cmd)
        if !empty(cd_back_cmd)
            exe cd_back_cmd
        endif
        if v:shell_error
            call neomake#log#debug(printf(
                        \ 'Failed to get cargo metadata for workspace using %s.',
                        \ string(cmd)))
            let b:_neomake_cargo_workspace = ''
        else
            let json = neomake#compat#json_decode(output)
            let b:_neomake_cargo_workspace = json['workspace_root']
        endif
    endif
    return b:_neomake_cargo_workspace
endfunction

function! s:get_cargo_maker_cwd(default) abort
    let cargo_workspace_root = s:get_cargo_workspace_root()
    if !empty(cargo_workspace_root)
        return cargo_workspace_root
    endif

    let cargo_toml = neomake#utils#FindGlobFile('Cargo.toml')
    if !empty(cargo_toml)
        return fnamemodify(cargo_toml, ':h')
    endif

    return a:default
endfunction

function! neomake#makers#ft#rust#cargotest() abort
    " NOTE: duplicates are removed due to https://github.com/rust-lang/cargo/issues/5128.
    let maker = {
        \ 'exe': 'cargo',
        \ 'args': ['test', '%:t:r', '--quiet'],
        \ 'append_file': 0,
        \ 'uses_filename': 0,
        \ 'postprocess': copy(g:neomake#postprocess#remove_duplicates),
        \ 'errorformat':
            \ '%-G,' .
            \ '%-Gtest %s,' .
            \ '%-Grunning %\\d%# test%s,' .
            \ '%-Gfailures:%s,' .
            \ '%-G----%s,' .
            \ '%-G%.%#--verbose%s,' .
            \ '%-G%.%#--explain%s,' .
            \ '%-Gerror: aborting due to previous error,' .
            \ '%-G%\ %#error: aborting due to %\\d%#%\ %#previous errors,' .
            \ '%E%\ %#error[E%n]:\ %m,' .
            \ '%E%\ %#error:\ %m,' .
            \ '%I%\ %#note:\ %m,'.
            \ '%W%\ %#warning:\ %m,' .
            \ '%-Z%\ %#-->\ %f:%l:%c,' .
            \ '%-G%\\d%# %#|\ %s,' .
            \ '%-G%\\d%# %#|,' .
            \ '%-G\ %#\= %*[^:]:\ %m,'.
            \ '%E%\ %#%m,' .
            \ '%G%\ %#%s%\\,,' .
            \ '%Z%\ %#%s%\\,%\\s%f:%l:%c'
    \ }

    function! maker.InitForJob(_jobinfo) abort
        if !has_key(self, 'cwd')
            let self.cwd = s:get_cargo_maker_cwd('%:p:h')
            return self
        endif
    endfunction
    return maker
endfunction

function! neomake#makers#ft#rust#cargo() abort
    let maker_command = get(b:, 'neomake_rust_cargo_command',
                \ get(g:, 'neomake_rust_cargo_command', ['check']))
    let maker = {
        \ 'args': maker_command + ['--message-format=json', '--quiet'],
        \ 'append_file': 0,
        \ 'process_output': function('neomake#makers#ft#rust#CargoProcessOutput'),
        \ }

    function! maker.InitForJob(_jobinfo) abort
        if !has_key(self, 'cwd')
            let self.cwd = s:get_cargo_maker_cwd('%:p:h')
            return self
        endif
    endfunction
    return maker
endfunction

" NOTE: does not use process_json, since cargo outputs multiple JSON root
" elements per line.
function! neomake#makers#ft#rust#CargoProcessOutput(context) abort
    let errors = []
    for line in a:context['output']
        if line[0] !=# '{'
            continue
        endif

        let decoded = neomake#compat#json_decode(line)
        let data = get(decoded, 'message', -1)
        if type(data) != type({}) || empty(data['spans'])
            continue
        endif

        let error = {'maker_name': 'cargo'}
        let code_dict = get(data, 'code', -1)
        if code_dict is g:neomake#compat#json_null
            if get(data, 'level', '') ==# 'warning'
                let error.type = 'W'
            else
                let error.type = 'E'
            endif
        else
            let error.type = code_dict['code'][0]
            let error.nr = code_dict['code'][1:]
        endif

        let span = data.spans[0]
        for candidate_span in data.spans
            if candidate_span.is_primary
                let span = candidate_span
                break
            endif
        endfor

        let expanded = 0
        let has_expansion = type(span.expansion) == type({})
                    \ && type(span.expansion.span) == type({})
                    \ && type(span.expansion.def_site_span) == type({})

        if span.file_name =~# '^<.*>$' && has_expansion
            let expanded = 1
            call neomake#makers#ft#rust#FillErrorFromSpan(error,
                        \ span.expansion.span)
        else
            call neomake#makers#ft#rust#FillErrorFromSpan(error, span)
        endif

        let error.text = data.message
        let detail = span.label
        let children = data.children
        if type(detail) == type('') && !empty(detail)
            let error.text = error.text . ': ' . detail
        elseif !empty(children) && has_key(children[0], 'message')
            let error.text = error.text . '. ' . children[0].message
        endif

        call add(errors, error)

        if has_expansion && !expanded
            let error = copy(error)
            call neomake#makers#ft#rust#FillErrorFromSpan(error,
                        \ span.expansion.span)
            call add(errors, error)
        endif

        for child in children[1:]
            if !has_key(child, 'message')
                continue
            endif

            let info = deepcopy(error)
            let info.type = 'I'
            let info.text = child.message
            call neomake#postprocess#compress_whitespace(info)
            if has_key(child, 'rendered')
                        \ && !(child.rendered is g:neomake#compat#json_null)
                let info.text = info.text . ': ' . child.rendered
            endif

            if len(child.spans)
                let span = child.spans[0]
                if span.file_name =~# '^<.*>$'
                            \ && type(span.expansion) == type({})
                            \ && type(span.expansion.span) == type({})
                            \ && type(span.expansion.def_site_span) == type({})
                    call neomake#makers#ft#rust#FillErrorFromSpan(info,
                                \ span.expansion.span)
                else
                    call neomake#makers#ft#rust#FillErrorFromSpan(info, span)
                endif
                let detail = span.label
                if type(detail) == type('') && len(detail)
                    let info.text = info.text . ': ' . detail
                endif
            endif

            call add(errors, info)
        endfor
    endfor
    return errors
endfunction

function! neomake#makers#ft#rust#FillErrorFromSpan(error, span) abort
    let a:error.filename = a:span.file_name
    let a:error.col = a:span.column_start
    let a:error.lnum = a:span.line_start
    let a:error.length = a:span.byte_end - a:span.byte_start
endfunction

" vim: ts=4 sw=4 et