173 lines
5.3 KiB
VimL
173 lines
5.3 KiB
VimL
|
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',
|
||
|
\})
|