From 8cc28cdfbd34dd76bd1cd392747c266be7833494 Mon Sep 17 00:00:00 2001 From: w0rp Date: Thu, 15 Sep 2016 20:20:41 +0100 Subject: [PATCH] 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. --- README.md | 2 + ale_linters/javascript/eslint.vim | 1 + ale_linters/python/flake8.vim | 1 + ale_linters/sh/shell.vim | 75 +++++++++++++++++++++++++++++++ plugin/ale/zmain.vim | 68 ++++++++++++++++++++++------ 5 files changed, 133 insertions(+), 14 deletions(-) create mode 100644 ale_linters/sh/shell.vim diff --git a/README.md b/README.md index f85ae38..af452c6 100644 --- a/README.md +++ b/README.md @@ -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/) | diff --git a/ale_linters/javascript/eslint.vim b/ale_linters/javascript/eslint.vim index 96dfef9..43e32c9 100644 --- a/ale_linters/javascript/eslint.vim +++ b/ale_linters/javascript/eslint.vim @@ -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', diff --git a/ale_linters/python/flake8.vim b/ale_linters/python/flake8.vim index 437bc01..19dfc22 100644 --- a/ale_linters/python/flake8.vim +++ b/ale_linters/python/flake8.vim @@ -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', diff --git a/ale_linters/sh/shell.vim b/ale_linters/sh/shell.vim new file mode 100644 index 0000000..6c93933 --- /dev/null +++ b/ale_linters/sh/shell.vim @@ -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', +\}) diff --git a/plugin/ale/zmain.vim b/plugin/ale/zmain.vim index 548ae29..5547f69 100644 --- a/plugin/ale/zmain.vim +++ b/plugin/ale/zmain.vim @@ -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