summaryrefslogtreecommitdiff
path: root/ale_linters/elm/make.vim
blob: 4b354d43852b772849ff32ef74984f561c2a73df (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
" Author: buffalocoder - https://github.com/buffalocoder, soywod - https://github.com/soywod, hecrj - https://github.com/hecrj
" Description: Elm linting in Ale. Closely follows the Syntastic checker in https://github.com/ElmCast/elm-vim.

call ale#Set('elm_make_executable', 'elm')
call ale#Set('elm_make_use_global', get(g:, 'ale_use_global_executables', 0))

function! ale_linters#elm#make#GetExecutable(buffer) abort
    return ale#node#FindExecutable(a:buffer, 'elm_make', [
    \   'node_modules/.bin/elm',
    \])
endfunction

function! ale_linters#elm#make#Handle(buffer, lines) abort
    let l:output = []
    let l:unparsed_lines = []

    for l:line in a:lines
        if l:line[0] is# '{'
            let l:report = json_decode(l:line)

            if l:report.type is? 'error'
                " General problem
                let l:details = ale_linters#elm#make#ParseMessage(l:report.message)

                if ale_linters#elm#make#FileIsBuffer(l:report.path)
                    call add(l:output, {
                                \    'lnum': 1,
                                \    'type': 'E',
                                \    'text': l:details,
                                \})
                else
                    call add(l:output, {
                                \    'lnum': 1,
                                \    'type': 'E',
                                \    'text': l:report.path .' - '. l:details,
                                \    'detail': l:report.path ." ----------\n\n". l:details,
                                \})
                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(l:output, {
                                        \    '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,
                                        \})
                        else
                            " Imported module has problems
                            let l:location = l:error.path .':'. l:problem.region.start.line
                            call add(l:output, {
                                        \    'lnum': 1,
                                        \    'type': 'E',
                                        \    'text': l:location .' - '. l:details,
                                        \    'detail': l:location ." ----------\n\n". l:details,
                                        \})
                        endif
                    endfor
                endfor
            endif
        else
            call add(l:unparsed_lines, l:line)
        endif
    endfor

    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

    return l:output
endfunction

function! ale_linters#elm#make#FileIsBuffer(path) abort
    let l:is_windows = has('win32')
    let l:temp_dir = l:is_windows ? $TMP : $TMPDIR

    if has('win32')
        return a:path[0:len(l:temp_dir) - 1] is? l:temp_dir
    else
        return a:path[0:len(l:temp_dir) - 1] is# l:temp_dir
    endif
endfunction

function! ale_linters#elm#make#ParseMessage(message) abort
    return join(map(copy(a:message), 'ale_linters#elm#make#ParseMessageItem(v:val)'), '')
endfunction

function! ale_linters#elm#make#ParseMessageItem(item) abort
    if type(a:item) == type('')
        return a:item
    else
        return a:item.string
    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
    let l:elm_json = ale#path#FindNearestFile(a:buffer, 'elm.json')
    let l:elm_exe = ale_linters#elm#make#GetExecutable(a:buffer)

    if empty(l:elm_json)
        let l:dir_set_cmd = ''
    else
        let l:root_dir = fnamemodify(l:elm_json, ':p:h')
        let l:dir_set_cmd = 'cd ' . ale#Escape(l:root_dir) . ' && '
    endif

    " The elm compiler, at the time of this writing, uses '/dev/null' as
    " a sort of flag to tell the compiler not to generate an output file,
    " which is why this is hard coded here.
    " Source: https://github.com/elm-lang/elm-compiler/blob/19d5a769b30ec0b2fc4475985abb4cd94cd1d6c3/builder/src/Generate/Output.hs#L253
    let l:elm_cmd = ale#Escape(l:elm_exe)
    \   . ' make'
    \   . ' --report=json'
    \   . ' --output=/dev/null'

    return l:dir_set_cmd . ' ' . l:elm_cmd . ' %t'
endfunction

call ale#linter#Define('elm', {
\    'name': 'make',
\    'executable_callback': 'ale_linters#elm#make#GetExecutable',
\    'output_stream': 'both',
\    'command_callback': 'ale_linters#elm#make#GetCommand',
\    'callback': 'ale_linters#elm#make#Handle'
\})