Compare commits

...

15 Commits

Author SHA1 Message Date
w0rp a94f7aaa7e Fix #964 - Remove signs when multiple signs end up on a single line 2017-10-03 10:03:10 +01:00
w0rp 21c4b9033a Merge pull request #951 from rzane/eslint-fix-config-option
ESLint --config option causes it to not detect node_modules?
2017-09-25 21:40:18 +01:00
w0rp f382afbe23 Fix #924 - Make changing the sign column color work again 2017-09-12 21:24:39 +01:00
w0rp 4da8c3607d Fix #921 - Capture both output streams for gosimple and staticcheck 2017-09-12 19:54:37 +01:00
w0rp c3844db973 Fix #923 Use package.json as a last resort for eslint --fix 2017-09-12 09:16:20 +01:00
w0rp 8a374a69a3 Fix #922 - Prefer nearer ESLint configuration files with lower precedence filenames for eslint --fix 2017-09-11 21:54:59 +01:00
w0rp 68bae8a1d1 Fix #920 - Shut up about E776 2017-09-10 20:18:52 +01:00
w0rp b8dcdc984b Fix #912 - Close lists automatically when g:ale_open_list is set to 'on_save' again 2017-09-08 20:11:28 +01:00
w0rp c6d3d646ac Fix #907 - Stop LSP integration breaking with empty string keys in NeoVim 2017-09-04 00:15:59 +01:00
w0rp c8de2d9766 Fix #899 - Make the quickfix and loclist windows close again 2017-09-03 19:51:38 +01:00
w0rp 6d50074984 Fix #891 - Do not check ctrlp-funky windows 2017-09-03 18:29:15 +01:00
w0rp 6b87dd24ee Fix #895 - Run Node.js scripts with node.exe instead of node on Windows 2017-08-31 13:13:33 +01:00
w0rp 11fafbfd66 Fix #888 - Disable pycodestyle by default 2017-08-30 09:15:14 +01:00
w0rp 06c3ee61e4 Add a missing scriptencoding line 2017-08-29 16:56:56 +01:00
w0rp 5a88395bbb #869 - Detect the shell dialect from the hashbang for shellcheck 2017-08-29 16:54:24 +01:00
35 changed files with 465 additions and 180 deletions

View File

@ -6,4 +6,5 @@ call ale#linter#Define('go', {
\ 'executable': 'gosimple',
\ 'command': 'gosimple %t',
\ 'callback': 'ale#handlers#unix#HandleAsWarning',
\ 'output_stream': 'both'
\})

View File

@ -6,4 +6,5 @@ call ale#linter#Define('go', {
\ 'executable': 'staticcheck',
\ 'command': 'staticcheck %t',
\ 'callback': 'ale#handlers#unix#HandleAsWarning',
\ 'output_stream': 'both'
\})

View File

@ -14,17 +14,9 @@ endfunction
function! ale_linters#javascript#standard#GetCommand(buffer) abort
let l:executable = ale_linters#javascript#standard#GetExecutable(a:buffer)
if ale#Has('win32') && l:executable =~? '\.js$'
" .js files have to be executed with Node on Windows.
let l:head = 'node ' . ale#Escape(l:executable)
else
let l:head = ale#Escape(l:executable)
endif
let l:options = ale#Var(a:buffer, 'javascript_standard_options')
return l:head
return ale#node#Executable(a:buffer, l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' --stdin %s'
endfunction

View File

@ -17,18 +17,10 @@ if !exists('g:ale_sh_shell_default_shell')
endif
function! ale_linters#sh#shell#GetExecutable(buffer) abort
let l:banglines = getbufline(a:buffer, 1)
let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
" Take the shell executable from the hashbang, if we can.
if len(l:banglines) == 1 && l:banglines[0] =~# '^#!'
" Remove options like -e, etc.
let l:line = substitute(l:banglines[0], '--\?[a-zA-Z0-9]\+', '', 'g')
for l:possible_shell in ['bash', 'tcsh', 'csh', 'zsh', 'sh']
if l:line =~# l:possible_shell . '\s*$'
return l:possible_shell
endif
endfor
if !empty(l:shell_type)
return l:shell_type
endif
return ale#Var(a:buffer, 'sh_shell_default_shell')

View File

@ -19,25 +19,35 @@ function! ale_linters#sh#shellcheck#GetExecutable(buffer) abort
return ale#Var(a:buffer, 'sh_shellcheck_executable')
endfunction
function! s:GetDialectArgument() abort
if exists('b:is_bash') && b:is_bash
return '-s bash'
elseif exists('b:is_sh') && b:is_sh
return '-s sh'
elseif exists('b:is_kornshell') && b:is_kornshell
return '-s ksh'
function! ale_linters#sh#shellcheck#GetDialectArgument(buffer) abort
let l:shell_type = ale#handlers#sh#GetShellType(a:buffer)
if !empty(l:shell_type)
return l:shell_type
endif
" If there's no hashbang, try using Vim's buffer variables.
if get(b:, 'is_bash')
return 'bash'
elseif get(b:, 'is_sh')
return 'sh'
elseif get(b:, 'is_kornshell')
return 'ksh'
endif
return ''
endfunction
function! ale_linters#sh#shellcheck#GetCommand(buffer) abort
let l:options = ale#Var(a:buffer, 'sh_shellcheck_options')
let l:exclude_option = ale#Var(a:buffer, 'sh_shellcheck_exclusions')
let l:dialect = ale_linters#sh#shellcheck#GetDialectArgument(a:buffer)
return ale_linters#sh#shellcheck#GetExecutable(a:buffer)
\ . ' ' . ale#Var(a:buffer, 'sh_shellcheck_options')
\ . ' ' . (!empty(l:exclude_option) ? '-e ' . l:exclude_option : '')
\ . ' ' . s:GetDialectArgument() . ' -f gcc -'
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . (!empty(l:exclude_option) ? ' -e ' . l:exclude_option : '')
\ . (!empty(l:dialect) ? ' -s ' . l:dialect : '')
\ . ' -f gcc -'
endfunction
call ale#linter#Define('sh', {

View File

@ -48,6 +48,7 @@ function! ale#ShouldDoNothing(buffer) abort
\ || ale#util#InSandbox()
\ || !ale#Var(a:buffer, 'enabled')
\ || ale#FileTooLarge()
\ || getbufvar(a:buffer, '&l:statusline') =~# 'CtrlPMode.*funky'
endfunction
" (delay, [linting_flag, buffer_number])

View File

@ -286,10 +286,6 @@ function! ale#engine#SetResults(buffer, loclist) abort
if g:ale_set_quickfix || g:ale_set_loclist
call ale#list#SetLists(a:buffer, a:loclist)
if l:linting_is_done
call ale#list#CloseWindowIfNeeded(a:buffer)
endif
endif
if exists('*ale#statusline#Update')

View File

@ -1,3 +1,4 @@
scriptencoding utf-8
" Author: Peter Renström <renstrom.peter@gmail.com>
" Description: Fixing C/C++ files with clang-format.

View File

@ -2,22 +2,23 @@
" Description: Fixing files with eslint.
function! s:FindConfig(buffer) abort
for l:filename in [
\ '.eslintrc.js',
\ '.eslintrc.yaml',
\ '.eslintrc.yml',
\ '.eslintrc.json',
\ '.eslintrc',
\ 'package.json',
\]
let l:config = ale#path#FindNearestFile(a:buffer, l:filename)
for l:path in ale#path#Upwards(expand('#' . a:buffer . ':p:h'))
for l:basename in [
\ '.eslintrc.js',
\ '.eslintrc.yaml',
\ '.eslintrc.yml',
\ '.eslintrc.json',
\ '.eslintrc',
\]
let l:config = ale#path#Simplify(l:path . '/' . l:basename)
if !empty(l:config)
return l:config
endif
if filereadable(l:config)
return l:config
endif
endfor
endfor
return ''
return ale#path#FindNearestFile(a:buffer, 'package.json')
endfunction
function! ale#fixers#eslint#Fix(buffer) abort
@ -28,17 +29,9 @@ function! ale#fixers#eslint#Fix(buffer) abort
return 0
endif
if ale#Has('win32') && l:executable =~? 'eslint\.js$'
" For Windows, if we detect an eslint.js script, we need to execute
" it with node, or the file can be opened with a text editor.
let l:head = 'node ' . ale#Escape(l:executable)
else
let l:head = ale#Escape(l:executable)
endif
return {
\ 'command': l:head
\ . ' --config ' . ale#Escape(l:config)
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . ' -c ' . ale#Escape(l:config)
\ . ' --fix %t',
\ 'read_temporary_file': 1,
\}

View File

@ -11,16 +11,8 @@ endfunction
function! ale#fixers#standard#Fix(buffer) abort
let l:executable = ale#fixers#standard#GetExecutable(a:buffer)
if ale#Has('win32') && l:executable =~? 'cmd\.js$'
" For Windows, if we detect an standard.js script, we need to execute
" it with node, or the file can be opened with a text editor.
let l:head = 'node ' . ale#Escape(l:executable)
else
let l:head = ale#Escape(l:executable)
endif
return {
\ 'command': l:head
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . ' --fix %t',
\ 'read_temporary_file': 1,
\}

View File

@ -15,16 +15,8 @@ endfunction
function! ale#fixers#stylelint#Fix(buffer) abort
let l:executable = ale#fixers#stylelint#GetExecutable(a:buffer)
if ale#Has('win32') && l:executable =~? 'stylelint\.js$'
" For Windows, if we detect an stylelint.js script, we need to execute
" it with node, or the file can be opened with a text editor.
let l:head = 'node ' . ale#Escape(l:executable)
else
let l:head = ale#Escape(l:executable)
endif
return {
\ 'command': l:head
\ 'command': ale#node#Executable(a:buffer, l:executable)
\ . ' --fix %t',
\ 'read_temporary_file': 1,
\}

View File

@ -17,17 +17,9 @@ endfunction
function! ale#handlers#eslint#GetCommand(buffer) abort
let l:executable = ale#handlers#eslint#GetExecutable(a:buffer)
if ale#Has('win32') && l:executable =~? 'eslint\.js$'
" For Windows, if we detect an eslint.js script, we need to execute
" it with node, or the file can be opened with a text editor.
let l:head = 'node ' . ale#Escape(l:executable)
else
let l:head = ale#Escape(l:executable)
endif
let l:options = ale#Var(a:buffer, 'javascript_eslint_options')
return l:head
return ale#node#Executable(a:buffer, l:executable)
\ . (!empty(l:options) ? ' ' . l:options : '')
\ . ' -f unix --stdin --stdin-filename %s'
endfunction

View File

@ -0,0 +1,20 @@
" Author: w0rp <devw0rp@gmail.com>
" Get the shell type for a buffer, based on the hashbang line.
function! ale#handlers#sh#GetShellType(buffer) abort
let l:bang_line = get(getbufline(a:buffer, 1), 0, '')
" Take the shell executable from the hashbang, if we can.
if l:bang_line[:1] is# '#!'
" Remove options like -e, etc.
let l:command = substitute(l:bang_line, ' --\?[a-zA-Z0-9]\+', '', 'g')
for l:possible_shell in ['bash', 'tcsh', 'csh', 'zsh', 'sh']
if l:command =~# l:possible_shell . '\s*$'
return l:possible_shell
endif
endfor
endif
return ''
endfunction

View File

@ -25,6 +25,7 @@ let s:default_ale_linters = {
\ 'csh': ['shell'],
\ 'go': ['gofmt', 'golint', 'go vet'],
\ 'help': [],
\ 'python': ['flake8', 'mypy', 'pylint'],
\ 'rust': ['cargo'],
\ 'spec': [],
\ 'text': [],

View File

@ -56,6 +56,10 @@ function! s:FixList(list) abort
return l:new_list
endfunction
function! s:BufWinId(buffer) abort
return exists('*bufwinid') ? bufwinid(str2nr(a:buffer)) : 0
endfunction
function! s:SetListsImpl(timer_id, buffer, loclist) abort
let l:title = expand('#' . a:buffer . ':p')
@ -72,7 +76,7 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
" If windows support is off, bufwinid() may not exist.
" We'll set result in the current window, which might not be correct,
" but is better than nothing.
let l:win_id = exists('*bufwinid') ? bufwinid(str2nr(a:buffer)) : 0
let l:win_id = s:BufWinId(a:buffer)
if has('nvim')
call setloclist(l:win_id, s:FixList(a:loclist), ' ', l:title)
@ -82,13 +86,11 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
endif
endif
let l:keep_open = ale#Var(a:buffer, 'keep_list_window_open')
" Open a window to show the problems if we need to.
"
" We'll check if the current buffer's List is not empty here, so the
" window will only be opened if the current buffer has problems.
if s:ShouldOpen(a:buffer) && (l:keep_open || !empty(a:loclist))
if s:ShouldOpen(a:buffer) && !empty(a:loclist)
let l:winnr = winnr()
let l:mode = mode()
let l:reset_visual_selection = l:mode is? 'v' || l:mode is# "\<c-v>"
@ -96,10 +98,10 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
if g:ale_set_quickfix
if !ale#list#IsQuickfixOpen()
execute 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size'))
silent! execute 'copen ' . str2nr(ale#Var(a:buffer, 'list_window_size'))
endif
elseif g:ale_set_loclist
execute 'lopen ' . str2nr(ale#Var(a:buffer, 'list_window_size'))
silent! execute 'lopen ' . str2nr(ale#Var(a:buffer, 'list_window_size'))
endif
" If focus changed, restore it (jump to the last window).
@ -117,10 +119,23 @@ function! s:SetListsImpl(timer_id, buffer, loclist) abort
endif
endif
endif
" If ALE isn't currently checking for more problems, close the window if
" needed now. This check happens inside of this timer function, so
" the window can be closed reliably.
if !ale#engine#IsCheckingBuffer(a:buffer)
call s:CloseWindowIfNeeded(a:buffer)
endif
endfunction
function! ale#list#SetLists(buffer, loclist) abort
if get(g:, 'ale_set_lists_synchronously') == 1
\|| getbufvar(a:buffer, 'ale_save_event_fired', 0)
" Update lists immediately if running a test synchronously, or if the
" buffer was saved.
"
" The lists need to be updated immediately when saving a buffer so
" that we can reliably close window automatically, if so configured.
call s:SetListsImpl(-1, a:buffer, a:loclist)
else
call ale#util#StartPartialTimer(
@ -131,7 +146,7 @@ function! ale#list#SetLists(buffer, loclist) abort
endif
endfunction
function! s:CloseWindowIfNeededImpl(timer_id, buffer) abort
function! s:CloseWindowIfNeeded(buffer) abort
if ale#Var(a:buffer, 'keep_list_window_open') || !s:ShouldOpen(a:buffer)
return
endif
@ -143,22 +158,14 @@ function! s:CloseWindowIfNeededImpl(timer_id, buffer) abort
if empty(getqflist())
cclose
endif
elseif g:ale_set_loclist && empty(getloclist(0))
lclose
else
let l:win_id = s:BufWinId(a:buffer)
if g:ale_set_loclist && empty(getloclist(l:win_id))
lclose
endif
endif
" Ignore 'Cannot close last window' errors.
catch /E444/
endtry
endfunction
function! ale#list#CloseWindowIfNeeded(buffer) abort
if get(g:, 'ale_set_lists_synchronously') == 1
call s:CloseWindowIfNeededImpl(-1, a:buffer)
else
call ale#util#StartPartialTimer(
\ 0,
\ function('s:CloseWindowIfNeededImpl'),
\ [a:buffer],
\)
endif
endfunction

View File

@ -242,10 +242,14 @@ function! s:HandleCommandMessage(job_id, message) abort
call ale#lsp#HandleMessage(l:conn, a:message)
endfunction
function! s:RegisterProject(conn, project_root) abort
if !has_key(a:conn.projects, a:project_root)
function! ale#lsp#RegisterProject(conn, project_root) abort
" Empty strings can't be used for Dictionary keys in NeoVim, due to E713.
" This appears to be a nonsensical bug in NeoVim.
let l:key = empty(a:project_root) ? '<<EMPTY>>' : a:project_root
if !has_key(a:conn.projects, l:key)
" Tools without project roots are ready right away, like tsserver.
let a:conn.projects[a:project_root] = {
let a:conn.projects[l:key] = {
\ 'initialized': empty(a:project_root),
\ 'init_request_id': 0,
\ 'message_queue': [],
@ -253,6 +257,12 @@ function! s:RegisterProject(conn, project_root) abort
endif
endfunction
function! ale#lsp#GetProject(conn, project_root) abort
let l:key = empty(a:project_root) ? '<<EMPTY>>' : a:project_root
return get(a:conn.projects, l:key, {})
endfunction
" Start a program for LSP servers which run with executables.
"
" The job ID will be returned for for the program if it ran, otherwise
@ -285,7 +295,7 @@ function! ale#lsp#StartProgram(executable, command, project_root, callback) abor
let l:conn.id = l:job_id
" Add the callback to the List if it's not there already.
call uniq(sort(add(l:conn.callback_list, a:callback)))
call s:RegisterProject(l:conn, a:project_root)
call ale#lsp#RegisterProject(l:conn, a:project_root)
return l:job_id
endfunction
@ -311,7 +321,7 @@ function! ale#lsp#ConnectToAddress(address, project_root, callback) abort
let l:conn.id = a:address
" Add the callback to the List if it's not there already.
call uniq(sort(add(l:conn.callback_list, a:callback)))
call s:RegisterProject(l:conn, a:project_root)
call ale#lsp#RegisterProject(l:conn, a:project_root)
return 1
endfunction
@ -344,7 +354,7 @@ function! ale#lsp#Send(conn_id, message, ...) abort
return 0
endif
let l:project = get(l:conn.projects, l:project_root, {})
let l:project = ale#lsp#GetProject(l:conn, l:project_root)
if empty(l:project)
return 0

View File

@ -1,6 +1,8 @@
" Author: w0rp <devw0rp@gmail.com>
" Description: Functions for working with Node executables.
call ale#Set('windows_node_executable_path', 'node.exe')
" Given a buffer number, a base variable name, and a list of paths to search
" for in ancestor directories, detect the executable path for a Node program.
"
@ -20,3 +22,21 @@ function! ale#node#FindExecutable(buffer, base_var_name, path_list) abort
return ale#Var(a:buffer, a:base_var_name . '_executable')
endfunction
" Create a executable string which executes a Node.js script command with a
" Node.js executable if needed.
"
" The executable string should not be escaped before passing it to this
" function, the executable string will be escaped when returned by this
" function.
"
" The executable is only prefixed for Windows machines
function! ale#node#Executable(buffer, executable) abort
if ale#Has('win32') && a:executable =~? '\.js$'
let l:node = ale#Var(a:buffer, 'windows_node_executable_path')
return ale#Escape(l:node) . ' ' . ale#Escape(a:executable)
endif
return ale#Escape(a:executable)
endfunction

View File

@ -184,16 +184,6 @@ function! s:GroupLoclistItems(buffer, loclist) abort
return l:grouped_items
endfunction
function! ale#sign#SetSignColumnHighlight(has_problems) abort
highlight clear SignColumn
if a:has_problems
highlight link SignColumn ALESignColumnWithErrors
else
highlight link SignColumn ALESignColumnWithoutErrors
endif
endfunction
function! s:UpdateLineNumbers(buffer, current_sign_list, loclist) abort
let l:line_map = {}
let l:line_numbers_changed = 0
@ -224,24 +214,32 @@ function! s:BuildSignMap(current_sign_list, grouped_items) abort
let l:sign_offset = g:ale_sign_offset
for [l:line, l:sign_id, l:name] in a:current_sign_list
let l:sign_map[l:line] = {
\ 'current_id': l:sign_id,
\ 'current_name': l:name,
let l:sign_info = get(l:sign_map, l:line, {
\ 'current_id_list': [],
\ 'current_name_list': [],
\ 'new_id': 0,
\ 'new_name': '',
\ 'items': [],
\}
\})
" Increment the sign offset for new signs, by the maximum sign ID.
if l:sign_id > l:sign_offset
let l:sign_offset = l:sign_id
endif
" Remember the sign names and IDs in separate Lists, so they are easy
" to work with.
call add(l:sign_info.current_id_list, l:sign_id)
call add(l:sign_info.current_name_list, l:name)
let l:sign_map[l:line] = l:sign_info
endfor
for l:group in a:grouped_items
let l:line = l:group[0].lnum
let l:sign_info = get(l:sign_map, l:line, {
\ 'current_id': 0,
\ 'current_name': '',
\ 'current_id_list': [],
\ 'current_name_list': [],
\ 'new_id': 0,
\ 'new_name': '',
\ 'items': [],
@ -250,11 +248,18 @@ function! s:BuildSignMap(current_sign_list, grouped_items) abort
let l:sign_info.new_name = ale#sign#GetSignName(l:group)
let l:sign_info.items = l:group
if l:sign_info.current_name isnot# l:sign_info.new_name
let l:index = index(
\ l:sign_info.current_name_list,
\ l:sign_info.new_name
\)
if l:index >= 0
" We have a sign with this name already, so use the same ID.
let l:sign_info.new_id = l:sign_info.current_id_list[l:index]
else
" This sign name replaces the previous name, so use a new ID.
let l:sign_info.new_id = l:sign_offset + 1
let l:sign_offset += 1
else
let l:sign_info.new_id = l:sign_info.current_id
endif
let l:sign_map[l:line] = l:sign_info
@ -288,7 +293,7 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort
let l:item.sign_id = l:info.new_id
endfor
if l:info.new_id isnot l:info.current_id
if index(l:info.current_id_list, l:info.new_id) < 0
call add(l:command_list, 'sign place '
\ . (l:info.new_id)
\ . ' line=' . l:line_str
@ -301,12 +306,14 @@ function! ale#sign#GetSignCommands(buffer, was_sign_set, sign_map) abort
" Remove signs without new IDs.
for l:info in values(a:sign_map)
if l:info.current_id && l:info.current_id isnot l:info.new_id
call add(l:command_list, 'sign unplace '
\ . (l:info.current_id)
\ . ' buffer=' . a:buffer
\)
endif
for l:current_id in l:info.current_id_list
if l:current_id isnot l:info.new_id
call add(l:command_list, 'sign unplace '
\ . l:current_id
\ . ' buffer=' . a:buffer
\)
endif
endfor
endfor
" Remove the dummy sign to close the sign column if we need to.
@ -347,7 +354,19 @@ function! ale#sign#SetSigns(buffer, loclist) abort
\ l:sign_map,
\)
" Change the sign column color if the option is on.
if g:ale_change_sign_column_color && !empty(a:loclist)
highlight clear SignColumn
highlight link SignColumn ALESignColumnWithErrors
endif
for l:command in l:command_list
silent! execute l:command
endfor
" Reset the sign column color when there are no more errors.
if g:ale_change_sign_column_color && empty(a:loclist)
highlight clear SignColumn
highlight link SignColumn ALESignColumnWithoutErrors
endif
endfunction

View File

@ -918,6 +918,22 @@ b:ale_warn_about_trailing_whitespace *b:ale_warn_about_trailing_whitespace*
This option may be configured on a per buffer basis.
g:ale_windows_node_executable_path *g:ale_windows_node_executable_path*
*b:ale_windows_node_executable_path*
Type: |String|
Default: `'node.exe'`
This variable is used as the path to the executable to use for executing
scripts with Node.js on Windows.
For Windows, any file with a `.js` file extension needs to be executed with
the node executable explicitly. Otherwise, Windows could try and open the
scripts with other applications, like a text editor. Therefore, these
scripts are executed with whatever executable is configured with this
setting.
-------------------------------------------------------------------------------
3.1. Highlights *ale-highlights*

View File

@ -0,0 +1,47 @@
Before:
Save g:ale_sh_shellcheck_exclusions
Save g:ale_sh_shellcheck_executable
Save g:ale_sh_shellcheck_options
unlet! g:ale_sh_shellcheck_exclusions
unlet! g:ale_sh_shellcheck_executable
unlet! g:ale_sh_shellcheck_options
runtime ale_linters/sh/shellcheck.vim
After:
Restore
unlet! b:ale_sh_shellcheck_exclusions
unlet! b:ale_sh_shellcheck_executable
unlet! b:ale_sh_shellcheck_options
unlet! b:is_bash
call ale#linter#Reset()
Execute(The default shellcheck command should be correct):
AssertEqual
\ 'shellcheck -f gcc -',
\ ale_linters#sh#shellcheck#GetCommand(bufnr(''))
Execute(The shellcheck command should accept options):
let b:ale_sh_shellcheck_options = '--foobar'
AssertEqual
\ 'shellcheck --foobar -f gcc -',
\ ale_linters#sh#shellcheck#GetCommand(bufnr(''))
Execute(The shellcheck command should accept options and exclusions):
let b:ale_sh_shellcheck_options = '--foobar'
let b:ale_sh_shellcheck_exclusions = 'foo,bar'
AssertEqual
\ 'shellcheck --foobar -e foo,bar -f gcc -',
\ ale_linters#sh#shellcheck#GetCommand(bufnr(''))
Execute(The shellcheck command should include the dialect):
let b:is_bash = 1
AssertEqual
\ 'shellcheck -s bash -f gcc -',
\ ale_linters#sh#shellcheck#GetCommand(bufnr(''))

View File

@ -67,7 +67,7 @@ Execute(.js files should be executed with node on Windows):
\ ale_linters#javascript#standard#GetExecutable(bufnr(''))
AssertEqual
\ 'node ' . ale#Escape(b:executable) . ' --stdin %s',
\ ale#Escape('node.exe') . ' ' . ale#Escape(b:executable) . ' --stdin %s',
\ ale_linters#javascript#standard#GetCommand(bufnr(''))
Execute(The global executable should be used otherwise):

View File

View File

@ -13,22 +13,45 @@ Execute(The path to eslint.js should be run on Unix):
\ 'read_temporary_file': 1,
\ 'command':
\ ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js'))
\ . ' --config ' . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js'))
\ . ' -c ' . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js'))
\ . ' --fix %t',
\ },
\ ale#fixers#eslint#Fix(bufnr(''))
Execute(The eslint fixer with eslint.js should be run with node on Windows):
call ale#test#SetFilename('../eslint-test-files/react-app/subdir/testfile.js')
let g:ale_has_override['win32'] = 1
Execute(The lower priority configuration file in a nested directory should be preferred):
call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-config/testfile.js')
" We have to execute the file with node.
AssertEqual
\ {
\ 'read_temporary_file': 1,
\ 'command': 'node '
\ . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js'))
\ . ' --config ' . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js'))
\ 'command':
\ ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js'))
\ . ' -c ' . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/subdir-with-config/.eslintrc'))
\ . ' --fix %t',
\ },
\ ale#fixers#eslint#Fix(bufnr(''))
Execute(package.json should be used as a last resort):
call ale#test#SetFilename('../eslint-test-files/react-app/subdir-with-package-json/testfile.js')
AssertEqual
\ {
\ 'read_temporary_file': 1,
\ 'command':
\ ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/eslint/bin/eslint.js'))
\ . ' -c ' . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/.eslintrc.js'))
\ . ' --fix %t',
\ },
\ ale#fixers#eslint#Fix(bufnr(''))
call ale#test#SetFilename('../eslint-test-files/package.json')
AssertEqual
\ {
\ 'read_temporary_file': 1,
\ 'command':
\ ale#Escape(simplify(g:dir . '/../eslint-test-files/node_modules/.bin/eslint'))
\ . ' -c ' . ale#Escape(simplify(g:dir . '/../eslint-test-files/package.json'))
\ . ' --fix %t',
\ },
\ ale#fixers#eslint#Fix(bufnr(''))

View File

@ -25,7 +25,7 @@ Execute(The standard fixer with standard.js should be run with node on Windows):
AssertEqual
\ {
\ 'read_temporary_file': 1,
\ 'command': 'node '
\ 'command': ale#Escape('node.exe') . ' '
\ . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/standard/bin/cmd.js'))
\ . ' --fix %t',
\ },

View File

@ -25,7 +25,7 @@ Execute(The stylelint fixer with stylelint.js should be run with node on Windows
AssertEqual
\ {
\ 'read_temporary_file': 1,
\ 'command': 'node '
\ 'command': ale#Escape('node.exe') . ' '
\ . ale#Escape(simplify(g:dir . '/../eslint-test-files/react-app/node_modules/stylelint/bin/stylelint.js'))
\ . ' --fix %t',
\ },

View File

@ -3,6 +3,7 @@ Before:
After:
unlet! b:data
unlet! b:conn
Execute(GetNextMessageID() should increment appropriately):
" We should get the initial ID, and increment a bit.
@ -220,3 +221,51 @@ Execute(ale#lsp#ReadMessageData() should handle a message with part of a second
\ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}'
\ . b:data
\ )
Execute(Projects with regular project roots should be registered correctly):
let b:conn = {'projects': {}}
call ale#lsp#RegisterProject(b:conn, '/foo/bar')
AssertEqual
\ {
\ 'projects': {
\ '/foo/bar': {'initialized': 0, 'message_queue': [], 'init_request_id': 0},
\ },
\ },
\ b:conn
Execute(Projects with regular project roots should be fetched correctly):
let b:conn = {
\ 'projects': {
\ '/foo/bar': {'initialized': 0, 'message_queue': [], 'init_request_id': 0},
\ },
\}
AssertEqual
\ {'initialized': 0, 'message_queue': [], 'init_request_id': 0},
\ ale#lsp#GetProject(b:conn, '/foo/bar')
Execute(Projects with empty project roots should be registered correctly):
let b:conn = {'projects': {}}
call ale#lsp#RegisterProject(b:conn, '')
AssertEqual
\ {
\ 'projects': {
\ '<<EMPTY>>': {'initialized': 1, 'message_queue': [], 'init_request_id': 0},
\ },
\ },
\ b:conn
Execute(Projects with empty project roots should be fetched correctly):
let b:conn = {
\ 'projects': {
\ '<<EMPTY>>': {'initialized': 1, 'message_queue': [], 'init_request_id': 0},
\ },
\}
AssertEqual
\ {'initialized': 1, 'message_queue': [], 'init_request_id': 0},
\ ale#lsp#GetProject(b:conn, '')

View File

@ -1,4 +1,6 @@
Before:
Save g:ale_change_sign_column_color
function! ParseHighlight(name) abort
redir => l:output
silent execute 'highlight ' . a:name
@ -20,14 +22,34 @@ Before:
let g:sign_highlight = ParseHighlight('SignColumn')
After:
Restore
delfunction ParseHighlight
call SetHighlight('SignColumn', g:sign_highlight)
delfunction SetHighlight
unlet! g:sign_highlight
sign unplace *
Execute(The SignColumn highlight shouldn't be changed if the option is off):
let g:ale_change_sign_column_color = 0
let b:sign_highlight = ParseHighlight('SignColumn')
call ale#sign#SetSigns(bufnr(''), [
\ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'W', 'text': 'x'},
\])
AssertEqual b:sign_highlight, ParseHighlight('SignColumn')
call ale#sign#SetSigns(bufnr(''), [])
AssertEqual b:sign_highlight, ParseHighlight('SignColumn')
Execute(The SignColumn highlight should be set and reset):
call ale#sign#SetSignColumnHighlight(1)
let g:ale_change_sign_column_color = 1
call ale#sign#SetSigns(bufnr(''), [
\ {'bufnr': bufnr(''), 'lnum': 1, 'col': 1, 'type': 'W', 'text': 'x'},
\])
AssertEqual 'links to ALESignColumnWithErrors', ParseHighlight('SignColumn')
call ale#sign#SetSignColumnHighlight(0)
call ale#sign#SetSigns(bufnr(''), [])
AssertEqual 'links to ALESignColumnWithoutErrors', ParseHighlight('SignColumn')

View File

@ -266,3 +266,14 @@ Execute(It should be possible to clear signs with empty lists):
Execute(No exceptions should be thrown when setting signs for invalid buffers):
call ale#sign#SetSigns(123456789, [{'lnum': 15, 'col': 2, 'type': 'W', 'text': 'e'}])
Execute(Signs should be removed when lines have multiple sign IDs on them):
" We can fail to remove signs if there are multiple signs on one line,
" say after deleting lines in Vim, etc.
exec 'sign place 1000347 line=3 name=ALEErrorSign buffer=' . bufnr('')
exec 'sign place 1000348 line=3 name=ALEWarningSign buffer=' . bufnr('')
exec 'sign place 1000349 line=10 name=ALEErrorSign buffer=' . bufnr('')
exec 'sign place 1000350 line=10 name=ALEWarningSign buffer=' . bufnr('')
call ale#sign#SetSigns(bufnr(''), [])
AssertEqual [], ParseSigns()

View File

@ -58,7 +58,7 @@ Execute(eslint.js executables should be run with node on Windows):
" We have to execute the file with node.
AssertEqual
\ 'node '''
\ . g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js'
\ . ''' -f unix --stdin --stdin-filename %s',
\ ale#Escape('node.exe') . ' '
\ . ale#Escape(g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js')
\ . ' -f unix --stdin --stdin-filename %s',
\ ale#handlers#eslint#GetCommand(bufnr(''))

View File

@ -6,12 +6,14 @@ Before:
Save g:ale_keep_list_window_open
Save g:ale_list_window_size
Save g:ale_buffer_info
Save g:ale_set_lists_synchronously
let g:ale_set_loclist = 1
let g:ale_set_quickfix = 0
let g:ale_open_list = 0
let g:ale_keep_list_window_open = 0
let g:ale_list_window_size = 10
let g:ale_set_lists_synchronously = 1
let g:loclist = [
\ {'bufnr': bufnr(''), 'lnum': 5, 'col': 5, 'text': 'x'},
@ -70,17 +72,14 @@ Execute(The quickfix window should open for just the loclist):
" It should not open for an empty list.
call ale#list#SetLists(bufnr('%'), [])
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert !ale#list#IsQuickfixOpen()
" With a non-empty loclist, the window must open.
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert ale#list#IsQuickfixOpen()
" Clear the list and it should close again.
call ale#list#SetLists(bufnr('%'), [])
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert !ale#list#IsQuickfixOpen()
Execute(The quickfix window height should be correct for the loclist):
@ -88,7 +87,6 @@ Execute(The quickfix window height should be correct for the loclist):
let g:ale_list_window_size = 7
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
AssertEqual 7, GetQuickfixHeight()
@ -97,7 +95,6 @@ Execute(The quickfix window height should be correct for the loclist with buffer
let b:ale_list_window_size = 8
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
AssertEqual 8, GetQuickfixHeight()
@ -107,16 +104,13 @@ Execute(The quickfix window should stay open for just the loclist):
" The window should stay open after even after it is made blank again.
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
call ale#list#SetLists(bufnr('%'), [])
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert ale#list#IsQuickfixOpen()
Execute(The quickfix window should not open by default when quickfix is on):
let g:ale_set_quickfix = 1
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert !ale#list#IsQuickfixOpen()
Execute(The quickfix window should open for the quickfix list):
@ -129,24 +123,20 @@ Execute(The quickfix window should open for the quickfix list):
" It should not open for an empty list.
call ale#list#SetLists(bufnr('%'), [])
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert !ale#list#IsQuickfixOpen(), 'The quickfix window was opened when the list was empty'
" With a non-empty quickfix list, the window must open.
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert ale#list#IsQuickfixOpen(), 'The quickfix window was closed when the list was not empty'
" Clear this List. The window should stay open, as there are other items.
let g:ale_buffer_info[bufnr('')].loclist = []
call ale#list#SetLists(bufnr('%'), [])
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert ale#list#IsQuickfixOpen(), 'The quickfix window closed even though there are items in another buffer'
" Clear the other List now. Now the window should close.
call remove(g:ale_buffer_info, bufnr('') + 1)
call ale#list#SetLists(bufnr('%'), [])
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert !ale#list#IsQuickfixOpen(), 'The quickfix window was not closed'
Execute(The quickfix window should stay open for the quickfix list):
@ -156,9 +146,7 @@ Execute(The quickfix window should stay open for the quickfix list):
" The window should stay open after even after it is made blank again.
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
call ale#list#SetLists(bufnr('%'), [])
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert ale#list#IsQuickfixOpen()
Execute(The quickfix window height should be correct for the quickfix list):
@ -167,7 +155,6 @@ Execute(The quickfix window height should be correct for the quickfix list):
let g:ale_list_window_size = 7
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
AssertEqual 7, GetQuickfixHeight()
@ -177,7 +164,6 @@ Execute(The quickfix window height should be correct for the quickfix list with
let b:ale_list_window_size = 8
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
AssertEqual 8, GetQuickfixHeight()
@ -192,9 +178,7 @@ Execute(The buffer ale_keep_list_window_open option should be respected):
let b:ale_keep_list_window_open = 1
call ale#list#SetLists(bufnr('%'), g:loclist)
call ale#list#CloseWindowIfNeeded(bufnr(''))
call ale#list#SetLists(bufnr('%'), [])
call ale#list#CloseWindowIfNeeded(bufnr(''))
Assert ale#list#IsQuickfixOpen()
@ -205,12 +189,19 @@ Execute(The ale_open_list='on_save' option should work):
" The list shouldn't open yet, the event wasn't fired.
Assert !ale#list#IsQuickfixOpen()
" Turn this option off, to ensure that we update lists immediately when we
" save buffers.
let g:ale_set_lists_synchronously = 0
let b:ale_save_event_fired = 1
call ale#list#SetLists(bufnr('%'), g:loclist)
" Now the list should have opened.
Assert ale#list#IsQuickfixOpen()
call ale#list#SetLists(bufnr('%'), [])
" The window should close again when the loclist is empty.
Assert !ale#list#IsQuickfixOpen()
Execute(The window shouldn't open on save when ale_open_list=0):
let b:ale_open_list = 0
let b:ale_save_event_fired = 1

View File

@ -27,12 +27,3 @@ Execute(The SetLists function should work when run in a timer):
\ 'type': 'E',
\ 'pattern': '',
\}], getloclist(0)
Execute(The CloseWindowIfNeeded function should work when run in a timer):
let g:ale_open_list = 1
lopen
call ale#list#CloseWindowIfNeeded(bufnr(''))
sleep 1ms
Assert !ale#list#IsQuickfixOpen(), 'The window was not closed!'

View File

@ -0,0 +1,83 @@
Before:
runtime ale_linters/sh/shell.vim
runtime ale_linters/sh/shellcheck.vim
After:
call ale#linter#Reset()
unlet! b:is_bash
unlet! b:is_sh
unlet! b:is_kornshell
Given(A file with a Bash hashbang):
#!/bin/bash
Execute(/bin/bash should be detected appropriately):
AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr(''))
AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr(''))
AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Given(A file with /bin/sh):
#!/usr/bin/env sh -eu --foobar
Execute(/bin/sh should be detected appropriately):
AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr(''))
AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr(''))
AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Given(A file with bash as an argument to env):
#!/usr/bin/env bash
Execute(/usr/bin/env bash should be detected appropriately):
AssertEqual 'bash', ale#handlers#sh#GetShellType(bufnr(''))
AssertEqual 'bash', ale_linters#sh#shell#GetExecutable(bufnr(''))
AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Given(A file with a tcsh hash bang and arguments):
#!/usr/bin/env tcsh -eu --foobar
Execute(tcsh should be detected appropriately):
AssertEqual 'tcsh', ale#handlers#sh#GetShellType(bufnr(''))
AssertEqual 'tcsh', ale_linters#sh#shell#GetExecutable(bufnr(''))
AssertEqual 'tcsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Given(A file with a zsh hash bang and arguments):
#!/usr/bin/env zsh -eu --foobar
Execute(zsh should be detected appropriately):
AssertEqual 'zsh', ale#handlers#sh#GetShellType(bufnr(''))
AssertEqual 'zsh', ale_linters#sh#shell#GetExecutable(bufnr(''))
AssertEqual 'zsh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Given(A file with a csh hash bang and arguments):
#!/usr/bin/env csh -eu --foobar
Execute(zsh should be detected appropriately):
AssertEqual 'csh', ale#handlers#sh#GetShellType(bufnr(''))
AssertEqual 'csh', ale_linters#sh#shell#GetExecutable(bufnr(''))
AssertEqual 'csh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Given(A file with a sh hash bang and arguments):
#!/usr/bin/env sh -eu --foobar
Execute(sh should be detected appropriately):
AssertEqual 'sh', ale#handlers#sh#GetShellType(bufnr(''))
AssertEqual 'sh', ale_linters#sh#shell#GetExecutable(bufnr(''))
AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Given(A file without a hashbang):
Execute(The bash dialect should be used for shellcheck if b:is_bash is 1):
let b:is_bash = 1
AssertEqual 'bash', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Execute(The sh dialect should be used for shellcheck if b:is_sh is 1):
let b:is_sh = 1
AssertEqual 'sh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))
Execute(The ksh dialect should be used for shellcheck if b:is_kornshell is 1):
let b:is_kornshell = 1
AssertEqual 'ksh', ale_linters#sh#shellcheck#GetDialectArgument(bufnr(''))

View File

@ -0,0 +1,12 @@
Before:
Save &l:statusline
After:
Restore
Execute(ALE shouldn't do much of anything for ctrlp-funky buffers):
Assert !ale#ShouldDoNothing(bufnr('')), 'The preliminary check failed'
let &l:statusline = '%#CtrlPMode2# prt %*%#CtrlPMode1# line %* <mru>={%#CtrlPMode1# funky %*}=<fil> <-> %=%<%#CtrlPMode2# %{getcwd()} %*'
Assert ale#ShouldDoNothing(bufnr(''))