diff options
author | Jon Parise <jon@indelible.org> | 2017-08-30 10:59:40 -0700 |
---|---|---|
committer | Jon Parise <jon@indelible.org> | 2017-08-30 11:08:06 -0700 |
commit | f4c5d29c64ccb1aa02a0d77ca84e52b0ef84eca0 (patch) | |
tree | 9fd69f2c9933766853c8481416efc3fa0cc22653 /ale_linters/thrift | |
parent | c7536fb4fdf76747dc3920cb21f39fd702477c90 (diff) | |
download | ale-f4c5d29c64ccb1aa02a0d77ca84e52b0ef84eca0.zip |
Add a linter for Apache Thrift IDL files
This linter works by invoking the `thrift` compiler with the buffer
contents and reporting any parser and code generation issues.
The handler rolls its own output-matching loop because we have the
(unfortunate) requirement of handling error output that spans multiple
lines.
Unit tests cover both the command callback and handler, and there is
initial documentation for all of the option variables.
Diffstat (limited to 'ale_linters/thrift')
-rw-r--r-- | ale_linters/thrift/thrift.vim | 91 |
1 files changed, 91 insertions, 0 deletions
diff --git a/ale_linters/thrift/thrift.vim b/ale_linters/thrift/thrift.vim new file mode 100644 index 00000000..2f62570a --- /dev/null +++ b/ale_linters/thrift/thrift.vim @@ -0,0 +1,91 @@ +" Author: Jon Parise <jon@indelible.org> + +call ale#Set('thrift_thrift_executable', 'thrift') +call ale#Set('thrift_thrift_generators', ['cpp']) +call ale#Set('thrift_thrift_includes', []) +call ale#Set('thrift_thrift_options', '-strict') + +function! ale_linters#thrift#thrift#GetExecutable(buffer) abort + return ale#Var(a:buffer, 'thrift_thrift_executable') +endfunction + +function! ale_linters#thrift#thrift#GetCommand(buffer) abort + let l:generators = ale#Var(a:buffer, 'thrift_thrift_generators') + let l:includes = ale#Var(a:buffer, 'thrift_thrift_includes') + + " The thrift compiler requires at least one generator. If none are set, + " fall back to our default value to avoid silently failing. We could also + " `throw` here, but that seems even less helpful. + if empty(l:generators) + let l:generators = ['cpp'] + endif + + let l:output_dir = tempname() + call mkdir(l:output_dir) + call ale#engine#ManageDirectory(a:buffer, l:output_dir) + + return ale#Escape(ale_linters#thrift#thrift#GetExecutable(a:buffer)) + \ . ' ' . join(map(copy(l:generators), "'--gen ' . v:val")) + \ . ' ' . join(map(copy(l:includes), "'-I ' . v:val")) + \ . ' ' . ale#Var(a:buffer, 'thrift_thrift_options') + \ . ' -out ' . ale#Escape(l:output_dir) + \ . ' %t' +endfunction + +function! ale_linters#thrift#thrift#Handle(buffer, lines) abort + " Matches lines like the following: + " + " [SEVERITY:/path/filename.thrift:31] Message text + " [ERROR:/path/filename.thrift:31] (last token was ';') + let l:pattern = '\v^\[(\u+):(.*):(\d+)\] (.*)$' + + let l:index = 0 + let l:output = [] + + " Roll our own output-matching loop instead of using ale#util#GetMatches + " because we need to support error messages that span multiple lines. + while l:index < len(a:lines) + let l:line = a:lines[l:index] + + let l:match = matchlist(l:line, l:pattern) + if empty(l:match) + let l:index += 1 + continue + endif + + let l:severity = l:match[1] + if l:severity is# 'WARNING' + let l:type = 'W' + else + let l:type = 'E' + endif + + " If our text looks like "(last token was ';')", the *next* line + " should contain a more descriptive error message. + let l:text = l:match[4] + if l:text =~# '\(last token was .*\)' + let l:index += 1 + let l:text = get(a:lines, l:index, 'Unknown error ' . l:text) + endif + + call add(l:output, { + \ 'lnum': l:match[3] + 0, + \ 'col': 0, + \ 'type': l:type, + \ 'text': l:text, + \}) + + let l:index += 1 + endwhile + + return l:output +endfunction + +call ale#linter#Define('thrift', { +\ 'name': 'thrift', +\ 'executable': 'thrift', +\ 'output_stream': 'both', +\ 'executable_callback': 'ale_linters#thrift#thrift#GetExecutable', +\ 'command_callback': 'ale_linters#thrift#thrift#GetCommand', +\ 'callback': 'ale_linters#thrift#thrift#Handle', +\}) |