Add prolog swipl linter (#1979)
* add prolog/swipl linter * use load_files/2 instead of read_term/2 Because it also checks some semantic warnings / errors not only syntactic warnings / errors. e.g.: * singleton warning * discontiguous warning * ... cf. http://www.swi-prolog.org/pldoc/doc_for?object=style_check/1 * support error messages with no line number :- module(module_name, [pred/0]). causes ERROR: Exported procedure module_name:pred/0 is not defined * add test for prolog/swipl handler * cosmetic fixes * detect timeout using SIGALRM * rename g:prolog_swipl_goals to g:prolog_swipl_load * write doc for prolog/swipl linter * update toc and README * fix ignore patterns
This commit is contained in:
parent
3d74a4f8a6
commit
34318aedf4
5 changed files with 255 additions and 0 deletions
|
@ -165,6 +165,7 @@ formatting.
|
|||
| PO | [alex](https://github.com/wooorm/alex) !!, [msgfmt](https://www.gnu.org/software/gettext/manual/html_node/msgfmt-Invocation.html), [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) |
|
||||
| Pod | [alex](https://github.com/wooorm/alex) !!, [proselint](http://proselint.com/), [write-good](https://github.com/btford/write-good) |
|
||||
| Pony | [ponyc](https://github.com/ponylang/ponyc) |
|
||||
| Prolog | [swipl](https://github.com/SWI-Prolog/swipl-devel) |
|
||||
| proto | [protoc-gen-lint](https://github.com/ckaznocha/protoc-gen-lint) |
|
||||
| Pug | [pug-lint](https://github.com/pugjs/pug-lint) |
|
||||
| Puppet | [languageserver](https://github.com/lingua-pupuli/puppet-editor-services), [puppet](https://puppet.com), [puppet-lint](https://puppet-lint.com) |
|
||||
|
|
100
ale_linters/prolog/swipl.vim
Normal file
100
ale_linters/prolog/swipl.vim
Normal file
|
@ -0,0 +1,100 @@
|
|||
" Author: Takuya Fujiwara <tyru.exe@gmail.com>
|
||||
" Description: swipl syntax / semantic check for Prolog files
|
||||
|
||||
call ale#Set('prolog_swipl_executable', 'swipl')
|
||||
call ale#Set('prolog_swipl_load', 'current_prolog_flag(argv, [File]), load_files(File, [sandboxed(true)]), halt.')
|
||||
call ale#Set('prolog_swipl_timeout', 3)
|
||||
call ale#Set('prolog_swipl_alarm', 'alarm(%t, (%h), _, [])')
|
||||
call ale#Set('prolog_swipl_alarm_handler', 'writeln(user_error, "ERROR: Exceeded %t seconds, Please change g:prolog_swipl_timeout to modify the limit."), halt(1)')
|
||||
|
||||
function! ale_linters#prolog#swipl#GetCommand(buffer) abort
|
||||
let l:goals = ale#Var(a:buffer, 'prolog_swipl_load')
|
||||
let l:goals = l:goals =~# '^\s*$' ? 'halt' : l:goals
|
||||
let l:timeout = ale#Var(a:buffer, 'prolog_swipl_timeout') + 0
|
||||
|
||||
if l:timeout > 0
|
||||
let l:goals = s:GetAlarm(a:buffer, l:timeout) . ', ' . l:goals
|
||||
endif
|
||||
|
||||
return '%e -g ' . ale#Escape(l:goals) . ' -- %s'
|
||||
endfunction
|
||||
|
||||
function! s:GetAlarm(buffer, timeout) abort
|
||||
let l:handler = ale#Var(a:buffer, 'prolog_swipl_alarm_handler')
|
||||
let l:handler = s:Subst(l:handler, {'t': a:timeout})
|
||||
let l:alarm = ale#Var(a:buffer, 'prolog_swipl_alarm')
|
||||
let l:alarm = s:Subst(l:alarm, {'t': a:timeout, 'h': l:handler})
|
||||
|
||||
return l:alarm
|
||||
endfunction
|
||||
|
||||
function! s:Subst(format, vars) abort
|
||||
let l:vars = extend(copy(a:vars), {'%': '%'})
|
||||
|
||||
return substitute(a:format, '%\(.\)', '\=get(l:vars, submatch(1), "")', 'g')
|
||||
endfunction
|
||||
|
||||
function! ale_linters#prolog#swipl#Handle(buffer, lines) abort
|
||||
let l:pattern = '\v^(ERROR|Warning)+%(:\s*[^:]+:(\d+)%(:(\d+))?)?:\s*(.*)$'
|
||||
let l:output = []
|
||||
let l:i = 0
|
||||
|
||||
while l:i < len(a:lines)
|
||||
let l:match = matchlist(a:lines[l:i], l:pattern)
|
||||
|
||||
if empty(l:match)
|
||||
let l:i += 1
|
||||
continue
|
||||
endif
|
||||
|
||||
let [l:i, l:text] = s:GetErrMsg(l:i, a:lines, l:match[4])
|
||||
let l:item = {
|
||||
\ 'lnum': (l:match[2] + 0 ? l:match[2] + 0 : 1),
|
||||
\ 'col': l:match[3] + 0,
|
||||
\ 'text': l:text,
|
||||
\ 'type': (l:match[1] is# 'ERROR' ? 'E' : 'W'),
|
||||
\}
|
||||
|
||||
if !s:Ignore(l:item)
|
||||
call add(l:output, l:item)
|
||||
endif
|
||||
endwhile
|
||||
|
||||
return l:output
|
||||
endfunction
|
||||
|
||||
" This returns [<next line number>, <error message string>]
|
||||
function! s:GetErrMsg(i, lines, text) abort
|
||||
if a:text !~# '^\s*$'
|
||||
return [a:i + 1, a:text]
|
||||
endif
|
||||
|
||||
let l:i = a:i + 1
|
||||
let l:text = []
|
||||
|
||||
while l:i < len(a:lines) && a:lines[l:i] =~# '^\s'
|
||||
call add(l:text, s:Trim(a:lines[l:i]))
|
||||
let l:i += 1
|
||||
endwhile
|
||||
|
||||
return [l:i, join(l:text, '. ')]
|
||||
endfunction
|
||||
|
||||
function! s:Trim(str) abort
|
||||
return substitute(a:str, '\v^\s+|\s+$', '', 'g')
|
||||
endfunction
|
||||
|
||||
" Skip sandbox error which is caused by directives
|
||||
" because what we want is syntactic or semantic check.
|
||||
function! s:Ignore(item) abort
|
||||
return a:item.type is# 'E' &&
|
||||
\ a:item.text =~# '\vNo permission to (call|directive|assert) sandboxed'
|
||||
endfunction
|
||||
|
||||
call ale#linter#Define('prolog', {
|
||||
\ 'name': 'swipl',
|
||||
\ 'output_stream': 'stderr',
|
||||
\ 'executable_callback': ale#VarFunc('prolog_swipl_executable'),
|
||||
\ 'command_callback': 'ale_linters#prolog#swipl#GetCommand',
|
||||
\ 'callback': 'ale_linters#prolog#swipl#Handle',
|
||||
\})
|
56
doc/ale-prolog.txt
Normal file
56
doc/ale-prolog.txt
Normal file
|
@ -0,0 +1,56 @@
|
|||
===============================================================================
|
||||
ALE Prolog Integration *ale-prolog-options*
|
||||
|
||||
|
||||
===============================================================================
|
||||
swipl *ale-prolog-swipl*
|
||||
|
||||
g:ale_prolog_swipl_executable *g:ale_prolog_swipl_executable*
|
||||
*b:ale_prolog_swipl_executable*
|
||||
Type: |String|
|
||||
Default: `'swipl'`
|
||||
|
||||
The executable that will be run for the `swipl` linter.
|
||||
|
||||
g:ale_prolog_swipl_load *g:ale_prolog_swipl_load*
|
||||
*b:ale_prolog_swipl_load*
|
||||
Type: |String|
|
||||
Default: `'current_prolog_flag(argv, [File]), load_files(File, [sandboxed(true)]), halt.'`
|
||||
|
||||
The prolog goals that will be passed to |g:ale_prolog_swipl_executable| with `-g` option.
|
||||
|
||||
It does:
|
||||
1. Takes the first command argument (current file path)
|
||||
2. Checks (syntactic / semantic) problems and output to stderr
|
||||
|
||||
NOTE: `sandboxed(true)` prohibits executing some directives such as 'initialization main'.
|
||||
|
||||
g:ale_prolog_swipl_timeout *g:ale_prolog_swipl_timeout*
|
||||
*b:ale_prolog_swipl_timeout*
|
||||
Type: |Number|
|
||||
Default: `3`
|
||||
|
||||
Timeout seconds to detect long-running linter.
|
||||
It is done by setting SIGALRM.
|
||||
See |g:ale_prolog_swipl_alarm| and |g:ale_prolog_swipl_alarm_handler|.
|
||||
|
||||
g:ale_prolog_swipl_alarm *g:ale_prolog_swipl_alarm*
|
||||
*b:ale_prolog_swipl_alarm*
|
||||
Type: |String|
|
||||
Default: `'alarm(%t, (%h), _, [])'`
|
||||
|
||||
The prolog goals to be expected to set SIGALRM.
|
||||
`%t` is replaced by |g:ale_prolog_swipl_timeout|.
|
||||
`%h` is replaced by |g:ale_prolog_swipl_alarm_handler|.
|
||||
|
||||
g:ale_prolog_swipl_alarm_handler *g:ale_prolog_swipl_alarm_handler*
|
||||
*b:ale_prolog_swipl_alarm_handler*
|
||||
Type: |String|
|
||||
Default: `'writeln(user_error, "ERROR: Exceeded %t seconds, Please change g:prolog_swipl_timeout to modify the limit."), halt(1)'`
|
||||
|
||||
The prolog goals to be expected that will be run on SIGALRM.
|
||||
`%t` is replaced by |g:ale_prolog_swipl_timeout|.
|
||||
|
||||
|
||||
===============================================================================
|
||||
vim:tw=78:ts=2:sts=2:sw=2:ft=help:norl:
|
|
@ -226,6 +226,8 @@ CONTENTS *ale-contents*
|
|||
write-good..........................|ale-pod-write-good|
|
||||
pony..................................|ale-pony-options|
|
||||
ponyc...............................|ale-pony-ponyc|
|
||||
prolog................................|ale-prolog-options|
|
||||
swipl...............................|ale-prolog-swipl|
|
||||
proto.................................|ale-proto-options|
|
||||
protoc-gen-lint.....................|ale-proto-protoc-gen-lint|
|
||||
pug...................................|ale-pug-options|
|
||||
|
@ -453,6 +455,7 @@ Notes:
|
|||
* PO: `alex`!!, `msgfmt`, `proselint`, `write-good`
|
||||
* Pod: `alex`!!, `proselint`, `write-good`
|
||||
* Pony: `ponyc`
|
||||
* Prolog: `swipl`
|
||||
* proto: `protoc-gen-lint`
|
||||
* Pug: `pug-lint`
|
||||
* Puppet: `languageserver`, `puppet`, `puppet-lint`
|
||||
|
|
95
test/handler/test_swipl_handler.vader
Normal file
95
test/handler/test_swipl_handler.vader
Normal file
|
@ -0,0 +1,95 @@
|
|||
Before:
|
||||
runtime ale_linters/prolog/swipl.vim
|
||||
|
||||
After:
|
||||
call ale#linter#Reset()
|
||||
|
||||
Execute (The swipl handler should handle oneline warning / error):
|
||||
call ale#test#SetFilename('test.pl')
|
||||
AssertEqual
|
||||
\ [
|
||||
\ {
|
||||
\ 'lnum': 5,
|
||||
\ 'col': 1,
|
||||
\ 'text': 'Syntax error: Operator expected',
|
||||
\ 'type': 'E',
|
||||
\ },
|
||||
\ ],
|
||||
\ ale_linters#prolog#swipl#Handle(bufnr(''), [
|
||||
\ 'ERROR: /path/to/test.pl:5:1: Syntax error: Operator expected',
|
||||
\ ])
|
||||
|
||||
Execute (The swipl handler should handle a warning / error of two lines):
|
||||
call ale#test#SetFilename('test.pl')
|
||||
AssertEqual
|
||||
\ [
|
||||
\ {
|
||||
\ 'lnum': 9,
|
||||
\ 'col': 0,
|
||||
\ 'text': 'Singleton variables: [M]',
|
||||
\ 'type': 'W',
|
||||
\ },
|
||||
\ ],
|
||||
\ ale_linters#prolog#swipl#Handle(bufnr(''), [
|
||||
\ 'Warning: /path/to/test.pl:9:',
|
||||
\ ' Singleton variables: [M]',
|
||||
\ ])
|
||||
|
||||
Execute (The swipl handler should join three or more lines with '. '):
|
||||
call ale#test#SetFilename('test.pl')
|
||||
AssertEqual
|
||||
\ [
|
||||
\ {
|
||||
\ 'lnum': 10,
|
||||
\ 'col': 0,
|
||||
\ 'text': 'Clauses of fib/2 are not together in the source-file. Earlier definition at /path/to/test.pl:7. Current predicate: f/0. Use :- discontiguous fib/2. to suppress this message',
|
||||
\ 'type': 'W',
|
||||
\ },
|
||||
\ ],
|
||||
\ ale_linters#prolog#swipl#Handle(bufnr(''), [
|
||||
\ 'Warning: /path/to/test.pl:10:',
|
||||
\ ' Clauses of fib/2 are not together in the source-file',
|
||||
\ ' Earlier definition at /path/to/test.pl:7',
|
||||
\ ' Current predicate: f/0',
|
||||
\ ' Use :- discontiguous fib/2. to suppress this message',
|
||||
\ ])
|
||||
|
||||
Execute (The swipl handler should ignore warnings / errors 'No permission to call sandboxed ...'):
|
||||
call ale#test#SetFilename('test.pl')
|
||||
AssertEqual
|
||||
\ [],
|
||||
\ ale_linters#prolog#swipl#Handle(bufnr(''), [
|
||||
\ 'ERROR: /path/to/test.pl:11:',
|
||||
\ ' No permission to call sandboxed `''$set_predicate_attribute''(_G3416:_G3417,_G3413,_G3414)''',
|
||||
\ ' Reachable from:',
|
||||
\ ' system:''$set_pattr''(A,B,C,D)',
|
||||
\ ' system:''$set_pattr''(vimscript:A,B,C)',
|
||||
\ ' vimscript: (multifile A)',
|
||||
\ 'ERROR: /path/to/test.pl:12:',
|
||||
\ ' No permission to call sandboxed `''$set_predicate_attribute''(_G205:_G206,_G202,_G203)''',
|
||||
\ ' Reachable from:',
|
||||
\ ' system:''$set_pattr''(A,B,C,D)',
|
||||
\ ' system:''$set_pattr''(vimscript:A,B,C)',
|
||||
\ ' vimscript: (multifile A)',
|
||||
\ 'ERROR: /path/to/test.pl:13:',
|
||||
\ ' No permission to call sandboxed `''$set_predicate_attribute''(_G1808:_G1809,_G1805,_G1806)''',
|
||||
\ ' Reachable from:',
|
||||
\ ' system:''$set_pattr''(A,B,C,D)',
|
||||
\ ' system:''$set_pattr''(vimscript:A,B,C)',
|
||||
\ ' vimscript: (multifile A)',
|
||||
\ ])
|
||||
|
||||
Execute (The swipl handler should handle a warning / error with no line number):
|
||||
call ale#test#SetFilename('test.pl')
|
||||
AssertEqual
|
||||
\ [
|
||||
\ {
|
||||
\ 'lnum': 1,
|
||||
\ 'col': 0,
|
||||
\ 'text': 'Exported procedure module_name:pred/0 is not defined',
|
||||
\ 'type': 'E',
|
||||
\ },
|
||||
\ ],
|
||||
\ ale_linters#prolog#swipl#Handle(bufnr(''), [
|
||||
\ 'ERROR: Exported procedure module_name:pred/0 is not defined',
|
||||
\ ])
|
Reference in a new issue