Add support for Bash and other shells. Add support for reading from stderr, and for generating the executable from functions. Both were needed to support shell linting.

This commit is contained in:
w0rp 2016-09-15 20:20:41 +01:00
parent d3047c9cf6
commit 8cc28cdfbd
5 changed files with 133 additions and 14 deletions

View File

@ -19,6 +19,8 @@ follow later.
| Language | Tools |
| -------- | ----- |
| Bash | -n flag |
| Bourne Shell | -n flag |
| JavaScript | [eslint](http://eslint.org/) |
| Python | [flake8](http://flake8.pycqa.org/en/latest/) |

View File

@ -43,6 +43,7 @@ function! ale_linters#javascript#eslint#Handle(buffer, lines)
endfunction
call ALEAddLinter('javascript', {
\ 'name': 'eslint',
\ 'executable': 'eslint',
\ 'command': 'eslint -f unix --stdin',
\ 'callback': 'ale_linters#javascript#eslint#Handle',

View File

@ -45,6 +45,7 @@ function! ale_linters#python#flake8#Handle(buffer, lines)
endfunction
call ALEAddLinter('python', {
\ 'name': 'flake8',
\ 'executable': 'flake8',
\ 'command': 'flake8 -',
\ 'callback': 'ale_linters#python#flake8#Handle',

75
ale_linters/sh/shell.vim Normal file
View File

@ -0,0 +1,75 @@
if exists('g:loaded_ale_linters_sh_shell')
finish
endif
let g:loaded_ale_linters_sh_shell = 1
" This option can be changed to change the default shell when the shell
" cannot be taken from the hashbang line.
if !exists('g:ale_linters_sh_shell_default_shell')
let g:ale_linters_sh_shell_default_shell = 'bash'
endif
function! ale_linters#sh#shell#GetExecutable(buffer)
let shell = g:ale_linters_sh_shell_default_shell
let banglines = getbufline(a:buffer, 1)
" Take the shell executable from the hashbang, if we can.
if len(banglines) == 1
let bangmatch = matchlist(banglines[0], '^#!\([^ ]\+\)')
if len(bangmatch) > 0
let shell = bangmatch[1]
endif
endif
return shell
endfunction
function! ale_linters#sh#shell#GetCommand(buffer)
return ale_linters#sh#shell#GetExecutable(a:buffer) . ' -n'
endfunction
function! ale_linters#sh#shell#Handle(buffer, lines)
" Matches patterns line the following:
"
" bash: line 13: syntax error near unexpected token `d'
" sh: 11: Syntax error: "(" unexpected
let pattern = '^[^:]\+: \%(line \|\)\(\d\+\): \(.\+\)'
let output = []
for line in a:lines
let l:match = matchlist(line, pattern)
if len(l:match) == 0
continue
endif
let line = l:match[1] + 0
let column = 1
let text = l:match[2]
let type = 'E'
" vcol is Needed to indicate that the column is a character.
call add(output, {
\ 'bufnr': a:buffer,
\ 'lnum': line,
\ 'vcol': 0,
\ 'col': column,
\ 'text': text,
\ 'type': type,
\ 'nr': -1,
\})
endfor
return output
endfunction
call ALEAddLinter('sh', {
\ 'name': 'shell',
\ 'output_stream': 'stderr',
\ 'executable_callback': 'ale_linters#sh#shell#GetExecutable',
\ 'command_callback': 'ale_linters#sh#shell#GetCommand',
\ 'callback': 'ale_linters#sh#shell#Handle',
\})

View File

@ -141,20 +141,36 @@ function! s:ApplyLinter(buffer, linter)
endif
if has('nvim')
let a:linter.job = jobstart(command, {
\ 'on_stdout': 's:GatherOutputNeoVim',
\ 'on_exit': 's:HandleExitNeoVim',
\})
if a:linter.output_stream ==# 'stderr'
" Read from stderr instead of stdout.
let a:linter.job = jobstart(command, {
\ 'on_stderr': 's:GatherOutputNeoVim',
\ 'on_exit': 's:HandleExitNeoVim',
\})
else
let a:linter.job = jobstart(command, {
\ 'on_stdout': 's:GatherOutputNeoVim',
\ 'on_exit': 's:HandleExitNeoVim',
\})
endif
else
" Vim 8 will read the stdin from the file's buffer.
let a:linter.job = job_start(command, {
let job_options = {
\ 'out_mode': 'nl',
\ 'err_mode': 'nl',
\ 'out_cb': function('s:GatherOutputVim'),
\ 'close_cb': function('s:HandleExitVim'),
\ 'in_io': 'buffer',
\ 'in_buf': a:buffer,
\})
\}
if a:linter.output_stream ==# 'stderr'
" Read from stderr instead of stdout.
let job_options.err_cb = function('s:GatherOutputVim')
else
let job_options.out_cb = function('s:GatherOutputVim')
endif
" Vim 8 will read the stdin from the file's buffer.
let a:linter.job = job_start(l:command, l:job_options)
call ch_close_in(job_getchannel(a:linter.job))
endif
@ -182,6 +198,18 @@ function! s:TimerHandler(...)
let g:ale_buffer_should_reset_map[buffer] = 1
for linter in linters
" Check if a given linter has a program which can be executed.
if has_key(linter, 'executable_callback')
let l:executable = s:GetFunction(linter.executable_callback)(buffer)
else
let l:executable = linter.executable
endif
if !executable(l:executable)
" The linter's program cannot be executed, so skip it.
continue
endif
call s:ApplyLinter(buffer, linter)
endfor
endfunction
@ -197,16 +225,20 @@ function s:BufferCleanup(buffer)
endfunction
function! ALEAddLinter(filetype, linter)
" Check if the linter program is executable before adding it.
if !executable(a:linter.executable)
return
endif
if !has_key(s:linters, a:filetype)
let s:linters[a:filetype] = []
endif
let new_linter = {'callback': a:linter.callback}
let new_linter = {
\ 'name': a:linter.name,
\ 'callback': a:linter.callback,
\}
if has_key(a:linter, 'executable_callback')
let new_linter.executable_callback = a:linter.executable_callback
else
let new_linter.executable = a:linter.executable
endif
if has_key(a:linter, 'command_callback')
let new_linter.command_callback = a:linter.command_callback
@ -214,6 +246,14 @@ function! ALEAddLinter(filetype, linter)
let new_linter.command = a:linter.command
endif
if has_key(a:linter, 'output_stream')
let new_linter.output_stream = a:linter.output_stream
else
let new_linter.output_stream = 'stdout'
endif
" TODO: Assert the value of the output_stream to be something sensible.
call add(s:linters[a:filetype], new_linter)
endfunction