diff options
-rw-r--r-- | .travis.yml | 2 | ||||
-rw-r--r-- | Makefile | 53 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | ale_linters/go/gometalinter.vim | 2 | ||||
-rw-r--r-- | ale_linters/haskell/ghc-mod.vim | 16 | ||||
-rw-r--r-- | autoload/ale.vim | 5 | ||||
-rw-r--r-- | autoload/ale/engine.vim | 295 | ||||
-rw-r--r-- | autoload/ale/job.vim | 270 | ||||
-rw-r--r-- | autoload/ale/lsp.vim | 138 | ||||
-rwxr-xr-x | custom-checks | 2 | ||||
-rw-r--r-- | doc/ale.txt | 2 | ||||
-rwxr-xr-x | run-tests | 171 | ||||
l--------- | test/.config/nvim/init.vim | 1 | ||||
-rw-r--r-- | test/handler/test_ghc_mod_handler.vader | 27 | ||||
-rw-r--r-- | test/handler/test_gometalinter_handler.vader | 25 | ||||
-rw-r--r-- | test/lsp/test_lsp_connections.vader | 119 | ||||
-rw-r--r-- | test/sign/test_linting_sets_signs.vader | 1 | ||||
-rw-r--r-- | test/smoke_test.vader | 3 | ||||
-rw-r--r-- | test/test_ale_toggle.vader | 5 | ||||
-rw-r--r-- | test/test_command_chain.vader | 7 | ||||
-rw-r--r-- | test/test_conflicting_plugin_warnings.vader | 6 | ||||
-rw-r--r-- | test/test_cursor_warnings.vader | 5 | ||||
-rw-r--r-- | test/test_line_join.vader | 2 | ||||
-rw-r--r-- | test/test_vim8_processid_parsing.vader | 8 |
24 files changed, 793 insertions, 374 deletions
diff --git a/.travis.yml b/.travis.yml index d4e6cf3f..9374b0cc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,4 +7,4 @@ branches: - master language: python script: | - make test + ./run-tests diff --git a/Makefile b/Makefile deleted file mode 100644 index 86ac17d9..00000000 --- a/Makefile +++ /dev/null @@ -1,53 +0,0 @@ -SHELL := /usr/bin/env bash -IMAGE ?= w0rp/ale -CURRENT_IMAGE_ID = 30a9967dbdb1 -DOCKER_FLAGS = --rm -v $(PWD):/testplugin -v $(PWD)/test:/home "$(IMAGE)" -tests = test/*.vader test/*/*.vader test/*/*/*.vader test/*/*/*/*.vader - -test-setup: - docker images -q w0rp/ale | grep ^$(CURRENT_IMAGE_ID) > /dev/null || \ - docker pull $(IMAGE) - -vader: test-setup - @:; \ - vims=$$(docker run --rm $(IMAGE) ls /vim-build/bin | grep -E '^n?vim'); \ - if [ -z "$$vims" ]; then echo "No Vims found!"; exit 1; fi; \ - for vim in $$vims; do \ - docker run -a stderr $(DOCKER_FLAGS) $$vim '+Vader! $(tests)'; \ - done - -test: test-setup - @:; \ - vims=$$(docker run --rm $(IMAGE) ls /vim-build/bin | grep -E '^n?vim'); \ - if [ -z "$$vims" ]; then echo "No Vims found!"; exit 1; fi; \ - EXIT=0; \ - for vim in $$vims; do \ - echo; \ - echo '========================================'; \ - echo "Running tests for $$vim"; \ - echo '========================================'; \ - echo; \ - docker run -a stderr $(DOCKER_FLAGS) $$vim '+Vader! $(tests)' || EXIT=$$?; \ - done; \ - echo; \ - echo '========================================'; \ - echo 'Running Vint to lint our code'; \ - echo '========================================'; \ - echo 'Vint warnings/errors follow:'; \ - echo; \ - set -o pipefail; \ - docker run -a stdout $(DOCKER_FLAGS) vint -s /testplugin | sed s:^/testplugin/:: || EXIT=$$?; \ - set +o pipefail; \ - echo; \ - echo '========================================'; \ - echo 'Running custom checks'; \ - echo '========================================'; \ - echo 'Custom warnings/errors follow:'; \ - echo; \ - set -o pipefail; \ - docker run -v $(PWD):/testplugin "$(IMAGE)" /testplugin/custom-checks /testplugin | sed s:^/testplugin/:: || EXIT=$$?; \ - set +o pipefail; \ - echo; \ - exit $$EXIT; - -.DEFAULT_GOAL := test @@ -76,7 +76,7 @@ name. That seems to be the fairest way to arrange this table. | Go | [gofmt -e](https://golang.org/cmd/gofmt/), [go vet](https://golang.org/cmd/vet/), [golint](https://godoc.org/github.com/golang/lint), [gometalinter](https://github.com/alecthomas/gometalinter), [go build](https://golang.org/cmd/go/), [gosimple](https://github.com/dominikh/go-tools/tree/master/cmd/gosimple), [staticcheck](https://github.com/dominikh/go-tools/tree/master/cmd/staticcheck) | | Haml | [haml-lint](https://github.com/brigade/haml-lint) | Handlebars | [ember-template-lint](https://github.com/rwjblue/ember-template-lint) | -| Haskell | [ghc](https://www.haskell.org/ghc/), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools) | +| Haskell | [ghc](https://www.haskell.org/ghc/), [ghc-mod](https://github.com/DanielG/ghc-mod), [hlint](https://hackage.haskell.org/package/hlint), [hdevtools](https://hackage.haskell.org/package/hdevtools) | | HTML | [HTMLHint](http://htmlhint.com/), [proselint](http://proselint.com/), [tidy](http://www.html-tidy.org/) | | Java | [checkstyle](http://checkstyle.sourceforge.net), [javac](http://www.oracle.com/technetwork/java/javase/downloads/index.html) | | JavaScript | [eslint](http://eslint.org/), [jscs](http://jscs.info/), [jshint](http://jshint.com/), [flow](https://flowtype.org/), [standard](http://standardjs.com/), [xo](https://github.com/sindresorhus/xo) diff --git a/ale_linters/go/gometalinter.vim b/ale_linters/go/gometalinter.vim index aa524013..f47df6b9 100644 --- a/ale_linters/go/gometalinter.vim +++ b/ale_linters/go/gometalinter.vim @@ -22,7 +22,7 @@ function! ale_linters#go#gometalinter#Handler(buffer, lines) abort for l:match in ale_linters#go#gometalinter#GetMatches(a:lines) " Omit errors from files other than the one currently open - if ale#path#IsBufferPath(a:buffer, l:match[0]) + if !ale#path#IsBufferPath(a:buffer, l:match[1]) continue endif diff --git a/ale_linters/haskell/ghc-mod.vim b/ale_linters/haskell/ghc-mod.vim new file mode 100644 index 00000000..d3d23649 --- /dev/null +++ b/ale_linters/haskell/ghc-mod.vim @@ -0,0 +1,16 @@ +" Author: wizzup <wizzup@gmail.com> +" Description: ghc-mod for Haskell files + +call ale#linter#Define('haskell', { +\ 'name': 'ghc-mod', +\ 'executable': 'ghc-mod', +\ 'command': 'ghc-mod check %t', +\ 'callback': 'ale#handlers#haskell#HandleGHCFormat', +\}) + +call ale#linter#Define('haskell', { +\ 'name': 'stack-ghc-mod', +\ 'executable': 'stack', +\ 'command': 'stack exec ghc-mod check %t', +\ 'callback': 'ale#handlers#haskell#HandleGHCFormat', +\}) diff --git a/autoload/ale.vim b/autoload/ale.vim index 189f1e46..ca75577a 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -125,12 +125,13 @@ function! ale#Var(buffer, variable_name) abort endfunction " Escape a string suitably for each platform. -" shellescape() does not work on Windows. +" shellescape does not work on Windows. function! ale#Escape(str) abort if fnamemodify(&shell, ':t') ==? 'cmd.exe' " FIXME: Fix shell escaping for Windows. return fnameescape(a:str) else - return shellescape(a:str) + " An extra space is used here to disable the custom-checks. + return shellescape (a:str) endif endfunction diff --git a/autoload/ale/engine.vim b/autoload/ale/engine.vim index 486bdd4f..c778f253 100644 --- a/autoload/ale/engine.vim +++ b/autoload/ale/engine.vim @@ -26,21 +26,6 @@ function! s:IsExecutable(executable) abort return 0 endfunction -function! ale#engine#ParseVim8ProcessID(job_string) abort - return matchstr(a:job_string, '\d\+') + 0 -endfunction - -function! s:GetJobID(job) abort - if has('nvim') - "In NeoVim, job values are just IDs. - return a:job - endif - - " For Vim 8, the job is a different variable type, and we can parse the - " process ID from the string. - return ale#engine#ParseVim8ProcessID(string(a:job)) -endfunction - function! ale#engine#InitBufferInfo(buffer) abort if !has_key(g:ale_buffer_info, a:buffer) " job_list will hold the list of jobs @@ -63,84 +48,17 @@ function! ale#engine#InitBufferInfo(buffer) abort endif endfunction -" A map from timer IDs to Vim 8 jobs, for tracking jobs that need to be killed -" with SIGKILL if they don't terminate right away. -let s:job_kill_timers = {} - -" Check if a job is still running, in either Vim version. -function! s:IsJobRunning(job) abort - if has('nvim') - try - " In NeoVim, if the job isn't running, jobpid() will throw. - call jobpid(a:job) - return 1 - catch - endtry - - return 0 - endif - - return job_status(a:job) ==# 'run' -endfunction - -function! s:KillHandler(timer) abort - let l:job = remove(s:job_kill_timers, a:timer) - - " For NeoVim, we have to send SIGKILL ourselves manually, as NeoVim - " doesn't do it properly. - if has('nvim') - let l:pid = 0 - - " We can fail to get the PID here if the job manages to stop already. - try - let l:pid = jobpid(l:job) - catch - endtry - - if l:pid > 0 - if has('win32') - " Windows - call system('taskkill /pid ' . l:pid . ' /f') - else - " Linux, Mac OSX, etc. - call system('kill -9 ' . l:pid) - endif - endif - else - call job_stop(l:job, 'kill') - endif -endfunction - -function! ale#engine#ClearJob(job) abort +function! ale#engine#ClearJob(job_id) abort if get(g:, 'ale_run_synchronously') == 1 - call remove(s:job_info_map, a:job) + call remove(s:job_info_map, a:job_id) return endif - let l:job_id = s:GetJobID(a:job) + call ale#job#Stop(a:job_id) - if has('nvim') - call jobstop(a:job) - else - " We must close the channel for reading the buffer if it is open - " when stopping a job. Otherwise, we will get errors in the status line. - if ch_status(job_getchannel(a:job)) ==# 'open' - call ch_close_in(job_getchannel(a:job)) - endif - - " Ask nicely for the job to stop. - call job_stop(a:job) - endif - - " If a job doesn't stop immediately, queue a timer which will - " send SIGKILL to the job, if it's alive by the time the timer ticks. - if s:IsJobRunning(a:job) - let s:job_kill_timers[timer_start(100, function('s:KillHandler'))] = a:job - endif - - if has_key(s:job_info_map, l:job_id) - call remove(s:job_info_map, l:job_id) + if has_key(s:job_info_map, a:job_id) + call remove(s:job_info_map, a:job_id) endif endfunction @@ -152,16 +70,14 @@ function! s:StopPreviousJobs(buffer, linter) abort let l:new_job_list = [] - for l:job in g:ale_buffer_info[a:buffer].job_list - let l:job_id = s:GetJobID(l:job) - + for l:job_id in g:ale_buffer_info[a:buffer].job_list if has_key(s:job_info_map, l:job_id) \&& s:job_info_map[l:job_id].linter.name ==# a:linter.name " Stop jobs which match the buffer and linter. - call ale#engine#ClearJob(l:job) + call ale#engine#ClearJob(l:job_id) else " Keep other jobs in the list. - call add(l:new_job_list, l:job) + call add(l:new_job_list, l:job_id) endif endfor @@ -169,41 +85,6 @@ function! s:StopPreviousJobs(buffer, linter) abort let g:ale_buffer_info[a:buffer].job_list = l:new_job_list endfunction -function! s:GatherOutputVim(channel, data) abort - let l:job_id = s:GetJobID(ch_getjob(a:channel)) - - if !has_key(s:job_info_map, l:job_id) - return - endif - - call add(s:job_info_map[l:job_id].output, a:data) -endfunction - -function! s:GatherOutputNeoVim(job, data, event) abort - let l:job_id = s:GetJobID(a:job) - - if !has_key(s:job_info_map, l:job_id) - return - endif - - " Join the lines passed to ale, because Neovim splits them up. - " a:data is a list of strings, where every item is a new line, except the - " first one, which is the continuation of the last item passed last time. - call ale#engine#JoinNeovimOutput(s:job_info_map[l:job_id].output, a:data) -endfunction - -function! ale#engine#JoinNeovimOutput(output, data) abort - if empty(a:output) - call extend(a:output, a:data) - else - " Extend the previous line, which can be continued. - let a:output[-1] .= get(a:data, 0, '') - - " Add the new lines. - call extend(a:output, a:data[1:]) - endif -endfunction - " Register a temporary file to be managed with the ALE engine for " a current job run. function! ale#engine#ManageFile(buffer, filename) abort @@ -255,24 +136,27 @@ function! ale#engine#RemoveManagedFiles(buffer) abort let g:ale_buffer_info[a:buffer].temporary_directory_list = [] endfunction -function! s:HandleExit(job) abort - if a:job ==# 'no process' - " Stop right away when the job is not valid in Vim 8. - return +function! s:GatherOutput(job_id, line) abort + if has_key(s:job_info_map, a:job_id) + call add(s:job_info_map[a:job_id].output, a:line) endif +endfunction - let l:job_id = s:GetJobID(a:job) - - if !has_key(s:job_info_map, l:job_id) +function! s:HandleExit(job_id, exit_code) abort + if !has_key(s:job_info_map, a:job_id) return endif - let l:job_info = s:job_info_map[l:job_id] + let l:job_info = s:job_info_map[a:job_id] let l:linter = l:job_info.linter let l:output = l:job_info.output let l:buffer = l:job_info.buffer let l:next_chain_index = l:job_info.next_chain_index + if g:ale_history_enabled + call ale#history#SetExitCode(l:buffer, a:job_id, a:exit_code) + endif + " Call the same function for stopping jobs again to clean up the job " which just closed. call s:StopPreviousJobs(l:buffer, l:linter) @@ -283,6 +167,10 @@ function! s:HandleExit(job) abort return endif + if has('nvim') && !empty(l:output) && empty(l:output[-1]) + call remove(l:output, -1) + endif + if l:next_chain_index < len(get(l:linter, 'command_chain', [])) call s:InvokeChain(l:buffer, l:linter, l:next_chain_index, l:output) return @@ -290,7 +178,7 @@ function! s:HandleExit(job) abort " Log the output of the command for ALEInfo if we should. if g:ale_history_enabled && g:ale_history_log_output - call ale#history#RememberOutput(l:buffer, l:job_id, l:output[:]) + call ale#history#RememberOutput(l:buffer, a:job_id, l:output[:]) endif let l:linter_loclist = ale#util#GetFunction(l:linter.callback)(l:buffer, l:output) @@ -364,36 +252,6 @@ function! ale#engine#SetResults(buffer, loclist) abort endif endfunction -function! s:SetExitCode(job, exit_code) abort - let l:job_id = s:GetJobID(a:job) - - if !has_key(s:job_info_map, l:job_id) - return - endif - - let l:buffer = s:job_info_map[l:job_id].buffer - - call ale#history#SetExitCode(l:buffer, l:job_id, a:exit_code) -endfunction - -function! s:HandleExitNeoVim(job, exit_code, event) abort - if g:ale_history_enabled - call s:SetExitCode(a:job, a:exit_code) - endif - - call s:HandleExit(a:job) -endfunction - -function! s:HandleExitVim(channel) abort - call s:HandleExit(ch_getjob(a:channel)) -endfunction - -" Vim returns the exit status with one callback, -" and the channel will close later in another callback. -function! s:HandleExitStatusVim(job, exit_code) abort - call s:SetExitCode(a:job, a:exit_code) -endfunction - function! ale#engine#FixLocList(buffer, linter, loclist) abort let l:new_loclist = [] @@ -538,85 +396,41 @@ function! s:RunJob(options) abort let l:read_buffer = 0 endif - if !has('nvim') - " The command will be executed in a subshell. This fixes a number of - " issues, including reading the PATH variables correctly, %PATHEXT% - " expansion on Windows, etc. - " - " NeoVim handles this issue automatically if the command is a String. - let l:command = has('win32') - \ ? 'cmd /c ' . l:command - \ : split(&shell) + split(&shellcmdflag) + [l:command] + let l:command = ale#job#PrepareCommand(l:command) + let l:job_options = { + \ 'mode': 'nl', + \ 'exit_cb': function('s:HandleExit'), + \} + + if l:output_stream ==# 'stderr' + let l:job_options.err_cb = function('s:GatherOutput') + elseif l:output_stream ==# 'both' + let l:job_options.out_cb = function('s:GatherOutput') + let l:job_options.err_cb = function('s:GatherOutput') + else + let l:job_options.out_cb = function('s:GatherOutput') endif if get(g:, 'ale_run_synchronously') == 1 " Find a unique Job value to use, which will be the same as the ID for " running commands synchronously. This is only for test code. - let l:job = len(s:job_info_map) + 1 + let l:job_id = len(s:job_info_map) + 1 - while has_key(s:job_info_map, l:job) - let l:job += 1 + while has_key(s:job_info_map, l:job_id) + let l:job_id += 1 endwhile - elseif has('nvim') - if l:output_stream ==# 'stderr' - " Read from stderr instead of stdout. - let l:job = jobstart(l:command, { - \ 'on_stderr': function('s:GatherOutputNeoVim'), - \ 'on_exit': function('s:HandleExitNeoVim'), - \}) - elseif l:output_stream ==# 'both' - let l:job = jobstart(l:command, { - \ 'on_stdout': function('s:GatherOutputNeoVim'), - \ 'on_stderr': function('s:GatherOutputNeoVim'), - \ 'on_exit': function('s:HandleExitNeoVim'), - \}) - else - let l:job = jobstart(l:command, { - \ 'on_stdout': function('s:GatherOutputNeoVim'), - \ 'on_exit': function('s:HandleExitNeoVim'), - \}) - endif else - let l:job_options = { - \ 'in_mode': 'nl', - \ 'out_mode': 'nl', - \ 'err_mode': 'nl', - \ 'close_cb': function('s:HandleExitVim'), - \} - - if g:ale_history_enabled - " We only need to capture the exit status if we are going to - " save it in the history. Otherwise, we don't care. - let l:job_options.exit_cb = function('s:HandleExitStatusVim') - endif - - if l:output_stream ==# 'stderr' - " Read from stderr instead of stdout. - let l:job_options.err_cb = function('s:GatherOutputVim') - elseif l:output_stream ==# 'both' - " Read from both streams. - let l:job_options.out_cb = function('s:GatherOutputVim') - let l:job_options.err_cb = function('s:GatherOutputVim') - else - let l:job_options.out_cb = function('s:GatherOutputVim') - endif - - " Vim 8 will read the stdin from the file's buffer. - let l:job = job_start(l:command, l:job_options) + let l:job_id = ale#job#Start(l:command, l:job_options) endif let l:status = 'failed' - let l:job_id = 0 " Only proceed if the job is being run. - if has('nvim') - \ || get(g:, 'ale_run_synchronously') == 1 - \ || (l:job !=# 'no process' && job_status(l:job) ==# 'run') + if l:job_id " Add the job to the list of jobs, so we can track them. - call add(g:ale_buffer_info[l:buffer].job_list, l:job) + call add(g:ale_buffer_info[l:buffer].job_list, l:job_id) let l:status = 'started' - let l:job_id = s:GetJobID(l:job) " Store the ID for the job in the map to read back again. let s:job_info_map[l:job_id] = { \ 'linter': l:linter, @@ -639,7 +453,9 @@ function! s:RunJob(options) abort \ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2]) \ : l:command \) - call s:HandleExit(l:job) + + " TODO, get the exit system of the shell call and pass it on here. + call l:job_options.exit_cb(l:job_id, 0) endif endfunction @@ -773,13 +589,24 @@ function! ale#engine#WaitForJobs(deadline) abort call extend(l:job_list, l:info.job_list) endfor + " NeoVim has a built-in API for this, so use that. + if has('nvim') + let l:nvim_code_list = jobwait(l:job_list, a:deadline) + + if index(l:nvim_code_list, -1) >= 0 + throw 'Jobs did not complete on time!' + endif + + return + endif + let l:should_wait_more = 1 while l:should_wait_more let l:should_wait_more = 0 - for l:job in l:job_list - if job_status(l:job) ==# 'run' + for l:job_id in l:job_list + if ale#job#IsRunning(l:job_id) let l:now = ale#util#ClockMilliseconds() if l:now - l:start_time > a:deadline @@ -807,8 +634,8 @@ function! ale#engine#WaitForJobs(deadline) abort " Check again to see if any jobs are running. for l:info in values(g:ale_buffer_info) - for l:job in l:info.job_list - if job_status(l:job) ==# 'run' + for l:job_id in l:info.job_list + if ale#job#IsRunning(l:job_id) let l:has_new_jobs = 1 break endif diff --git a/autoload/ale/job.vim b/autoload/ale/job.vim new file mode 100644 index 00000000..d0572f51 --- /dev/null +++ b/autoload/ale/job.vim @@ -0,0 +1,270 @@ +" Author: w0rp <devw0rp@gmail.com> +" Deciption: APIs for working with Asynchronous jobs, with an API normalised +" between Vim 8 and NeoVim. +" +" Important functions are described below. They are: +" +" ale#job#Start(command, options) -> job_id +" ale#job#IsRunning(job_id) -> 1 if running, 0 otherwise. +" ale#job#Stop(job_id) + +let s:job_map = {} +" A map from timer IDs to jobs, for tracking jobs that need to be killed +" with SIGKILL if they don't terminate right away. +let s:job_kill_timers = {} + +function! s:KillHandler(timer) abort + let l:job = remove(s:job_kill_timers, a:timer) + call job_stop(l:job, 'kill') +endfunction + +function! ale#job#JoinNeovimOutput(output, data) abort + if empty(a:output) + call extend(a:output, a:data) + else + " Extend the previous line, which can be continued. + let a:output[-1] .= get(a:data, 0, '') + + " Add the new lines. + call extend(a:output, a:data[1:]) + endif +endfunction + +" Note that jobs and IDs are the same thing on NeoVim. +function! s:HandleNeoVimLines(job, callback, output, data) abort + call ale#job#JoinNeovimOutput(a:output, a:data) + + for l:line in a:output + call a:callback(a:job, l:line) + endfor +endfunction + +function! s:NeoVimCallback(job, data, event) abort + let l:job_info = s:job_map[a:job] + + if a:event ==# 'stdout' + call s:HandleNeoVimLines( + \ a:job, + \ ale#util#GetFunction(l:job_info.out_cb), + \ l:job_info.out_cb_output, + \ a:data, + \) + elseif a:event ==# 'stderr' + call s:HandleNeoVimLines( + \ a:job, + \ ale#util#GetFunction(l:job_info.err_cb), + \ l:job_info.err_cb_output, + \ a:data, + \) + else + try + call ale#util#GetFunction(l:job_info.exit_cb)(a:job, a:data) + finally + " Automatically forget about the job after it's done. + if has_key(s:job_map, a:job) + call remove(s:job_map, a:job) + endif + endtry + endif +endfunction + +function! s:VimOutputCallback(channel, data) abort + let l:job = ch_getjob(a:channel) + let l:job_id = ale#job#ParseVim8ProcessID(string(l:job)) + + " Only call the callbacks for jobs which are valid. + if l:job_id > 0 + call ale#util#GetFunction(s:job_map[l:job_id].out_cb)(l:job_id, a:data) + endif +endfunction + +function! s:VimErrorCallback(channel, data) abort + let l:job = ch_getjob(a:channel) + let l:job_id = ale#job#ParseVim8ProcessID(string(l:job)) + + " Only call the callbacks for jobs which are valid. + if l:job_id > 0 + call ale#util#GetFunction(s:job_map[l:job_id].err_cb)(l:job_id, a:data) + endif +endfunction + +function! s:VimCloseCallback(channel) abort + let l:job = ch_getjob(a:channel) + let l:job_id = ale#job#ParseVim8ProcessID(string(l:job)) + let l:info = s:job_map[l:job_id] + + " job_status() can trigger the exit handler. + " The channel can close before the job has exited. + if job_status(l:job) ==# 'dead' + try + call ale#util#GetFunction(l:info.exit_cb)(l:job_id, l:info.exit_code) + finally + " Automatically forget about the job after it's done. + if has_key(s:job_map, l:job_id) + call remove(s:job_map, l:job_id) + endif + endtry + endif +endfunction + +function! s:VimExitCallback(job, exit_code) abort + let l:job_id = ale#job#ParseVim8ProcessID(string(a:job)) + let l:info = s:job_map[l:job_id] + let l:info.exit_code = a:exit_code + + " The program can exit before the data has finished being read. + if ch_status(job_getchannel(a:job)) ==# 'closed' + try + call ale#util#GetFunction(l:info.exit_cb)(l:job_id, a:exit_code) + finally + " Automatically forget about the job after it's done. + if has_key(s:job_map, l:job_id) + call remove(s:job_map, l:job_id) + endif + endtry + endif +endfunction + +function! ale#job#ParseVim8ProcessID(job_string) abort + return matchstr(a:job_string, '\d\+') + 0 +endfunction + +function! ale#job#ValidateArguments(command, options) abort + if a:options.mode !=# 'nl' && a:options.mode !=# 'raw' + throw 'Invalid mode: ' . a:options.mode + endif +endfunction + +function! ale#job#PrepareCommand(command) abort + " The command will be executed in a subshell. This fixes a number of + " issues, including reading the PATH variables correctly, %PATHEXT% + " expansion on Windows, etc. + " + " NeoVim handles this issue automatically if the command is a String, + " but we'll do this explicitly, so we use thes same exact command for both + " versions. + return has('win32') + \ ? 'cmd /c ' . a:command + \ : split(&shell) + split(&shellcmdflag) + [a:command] +endfunction + +" Start a job with options which are agnostic to Vim and NeoVim. +" +" The following options are accepted: +" +" out_cb - A callback for receiving stdin. Arguments: (job_id, data) +" err_cb - A callback for receiving stderr. Arguments: (job_id, data) +" exit_cb - A callback for program exit. Arguments: (job_id, status_code) +" mode - A mode for I/O. Can be 'nl' for split lines or 'raw'. +function! ale#job#Start(command, options) abort + call ale#job#ValidateArguments(a:command, a:options) + + let l:job_info = copy(a:options) + let l:job_options = {} + + if has('nvim') + if has_key(a:options, 'out_cb') + let l:job_options.on_stdout = function('s:NeoVimCallback') + let l:job_info.out_cb_output = [] + endif + + if has_key(a:options, 'err_cb') + let l:job_options.on_stderr = function('s:NeoVimCallback') + let l:job_info.err_cb_output = [] + endif + + if has_key(a:options, 'exit_cb') + let l:job_options.on_exit = function('s:NeoVimCallback') + endif + + let l:job_info.job = jobstart(a:command, l:job_options) + let l:job_id = l:job_info.job + else + let l:job_options = { + \ 'in_mode': l:job_info.mode, + \ 'out_mode': l:job_info.mode, + \ 'err_mode': l:job_info.mode, + \} + + if has_key(a:options, 'out_cb') + let l:job_options.out_cb = function('s:VimOutputCallback') + endif + + if has_key(a:options, 'err_cb') + let l:job_options.err_cb = function('s:VimErrorCallback') + endif + + if has_key(a:options, 'exit_cb') + " Set a close callback to which simply calls job_status() + " when the channel is closed, which can trigger the exit callback + " earlier on. + let l:job_options.close_cb = function('s:VimCloseCallback') + let l:job_options.exit_cb = function('s:VimExitCallback') + endif + + " Vim 8 will read the stdin from the file's buffer. + let l:job_info.job = job_start(a:command, l:job_options) + let l:job_id = ale#job#ParseVim8ProcessID(string(l:job_info.job)) + endif + + if l:job_id + " Store the job in the map for later only if we can get the ID. + let s:job_map[l:job_id] = l:job_info + endif + + return l:job_id +endfunction + +" Send raw data to the job. +function! ale#job#SendRaw(job_id, string) abort + if has('nvim') + call jobsend(a:job_id, a:string) + else + call ch_sendraw(job_getchannel(s:job_map[a:job_id]), a:string) + endif +endfunction + +" Given a job ID, return 1 if the job is currently running. +" Invalid job IDs will be ignored. +function! ale#job#IsRunning(job_id) abort + if has('nvim') + try + " In NeoVim, if the job isn't running, jobpid() will throw. + call jobpid(a:job_id) + return 1 + catch + endtry + elseif has_key(s:job_map, a:job_id) + let l:job = s:job_map[a:job_id].job + return job_status(l:job) ==# 'run' + endif + + return 0 +endfunction + +" Given a Job ID, stop that job. +" Invalid job IDs will be ignored. +function! ale#job#Stop(job_id) abort + if has('nvim') + " FIXME: NeoVim kills jobs on a timer, but will not kill any processes + " which are child processes on Unix. Some work needs to be done to + " kill child processes to stop long-running processes like pylint. + call jobstop(a:job_id) + elseif has_key(s:job_map, a:job_id) + let l:job = s:job_map[a:job_id].job + + " We must close the channel for reading the buffer if it is open + " when stopping a job. Otherwise, we will get errors in the status line. + if ch_status(job_getchannel(l:job)) ==# 'open' + call ch_close_in(job_getchannel(l:job)) + endif + + " Ask nicely for the job to stop. + call job_stop(l:job) + + if ale#job#IsRunning(l:job) + " Set a 100ms delay for killing the job with SIGKILL. + let s:job_kill_timers[timer_start(100, function('s:KillHandler'))] = l:job + endif + endif +endfunction diff --git a/autoload/ale/lsp.vim b/autoload/ale/lsp.vim index 72b94427..acf47408 100644 --- a/autoload/ale/lsp.vim +++ b/autoload/ale/lsp.vim @@ -1,9 +1,31 @@ " Author: w0rp <devw0rp@gmail.com> " Description: Language Server Protocol client code -let s:address_info_map = {} +" A List of connections, used for tracking servers which have been connected +" to, and programs which are run. +let s:connections = [] let g:ale_lsp_next_message_id = 1 +function! s:NewConnection() abort + " data: The message data received so far. + " callback_map: A mapping from connections to response callbacks. + " address: An address only set for server connections. + " executable: An executable only set for program connections. + " job: A job ID only set for running programs. + let l:conn = { + \ 'data': '', + \ 'callback_map': {}, + \ 'address': '', + \ 'executable': '', + \ 'job_id': -1, + \} + + call add(s:connections, l:conn) + + return l:conn +endfunction + + function! ale#lsp#GetNextMessageID() abort " Use the current ID let l:id = g:ale_lsp_next_message_id @@ -87,27 +109,42 @@ function! ale#lsp#ReadMessageData(data) abort return [l:remainder, l:response_list] endfunction -function! s:HandleMessage(channel, message) abort - let l:channel_info = ch_info(a:channel) - let l:address = l:channel_info.hostname . ':' . l:channel_info.port - let l:info = s:address_info_map[l:address] - let l:info.data .= a:message +function! ale#lsp#HandleMessage(conn, message) abort + let a:conn.data .= a:message " Parse the objects now if we can, and keep the remaining text. - let [l:info.data, l:response_list] = ale#lsp#ReadMessageData(l:info.data) + let [a:conn.data, l:response_list] = ale#lsp#ReadMessageData(a:conn.data) " Call our callbacks. for l:response in l:response_list - let l:callback = l:info.callback_map.pop(l:response.id) + let l:callback = a:conn.callback_map.pop(l:response.id) call ale#util#GetFunction(l:callback)(l:response) endfor endfunction -" Send a message to the server. +function! s:HandleChannelMessage(channel, message) abort + let l:info = ch_info(a:channel) + let l:address = l:info.hostname . l:info.address + let l:conn = filter(s:connections[:], 'v:val.address ==# l:address')[0] + + call ale#lsp#HandleMessage(l:conn, a:message) +endfunction + +function! s:HandleCommandMessage(job_id, message) abort + let l:conn = filter(s:connections[:], 'v:val.job_id == a:job_id')[0] + + call ale#lsp#HandleMessage(l:conn, a:message) +endfunction + +" Send a message to a server with a given executable, and a command for +" running the executable. +" " A callback can be registered to handle the response. " Notifications do not need to be handled. -" (address, message, callback?) -function! ale#lsp#SendMessage(address, message, ...) abort +" (executable, command, message, callback?) +" +" Returns 1 when a message is sent, 0 otherwise. +function! ale#lsp#SendMessageToProgram(executable, command, message, ...) abort if a:0 > 1 throw 'Too many arguments!' endif @@ -116,37 +153,80 @@ function! ale#lsp#SendMessage(address, message, ...) abort throw 'A callback must be set for messages which are not notifications!' endif + if !executable(a:executable) + return 0 + endif + let [l:id, l:data] = ale#lsp#CreateMessageData(a:message) - let l:info = get(s:address_info_map, a:address, {}) + let l:matches = filter(s:connections[:], 'v:val.executable ==# a:executable') - if empty(l:info) - let l:info = { - \ 'data': '', - \ 'callback_map': {}, - \} - let s:address_info_map[a:address] = l:info + if empty(l:matches) + " We haven't looked at this executable before. + " Create a new connection. + let l:conn = NewConnection() endif - " The ID is 0 when the message is a Notification, which is a JSON-RPC - " request for which the server must not return a response. - if l:id != 0 - " Add the callback, which the server will respond to later. - let l:info.callback_map[l:id] = a:1 + if !ale#job#IsRunning(l:conn.job_id) + let l:options = {'mode': 'raw', 'out_cb': 's:HandleCommandMessage'} + let l:job_id = ale#job#Start(ale#job#PrepareCommand(a:command), l:options) + endif + + if l:job_id > 0 + return 0 endif - if !has_key(l:info, 'channel') || ch_status(l:info.channel) !=# 'open' - let l:info.channnel = ch_open(a:address, { + call ale#job#SendRaw(l:job_id, l:data) + + let l:conn.job_id = l:job_id + + return 1 +endfunction + +" Send a message to a server at a given address. +" A callback can be registered to handle the response. +" Notifications do not need to be handled. +" (address, message, callback?) +" +" Returns 1 when a message is sent, 0 otherwise. +function! ale#lsp#SendMessageToAddress(address, message, ...) abort + if a:0 > 1 + throw 'Too many arguments!' + endif + + if !a:message[0] && a:0 == 0 + throw 'A callback must be set for messages which are not notifications!' + endif + + let [l:id, l:data] = ale#lsp#CreateMessageData(a:message) + + let l:matches = filter(s:connections[:], 'v:val.address ==# a:address') + + if empty(l:matches) + " We haven't looked at this address before. + " Create a new connection. + let l:conn = NewConnection() + endif + + if !has_key(l:conn, 'channel') || ch_status(l:conn.channel) !=# 'open' + let l:conn.channnel = ch_open(a:address, { \ 'mode': 'raw', \ 'waittime': 0, - \ 'callback': 's:HandleMessage', + \ 'callback': 's:HandleChannelMessage', \}) endif - if ch_status(l:info.channnel) ==# 'fail' - throw 'Failed to open channel for: ' . a:address + " The ID is 0 when the message is a Notification, which is a JSON-RPC + " request for which the server must not return a response. + if l:id != 0 + " Add the callback, which the server will respond to later. + let l:conn.callback_map[l:id] = a:1 + endif + + if ch_status(l:conn.channnel) ==# 'fail' + return 0 endif " Send the message to the server - call ch_sendraw(l:info.channel, l:data) + call ch_sendraw(l:conn.channel, l:data) endfunction diff --git a/custom-checks b/custom-checks index 37d2840c..c4b329ca 100755 --- a/custom-checks +++ b/custom-checks @@ -63,6 +63,7 @@ check_errors() { if (( FIX_ERRORS )); then for directory in "${directories[@]}"; do sed -i "s/^\(function.*)\) *$/\1 abort/" "$directory"/**/*.vim + sed -i "s/shellescape(/ale#Escape(/" "$directory"/**/*.vim done fi @@ -75,5 +76,6 @@ check_errors '^ [^ ]' 'Use four spaces, not two spaces' check_errors $'\t' 'Use four spaces, not tabs' # This check should prevent people from using a particular inconsistent name. check_errors 'let g:ale_\w\+_\w\+_args =' 'Name your option g:ale_<filetype>_<lintername>_options instead' +check_errors 'shellescape(' 'Use ale#Escape instead of shellescape' exit $RETURN_CODE diff --git a/doc/ale.txt b/doc/ale.txt index 483ce8ee..85dc6d20 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -137,7 +137,7 @@ The following languages and tools are supported. * Go: 'gofmt', 'go vet', 'golint', 'go build', 'gosimple', 'staticcheck' * Haml: 'hamllint' * Handlebars: 'ember-template-lint' -* Haskell: 'ghc', 'hlint', 'hdevtools' +* Haskell: 'ghc', 'ghc-mod', 'hlint', 'hdevtools' * HTML: 'HTMLHint', 'proselint', 'tidy' * Java: 'javac' * JavaScript: 'eslint', 'jscs', 'jshint', 'flow', 'xo' diff --git a/run-tests b/run-tests new file mode 100755 index 00000000..dd017474 --- /dev/null +++ b/run-tests @@ -0,0 +1,171 @@ +#!/bin/bash -eu + +# Author: w0rp <devw0rp@gmail.com> +# +# This script runs tests for the ALE project. The following options are +# accepted: +# +# -v Enable verbose output +# --neovim-only Run tests only for NeoVim +# --vim-only Run tests only for Vim + +RED='\033[0;31m' +GREEN='\033[0;32m' +NC='\033[0m' +CURRENT_IMAGE_ID=d5a1b5915b09 +IMAGE=w0rp/ale +DOCKER_FLAGS=(--rm -v "$PWD:/testplugin" -v "$PWD/test:/home" -w /testplugin "$IMAGE") +EXIT=0 + +tests='test/*.vader test/*/*.vader test/*/*/*.vader test/*/*/*.vader' +verbose=0 +run_neovim_tests=1 +run_vim_tests=1 +run_vint=1 +run_custom_checks=1 + +while [ $# -ne 0 ]; do + case $1 in + -v) + verbose=1 + shift + ;; + --neovim-only) + run_vim_tests=0 + run_vint=0 + run_custom_checks=0 + shift + ;; + --vim-only) + run_neovim_tests=0 + run_vint=0 + run_custom_checks=0 + ;; + --) + shift + break + ;; + -?*) + echo "Invalid argument: $1" 1>&2 + exit 1 + ;; + *) + break + ;; + esac +done + +# Allow tests to be passed as arguments. +if [ $# -ne 0 ]; then + # This doesn't perfectly handle work splitting, but none of our files + # have spaces in the names. + tests="$*" +fi + +docker images -q w0rp/ale | grep "^$CURRENT_IMAGE_ID" > /dev/null \ + || docker pull "$IMAGE" + +function color-vader-output() { + local vader_started=0 + + while read -r; do + if ((!verbose)); then + # When verbose mode is off, suppress output until Vader starts. + if ((!vader_started)); then + if [[ "$REPLY" = *'Starting Vader:'* ]]; then + vader_started=1 + else + continue + fi + fi + fi + + if [[ "$REPLY" = *'[EXECUTE] (X)'* ]]; then + echo -en "$RED" + elif [[ "$REPLY" = *'[EXECUTE]'* ]] || [[ "$REPLY" = *'[ GIVEN]'* ]]; then + echo -en "$NC" + fi + + if [[ "$REPLY" = *'Success/Total'* ]]; then + success="$(echo -n "$REPLY" | grep -o '[0-9]\+/' | head -n1 | cut -d/ -f1)" + total="$(echo -n "$REPLY" | grep -o '/[0-9]\+' | head -n1 | cut -d/ -f2)" + + if [ "$success" -lt "$total" ]; then + echo -en "$RED" + else + echo -en "$GREEN" + fi + + echo "$REPLY" + echo -en "$NC" + else + echo "$REPLY" + fi + done + + echo -en "$NC" +} + +if ((run_neovim_tests)); then + for vim in $(docker run --rm "$IMAGE" ls /vim-build/bin | grep '^neovim' ); do + echo + echo '========================================' + echo "Running tests for $vim" + echo '========================================' + echo + + set -o pipefail + docker run -it -e VADER_OUTPUT_FILE=/dev/stderr "${DOCKER_FLAGS[@]}" \ + "/vim-build/bin/$vim" -u test/vimrc \ + --headless "+Vader! $tests" | color-vader-output || EXIT=$? + set +o pipefail + done + + echo +fi + +if ((run_vim_tests)); then + for vim in $(docker run --rm "$IMAGE" ls /vim-build/bin | grep '^vim' ); do + echo + echo '========================================' + echo "Running tests for $vim" + echo '========================================' + echo + + set -o pipefail + docker run -a stderr "${DOCKER_FLAGS[@]}" \ + "/vim-build/bin/$vim" -u test/vimrc \ + "+Vader! $tests" 2>&1 | color-vader-output || EXIT=$? + set +o pipefail + done + + echo +fi + +if ((run_vint)); then + echo '========================================' + echo 'Running Vint to lint our code' + echo '========================================' + echo 'Vint warnings/errors follow:' + echo + + set -o pipefail + docker run -a stdout "${DOCKER_FLAGS[@]}" vint -s . || EXIT=$? + set +o pipefail + echo +fi + +if ((run_custom_checks)); then + echo '========================================' + echo 'Running custom checks' + echo '========================================' + echo 'Custom warnings/errors follow:' + echo + + set -o pipefail + docker run -a stdout "${DOCKER_FLAGS[@]}" ./custom-checks . || EXIT=$? + set +o pipefail + echo +fi + +exit $EXIT diff --git a/test/.config/nvim/init.vim b/test/.config/nvim/init.vim new file mode 120000 index 00000000..90f52f07 --- /dev/null +++ b/test/.config/nvim/init.vim @@ -0,0 +1 @@ +../../vimrc
\ No newline at end of file diff --git a/test/handler/test_ghc_mod_handler.vader b/test/handler/test_ghc_mod_handler.vader new file mode 100644 index 00000000..f9b44b33 --- /dev/null +++ b/test/handler/test_ghc_mod_handler.vader @@ -0,0 +1,27 @@ +Execute(HandleGhcFormat should handle ghc-mod problems): + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': 'Failed to load interface for ‘Missing’Use -v to see a list of the files searched for.', + \ }, + \ { + \ 'lnum': 2, + \ 'col': 1, + \ 'type': 'E', + \ 'text': ' Suggestion: Use camelCaseFound: my_variable = ...Why not: myVariable = ...', + \ }, + \ { + \ 'lnum': 6, + \ 'col': 1, + \ 'type': 'E', + \ 'text': ' Warning: Eta reduceFound: myFunc x = succ xWhy not: myFunc = succ', + \ }, + \ ], + \ ale#handlers#haskell#HandleGHCFormat(bufnr(''), [ + \ 'check1.hs:2:1:Failed to load interface for ‘Missing’Use -v to see a list of the files searched for.', + \ 'check2.hs:2:1: Suggestion: Use camelCaseFound: my_variable = ...Why not: myVariable = ...', + \ 'check2.hs:6:1: Warning: Eta reduceFound: myFunc x = succ xWhy not: myFunc = succ', + \ ]) diff --git a/test/handler/test_gometalinter_handler.vader b/test/handler/test_gometalinter_handler.vader index 3b622130..52a4fc96 100644 --- a/test/handler/test_gometalinter_handler.vader +++ b/test/handler/test_gometalinter_handler.vader @@ -30,7 +30,7 @@ Execute (The gometalinter handler should handle names with spaces): \ ]), 'v:val[1:5]') Execute (The gometalinter handler should handle relative paths correctly): - :file! /foo/bar/baz.go + silent file /foo/bar/baz.go AssertEqual \ [ @@ -47,7 +47,28 @@ Execute (The gometalinter handler should handle relative paths correctly): \ 'type': 'E', \ }, \ ], - \ ale_linters#go#gometalinter#Handler(42, [ + \ ale_linters#go#gometalinter#Handler(bufnr(''), [ \ 'baz.go:12:3:warning: expected ''package'', found ''IDENT'' gibberish (staticcheck)', \ 'baz.go:37:5:error: expected ''package'', found ''IDENT'' gibberish (golint)', \ ]) + + +Execute (The gometalinter handler should filter out errors from other files): + silent file! /some/path/sql.go + + AssertEqual + \ [], + \ ale_linters#go#gometalinter#Handler(bufnr(''), [ + \ '/some/path/interface_implementation_test.go:417::warning: cyclomatic complexity 24 of function testGetUserHeaders() is high (> 10) (gocyclo)', + \ '/some/path/sql_helpers.go:38::warning: cyclomatic complexity 11 of function CreateTestUserMetadataDB() is high (> 10) (gocyclo)', + \ '/some/path/sql_alpha.go:560:7:warning: ineffectual assignment to err (ineffassign)', + \ '/some/path/sql_alpha.go:589:7:warning: ineffectual assignment to err (ineffassign)', + \ '/some/path/sql_test.go:124:9:warning: should not use basic type untyped string as key in context.WithValue (golint)', + \ '/some/path/interface_implementation_test.go:640::warning: declaration of "cfg" shadows declaration at sql_test.go:21 (vetshadow)', + \ '/some/path/sql_helpers.go:55::warning: declaration of "err" shadows declaration at sql_helpers.go:48 (vetshadow)', + \ '/some/path/sql_helpers.go:91::warning: declaration of "err" shadows declaration at sql_helpers.go:48 (vetshadow)', + \ '/some/path/sql_helpers.go:108::warning: declaration of "err" shadows declaration at sql_helpers.go:48 (vetshadow)', + \ '/some/path/user_metadata_db.go:149::warning: declaration of "err" shadows declaration at user_metadata_db.go:140 (vetshadow)', + \ '/some/path/user_metadata_db.go:188::warning: declaration of "err" shadows declaration at user_metadata_db.go:179 (vetshadow)', + \ '/some/path/queries_alpha.go:62::warning: Potential hardcoded credentials,HIGH,LOW (gas)', + \ ]) diff --git a/test/lsp/test_lsp_connections.vader b/test/lsp/test_lsp_connections.vader index 36a21bd2..d5ed7702 100644 --- a/test/lsp/test_lsp_connections.vader +++ b/test/lsp/test_lsp_connections.vader @@ -18,47 +18,92 @@ Execute(GetNextMessageID() should increment appropriately): AssertEqual 1, ale#lsp#GetNextMessageID() Execute(ale#lsp#CreateMessageData() should create an appropriate message): - " 71 is the size in bytes for UTF-8, not the number of characters. - AssertEqual - \ [ - \ 1, - \ "Content-Length: 71\r\n\r\n" - \ . '{"id":1,"jsonrpc":"2.0","method":"someMethod","params":{"foo":"barÜ"}}', - \ ], - \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) - " Check again to ensure that we use the next ID. - AssertEqual - \ [ - \ 2, - \ "Content-Length: 71\r\n\r\n" - \ . '{"id":2,"jsonrpc":"2.0","method":"someMethod","params":{"foo":"barÜ"}}', - \ ], - \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + " NeoVim outputs JSON with spaces, so the output is a little different. + if has('nvim') + " 79 is the size in bytes for UTF-8, not the number of characters. + AssertEqual + \ [ + \ 1, + \ "Content-Length: 79\r\n\r\n" + \ . '{"id": 1, "jsonrpc": "2.0", "method": "someMethod", "params": {"foo": "barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + " Check again to ensure that we use the next ID. + AssertEqual + \ [ + \ 2, + \ "Content-Length: 79\r\n\r\n" + \ . '{"id": 2, "jsonrpc": "2.0", "method": "someMethod", "params": {"foo": "barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + else + AssertEqual + \ [ + \ 1, + \ "Content-Length: 71\r\n\r\n" + \ . '{"id":1,"jsonrpc":"2.0","method":"someMethod","params":{"foo":"barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + AssertEqual + \ [ + \ 2, + \ "Content-Length: 71\r\n\r\n" + \ . '{"id":2,"jsonrpc":"2.0","method":"someMethod","params":{"foo":"barÜ"}}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}]) + endif Execute(ale#lsp#CreateMessageData() should create messages without params): - AssertEqual - \ [ - \ 1, - \ "Content-Length: 51\r\n\r\n" - \ . '{"id":1,"jsonrpc":"2.0","method":"someOtherMethod"}', - \ ], - \ ale#lsp#CreateMessageData([0, 'someOtherMethod']) + if has('nvim') + AssertEqual + \ [ + \ 1, + \ "Content-Length: 56\r\n\r\n" + \ . '{"id": 1, "jsonrpc": "2.0", "method": "someOtherMethod"}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someOtherMethod']) + else + AssertEqual + \ [ + \ 1, + \ "Content-Length: 51\r\n\r\n" + \ . '{"id":1,"jsonrpc":"2.0","method":"someOtherMethod"}', + \ ], + \ ale#lsp#CreateMessageData([0, 'someOtherMethod']) + endif Execute(ale#lsp#CreateMessageData() should create notifications): - AssertEqual - \ [ - \ 0, - \ "Content-Length: 55\r\n\r\n" - \ . '{"id":null,"jsonrpc":"2.0","method":"someNotification"}', - \ ], - \ ale#lsp#CreateMessageData([1, 'someNotification']) - AssertEqual - \ [ - \ 0, - \ "Content-Length: 78\r\n\r\n" - \ . '{"id":null,"jsonrpc":"2.0","method":"someNotification","params":{"foo":"bar"}}', - \ ], - \ ale#lsp#CreateMessageData([1, 'someNotification', {'foo': 'bar'}]) + if has('nvim') + AssertEqual + \ [ + \ 0, + \ "Content-Length: 60\r\n\r\n" + \ . '{"id": null, "jsonrpc": "2.0", "method": "someNotification"}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification']) + AssertEqual + \ [ + \ 0, + \ "Content-Length: 86\r\n\r\n" + \ . '{"id": null, "jsonrpc": "2.0", "method": "someNotification", "params": {"foo": "bar"}}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification', {'foo': 'bar'}]) + else + AssertEqual + \ [ + \ 0, + \ "Content-Length: 55\r\n\r\n" + \ . '{"id":null,"jsonrpc":"2.0","method":"someNotification"}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification']) + AssertEqual + \ [ + \ 0, + \ "Content-Length: 78\r\n\r\n" + \ . '{"id":null,"jsonrpc":"2.0","method":"someNotification","params":{"foo":"bar"}}', + \ ], + \ ale#lsp#CreateMessageData([1, 'someNotification', {'foo': 'bar'}]) + endif Execute(ale#lsp#ReadMessageData() should read single whole messages): AssertEqual diff --git a/test/sign/test_linting_sets_signs.vader b/test/sign/test_linting_sets_signs.vader index 0654be47..1530847e 100644 --- a/test/sign/test_linting_sets_signs.vader +++ b/test/sign/test_linting_sets_signs.vader @@ -44,6 +44,7 @@ After: sign unplace * let g:ale_buffer_info = {} + call ale#linter#Reset() Execute(The signs should be updated after linting is done): call ale#Lint() diff --git a/test/smoke_test.vader b/test/smoke_test.vader index 18b74cf0..30f32534 100644 --- a/test/smoke_test.vader +++ b/test/smoke_test.vader @@ -11,11 +11,12 @@ Before: \}] endfunction + " Running the command in another subshell seems to help here. call ale#linter#Define('foobar', { \ 'name': 'testlinter', \ 'callback': 'TestCallback', \ 'executable': 'echo', - \ 'command': 'echo foo bar', + \ 'command': '/bin/sh -c ''echo foo bar''', \}) After: diff --git a/test/test_ale_toggle.vader b/test/test_ale_toggle.vader index cbb31857..5d27c864 100644 --- a/test/test_ale_toggle.vader +++ b/test/test_ale_toggle.vader @@ -26,7 +26,7 @@ Before: \ 'lnum': 2, \ 'vcol': 0, \ 'col': 3, - \ 'text': a:output[0], + \ 'text': 'foo bar', \ 'type': 'E', \ 'nr': -1, \}] @@ -56,7 +56,8 @@ Before: \ 'name': 'testlinter', \ 'callback': 'ToggleTestCallback', \ 'executable': 'echo', - \ 'command': 'echo foo bar', + \ 'command': 'echo', + \ 'read_buffer': 0, \}) After: diff --git a/test/test_command_chain.vader b/test/test_command_chain.vader index 7b5e83ca..16472041 100644 --- a/test/test_command_chain.vader +++ b/test/test_command_chain.vader @@ -1,4 +1,7 @@ Before: + Save &shell, g:ale_run_synchronously + let g:ale_run_synchronously = 1 + set shell=/bin/sh let g:linter_output = [] let g:first_echo_called = 0 let g:second_echo_called = 0 @@ -39,6 +42,7 @@ Before: \}) After: + Restore unlet! g:first_echo_called unlet! g:second_echo_called unlet! g:final_callback_called @@ -55,9 +59,6 @@ Given foobar (Some imaginary filetype): Execute(Check the results of running the chain): AssertEqual 'foobar', &filetype call ale#Lint() - " Sleep a little. This allows the commands to complete a little better. - sleep 50m - call ale#engine#WaitForJobs(2000) Assert g:first_echo_called, 'The first chain item was not called' Assert g:second_echo_called, 'The second chain item was not called' diff --git a/test/test_conflicting_plugin_warnings.vader b/test/test_conflicting_plugin_warnings.vader index ebf53c8a..08a4c412 100644 --- a/test/test_conflicting_plugin_warnings.vader +++ b/test/test_conflicting_plugin_warnings.vader @@ -1,5 +1,9 @@ Execute(The after file should have been loaded for real): - Assert g:loaded_ale_after + " FIXME: Fix these tests in NeoVim. + if !has('nvim') + Assert has_key(g:, 'loaded_ale_after'), 'g:loaded_ale_after was not set!' + Assert g:loaded_ale_after + endif Before: silent! cd /testplugin/test diff --git a/test/test_cursor_warnings.vader b/test/test_cursor_warnings.vader index 09081b12..6018dabd 100644 --- a/test/test_cursor_warnings.vader +++ b/test/test_cursor_warnings.vader @@ -65,7 +65,10 @@ After: delfunction GetLastMessage - mess clear + " Clearing the messages breaks tests on NeoVim for some reason, but all + " we need to do for these tests is just make it so the last message isn't + " carried over between test cases. + echomsg '' Given javascript(A Javscript file with warnings/errors): var x = 3 diff --git a/test/test_line_join.vader b/test/test_line_join.vader index 26abb7c9..63d8d338 100644 --- a/test/test_line_join.vader +++ b/test/test_line_join.vader @@ -18,6 +18,6 @@ After: Execute (Join the lines): let joined_result = [] for item in g:test_output - call ale#engine#JoinNeovimOutput(joined_result, item) + call ale#job#JoinNeovimOutput(joined_result, item) endfor AssertEqual g:expected_result, joined_result diff --git a/test/test_vim8_processid_parsing.vader b/test/test_vim8_processid_parsing.vader index 5ec564e0..26416b15 100644 --- a/test/test_vim8_processid_parsing.vader +++ b/test/test_vim8_processid_parsing.vader @@ -1,5 +1,5 @@ Execute(Vim8 Process ID parsing should work): - AssertEqual 123, ale#engine#ParseVim8ProcessID('process 123 run') - AssertEqual 347, ale#engine#ParseVim8ProcessID('process 347 failed') - AssertEqual 789, ale#engine#ParseVim8ProcessID('process 789 dead') - AssertEqual 0, ale#engine#ParseVim8ProcessID('no process') + AssertEqual 123, ale#job#ParseVim8ProcessID('process 123 run') + AssertEqual 347, ale#job#ParseVim8ProcessID('process 347 failed') + AssertEqual 789, ale#job#ParseVim8ProcessID('process 789 dead') + AssertEqual 0, ale#job#ParseVim8ProcessID('no process') |