#591 Support fixing files on save

This commit is contained in:
w0rp 2017-05-30 21:32:51 +01:00
parent bc317a7be5
commit 6ec965c8e4
6 changed files with 234 additions and 35 deletions

14
autoload/ale/events.vim Normal file
View File

@ -0,0 +1,14 @@
" Author: w0rp <devw0rp@gmail.com>
function! ale#events#SaveEvent() abort
let l:should_lint = g:ale_enabled && g:ale_lint_on_save
if g:ale_fix_on_save
let l:will_fix = ale#fix#Fix('save_file')
let l:should_lint = l:should_lint && !l:will_fix
endif
if l:should_lint
call ale#Queue(0, 'lint_file')
endif
endfunction

View File

@ -26,6 +26,8 @@ function! ale#fix#ApplyQueuedFixes() abort
endif endif
call remove(g:ale_fix_buffer_data, l:buffer) call remove(g:ale_fix_buffer_data, l:buffer)
if l:data.changes_made
call setline(1, l:data.output) call setline(1, l:data.output)
let l:start_line = len(l:data.output) + 1 let l:start_line = len(l:data.output) + 1
@ -37,10 +39,21 @@ function! ale#fix#ApplyQueuedFixes() abort
call winrestview(l:save) call winrestview(l:save)
endif endif
if l:data.should_save
set nomodified
endif
endif
if l:data.should_save
let l:should_lint = g:ale_fix_on_save
else
let l:should_lint = l:data.changes_made
endif
" If ALE linting is enabled, check for problems with the file again after " If ALE linting is enabled, check for problems with the file again after
" fixing problems. " fixing problems.
if g:ale_enabled if g:ale_enabled && l:should_lint
call ale#Queue(g:ale_lint_delay) call ale#Queue(0, l:data.should_save ? 'lint_file' : '')
endif endif
endfunction endfunction
@ -49,14 +62,9 @@ function! ale#fix#ApplyFixes(buffer, output) abort
let l:data = g:ale_fix_buffer_data[a:buffer] let l:data = g:ale_fix_buffer_data[a:buffer]
let l:data.output = a:output let l:data.output = a:output
let l:data.changes_made = l:data.lines_before != l:data.output
if l:data.lines_before == l:data.output if l:data.changes_made && bufexists(a:buffer)
" Don't modify the buffer if nothing has changed.
call remove(g:ale_fix_buffer_data, a:buffer)
return
endif
if bufexists(a:buffer)
let l:lines = getbufline(a:buffer, 1, '$') let l:lines = getbufline(a:buffer, 1, '$')
if l:data.lines_before != l:lines if l:data.lines_before != l:lines
@ -66,7 +74,13 @@ function! ale#fix#ApplyFixes(buffer, output) abort
endif endif
let l:data.done = 1 let l:data.done = 1
else endif
if l:data.changes_made && l:data.should_save
call writefile(a:output, l:data.filename)
endif
if !bufexists(a:buffer)
" Remove the buffer data when it doesn't exist. " Remove the buffer data when it doesn't exist.
call remove(g:ale_fix_buffer_data, a:buffer) call remove(g:ale_fix_buffer_data, a:buffer)
endif endif
@ -265,7 +279,6 @@ function! s:GetCallbacks() abort
endfor endfor
if empty(l:callback_list) if empty(l:callback_list)
echoerr 'No fixers have been defined. Try :ALEFixSuggest'
return [] return []
endif endif
@ -288,33 +301,56 @@ function! s:GetCallbacks() abort
return l:corrected_list return l:corrected_list
endfunction endfunction
function! ale#fix#Fix() abort function! ale#fix#InitBufferData(buffer, fixing_flag) abort
" The 'done' flag tells the function for applying changes when fixing
" is complete.
let g:ale_fix_buffer_data[a:buffer] = {
\ 'lines_before': getbufline(a:buffer, 1, '$'),
\ 'filename': expand('#' . a:buffer . ':p'),
\ 'done': 0,
\ 'should_save': a:fixing_flag ==# 'save_file',
\ 'temporary_directory_list': [],
\}
endfunction
" Accepts an optional argument for what to do when fixing.
"
" Returns 0 if no fixes can be applied, and 1 if fixing can be done.
function! ale#fix#Fix(...) abort
if len(a:0) > 1
throw 'too many arguments!'
endif
let l:fixing_flag = get(a:000, 0, '')
if l:fixing_flag !=# '' && l:fixing_flag !=# 'save_file'
throw "fixing_flag must be either '' or 'save_file'"
endif
let l:callback_list = s:GetCallbacks() let l:callback_list = s:GetCallbacks()
if empty(l:callback_list) if empty(l:callback_list)
return if l:fixing_flag ==# ''
echoerr 'No fixers have been defined. Try :ALEFixSuggest'
endif
return 0
endif endif
let l:buffer = bufnr('') let l:buffer = bufnr('')
let l:input = getbufline(l:buffer, 1, '$')
" Clean up any files we might have left behind from a previous run. " Clean up any files we might have left behind from a previous run.
call ale#fix#RemoveManagedFiles(l:buffer) call ale#fix#RemoveManagedFiles(l:buffer)
call ale#fix#InitBufferData(l:buffer, l:fixing_flag)
" The 'done' flag tells the function for applying changes when fixing
" is complete.
let g:ale_fix_buffer_data[l:buffer] = {
\ 'lines_before': l:input,
\ 'done': 0,
\ 'temporary_directory_list': [],
\}
call s:RunFixer({ call s:RunFixer({
\ 'buffer': l:buffer, \ 'buffer': l:buffer,
\ 'input': l:input, \ 'input': g:ale_fix_buffer_data[l:buffer].lines_before,
\ 'callback_index': 0, \ 'callback_index': 0,
\ 'callback_list': l:callback_list, \ 'callback_list': l:callback_list,
\}) \})
return 1
endfunction endfunction
" Set up an autocmd command to try and apply buffer fixes when available. " Set up an autocmd command to try and apply buffer fixes when available.

View File

@ -311,6 +311,18 @@ g:ale_fixers *g:ale_fixers*
This variable can be overriden with variables in each buffer. This variable can be overriden with variables in each buffer.
g:ale_fix_on_save *g:ale_fix_on_save*
Type: |Number|
Default: `0`
When set to 1, ALE will fix files when they are saved.
If |g:ale_lint_on_save| is set to 1, files will be checked with linters
after files are fixed, only when the buffer is open, or re-opened. Changes
to the file will saved to the file on disk.
g:ale_history_enabled *g:ale_history_enabled* g:ale_history_enabled *g:ale_history_enabled*
Type: |Number| Type: |Number|
@ -770,6 +782,11 @@ upon some lines immediately, then run `eslint` from the ALE registry, and
then call a lambda function which will remove every single line comment then call a lambda function which will remove every single line comment
from the file. from the file.
Files can be fixed automatically with the following options, which are all off
by default.
|g:ale_fix_on_save| - Fix files when they are saved.
=============================================================================== ===============================================================================
5. Integration Documentation *ale-integrations* 5. Integration Documentation *ale-integrations*

View File

@ -89,6 +89,8 @@ let g:ale_lint_on_save = get(g:, 'ale_lint_on_save', 1)
" This flag can be set to 1 to enable linting when the filetype is changed. " This flag can be set to 1 to enable linting when the filetype is changed.
let g:ale_lint_on_filetype_changed = get(g:, 'ale_lint_on_filetype_changed', 1) let g:ale_lint_on_filetype_changed = get(g:, 'ale_lint_on_filetype_changed', 1)
call ale#Set('fix_on_save', 0)
" This flag may be set to 0 to disable ale. After ale is loaded, :ALEToggle " This flag may be set to 0 to disable ale. After ale is loaded, :ALEToggle
" should be used instead. " should be used instead.
let g:ale_enabled = get(g:, 'ale_enabled', 1) let g:ale_enabled = get(g:, 'ale_enabled', 1)
@ -218,8 +220,8 @@ function! ALEInitAuGroups() abort
augroup ALERunOnSaveGroup augroup ALERunOnSaveGroup
autocmd! autocmd!
if g:ale_enabled && g:ale_lint_on_save if (g:ale_enabled && g:ale_lint_on_save) || g:ale_fix_on_save
autocmd BufWrite * call ale#Queue(0, 'lint_file') autocmd BufWrite * call ale#events#SaveEvent()
endif endif
augroup END augroup END
@ -242,10 +244,13 @@ function! ALEInitAuGroups() abort
augroup END augroup END
if !g:ale_enabled if !g:ale_enabled
if !g:ale_fix_on_save
augroup! ALERunOnSaveGroup
endif
augroup! ALEPatternOptionsGroup augroup! ALEPatternOptionsGroup
augroup! ALERunOnTextChangedGroup augroup! ALERunOnTextChangedGroup
augroup! ALERunOnEnterGroup augroup! ALERunOnEnterGroup
augroup! ALERunOnSaveGroup
augroup! ALERunOnInsertLeave augroup! ALERunOnInsertLeave
augroup! ALECursorGroup augroup! ALECursorGroup
endif endif

View File

@ -1,6 +1,15 @@
Before: Before:
Save g:ale_fixers, &shell, g:ale_enabled Save g:ale_fixers
Save &shell
Save g:ale_enabled
Save g:ale_fix_on_save
Save g:ale_lint_on_save
Save g:ale_echo_cursor
silent! cd /testplugin/test
let g:ale_enabled = 0 let g:ale_enabled = 0
let g:ale_echo_cursor = 0
let g:ale_run_synchronously = 1 let g:ale_run_synchronously = 1
let g:ale_fixers = { let g:ale_fixers = {
\ 'testft': [], \ 'testft': [],
@ -33,6 +42,19 @@ Before:
return ['a', 'b'] return ['a', 'b']
endfunction endfunction
function! TestCallback(buffer, output)
return [{'lnum': 1, 'col': 1, 'text': 'xxx'}]
endfunction
function! SetUpLinters()
call ale#linter#Define('testft', {
\ 'name': 'testlinter',
\ 'callback': 'TestCallback',
\ 'executable': 'true',
\ 'command': 'true',
\})
endfunction
After: After:
Restore Restore
unlet! g:ale_run_synchronously unlet! g:ale_run_synchronously
@ -44,7 +66,14 @@ After:
delfunction CatLine delfunction CatLine
delfunction ReplaceWithTempFile delfunction ReplaceWithTempFile
delfunction RemoveLastLine delfunction RemoveLastLine
delfunction TestCallback
delfunction SetUpLinters
call ale#fix#registry#ResetToDefaults() call ale#fix#registry#ResetToDefaults()
call ale#linter#Reset()
if filereadable('fix_test_file')
call delete('fix_test_file')
endif
Given testft (A file with three lines): Given testft (A file with three lines):
a a
@ -185,3 +214,80 @@ Execute(ALEFix should user buffer-local fixer settings):
Expect(There should be only two lines): Expect(There should be only two lines):
a a
b b
Given testft (A file with three lines):
a
b
c
Execute(ALEFix should save files on the save event):
let g:ale_fix_on_save = 1
let g:ale_lint_on_save = 1
let g:ale_enabled = 1
noautocmd silent file fix_test_file
let g:ale_fixers.testft = ['AddDollars']
call SetUpLinters()
call ale#events#SaveEvent()
" We should save the file.
Assert filereadable('fix_test_file'), 'The file cannot be read'
AssertEqual ['$a', '$b', '$c'], readfile('fix_test_file')
Assert !&modified, 'The was marked as ''modified'''
" We have run the linter.
AssertEqual [{
\ 'bufnr': bufnr('%'),
\ 'lnum': 1,
\ 'vcol': 0,
\ 'col': 1,
\ 'text': 'xxx',
\ 'type': 'E',
\ 'nr': -1,
\ 'pattern': '',
\ 'valid': 1,
\}], getloclist(0)
Expect(The buffer should be modified):
$a
$b
$c
Given testft (A file with three lines):
a
b
c
Execute(ALEFix should still lint with no linters to be applied):
let g:ale_fix_on_save = 1
let g:ale_lint_on_save = 1
let g:ale_enabled = 1
noautocmd silent file fix_test_file
let g:ale_fixers.testft = []
call SetUpLinters()
call ale#events#SaveEvent()
Assert !filereadable('fix_test_file'), 'The file should not have been saved'
" We have run the linter.
AssertEqual [{
\ 'bufnr': bufnr('%'),
\ 'lnum': 1,
\ 'vcol': 0,
\ 'col': 1,
\ 'text': 'xxx',
\ 'type': 'E',
\ 'nr': -1,
\ 'pattern': '',
\ 'valid': 1,
\}], getloclist(0)
Expect(The buffer should be the same):
a
b
c

View File

@ -31,6 +31,7 @@ Before:
return l:matches return l:matches
endfunction endfunction
Save g:ale_enabled
Save g:ale_lint_on_text_changed Save g:ale_lint_on_text_changed
Save g:ale_lint_on_insert_leave Save g:ale_lint_on_insert_leave
Save g:ale_pattern_options_enabled Save g:ale_pattern_options_enabled
@ -38,6 +39,7 @@ Before:
Save g:ale_lint_on_filetype_changed Save g:ale_lint_on_filetype_changed
Save g:ale_lint_on_save Save g:ale_lint_on_save
Save g:ale_echo_cursor Save g:ale_echo_cursor
Save g:ale_fix_on_save
After: After:
delfunction CheckAutocmd delfunction CheckAutocmd
@ -138,14 +140,33 @@ Execute (g:ale_lint_on_filetype_changed = 1 should bind FileType, and required b
Execute (g:ale_lint_on_save = 0 should bind no events): Execute (g:ale_lint_on_save = 0 should bind no events):
let g:ale_lint_on_save = 0 let g:ale_lint_on_save = 0
let g:ale_fix_on_save = 0
AssertEqual [], CheckAutocmd('ALERunOnSaveGroup') AssertEqual [], CheckAutocmd('ALERunOnSaveGroup')
Execute (g:ale_lint_on_save = 1 should bind no events): Execute (g:ale_lint_on_save = 1 should bind no events):
let g:ale_lint_on_save = 1 let g:ale_lint_on_save = 1
let g:ale_fix_on_save = 0
AssertEqual [ AssertEqual [
\ 'BufWritePre * call ale#Queue(0, ''lint_file'')', \ 'BufWritePre * call ale#events#SaveEvent()',
\], CheckAutocmd('ALERunOnSaveGroup')
Execute (g:ale_lint_on_save = 0 and g:ale_fix_on_save = 1 should bind events):
let g:ale_lint_on_save = 0
let g:ale_fix_on_save = 1
AssertEqual [
\ 'BufWritePre * call ale#events#SaveEvent()',
\], CheckAutocmd('ALERunOnSaveGroup')
Execute (g:ale_fix_on_save = 1 should bind events even when ALE is disabled):
let g:ale_enabled = 0
let g:ale_lint_on_save = 0
let g:ale_fix_on_save = 1
AssertEqual [
\ 'BufWritePre * call ale#events#SaveEvent()',
\], CheckAutocmd('ALERunOnSaveGroup') \], CheckAutocmd('ALERunOnSaveGroup')
Execute (g:ale_echo_cursor = 0 should bind no events): Execute (g:ale_echo_cursor = 0 should bind no events):