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
|
call ale#Set('ruby_steep_executable', 'steep')
call ale#Set('ruby_steep_options', '')
" Find the nearest dir containing a Steepfile
function! ale_linters#ruby#steep#FindRoot(buffer) abort
for l:name in ['Steepfile']
let l:dir = fnamemodify(
\ ale#path#FindNearestFile(a:buffer, l:name),
\ ':h'
\)
if l:dir isnot# '.' && isdirectory(l:dir)
return l:dir
endif
endfor
return ''
endfunction
" Rename path relative to root
function! ale_linters#ruby#steep#RelativeToRoot(buffer, path) abort
let l:separator = has('win32') ? '\' : '/'
let l:steep_root = ale_linters#ruby#steep#FindRoot(a:buffer)
" path isn't under root
if l:steep_root is# ''
return ''
endif
let l:steep_root_prefix = l:steep_root . l:separator
" win32 path separators get interpreted by substitute, escape them
if has('win32')
let l:steep_root_pat = substitute(l:steep_root_prefix, '\\', '\\\\', 'g')
else
let l:steep_root_pat = l:steep_root_prefix
endif
return substitute(a:path, l:steep_root_pat, '', '')
endfunction
function! ale_linters#ruby#steep#GetCommand(buffer) abort
let l:executable = ale#Var(a:buffer, 'ruby_steep_executable')
" steep check needs to apply some config from the file path so:
" - steep check can't use stdin (no path)
" - steep check can't use %t (path outside of project)
" => we can only use %s
" somehow :ALEInfo shows that ALE still appends '< %t' to the command
" => luckily steep check ignores stdin
" somehow steep has a problem with absolute path to file but a path
" relative to Steepfile directory works:
" see https://github.com/soutaro/steep/pull/975
" => change to Steepfile directory and remove leading path
let l:buffer_filename = fnamemodify(bufname(a:buffer), ':p')
let l:buffer_filename = fnameescape(l:buffer_filename)
let l:relative = ale_linters#ruby#steep#RelativeToRoot(a:buffer, l:buffer_filename)
" if file is not under steep root, steep can't type check
if l:relative is# ''
" don't execute
return ''
endif
return ale#ruby#EscapeExecutable(l:executable, 'steep')
\ . ' check '
\ . ale#Var(a:buffer, 'ruby_steep_options')
\ . ' ' . fnameescape(l:relative)
endfunction
function! ale_linters#ruby#steep#GetType(severity) abort
if a:severity is? 'information'
\|| a:severity is? 'hint'
return 'I'
endif
if a:severity is? 'warning'
return 'W'
endif
return 'E'
endfunction
" Handle output from steep
function! ale_linters#ruby#steep#HandleOutput(buffer, lines) abort
let l:output = []
let l:in = 0
let l:item = {}
for l:line in a:lines
" Look for first line of a message block
" If not in-message (l:in == 0) that's expected
" If in-message (l:in > 0) that's less expected but let's recover
let l:match = matchlist(l:line, '^\([^:]*\):\([0-9]*\):\([0-9]*\): \[\([^]]*\)\] \(.*\)')
if len(l:match) > 0
" Something is lingering: recover by pushing what is there
if len(l:item) > 0
call add(l:output, l:item)
let l:item = {}
endif
let l:filename = l:match[1]
" Steep's reported column is offset by 1 (zero-indexed?)
let l:item = {
\ 'lnum': l:match[2] + 0,
\ 'col': l:match[3] + 1,
\ 'type': ale_linters#ruby#steep#GetType(l:match[4]),
\ 'text': l:match[5],
\}
" Done with this line, mark being in-message and go on with next line
let l:in = 1
continue
endif
" We're past the first line of a message block
if l:in > 0
" Look for code in subsequent lines of the message block
if l:line =~# '^│ Diagnostic ID:'
let l:match = matchlist(l:line, '^│ Diagnostic ID: \(.*\)')
if len(l:match) > 0
let l:item.code = l:match[1]
endif
" Done with the line
continue
endif
" Look for last line of the message block
if l:line =~# '^└'
" Done with the line, mark looking for underline and go on with the next line
let l:in = 2
continue
endif
" Look for underline right after last line
if l:in == 2
let l:match = matchlist(l:line, '\([~][~]*\)')
if len(l:match) > 0
let l:item.end_col = l:item['col'] + len(l:match[1]) - 1
endif
call add(l:output, l:item)
" Done with the line, mark looking for first line and go on with the next line
let l:in = 0
let l:item = {}
continue
endif
endif
endfor
return l:output
endfunction
call ale#linter#Define('ruby', {
\ 'name': 'steep',
\ 'executable': {b -> ale#Var(b, 'ruby_steep_executable')},
\ 'language': 'ruby',
\ 'command': function('ale_linters#ruby#steep#GetCommand'),
\ 'project_root': function('ale_linters#ruby#steep#FindRoot'),
\ 'callback': 'ale_linters#ruby#steep#HandleOutput',
\})
|