]> git.madduck.net Git - etc/vim.git/blobdiff - .vim/bundle/ale/ale_linters/ruby/steep.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:

Merge commit '76265755a1add77121c8f9dabb3e9bb70fe9a972' as '.vim/bundle/ale'
[etc/vim.git] / .vim / bundle / ale / ale_linters / ruby / steep.vim
diff --git a/.vim/bundle/ale/ale_linters/ruby/steep.vim b/.vim/bundle/ale/ale_linters/ruby/steep.vim
new file mode 100644 (file)
index 0000000..4fd5262
--- /dev/null
@@ -0,0 +1,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',
+\})