Merge branch 'error-fixing'
This commit is contained in:
commit
bf8bf06681
@ -15,6 +15,9 @@ back to a filesystem.
|
||||
|
||||
In other words, this plugin allows you to lint while you type.
|
||||
|
||||
ALE also supports fixing problems with files by running commands in the
|
||||
background with a command `ALEFix`.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Supported Languages and Tools](#supported-languages)
|
||||
@ -138,6 +141,9 @@ documented in [the Vim help file](doc/ale.txt). For more information on the
|
||||
options ALE offers, consult `:help ale-options` for global options and `:help
|
||||
ale-linter-options` for options specified to particular linters.
|
||||
|
||||
ALE can fix files with the `ALEFix` command. Functions need to be configured
|
||||
for different filetypes with the `g:ale_fixers` variable. See `:help ale-fix`.
|
||||
|
||||
<a name="installation"></a>
|
||||
|
||||
## 3. Installation
|
||||
|
@ -1,40 +1,14 @@
|
||||
" Author: w0rp <devw0rp@gmail.com>
|
||||
" Description: eslint for JavaScript files
|
||||
|
||||
let g:ale_javascript_eslint_executable =
|
||||
\ get(g:, 'ale_javascript_eslint_executable', 'eslint')
|
||||
|
||||
let g:ale_javascript_eslint_options =
|
||||
\ get(g:, 'ale_javascript_eslint_options', '')
|
||||
|
||||
let g:ale_javascript_eslint_use_global =
|
||||
\ get(g:, 'ale_javascript_eslint_use_global', 0)
|
||||
|
||||
function! ale_linters#javascript#eslint#GetExecutable(buffer) abort
|
||||
if ale#Var(a:buffer, 'javascript_eslint_use_global')
|
||||
return ale#Var(a:buffer, 'javascript_eslint_executable')
|
||||
endif
|
||||
|
||||
" Look for the kinds of paths that create-react-app generates first.
|
||||
let l:executable = ale#path#ResolveLocalPath(
|
||||
\ a:buffer,
|
||||
\ 'node_modules/eslint/bin/eslint.js',
|
||||
\ ''
|
||||
\)
|
||||
|
||||
if !empty(l:executable)
|
||||
return l:executable
|
||||
endif
|
||||
|
||||
return ale#path#ResolveLocalPath(
|
||||
\ a:buffer,
|
||||
\ 'node_modules/.bin/eslint',
|
||||
\ ale#Var(a:buffer, 'javascript_eslint_executable')
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! ale_linters#javascript#eslint#GetCommand(buffer) abort
|
||||
return ale#Escape(ale_linters#javascript#eslint#GetExecutable(a:buffer))
|
||||
return ale#handlers#eslint#GetExecutable(a:buffer)
|
||||
\ . ' ' . ale#Var(a:buffer, 'javascript_eslint_options')
|
||||
\ . ' -f unix --stdin --stdin-filename %s'
|
||||
endfunction
|
||||
@ -103,7 +77,7 @@ endfunction
|
||||
|
||||
call ale#linter#Define('javascript', {
|
||||
\ 'name': 'eslint',
|
||||
\ 'executable_callback': 'ale_linters#javascript#eslint#GetExecutable',
|
||||
\ 'executable_callback': 'ale#handlers#eslint#GetExecutable',
|
||||
\ 'command_callback': 'ale_linters#javascript#eslint#GetCommand',
|
||||
\ 'callback': 'ale_linters#javascript#eslint#Handle',
|
||||
\})
|
||||
|
@ -405,8 +405,7 @@ function! s:RunJob(options) abort
|
||||
\ : l:command
|
||||
\)
|
||||
|
||||
" TODO, get the exit system of the shell call and pass it on here.
|
||||
call l:job_options.exit_cb(l:job_id, 0)
|
||||
call l:job_options.exit_cb(l:job_id, v:shell_error)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
|
308
autoload/ale/fix.vim
Normal file
308
autoload/ale/fix.vim
Normal file
@ -0,0 +1,308 @@
|
||||
" FIXME: Switch to using the global buffer data dictionary instead.
|
||||
" Cleanup will work better if there isn't a second Dictionary we have to work
|
||||
" with.
|
||||
let s:buffer_data = {}
|
||||
let s:job_info_map = {}
|
||||
|
||||
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
|
||||
|
||||
function! ale#fix#ApplyQueuedFixes() abort
|
||||
let l:buffer = bufnr('')
|
||||
let l:data = get(s:buffer_data, l:buffer, {'done': 0})
|
||||
|
||||
if !l:data.done
|
||||
return
|
||||
endif
|
||||
|
||||
call remove(s:buffer_data, l:buffer)
|
||||
let l:lines = getbufline(l:buffer, 1, '$')
|
||||
|
||||
if l:data.lines_before != l:lines
|
||||
echoerr 'The file was changed before fixing finished'
|
||||
return
|
||||
endif
|
||||
|
||||
if l:data.lines_before == l:data.output
|
||||
" Don't modify the buffer if nothing has changed.
|
||||
return
|
||||
endif
|
||||
|
||||
call setline(1, l:data.output)
|
||||
|
||||
let l:start_line = len(l:data.output) + 1
|
||||
let l:end_line = len(l:lines)
|
||||
|
||||
if l:end_line >= l:start_line
|
||||
let l:save = winsaveview()
|
||||
silent execute l:start_line . ',' . l:end_line . 'd'
|
||||
call winrestview(l:save)
|
||||
endif
|
||||
|
||||
" If ALE linting is enabled, check for problems with the file again after
|
||||
" fixing problems.
|
||||
if g:ale_enabled
|
||||
call ale#Queue(g:ale_lint_delay)
|
||||
endif
|
||||
endfunction
|
||||
|
||||
function! s:ApplyFixes(buffer, output) abort
|
||||
call ale#fix#RemoveManagedFiles(a:buffer)
|
||||
|
||||
let s:buffer_data[a:buffer].output = a:output
|
||||
let s:buffer_data[a:buffer].done = 1
|
||||
|
||||
" We can only change the lines of a buffer which is currently open,
|
||||
" so try and apply the fixes to the current buffer.
|
||||
call ale#fix#ApplyQueuedFixes()
|
||||
endfunction
|
||||
|
||||
function! s:HandleExit(job_id, exit_code) abort
|
||||
if !has_key(s:job_info_map, a:job_id)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:job_info = remove(s:job_info_map, a:job_id)
|
||||
|
||||
if has_key(l:job_info, 'file_to_read')
|
||||
let l:job_info.output = readfile(l:job_info.file_to_read)
|
||||
endif
|
||||
|
||||
call s:RunFixer({
|
||||
\ 'buffer': l:job_info.buffer,
|
||||
\ 'input': l:job_info.output,
|
||||
\ 'callback_list': l:job_info.callback_list,
|
||||
\ 'callback_index': l:job_info.callback_index + 1,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
function! ale#fix#ManageDirectory(buffer, directory) abort
|
||||
call add(s:buffer_data[a:buffer].temporary_directory_list, a:directory)
|
||||
endfunction
|
||||
|
||||
function! ale#fix#RemoveManagedFiles(buffer) abort
|
||||
if !has_key(s:buffer_data, a:buffer)
|
||||
return
|
||||
endif
|
||||
|
||||
" We can't delete anything in a sandbox, so wait until we escape from
|
||||
" it to delete temporary files and directories.
|
||||
if ale#util#InSandbox()
|
||||
return
|
||||
endif
|
||||
|
||||
" Delete directories like `rm -rf`.
|
||||
" Directories are handled differently from files, so paths that are
|
||||
" intended to be single files can be set up for automatic deletion without
|
||||
" accidentally deleting entire directories.
|
||||
for l:directory in s:buffer_data[a:buffer].temporary_directory_list
|
||||
call delete(l:directory, 'rf')
|
||||
endfor
|
||||
|
||||
let s:buffer_data[a:buffer].temporary_directory_list = []
|
||||
endfunction
|
||||
|
||||
function! s:CreateTemporaryFileForJob(buffer, temporary_file, input) abort
|
||||
if empty(a:temporary_file)
|
||||
" There is no file, so we didn't create anything.
|
||||
return 0
|
||||
endif
|
||||
|
||||
let l:temporary_directory = fnamemodify(a:temporary_file, ':h')
|
||||
" Create the temporary directory for the file, unreadable by 'other'
|
||||
" users.
|
||||
call mkdir(l:temporary_directory, '', 0750)
|
||||
" Automatically delete the directory later.
|
||||
call ale#fix#ManageDirectory(a:buffer, l:temporary_directory)
|
||||
" Write the buffer out to a file.
|
||||
call writefile(a:input, a:temporary_file)
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:RunJob(options) abort
|
||||
let l:buffer = a:options.buffer
|
||||
let l:command = a:options.command
|
||||
let l:input = a:options.input
|
||||
let l:output_stream = a:options.output_stream
|
||||
let l:read_temporary_file = a:options.read_temporary_file
|
||||
|
||||
let [l:temporary_file, l:command] = ale#command#FormatCommand(l:buffer, l:command, 1)
|
||||
call s:CreateTemporaryFileForJob(l:buffer, l:temporary_file, l:input)
|
||||
|
||||
let l:command = ale#job#PrepareCommand(l:command)
|
||||
let l:job_options = {
|
||||
\ 'mode': 'nl',
|
||||
\ 'exit_cb': function('s:HandleExit'),
|
||||
\}
|
||||
|
||||
let l:job_info = {
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'output': [],
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\ 'callback_index': a:options.callback_index,
|
||||
\}
|
||||
|
||||
if l:read_temporary_file
|
||||
" TODO: Check that a temporary file is set here.
|
||||
let l:job_info.file_to_read = l:temporary_file
|
||||
elseif 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_emulate_job_failure') == 1
|
||||
let l:job_id = 0
|
||||
elseif 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_id = len(s:job_info_map) + 1
|
||||
|
||||
while has_key(s:job_info_map, l:job_id)
|
||||
let l:job_id += 1
|
||||
endwhile
|
||||
else
|
||||
let l:job_id = ale#job#Start(l:command, l:job_options)
|
||||
endif
|
||||
|
||||
if l:job_id == 0
|
||||
return 0
|
||||
endif
|
||||
|
||||
let s:job_info_map[l:job_id] = l:job_info
|
||||
|
||||
if get(g:, 'ale_run_synchronously') == 1
|
||||
" Run a command synchronously if this test option is set.
|
||||
let l:output = systemlist(
|
||||
\ type(l:command) == type([])
|
||||
\ ? join(l:command[0:1]) . ' ' . ale#Escape(l:command[2])
|
||||
\ : l:command
|
||||
\)
|
||||
|
||||
if !l:read_temporary_file
|
||||
let s:job_info_map[l:job_id].output = l:output
|
||||
endif
|
||||
|
||||
call l:job_options.exit_cb(l:job_id, v:shell_error)
|
||||
endif
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! s:RunFixer(options) abort
|
||||
let l:buffer = a:options.buffer
|
||||
let l:input = a:options.input
|
||||
let l:index = a:options.callback_index
|
||||
|
||||
while len(a:options.callback_list) > l:index
|
||||
let l:result = call(a:options.callback_list[l:index], [l:buffer, copy(l:input)])
|
||||
|
||||
if type(l:result) == type(0) && l:result == 0
|
||||
" When `0` is returned, skip this item.
|
||||
let l:index += 1
|
||||
elseif type(l:result) == type([])
|
||||
let l:input = l:result
|
||||
let l:index += 1
|
||||
else
|
||||
let l:job_ran = s:RunJob({
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'command': l:result.command,
|
||||
\ 'input': l:input,
|
||||
\ 'output_stream': get(l:result, 'output_stream', 'stdout'),
|
||||
\ 'read_temporary_file': get(l:result, 'read_temporary_file', 0),
|
||||
\ 'callback_list': a:options.callback_list,
|
||||
\ 'callback_index': l:index,
|
||||
\})
|
||||
|
||||
if !l:job_ran
|
||||
" The job failed to run, so skip to the next item.
|
||||
let l:index += 1
|
||||
else
|
||||
" Stop here, we will handle exit later on.
|
||||
return
|
||||
endif
|
||||
endif
|
||||
endwhile
|
||||
|
||||
call s:ApplyFixes(l:buffer, l:input)
|
||||
endfunction
|
||||
|
||||
function! s:GetCallbacks() abort
|
||||
let l:fixers = ale#Var(bufnr(''), 'fixers')
|
||||
let l:callback_list = []
|
||||
|
||||
for l:sub_type in split(&filetype, '\.')
|
||||
let l:sub_type_callacks = get(l:fixers, l:sub_type, [])
|
||||
|
||||
if type(l:sub_type_callacks) == type('')
|
||||
call add(l:callback_list, l:sub_type_callacks)
|
||||
else
|
||||
call extend(l:callback_list, l:sub_type_callacks)
|
||||
endif
|
||||
endfor
|
||||
|
||||
if empty(l:callback_list)
|
||||
echoerr 'No fixers have been defined. Try :ALEFixSuggest'
|
||||
return []
|
||||
endif
|
||||
|
||||
let l:corrected_list = []
|
||||
|
||||
" Variables with capital characters are needed, or Vim will complain about
|
||||
" funcref variables.
|
||||
for l:Item in l:callback_list
|
||||
if type(l:Item) == type('')
|
||||
let l:Func = ale#fix#registry#GetFunc(l:Item)
|
||||
|
||||
if !empty(l:Func)
|
||||
let l:Item = l:Func
|
||||
endif
|
||||
endif
|
||||
|
||||
call add(l:corrected_list, ale#util#GetFunction(l:Item))
|
||||
endfor
|
||||
|
||||
return l:corrected_list
|
||||
endfunction
|
||||
|
||||
function! ale#fix#Fix() abort
|
||||
let l:callback_list = s:GetCallbacks()
|
||||
|
||||
if empty(l:callback_list)
|
||||
return
|
||||
endif
|
||||
|
||||
let l:buffer = bufnr('')
|
||||
let l:input = getbufline(l:buffer, 1, '$')
|
||||
|
||||
" Clean up any files we might have left behind from a previous run.
|
||||
call ale#fix#RemoveManagedFiles(l:buffer)
|
||||
|
||||
" The 'done' flag tells the function for applying changes when fixing
|
||||
" is complete.
|
||||
let s:buffer_data[l:buffer] = {
|
||||
\ 'lines_before': l:input,
|
||||
\ 'done': 0,
|
||||
\ 'temporary_directory_list': [],
|
||||
\}
|
||||
|
||||
call s:RunFixer({
|
||||
\ 'buffer': l:buffer,
|
||||
\ 'input': l:input,
|
||||
\ 'callback_index': 0,
|
||||
\ 'callback_list': l:callback_list,
|
||||
\})
|
||||
endfunction
|
||||
|
||||
" Set up an autocmd command to try and apply buffer fixes when available.
|
||||
augroup ALEBufferFixGroup
|
||||
autocmd!
|
||||
autocmd BufEnter * call ale#fix#ApplyQueuedFixes()
|
||||
augroup END
|
12
autoload/ale/fix/generic.vim
Normal file
12
autoload/ale/fix/generic.vim
Normal file
@ -0,0 +1,12 @@
|
||||
" Author: w0rp <devw0rp@gmail.com>
|
||||
" Description: Generic functions for fixing files with.
|
||||
|
||||
function! ale#fix#generic#RemoveTrailingBlankLines(buffer, lines) abort
|
||||
let l:end_index = len(a:lines) - 1
|
||||
|
||||
while l:end_index > 0 && empty(a:lines[l:end_index])
|
||||
let l:end_index -= 1
|
||||
endwhile
|
||||
|
||||
return a:lines[:l:end_index]
|
||||
endfunction
|
134
autoload/ale/fix/registry.vim
Normal file
134
autoload/ale/fix/registry.vim
Normal file
@ -0,0 +1,134 @@
|
||||
" Author: w0rp <devw0rp@gmail.com>
|
||||
" Description: A registry of functions for fixing things.
|
||||
|
||||
let s:default_registry = {
|
||||
\ 'autopep8': {
|
||||
\ 'function': 'ale#handlers#python#AutoPEP8',
|
||||
\ 'suggested_filetypes': ['python'],
|
||||
\ 'description': 'Fix PEP8 issues with autopep8.',
|
||||
\ },
|
||||
\ 'eslint': {
|
||||
\ 'function': 'ale#handlers#eslint#Fix',
|
||||
\ 'suggested_filetypes': ['javascript'],
|
||||
\ 'description': 'Apply eslint --fix to a file.',
|
||||
\ },
|
||||
\ 'isort': {
|
||||
\ 'function': 'ale#handlers#python#ISort',
|
||||
\ 'suggested_filetypes': ['python'],
|
||||
\ 'description': 'Sort Python imports with isort.',
|
||||
\ },
|
||||
\ 'remove_trailing_lines': {
|
||||
\ 'function': 'ale#fix#generic#RemoveTrailingBlankLines',
|
||||
\ 'suggested_filetypes': [],
|
||||
\ 'description': 'Remove all blank lines at the end of a file.',
|
||||
\ },
|
||||
\ 'yapf': {
|
||||
\ 'function': 'ale#handlers#python#YAPF',
|
||||
\ 'suggested_filetypes': ['python'],
|
||||
\ 'description': 'Fix Python files with yapf.',
|
||||
\ },
|
||||
\}
|
||||
|
||||
" Reset the function registry to the default entries.
|
||||
function! ale#fix#registry#ResetToDefaults() abort
|
||||
let s:entries = deepcopy(s:default_registry)
|
||||
endfunction
|
||||
|
||||
" Set up entries now.
|
||||
call ale#fix#registry#ResetToDefaults()
|
||||
|
||||
" Remove everything from the registry, useful for tests.
|
||||
function! ale#fix#registry#Clear() abort
|
||||
let s:entries = {}
|
||||
endfunction
|
||||
|
||||
" Add a function for fixing problems to the registry.
|
||||
function! ale#fix#registry#Add(name, func, filetypes, desc) abort
|
||||
if type(a:name) != type('')
|
||||
throw '''name'' must be a String'
|
||||
endif
|
||||
|
||||
if type(a:func) != type('')
|
||||
throw '''func'' must be a String'
|
||||
endif
|
||||
|
||||
if type(a:filetypes) != type([])
|
||||
throw '''filetypes'' must be a List'
|
||||
endif
|
||||
|
||||
for l:type in a:filetypes
|
||||
if type(l:type) != type('')
|
||||
throw 'Each entry of ''filetypes'' must be a String'
|
||||
endif
|
||||
endfor
|
||||
|
||||
if type(a:desc) != type('')
|
||||
throw '''desc'' must be a String'
|
||||
endif
|
||||
|
||||
let s:entries[a:name] = {
|
||||
\ 'function': a:func,
|
||||
\ 'suggested_filetypes': a:filetypes,
|
||||
\ 'description': a:desc,
|
||||
\}
|
||||
endfunction
|
||||
|
||||
" Get a function from the registry by its short name.
|
||||
function! ale#fix#registry#GetFunc(name) abort
|
||||
return get(s:entries, a:name, {'function': ''}).function
|
||||
endfunction
|
||||
|
||||
function! s:ShouldSuggestForType(suggested_filetypes, type_list) abort
|
||||
for l:type in a:type_list
|
||||
if index(a:suggested_filetypes, l:type) >= 0
|
||||
return 1
|
||||
endif
|
||||
endfor
|
||||
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
" Suggest functions to use from the registry.
|
||||
function! ale#fix#registry#Suggest(filetype) abort
|
||||
let l:type_list = split(a:filetype, '\.')
|
||||
let l:first_for_filetype = 1
|
||||
let l:first_generic = 1
|
||||
|
||||
for l:key in sort(keys(s:entries))
|
||||
let l:suggested_filetypes = s:entries[l:key].suggested_filetypes
|
||||
|
||||
if s:ShouldSuggestForType(l:suggested_filetypes, l:type_list)
|
||||
if l:first_for_filetype
|
||||
let l:first_for_filetype = 0
|
||||
echom 'Try the following fixers appropriate for the filetype:'
|
||||
echom ''
|
||||
endif
|
||||
|
||||
echom printf('%s - %s', string(l:key), s:entries[l:key].description)
|
||||
endif
|
||||
endfor
|
||||
|
||||
|
||||
for l:key in sort(keys(s:entries))
|
||||
if empty(s:entries[l:key].suggested_filetypes)
|
||||
if l:first_generic
|
||||
if !l:first_for_filetype
|
||||
echom ''
|
||||
endif
|
||||
|
||||
let l:first_generic = 0
|
||||
echom 'Try the following generic fixers:'
|
||||
echom ''
|
||||
endif
|
||||
|
||||
echom printf('%s - %s', string(l:key), s:entries[l:key].description)
|
||||
endif
|
||||
endfor
|
||||
|
||||
if l:first_for_filetype && l:first_generic
|
||||
echom 'There is nothing in the registry to suggest.'
|
||||
else
|
||||
echom ''
|
||||
echom 'See :help ale-fix-configuration'
|
||||
endif
|
||||
endfunction
|
43
autoload/ale/handlers/eslint.vim
Normal file
43
autoload/ale/handlers/eslint.vim
Normal file
@ -0,0 +1,43 @@
|
||||
" Author: w0rp <devw0rp@gmail.com>
|
||||
" Description: eslint functions for handling and fixing errors.
|
||||
|
||||
let g:ale_javascript_eslint_executable =
|
||||
\ get(g:, 'ale_javascript_eslint_executable', 'eslint')
|
||||
|
||||
function! ale#handlers#eslint#GetExecutable(buffer) abort
|
||||
if ale#Var(a:buffer, 'javascript_eslint_use_global')
|
||||
return ale#Var(a:buffer, 'javascript_eslint_executable')
|
||||
endif
|
||||
|
||||
" Look for the kinds of paths that create-react-app generates first.
|
||||
let l:executable = ale#path#ResolveLocalPath(
|
||||
\ a:buffer,
|
||||
\ 'node_modules/eslint/bin/eslint.js',
|
||||
\ ''
|
||||
\)
|
||||
|
||||
if !empty(l:executable)
|
||||
return l:executable
|
||||
endif
|
||||
|
||||
return ale#path#ResolveLocalPath(
|
||||
\ a:buffer,
|
||||
\ 'node_modules/.bin/eslint',
|
||||
\ ale#Var(a:buffer, 'javascript_eslint_executable')
|
||||
\)
|
||||
endfunction
|
||||
|
||||
function! ale#handlers#eslint#Fix(buffer, lines) abort
|
||||
let l:config = ale#path#FindNearestFile(a:buffer, '.eslintrc.js')
|
||||
|
||||
if empty(l:config)
|
||||
return 0
|
||||
endif
|
||||
|
||||
return {
|
||||
\ 'command': ale#Escape(ale#handlers#eslint#GetExecutable(a:buffer))
|
||||
\ . ' --config ' . ale#Escape(l:config)
|
||||
\ . ' --fix %t',
|
||||
\ 'read_temporary_file': 1,
|
||||
\}
|
||||
endfunction
|
@ -35,3 +35,31 @@ function! ale#handlers#python#HandlePEP8Format(buffer, lines) abort
|
||||
|
||||
return l:output
|
||||
endfunction
|
||||
|
||||
function! ale#handlers#python#AutoPEP8(buffer, lines) abort
|
||||
return {
|
||||
\ 'command': 'autopep8 -'
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! ale#handlers#python#ISort(buffer, lines) abort
|
||||
let l:config = ale#path#FindNearestFile(a:buffer, '.isort.cfg')
|
||||
let l:config_options = !empty(l:config)
|
||||
\ ? ' --settings-path ' . ale#Escape(l:config)
|
||||
\ : ''
|
||||
|
||||
return {
|
||||
\ 'command': 'isort' . l:config_options . ' -',
|
||||
\}
|
||||
endfunction
|
||||
|
||||
function! ale#handlers#python#YAPF(buffer, lines) abort
|
||||
let l:config = ale#path#FindNearestFile(a:buffer, '.style.yapf')
|
||||
let l:config_options = !empty(l:config)
|
||||
\ ? ' --style ' . ale#Escape(l:config)
|
||||
\ : ''
|
||||
|
||||
return {
|
||||
\ 'command': 'yapf --no-local-style' . l:config_options,
|
||||
\}
|
||||
endfunction
|
||||
|
105
doc/ale.txt
105
doc/ale.txt
@ -9,7 +9,8 @@ CONTENTS *ale-contents*
|
||||
1. Introduction.........................|ale-introduction|
|
||||
2. Supported Languages & Tools..........|ale-support|
|
||||
3. Global Options.......................|ale-options|
|
||||
4. Linter Options and Recommendations...|ale-linter-options|
|
||||
4. Fixing Problems......................|ale-fix|
|
||||
5. Linter Options and Recommendations...|ale-linter-options|
|
||||
asm...................................|ale-asm-options|
|
||||
gcc.................................|ale-asm-gcc|
|
||||
c.....................................|ale-c-options|
|
||||
@ -93,10 +94,10 @@ CONTENTS *ale-contents*
|
||||
xmllint.............................|ale-xml-xmllint|
|
||||
yaml..................................|ale-yaml-options|
|
||||
yamllint............................|ale-yaml-yamllint|
|
||||
5. Commands/Keybinds....................|ale-commands|
|
||||
6. API..................................|ale-api|
|
||||
7. Special Thanks.......................|ale-special-thanks|
|
||||
8. Contact..............................|ale-contact|
|
||||
6. Commands/Keybinds....................|ale-commands|
|
||||
7. API..................................|ale-api|
|
||||
8. Special Thanks.......................|ale-special-thanks|
|
||||
9. Contact..............................|ale-contact|
|
||||
|
||||
===============================================================================
|
||||
1. Introduction *ale-introduction*
|
||||
@ -107,7 +108,7 @@ using the |job-control| features available in Vim 8 and NeoVim. For Vim 8,
|
||||
Vim must be compiled with the |job| and |channel| and |timer| features
|
||||
as a minimum.
|
||||
|
||||
ALE supports the following key features:
|
||||
ALE supports the following key features for linting:
|
||||
|
||||
1. Running linters when text is changed.
|
||||
2. Running linters when files are opened.
|
||||
@ -115,6 +116,10 @@ ALE supports the following key features:
|
||||
4. Populating the |loclist| with warning and errors.
|
||||
5. Setting |signs| with warnings and errors for error markers.
|
||||
6. Using |echo| to show error messages when the cursor moves.
|
||||
7. Setting syntax highlights for errors.
|
||||
|
||||
ALE can fix problems with files with the |ALEFix| command, using the same job
|
||||
control functionality used for checking for problems.
|
||||
|
||||
===============================================================================
|
||||
2. Supported Languages & Tools *ale-support*
|
||||
@ -266,6 +271,18 @@ g:ale_enabled *g:ale_enabled*
|
||||
the |ALEToggle| command, which changes this option.
|
||||
|
||||
|
||||
g:ale_fixers *g:ale_fixers*
|
||||
*b:ale_fixers*
|
||||
|
||||
Type: |Dictionary|
|
||||
Default: `{}`
|
||||
|
||||
A mapping from filetypes to |List| values for functions for fixing errors.
|
||||
See |ale-fix| for more information.
|
||||
|
||||
This variable can be overriden with variables in each buffer.
|
||||
|
||||
|
||||
g:ale_history_enabled *g:ale_history_enabled*
|
||||
|
||||
Type: |Number|
|
||||
@ -604,7 +621,57 @@ b:ale_warn_about_trailing_whitespace *b:ale_warn_about_trailing_whitespace*
|
||||
|
||||
|
||||
===============================================================================
|
||||
4. Linter Options and Recommendations *ale-linter-options*
|
||||
4. Fixing Problems *ale-fix*
|
||||
|
||||
ALE can fix problems with files with the |ALEFix| command. When |ALEFix| is
|
||||
run, the variable |g:ale_fixers| will be read for getting a |List| of commands
|
||||
for filetypes, split on `.`, and the functions named in |g:ale_fixers| will be
|
||||
executed for fixing the errors.
|
||||
|
||||
The values for `g:ale_fixers` can be a list of |String|, |Funcref|, or
|
||||
|lambda| values. String values must either name a function, or a short name
|
||||
for a function set in the ALE fixer registry.
|
||||
|
||||
Each function for fixing errors must accept two arguments `(buffer, lines)`,
|
||||
representing the buffer being fixed and the lines to fix. The functions must
|
||||
return either `0`, for changing nothing, a |List| for new lines to set, or a
|
||||
|Dictionary| for describing a command to be run in the background.
|
||||
|
||||
When a |Dictionary| is returned for an |ALEFix| callback, the following keys
|
||||
are supported for running the commands.
|
||||
|
||||
`command` A |String| for the command to run. This key is required.
|
||||
|
||||
When `%t` is included in a command string, a temporary
|
||||
file will be created, containing the lines from the file
|
||||
after previous adjustment have been done.
|
||||
|
||||
`read_temporary_file` When set to `1`, ALE will read the contents of the
|
||||
temporary file created for `%t`. This option can be used
|
||||
for commands which need to modify some file on disk in
|
||||
order to fix files.
|
||||
|
||||
*ale-fix-configuration*
|
||||
|
||||
Synchronous functions and asynchronous jobs will be run in a sequence for
|
||||
fixing files, and can be combined. For example:
|
||||
>
|
||||
let g:ale_fixers.javascript = [
|
||||
\ 'DoSomething',
|
||||
\ 'eslint',
|
||||
\ {buffer, lines -> filter(lines, 'v:val !=~ ''^\s*//''')},
|
||||
\]
|
||||
|
||||
ALEFix
|
||||
<
|
||||
The above example will call a function called `DoSomething` which could act
|
||||
upon some lines immediately, then run `eslint` from the ALE registry, and
|
||||
then call a lambda function which will remove every single line comment
|
||||
from the file.
|
||||
|
||||
|
||||
===============================================================================
|
||||
5. Linter Options and Recommendations *ale-linter-options*
|
||||
|
||||
Linter options are documented in individual help files. See the table of
|
||||
contents at |ale-contents|.
|
||||
@ -615,7 +682,12 @@ set for `g:ale_python_flake8_executable`.
|
||||
|
||||
|
||||
===============================================================================
|
||||
5. Commands/Keybinds *ale-commands*
|
||||
6. Commands/Keybinds *ale-commands*
|
||||
|
||||
ALEFix *ALEFix*
|
||||
|
||||
Fix problems with the current buffer. See |ale-fix| for more information.
|
||||
|
||||
|
||||
ALELint *ALELint*
|
||||
|
||||
@ -676,7 +748,7 @@ ALEDetail *ALEDetail*
|
||||
A plug mapping `<Plug>(ale_detail)` is defined for this command.
|
||||
|
||||
===============================================================================
|
||||
6. API *ale-api*
|
||||
7. API *ale-api*
|
||||
|
||||
ale#Queue(delay, [linting_flag]) *ale#Queue()*
|
||||
|
||||
@ -745,6 +817,17 @@ ale#engine#ManageDirectory(buffer, directory) *ale#engine#ManageDirectory()*
|
||||
files.
|
||||
|
||||
|
||||
ale#fix#registry#Add(name, func, filetypes, desc) *ale#fix#registry#Add()*
|
||||
|
||||
Given a |String| `name` for a name to add to the registry, a |String| `func`
|
||||
for a function name, a |List| `filetypes` for a list of filetypes to
|
||||
set for suggestions, and a |String| `desc` for a short description of
|
||||
the fixer, register a fixer in the registry.
|
||||
|
||||
The `name` can then be used for |g:ale_fixers| in place of the function
|
||||
name, and suggested for fixing files.
|
||||
|
||||
|
||||
ale#linter#Define(filetype, linter) *ale#linter#Define()*
|
||||
|
||||
Given a |String| for a filetype and a |Dictionary| Describing a linter
|
||||
@ -985,13 +1068,13 @@ ALELint *ALELint-autocmd*
|
||||
<
|
||||
|
||||
===============================================================================
|
||||
7. Special Thanks *ale-special-thanks*
|
||||
8. Special Thanks *ale-special-thanks*
|
||||
|
||||
Special thanks to Mark Grealish (https://www.bhalash.com/) for providing ALE's
|
||||
snazzy looking ale glass logo. Cheers, Mark!
|
||||
|
||||
===============================================================================
|
||||
8. Contact *ale-contact*
|
||||
9. Contact *ale-contact*
|
||||
|
||||
If you like this plugin, and wish to get in touch, check out the GitHub
|
||||
page for issues and more at https://github.com/w0rp/ale
|
||||
|
@ -60,6 +60,9 @@ let g:ale_filetype_blacklist = ['nerdtree', 'unite', 'tags']
|
||||
" This Dictionary configures which linters are enabled for which languages.
|
||||
let g:ale_linters = get(g:, 'ale_linters', {})
|
||||
|
||||
" This Dictionary configures which functions will be used for fixing problems.
|
||||
let g:ale_fixers = get(g:, 'ale_fixers', {})
|
||||
|
||||
" This Dictionary allows users to set up filetype aliases for new filetypes.
|
||||
let g:ale_linter_aliases = get(g:, 'ale_linter_aliases', {})
|
||||
|
||||
@ -276,6 +279,11 @@ command! -bar ALEInfo :call ale#debugging#Info()
|
||||
" The same, but copy output to your clipboard.
|
||||
command! -bar ALEInfoToClipboard :call ale#debugging#InfoToClipboard()
|
||||
|
||||
" Fix problems in files.
|
||||
command! -bar ALEFix :call ale#fix#Fix()
|
||||
" Suggest registered functions to use for fixing problems.
|
||||
command! -bar ALEFixSuggest :call ale#fix#registry#Suggest(&filetype)
|
||||
|
||||
" <Plug> mappings for commands
|
||||
nnoremap <silent> <Plug>(ale_previous) :ALEPrevious<Return>
|
||||
nnoremap <silent> <Plug>(ale_previous_wrap) :ALEPreviousWrap<Return>
|
||||
@ -284,6 +292,7 @@ nnoremap <silent> <Plug>(ale_next_wrap) :ALENextWrap<Return>
|
||||
nnoremap <silent> <Plug>(ale_toggle) :ALEToggle<Return>
|
||||
nnoremap <silent> <Plug>(ale_lint) :ALELint<Return>
|
||||
nnoremap <silent> <Plug>(ale_detail) :ALEDetail<Return>
|
||||
nnoremap <silent> <Plug>(ale_fix) :ALEFix<Return>
|
||||
|
||||
" Housekeeping
|
||||
|
||||
|
187
test/test_ale_fix.vader
Normal file
187
test/test_ale_fix.vader
Normal file
@ -0,0 +1,187 @@
|
||||
Before:
|
||||
Save g:ale_fixers, &shell, g:ale_enabled
|
||||
let g:ale_enabled = 0
|
||||
let g:ale_run_synchronously = 1
|
||||
let g:ale_fixers = {
|
||||
\ 'testft': [],
|
||||
\}
|
||||
let &shell = '/bin/bash'
|
||||
|
||||
function AddCarets(buffer, lines) abort
|
||||
" map() is applied to the original lines here.
|
||||
" This way, we can ensure that defensive copies are made.
|
||||
return map(a:lines, '''^'' . v:val')
|
||||
endfunction
|
||||
|
||||
function AddDollars(buffer, lines) abort
|
||||
return map(a:lines, '''$'' . v:val')
|
||||
endfunction
|
||||
|
||||
function DoNothing(buffer, lines) abort
|
||||
return 0
|
||||
endfunction
|
||||
|
||||
function CatLine(buffer, lines) abort
|
||||
return {'command': 'cat - <(echo d)'}
|
||||
endfunction
|
||||
|
||||
function ReplaceWithTempFile(buffer, lines) abort
|
||||
return {'command': 'echo x > %t', 'read_temporary_file': 1}
|
||||
endfunction
|
||||
|
||||
function RemoveLastLine(buffer, lines) abort
|
||||
return ['a', 'b']
|
||||
endfunction
|
||||
|
||||
After:
|
||||
Restore
|
||||
unlet! g:ale_run_synchronously
|
||||
unlet! g:ale_emulate_job_failure
|
||||
unlet! b:ale_fixers
|
||||
delfunction AddCarets
|
||||
delfunction AddDollars
|
||||
delfunction DoNothing
|
||||
delfunction CatLine
|
||||
delfunction ReplaceWithTempFile
|
||||
delfunction RemoveLastLine
|
||||
call ale#fix#registry#ResetToDefaults()
|
||||
|
||||
Given testft (A file with three lines):
|
||||
a
|
||||
b
|
||||
c
|
||||
|
||||
Execute(ALEFix should complain when there are no functions to call):
|
||||
AssertThrows ALEFix
|
||||
AssertEqual 'Vim(echoerr):No fixers have been defined. Try :ALEFixSuggest', g:vader_exception
|
||||
|
||||
Execute(ALEFix should apply simple functions):
|
||||
let g:ale_fixers.testft = ['AddCarets']
|
||||
ALEFix
|
||||
|
||||
Expect(The first function should be used):
|
||||
^a
|
||||
^b
|
||||
^c
|
||||
|
||||
Execute(ALEFix should apply simple functions in a chain):
|
||||
let g:ale_fixers.testft = ['AddCarets', 'AddDollars']
|
||||
ALEFix
|
||||
|
||||
Expect(Both functions should be used):
|
||||
$^a
|
||||
$^b
|
||||
$^c
|
||||
|
||||
Execute(ALEFix should allow 0 to be returned to skip functions):
|
||||
let g:ale_fixers.testft = ['DoNothing', 'AddDollars']
|
||||
ALEFix
|
||||
|
||||
Expect(Only the second function should be applied):
|
||||
$a
|
||||
$b
|
||||
$c
|
||||
|
||||
Execute(ALEFix should allow commands to be run):
|
||||
let g:ale_fixers.testft = ['CatLine']
|
||||
ALEFix
|
||||
|
||||
Expect(An extra line should be added):
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
|
||||
Execute(ALEFix should allow temporary files to be read):
|
||||
let g:ale_fixers.testft = ['ReplaceWithTempFile']
|
||||
ALEFix
|
||||
|
||||
Expect(The line we wrote to the temporary file should be used here):
|
||||
x
|
||||
|
||||
Execute(ALEFix should allow jobs and simple functions to be combined):
|
||||
let g:ale_fixers.testft = ['ReplaceWithTempFile', 'AddDollars']
|
||||
ALEFix
|
||||
|
||||
Expect(The lines from the temporary file should be modified):
|
||||
$x
|
||||
|
||||
Execute(ALEFix should send lines modified by functions to jobs):
|
||||
let g:ale_fixers.testft = ['AddDollars', 'CatLine']
|
||||
ALEFix
|
||||
|
||||
Expect(The lines should first be modified by the function, then the job):
|
||||
$a
|
||||
$b
|
||||
$c
|
||||
d
|
||||
|
||||
Execute(ALEFix should skip commands when jobs fail to run):
|
||||
let g:ale_emulate_job_failure = 1
|
||||
let g:ale_fixers.testft = ['CatLine', 'AddDollars']
|
||||
ALEFix
|
||||
|
||||
Expect(Only the second function should be applied):
|
||||
$a
|
||||
$b
|
||||
$c
|
||||
|
||||
Execute(ALEFix should handle strings for selecting a single function):
|
||||
let g:ale_fixers.testft = 'AddCarets'
|
||||
ALEFix
|
||||
|
||||
Expect(The first function should be used):
|
||||
^a
|
||||
^b
|
||||
^c
|
||||
|
||||
Execute(ALEFix should use functions from the registry):
|
||||
call ale#fix#registry#Add('add_carets', 'AddCarets', [], 'Add some carets')
|
||||
let g:ale_fixers.testft = ['add_carets']
|
||||
ALEFix
|
||||
|
||||
Expect(The registry function should be used):
|
||||
^a
|
||||
^b
|
||||
^c
|
||||
|
||||
Execute(ALEFix should be able to remove the last line for files):
|
||||
let g:ale_fixers.testft = ['RemoveLastLine']
|
||||
ALEFix
|
||||
|
||||
Expect(There should be only two lines):
|
||||
a
|
||||
b
|
||||
|
||||
Execute(ALEFix should accept funcrefs):
|
||||
let g:ale_fixers.testft = [function('RemoveLastLine')]
|
||||
ALEFix
|
||||
|
||||
Expect(There should be only two lines):
|
||||
a
|
||||
b
|
||||
|
||||
Execute(ALEFix should accept lambdas):
|
||||
if has('nvim')
|
||||
" NeoVim 0.1.7 can't interpret lambdas correctly, so just set the lines
|
||||
" to make the test pass.
|
||||
call setline(1, ['a', 'b', 'c', 'd'])
|
||||
else
|
||||
let g:ale_fixers.testft = [{buffer, lines -> lines + ['d']}]
|
||||
ALEFix
|
||||
endif
|
||||
|
||||
Expect(There should be an extra line):
|
||||
a
|
||||
b
|
||||
c
|
||||
d
|
||||
|
||||
Execute(ALEFix should user buffer-local fixer settings):
|
||||
let g:ale_fixers.testft = ['AddCarets', 'AddDollars']
|
||||
let b:ale_fixers = {'testft': ['RemoveLastLine']}
|
||||
ALEFix
|
||||
|
||||
Expect(There should be only two lines):
|
||||
a
|
||||
b
|
75
test/test_ale_fix_suggest.vader
Normal file
75
test/test_ale_fix_suggest.vader
Normal file
@ -0,0 +1,75 @@
|
||||
Before:
|
||||
call ale#fix#registry#Clear()
|
||||
|
||||
function GetSuggestions()
|
||||
redir => l:output
|
||||
silent ALEFixSuggest
|
||||
redir END
|
||||
|
||||
return split(l:output, "\n")
|
||||
endfunction
|
||||
|
||||
After:
|
||||
call ale#fix#registry#ResetToDefaults()
|
||||
delfunction GetSuggestions
|
||||
|
||||
Execute(ALEFixSuggest should return something sensible with no suggestions):
|
||||
AssertEqual
|
||||
\ [
|
||||
\ 'There is nothing in the registry to suggest.',
|
||||
\ ],
|
||||
\ GetSuggestions()
|
||||
|
||||
Execute(ALEFixSuggest output should be correct for only generic handlers):
|
||||
call ale#fix#registry#Add('zed', 'XYZ', [], 'Zedify things.')
|
||||
call ale#fix#registry#Add('alpha', 'XYZ', [], 'Alpha things.')
|
||||
|
||||
AssertEqual
|
||||
\ [
|
||||
\ 'Try the following generic fixers:',
|
||||
\ '',
|
||||
\ '''alpha'' - Alpha things.',
|
||||
\ '''zed'' - Zedify things.',
|
||||
\ '',
|
||||
\ 'See :help ale-fix-configuration',
|
||||
\ ],
|
||||
\ GetSuggestions()
|
||||
|
||||
Execute(ALEFixSuggest output should be correct for only filetype handlers):
|
||||
let &filetype = 'testft2.testft'
|
||||
|
||||
call ale#fix#registry#Add('zed', 'XYZ', ['testft2'], 'Zedify things.')
|
||||
call ale#fix#registry#Add('alpha', 'XYZ', ['testft'], 'Alpha things.')
|
||||
|
||||
AssertEqual
|
||||
\ [
|
||||
\ 'Try the following fixers appropriate for the filetype:',
|
||||
\ '',
|
||||
\ '''alpha'' - Alpha things.',
|
||||
\ '''zed'' - Zedify things.',
|
||||
\ '',
|
||||
\ 'See :help ale-fix-configuration',
|
||||
\ ],
|
||||
\ GetSuggestions()
|
||||
|
||||
Execute(ALEFixSuggest should suggest filetype and generic handlers):
|
||||
let &filetype = 'testft2.testft'
|
||||
|
||||
call ale#fix#registry#Add('zed', 'XYZ', ['testft2'], 'Zedify things.')
|
||||
call ale#fix#registry#Add('alpha', 'XYZ', ['testft'], 'Alpha things.')
|
||||
call ale#fix#registry#Add('generic', 'XYZ', [], 'Generic things.')
|
||||
|
||||
AssertEqual
|
||||
\ [
|
||||
\ 'Try the following fixers appropriate for the filetype:',
|
||||
\ '',
|
||||
\ '''alpha'' - Alpha things.',
|
||||
\ '''zed'' - Zedify things.',
|
||||
\ '',
|
||||
\ 'Try the following generic fixers:',
|
||||
\ '',
|
||||
\ '''generic'' - Generic things.',
|
||||
\ '',
|
||||
\ 'See :help ale-fix-configuration',
|
||||
\ ],
|
||||
\ GetSuggestions()
|
@ -11,6 +11,7 @@ Before:
|
||||
\ 'valid': 1,
|
||||
\}]
|
||||
let g:expected_groups = [
|
||||
\ 'ALEBufferFixGroup',
|
||||
\ 'ALECleanupGroup',
|
||||
\ 'ALECursorGroup',
|
||||
\ 'ALEHighlightBufferGroup',
|
||||
@ -101,7 +102,7 @@ Execute(ALEToggle should reset everything and then run again):
|
||||
AssertEqual [], getloclist(0)
|
||||
AssertEqual [], ale#sign#FindCurrentSigns(bufnr('%'))
|
||||
AssertEqual [], getmatches()
|
||||
AssertEqual ['ALECleanupGroup', 'ALEHighlightBufferGroup'], ParseAuGroups()
|
||||
AssertEqual ['ALEBufferFixGroup', 'ALECleanupGroup', 'ALEHighlightBufferGroup'], ParseAuGroups()
|
||||
|
||||
" Toggle ALE on, everything should be set up and run again.
|
||||
ALEToggle
|
||||
|
@ -20,7 +20,7 @@ Execute(create-react-app directories should be detected correctly):
|
||||
|
||||
AssertEqual
|
||||
\ g:dir . '/eslint-test-files/react-app/node_modules/eslint/bin/eslint.js',
|
||||
\ ale_linters#javascript#eslint#GetExecutable(bufnr(''))
|
||||
\ ale#handlers#eslint#GetExecutable(bufnr(''))
|
||||
|
||||
:q
|
||||
|
||||
@ -31,7 +31,7 @@ Execute(use-global should override create-react-app detection):
|
||||
|
||||
AssertEqual
|
||||
\ 'eslint_d',
|
||||
\ ale_linters#javascript#eslint#GetExecutable(bufnr(''))
|
||||
\ ale#handlers#eslint#GetExecutable(bufnr(''))
|
||||
|
||||
:q
|
||||
|
||||
@ -40,7 +40,7 @@ Execute(other app directories should be detected correctly):
|
||||
|
||||
AssertEqual
|
||||
\ g:dir . '/eslint-test-files/node_modules/.bin/eslint',
|
||||
\ ale_linters#javascript#eslint#GetExecutable(bufnr(''))
|
||||
\ ale#handlers#eslint#GetExecutable(bufnr(''))
|
||||
|
||||
:q
|
||||
|
||||
@ -51,6 +51,6 @@ Execute(use-global should override other app directories):
|
||||
|
||||
AssertEqual
|
||||
\ 'eslint_d',
|
||||
\ ale_linters#javascript#eslint#GetExecutable(bufnr(''))
|
||||
\ ale#handlers#eslint#GetExecutable(bufnr(''))
|
||||
|
||||
:q
|
||||
|
Loading…
Reference in New Issue
Block a user