]> git.madduck.net Git - etc/vim.git/blob - ale_linters/elm/make.vim

madduck's git repository

Every one of the projects in this repository is available at the canonical URL git://git.madduck.net/madduck/pub/<projectpath> — see each project's metadata for the exact URL.

All patches and comments are welcome. Please squash your changes to logical commits before using git-format-patch and git-send-email to patches@git.madduck.net. If you'd read over the Git project's submission guidelines and adhered to them, I'd be especially grateful.

SSH access, as well as push access can be individually arranged.

If you use my repositories frequently, consider adding the following snippet to ~/.gitconfig and using the third clone URL listed for each project:

[url "git://git.madduck.net/madduck/"]
  insteadOf = madduck:

Squashed '.vim/bundle/ale/' content from commit 22185c4c
[etc/vim.git] / ale_linters / elm / make.vim
1 " Author: buffalocoder - https://github.com/buffalocoder, soywod - https://github.com/soywod, hecrj - https://github.com/hecrj
2 " Description: Elm linting in Ale. Closely follows the Syntastic checker in https://github.com/ElmCast/elm-vim.
3
4 call ale#Set('elm_make_executable', 'elm')
5 call ale#Set('elm_make_use_global', get(g:, 'ale_use_global_executables', 0))
6
7 function! ale_linters#elm#make#Handle(buffer, lines) abort
8     let l:output = []
9     let l:unparsed_lines = []
10
11     for l:line in a:lines
12         if l:line[0] is# '{'
13             " Elm 0.19
14             call ale_linters#elm#make#HandleElm019Line(l:line, l:output)
15         elseif l:line[0] is# '['
16             " Elm 0.18
17             call ale_linters#elm#make#HandleElm018Line(l:line, l:output)
18         elseif l:line isnot# 'Successfully generated /dev/null'
19             call add(l:unparsed_lines, l:line)
20         endif
21     endfor
22
23     if len(l:unparsed_lines) > 0
24         call add(l:output, {
25         \    'lnum': 1,
26         \    'type': 'E',
27         \    'text': l:unparsed_lines[0],
28         \    'detail': join(l:unparsed_lines, "\n")
29         \})
30     endif
31
32     return l:output
33 endfunction
34
35 function! ale_linters#elm#make#HandleElm019Line(line, output) abort
36     let l:report = json_decode(a:line)
37
38     if l:report.type is? 'error'
39         " General problem
40         let l:details = ale_linters#elm#make#ParseMessage(l:report.message)
41
42         if empty(l:report.path)
43             let l:report.path = 'Elm'
44         endif
45
46         if ale_linters#elm#make#FileIsBuffer(l:report.path)
47             call add(a:output, {
48             \    'lnum': 1,
49             \    'type': 'E',
50             \    'text': l:details,
51             \})
52         else
53             call add(a:output, {
54             \    'lnum': 1,
55             \    'type': 'E',
56             \    'text': l:report.path .' - '. l:details,
57             \    'detail': l:report.path ." ----------\n\n". l:details,
58             \})
59         endif
60     else
61         " Compilation errors
62         for l:error in l:report.errors
63             let l:file_is_buffer = ale_linters#elm#make#FileIsBuffer(l:error.path)
64
65             for l:problem in l:error.problems
66                 let l:details = ale_linters#elm#make#ParseMessage(l:problem.message)
67
68                 if l:file_is_buffer
69                     " Buffer module has problems
70                     call add(a:output, {
71                     \    'lnum': l:problem.region.start.line,
72                     \    'col': l:problem.region.start.column,
73                     \    'end_lnum': l:problem.region.end.line,
74                     \    'end_col': l:problem.region.end.column,
75                     \    'type': 'E',
76                     \    'text': l:details,
77                     \})
78                 else
79                     " Imported module has problems
80                     let l:location = l:error.path .':'. l:problem.region.start.line
81                     call add(a:output, {
82                     \    'lnum': 1,
83                     \    'type': 'E',
84                     \    'text': l:location .' - '. l:details,
85                     \    'detail': l:location ." ----------\n\n". l:details,
86                     \})
87                 endif
88             endfor
89         endfor
90     endif
91 endfunction
92
93 function! ale_linters#elm#make#HandleElm018Line(line, output) abort
94     let l:errors = json_decode(a:line)
95
96     for l:error in l:errors
97         let l:file_is_buffer = ale_linters#elm#make#FileIsBuffer(l:error.file)
98
99         if l:file_is_buffer
100             " Current buffer has problems
101             call add(a:output, {
102             \    'lnum': l:error.region.start.line,
103             \    'col': l:error.region.start.column,
104             \    'end_lnum': l:error.region.end.line,
105             \    'end_col': l:error.region.end.column,
106             \    'type': (l:error.type is? 'error') ? 'E' : 'W',
107             \    'text': l:error.overview,
108             \    'detail': l:error.overview . "\n\n" . l:error.details
109             \})
110         elseif l:error.type is? 'error'
111             " Imported module has errors
112             let l:location = l:error.file .':'. l:error.region.start.line
113
114             call add(a:output, {
115             \    'lnum': 1,
116             \    'type': 'E',
117             \    'text': l:location .' - '. l:error.overview,
118             \    'detail': l:location ." ----------\n\n". l:error.overview . "\n\n" . l:error.details
119             \})
120         endif
121     endfor
122 endfunction
123
124 function! ale_linters#elm#make#FileIsBuffer(path) abort
125     return ale#path#IsTempName(a:path)
126 endfunction
127
128 function! ale_linters#elm#make#ParseMessage(message) abort
129     return join(map(copy(a:message), 'ale_linters#elm#make#ParseMessageItem(v:val)'), '')
130 endfunction
131
132 function! ale_linters#elm#make#ParseMessageItem(item) abort
133     if type(a:item) is v:t_string
134         return a:item
135     else
136         return a:item.string
137     endif
138 endfunction
139
140 function! ale_linters#elm#make#GetPackageFile(buffer) abort
141     let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm.json')
142
143     if empty(l:elm_json)
144         " Fallback to Elm 0.18
145         let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm-package.json')
146     endif
147
148     return l:elm_json
149 endfunction
150
151 function! ale_linters#elm#make#IsVersionGte19(buffer) abort
152     let l:elm_json = ale_linters#elm#make#GetPackageFile(a:buffer)
153
154     if l:elm_json =~# '-package'
155         return 0
156     else
157         return 1
158     endif
159 endfunction
160
161 function! ale_linters#elm#make#GetRootDir(buffer) abort
162     let l:elm_json = ale_linters#elm#make#GetPackageFile(a:buffer)
163
164     if empty(l:elm_json)
165         return ''
166     else
167         return fnamemodify(l:elm_json, ':p:h')
168     endif
169 endfunction
170
171 function! ale_linters#elm#make#IsTest(buffer) abort
172     let l:root_dir = ale_linters#elm#make#GetRootDir(a:buffer)
173
174     if empty(l:root_dir)
175         return 0
176     endif
177
178     let l:tests_dir = join([l:root_dir, 'tests', ''], has('win32') ? '\' : '/')
179
180     let l:buffer_path = fnamemodify(bufname(a:buffer), ':p')
181
182     if stridx(l:buffer_path, l:tests_dir) == 0
183         return 1
184     else
185         return 0
186     endif
187 endfunction
188
189 function! ale_linters#elm#make#GetCwd(buffer) abort
190     let l:root_dir = ale_linters#elm#make#GetRootDir(a:buffer)
191
192     return !empty(l:root_dir) ? l:root_dir : ''
193 endfunction
194
195 " Return the command to execute the linter in the projects directory.
196 " If it doesn't, then this will fail when imports are needed.
197 function! ale_linters#elm#make#GetCommand(buffer) abort
198     let l:executable = ale_linters#elm#make#GetExecutable(a:buffer)
199     let l:is_v19 = ale_linters#elm#make#IsVersionGte19(a:buffer)
200     let l:is_using_elm_test = l:executable =~# 'elm-test$'
201
202     " elm-test needs to know the path of elm-make if elm isn't installed globally.
203     " https://github.com/rtfeldman/node-test-runner/blob/57728f10668f2d2ab3179e7e3208bcfa9a1f19aa/README.md#--compiler
204     if l:is_v19 && l:is_using_elm_test
205         let l:elm_make_executable = ale#path#FindExecutable(a:buffer, 'elm_make', ['node_modules/.bin/elm'])
206         let l:elm_test_compiler_flag = ' --compiler ' . l:elm_make_executable . ' '
207     else
208         let l:elm_test_compiler_flag = ' '
209     endif
210
211     " The elm compiler, at the time of this writing, uses '/dev/null' as
212     " a sort of flag to tell the compiler not to generate an output file,
213     " which is why this is hard coded here.
214     " Source: https://github.com/elm-lang/elm-compiler/blob/19d5a769b30ec0b2fc4475985abb4cd94cd1d6c3/builder/src/Generate/Output.hs#L253
215     return '%e make --report=json --output=/dev/null'
216     \   . l:elm_test_compiler_flag
217     \   . '%t'
218 endfunction
219
220 function! ale_linters#elm#make#GetExecutable(buffer) abort
221     let l:is_test = ale_linters#elm#make#IsTest(a:buffer)
222     let l:is_v19 = ale_linters#elm#make#IsVersionGte19(a:buffer)
223
224     if l:is_test && l:is_v19
225         return ale#path#FindExecutable(
226         \   a:buffer,
227         \   'elm_make',
228         \   ['node_modules/.bin/elm-test', 'node_modules/.bin/elm']
229         \)
230     else
231         return ale#path#FindExecutable(a:buffer, 'elm_make', ['node_modules/.bin/elm'])
232     endif
233 endfunction
234
235 call ale#linter#Define('elm', {
236 \   'name': 'make',
237 \   'executable': function('ale_linters#elm#make#GetExecutable'),
238 \   'output_stream': 'both',
239 \   'cwd': function('ale_linters#elm#make#GetCwd'),
240 \   'command': function('ale_linters#elm#make#GetCommand'),
241 \   'callback': 'ale_linters#elm#make#Handle'
242 \})