summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTakuya Fujiwara <tyru.exe@gmail.com>2018-10-27 01:29:17 +0900
committerw0rp <w0rp@users.noreply.github.com>2018-10-26 17:29:17 +0100
commit34318aedf4ffaf4558a102db6c03ae3b13d5ff08 (patch)
tree283fb2a9e72c80aacf07dc2bdeb2bb0ca1960c2d
parent3d74a4f8a6d89ccaf5194736ab91936124d62cb1 (diff)
downloadale-34318aedf4ffaf4558a102db6c03ae3b13d5ff08.zip
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
-rw-r--r--README.md1
-rw-r--r--ale_linters/prolog/swipl.vim100
-rw-r--r--doc/ale-prolog.txt56
-rw-r--r--doc/ale.txt3
-rw-r--r--test/handler/test_swipl_handler.vader95
5 files changed, 255 insertions, 0 deletions
diff --git a/README.md b/README.md
index f3d616f3..4b6d7663 100644
--- a/README.md
+++ b/README.md
@@ -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) |
diff --git a/ale_linters/prolog/swipl.vim b/ale_linters/prolog/swipl.vim
new file mode 100644
index 00000000..401e52b6
--- /dev/null
+++ b/ale_linters/prolog/swipl.vim
@@ -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',
+\})
diff --git a/doc/ale-prolog.txt b/doc/ale-prolog.txt
new file mode 100644
index 00000000..14062a5a
--- /dev/null
+++ b/doc/ale-prolog.txt
@@ -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:
diff --git a/doc/ale.txt b/doc/ale.txt
index 66ce8ab1..95110431 100644
--- a/doc/ale.txt
+++ b/doc/ale.txt
@@ -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`
diff --git a/test/handler/test_swipl_handler.vader b/test/handler/test_swipl_handler.vader
new file mode 100644
index 00000000..9e425cf6
--- /dev/null
+++ b/test/handler/test_swipl_handler.vader
@@ -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',
+ \ ])