2018-05-11 18:14:00 +00:00
|
|
|
" Author: buffalocoder - https://github.com/buffalocoder, soywod - https://github.com/soywod, hecrj - https://github.com/hecrj
|
2016-12-13 09:06:04 +00:00
|
|
|
" Description: Elm linting in Ale. Closely follows the Syntastic checker in https://github.com/ElmCast/elm-vim.
|
|
|
|
|
2018-05-11 18:07:28 +00:00
|
|
|
call ale#Set('elm_make_executable', 'elm')
|
|
|
|
call ale#Set('elm_make_use_global', get(g:, 'ale_use_global_executables', 0))
|
2017-09-10 11:58:42 +00:00
|
|
|
|
2017-01-22 14:54:57 +00:00
|
|
|
function! ale_linters#elm#make#Handle(buffer, lines) abort
|
2016-12-13 09:06:04 +00:00
|
|
|
let l:output = []
|
2017-06-25 16:12:40 +00:00
|
|
|
let l:unparsed_lines = []
|
2018-05-11 17:15:40 +00:00
|
|
|
|
2016-12-13 09:06:04 +00:00
|
|
|
for l:line in a:lines
|
2018-05-11 17:15:40 +00:00
|
|
|
if l:line[0] is# '{'
|
2018-05-15 15:06:52 +00:00
|
|
|
" Elm 0.19
|
|
|
|
call ale_linters#elm#make#HandleElm019Line(l:line, l:output)
|
|
|
|
elseif l:line[0] is# '['
|
|
|
|
" Elm 0.18
|
|
|
|
call ale_linters#elm#make#HandleElm018Line(l:line, l:output)
|
|
|
|
elseif l:line isnot# 'Successfully generated /dev/null'
|
2017-06-25 16:12:40 +00:00
|
|
|
call add(l:unparsed_lines, l:line)
|
2016-12-13 09:06:04 +00:00
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
|
2017-06-25 16:12:40 +00:00
|
|
|
if len(l:unparsed_lines) > 0
|
|
|
|
call add(l:output, {
|
|
|
|
\ 'lnum': 1,
|
|
|
|
\ 'type': 'E',
|
|
|
|
\ 'text': l:unparsed_lines[0],
|
|
|
|
\ 'detail': join(l:unparsed_lines, "\n")
|
|
|
|
\})
|
|
|
|
endif
|
|
|
|
|
2016-12-13 09:06:04 +00:00
|
|
|
return l:output
|
|
|
|
endfunction
|
|
|
|
|
2018-05-15 15:06:52 +00:00
|
|
|
function! ale_linters#elm#make#HandleElm019Line(line, output) abort
|
|
|
|
let l:report = json_decode(a:line)
|
|
|
|
|
|
|
|
if l:report.type is? 'error'
|
|
|
|
" General problem
|
|
|
|
let l:details = ale_linters#elm#make#ParseMessage(l:report.message)
|
|
|
|
|
|
|
|
if empty(l:report.path)
|
|
|
|
let l:report.path = 'Elm'
|
|
|
|
endif
|
|
|
|
|
|
|
|
if ale_linters#elm#make#FileIsBuffer(l:report.path)
|
|
|
|
call add(a:output, {
|
2018-05-25 20:22:47 +00:00
|
|
|
\ 'lnum': 1,
|
|
|
|
\ 'type': 'E',
|
|
|
|
\ 'text': l:details,
|
|
|
|
\})
|
2018-05-15 15:06:52 +00:00
|
|
|
else
|
|
|
|
call add(a:output, {
|
2018-05-25 20:22:47 +00:00
|
|
|
\ 'lnum': 1,
|
|
|
|
\ 'type': 'E',
|
|
|
|
\ 'text': l:report.path .' - '. l:details,
|
|
|
|
\ 'detail': l:report.path ." ----------\n\n". l:details,
|
|
|
|
\})
|
2018-05-15 15:06:52 +00:00
|
|
|
endif
|
|
|
|
else
|
|
|
|
" Compilation errors
|
|
|
|
for l:error in l:report.errors
|
|
|
|
let l:file_is_buffer = ale_linters#elm#make#FileIsBuffer(l:error.path)
|
|
|
|
|
|
|
|
for l:problem in l:error.problems
|
|
|
|
let l:details = ale_linters#elm#make#ParseMessage(l:problem.message)
|
|
|
|
|
|
|
|
if l:file_is_buffer
|
|
|
|
" Buffer module has problems
|
|
|
|
call add(a:output, {
|
2018-05-25 20:22:47 +00:00
|
|
|
\ 'lnum': l:problem.region.start.line,
|
|
|
|
\ 'col': l:problem.region.start.column,
|
|
|
|
\ 'end_lnum': l:problem.region.end.line,
|
|
|
|
\ 'end_col': l:problem.region.end.column,
|
|
|
|
\ 'type': 'E',
|
|
|
|
\ 'text': l:details,
|
|
|
|
\})
|
2018-05-15 15:06:52 +00:00
|
|
|
else
|
|
|
|
" Imported module has problems
|
|
|
|
let l:location = l:error.path .':'. l:problem.region.start.line
|
|
|
|
call add(a:output, {
|
2018-05-25 20:22:47 +00:00
|
|
|
\ 'lnum': 1,
|
|
|
|
\ 'type': 'E',
|
|
|
|
\ 'text': l:location .' - '. l:details,
|
|
|
|
\ 'detail': l:location ." ----------\n\n". l:details,
|
|
|
|
\})
|
2018-05-15 15:06:52 +00:00
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
endfor
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! ale_linters#elm#make#HandleElm018Line(line, output) abort
|
|
|
|
let l:errors = json_decode(a:line)
|
|
|
|
|
|
|
|
for l:error in l:errors
|
|
|
|
let l:file_is_buffer = ale_linters#elm#make#FileIsBuffer(l:error.file)
|
|
|
|
|
|
|
|
if l:file_is_buffer
|
|
|
|
" Current buffer has problems
|
|
|
|
call add(a:output, {
|
2018-05-25 20:22:47 +00:00
|
|
|
\ 'lnum': l:error.region.start.line,
|
|
|
|
\ 'col': l:error.region.start.column,
|
|
|
|
\ 'end_lnum': l:error.region.end.line,
|
|
|
|
\ 'end_col': l:error.region.end.column,
|
|
|
|
\ 'type': (l:error.type is? 'error') ? 'E' : 'W',
|
|
|
|
\ 'text': l:error.overview,
|
|
|
|
\ 'detail': l:error.overview . "\n\n" . l:error.details
|
|
|
|
\})
|
2018-05-15 15:06:52 +00:00
|
|
|
elseif l:error.type is? 'error'
|
|
|
|
" Imported module has errors
|
|
|
|
let l:location = l:error.file .':'. l:error.region.start.line
|
|
|
|
|
|
|
|
call add(a:output, {
|
2018-05-25 20:22:47 +00:00
|
|
|
\ 'lnum': 1,
|
|
|
|
\ 'type': 'E',
|
|
|
|
\ 'text': l:location .' - '. l:error.overview,
|
|
|
|
\ 'detail': l:location ." ----------\n\n". l:error.overview . "\n\n" . l:error.details
|
|
|
|
\})
|
2018-05-15 15:06:52 +00:00
|
|
|
endif
|
|
|
|
endfor
|
|
|
|
endfunction
|
|
|
|
|
2018-05-11 17:15:40 +00:00
|
|
|
function! ale_linters#elm#make#FileIsBuffer(path) abort
|
2018-07-12 12:05:59 +00:00
|
|
|
return ale#path#IsTempName(a:path)
|
2018-05-11 17:15:40 +00:00
|
|
|
endfunction
|
|
|
|
|
2018-05-12 02:16:14 +00:00
|
|
|
function! ale_linters#elm#make#ParseMessage(message) abort
|
|
|
|
return join(map(copy(a:message), 'ale_linters#elm#make#ParseMessageItem(v:val)'), '')
|
|
|
|
endfunction
|
|
|
|
|
2018-05-11 17:15:40 +00:00
|
|
|
function! ale_linters#elm#make#ParseMessageItem(item) abort
|
2018-07-25 00:27:28 +00:00
|
|
|
if type(a:item) is v:t_string
|
2018-05-11 17:15:40 +00:00
|
|
|
return a:item
|
|
|
|
else
|
|
|
|
return a:item.string
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
2019-01-01 18:42:06 +00:00
|
|
|
function! ale_linters#elm#make#GetPackageFile(buffer) abort
|
2018-05-11 17:15:40 +00:00
|
|
|
let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm.json')
|
|
|
|
|
2018-05-15 15:06:52 +00:00
|
|
|
if empty(l:elm_json)
|
|
|
|
" Fallback to Elm 0.18
|
|
|
|
let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm-package.json')
|
|
|
|
endif
|
|
|
|
|
2019-01-01 18:42:06 +00:00
|
|
|
return l:elm_json
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! ale_linters#elm#make#IsVersionGte19(buffer) abort
|
|
|
|
let l:elm_json = ale_linters#elm#make#GetPackageFile(a:buffer)
|
|
|
|
|
|
|
|
if l:elm_json =~# '-package'
|
|
|
|
return 0
|
|
|
|
else
|
|
|
|
return 1
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! ale_linters#elm#make#GetRootDir(buffer) abort
|
|
|
|
let l:elm_json = ale_linters#elm#make#GetPackageFile(a:buffer)
|
|
|
|
|
2018-05-11 17:15:40 +00:00
|
|
|
if empty(l:elm_json)
|
2019-01-01 18:42:06 +00:00
|
|
|
return ''
|
|
|
|
else
|
|
|
|
return fnamemodify(l:elm_json, ':p:h')
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
function! ale_linters#elm#make#IsTest(buffer) abort
|
|
|
|
let l:root_dir = ale_linters#elm#make#GetRootDir(a:buffer)
|
|
|
|
|
|
|
|
if empty(l:root_dir)
|
|
|
|
return 0
|
|
|
|
endif
|
|
|
|
|
|
|
|
let l:tests_dir = join([l:root_dir, 'tests', ''], has('win32') ? '\' : '/')
|
|
|
|
|
|
|
|
let l:buffer_path = fnamemodify(bufname(a:buffer), ':p')
|
|
|
|
|
2019-01-03 19:45:25 +00:00
|
|
|
if stridx(l:buffer_path, l:tests_dir) == 0
|
2019-01-01 18:42:06 +00:00
|
|
|
return 1
|
|
|
|
else
|
|
|
|
return 0
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
|
|
|
" Return the command to execute the linter in the projects directory.
|
|
|
|
" If it doesn't, then this will fail when imports are needed.
|
|
|
|
function! ale_linters#elm#make#GetCommand(buffer) abort
|
2019-01-19 21:04:41 +00:00
|
|
|
let l:executable = ale_linters#elm#make#GetExecutable(a:buffer)
|
2019-01-01 18:42:06 +00:00
|
|
|
let l:root_dir = ale_linters#elm#make#GetRootDir(a:buffer)
|
2019-01-19 21:04:41 +00:00
|
|
|
let l:is_v19 = ale_linters#elm#make#IsVersionGte19(a:buffer)
|
|
|
|
let l:is_using_elm_test = l:executable =~# 'elm-test$'
|
2019-01-01 18:42:06 +00:00
|
|
|
|
|
|
|
if empty(l:root_dir)
|
2016-12-13 09:06:04 +00:00
|
|
|
let l:dir_set_cmd = ''
|
|
|
|
else
|
2017-05-12 08:20:16 +00:00
|
|
|
let l:dir_set_cmd = 'cd ' . ale#Escape(l:root_dir) . ' && '
|
2016-12-13 09:06:04 +00:00
|
|
|
endif
|
|
|
|
|
2019-01-19 21:04:41 +00:00
|
|
|
" elm-test needs to know the path of elm-make if elm isn't installed globally.
|
|
|
|
" https://github.com/rtfeldman/node-test-runner/blob/57728f10668f2d2ab3179e7e3208bcfa9a1f19aa/README.md#--compiler
|
|
|
|
if l:is_v19 && l:is_using_elm_test
|
|
|
|
let l:elm_make_executable = ale#node#FindExecutable(a:buffer, 'elm_make', ['node_modules/.bin/elm'])
|
|
|
|
let l:elm_test_compiler_flag = ' --compiler ' . l:elm_make_executable . ' '
|
|
|
|
else
|
|
|
|
let l:elm_test_compiler_flag = ' '
|
|
|
|
endif
|
|
|
|
|
2018-05-11 17:15:40 +00:00
|
|
|
" The elm compiler, at the time of this writing, uses '/dev/null' as
|
Elm file filter & Windows bug fixes (#223)
* Filters out unrelated errors in Elm linter
The function now filters out errors that are unrelated to the file,
those that were found in imported modules.
It does this by comparing the temp directory environment variable to the
file name in the elm output. If the file begins with the temp directory,
then it sould be included (it's from the buffer).
* Changing output to '/dev/null'
Turns out the compiler only accepts /dev/null as an ignorable name. It's
hard-coded here
https://github.com/elm-lang/elm-make/blob/master/src/Flags.hs
Changing this allows Windows linting to work. Otherwise the compiler
errors when using "nul"
* Fixes for Windows
Should now be able to successfully handle Windows.
Windows seemed to not handle the ";" properly, so I switched it to "&&",
which probably should've been done anyway to prevent false positives.
Oddly, matchend(l:error.file, l:temp_dir), and various other regex
solutions, couldn't properly match the two. Subsetting did though, hence
the new solution.
* Applying corrections
Made the file check case-insensitive for Windows, case-sensitive for
Unix/non-windows.
Added comment explaining hard coding of 'dev/null'
* Spelling correction
* Minor corrections
Actually uses the is_file_buffer variable now, added space between the
if statements, and added space between '-'
2016-12-16 10:41:21 +00:00
|
|
|
" a sort of flag to tell the compiler not to generate an output file,
|
2018-05-11 17:15:40 +00:00
|
|
|
" which is why this is hard coded here.
|
|
|
|
" Source: https://github.com/elm-lang/elm-compiler/blob/19d5a769b30ec0b2fc4475985abb4cd94cd1d6c3/builder/src/Generate/Output.hs#L253
|
2019-01-19 21:04:41 +00:00
|
|
|
return l:dir_set_cmd . '%e make --report=json --output=/dev/null' . l:elm_test_compiler_flag . '%t'
|
2016-12-13 09:06:04 +00:00
|
|
|
endfunction
|
|
|
|
|
2019-01-01 18:42:06 +00:00
|
|
|
function! ale_linters#elm#make#GetExecutable(buffer) abort
|
|
|
|
let l:is_test = ale_linters#elm#make#IsTest(a:buffer)
|
|
|
|
let l:is_v19 = ale_linters#elm#make#IsVersionGte19(a:buffer)
|
|
|
|
|
|
|
|
if l:is_test && l:is_v19
|
2019-01-04 06:57:14 +00:00
|
|
|
return ale#node#FindExecutable(
|
2019-02-06 18:05:13 +00:00
|
|
|
\ a:buffer,
|
|
|
|
\ 'elm_make',
|
|
|
|
\ ['node_modules/.bin/elm-test', 'node_modules/.bin/elm']
|
|
|
|
\)
|
2019-01-01 18:42:06 +00:00
|
|
|
else
|
|
|
|
return ale#node#FindExecutable(a:buffer, 'elm_make', ['node_modules/.bin/elm'])
|
|
|
|
endif
|
|
|
|
endfunction
|
|
|
|
|
2016-12-13 09:06:04 +00:00
|
|
|
call ale#linter#Define('elm', {
|
2018-08-02 22:44:12 +00:00
|
|
|
\ 'name': 'make',
|
2019-01-01 18:42:06 +00:00
|
|
|
\ 'executable_callback': 'ale_linters#elm#make#GetExecutable',
|
2018-08-02 22:44:12 +00:00
|
|
|
\ 'output_stream': 'both',
|
|
|
|
\ 'command_callback': 'ale_linters#elm#make#GetCommand',
|
|
|
|
\ 'callback': 'ale_linters#elm#make#Handle'
|
2016-12-13 09:06:04 +00:00
|
|
|
\})
|