From 620951b6d3420ca69754f3e1068167729d8c651b Mon Sep 17 00:00:00 2001 From: w0rp Date: Sun, 24 Jun 2018 21:16:45 +0100 Subject: [PATCH] Close #1453 #1172 - Add ale_linters_ignore for ignoring linter results --- autoload/ale.vim | 11 +- autoload/ale/engine/ignore.vim | 46 ++++++ autoload/ale/lsp_linter.vim | 26 +++- doc/ale.txt | 26 ++++ test/test_ignoring_linters.vader | 250 +++++++++++++++++++++++++++++++ 5 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 autoload/ale/engine/ignore.vim create mode 100644 test/test_ignoring_linters.vader diff --git a/autoload/ale.vim b/autoload/ale.vim index 205a5abb..dcc12150 100644 --- a/autoload/ale.vim +++ b/autoload/ale.vim @@ -6,6 +6,8 @@ let g:ale_echo_msg_error_str = get(g:, 'ale_echo_msg_error_str', 'Error') let g:ale_echo_msg_info_str = get(g:, 'ale_echo_msg_info_str', 'Info') let g:ale_echo_msg_warning_str = get(g:, 'ale_echo_msg_warning_str', 'Warning') +" Ignoring linters, for disabling some, or ignoring LSP diagnostics. +let g:ale_linters_ignore = get(g:, 'ale_linters_ignore', {}) let s:lint_timer = -1 let s:queued_buffer_number = -1 @@ -150,7 +152,8 @@ function! ale#Lint(...) abort endif " Use the filetype from the buffer - let l:linters = ale#linter#Get(getbufvar(l:buffer, '&filetype')) + let l:filetype = getbufvar(l:buffer, '&filetype') + let l:linters = ale#linter#Get(l:filetype) let l:should_lint_file = 0 " Check if we previously requested checking the file. @@ -160,6 +163,12 @@ function! ale#Lint(...) abort let l:should_lint_file = filereadable(expand('#' . l:buffer . ':p')) endif + " Apply ignore lists for linters only if needed. + let l:ignore_config = ale#Var(l:buffer, 'linters_ignore') + let l:linters = !empty(l:ignore_config) + \ ? ale#engine#ignore#Exclude(l:filetype, l:linters, l:ignore_config) + \ : l:linters + call ale#engine#RunLinters(l:buffer, l:linters, l:should_lint_file) endfunction diff --git a/autoload/ale/engine/ignore.vim b/autoload/ale/engine/ignore.vim new file mode 100644 index 00000000..65347e21 --- /dev/null +++ b/autoload/ale/engine/ignore.vim @@ -0,0 +1,46 @@ +" Author: w0rp +" Description: Code for ignoring linters. Only loaded and if configured. + +" Given a filetype and a configuration for ignoring linters, return a List of +" Strings for linter names to ignore. +function! ale#engine#ignore#GetList(filetype, config) abort + if type(a:config) is type([]) + return a:config + endif + + if type(a:config) is type({}) + let l:names_to_remove = [] + + for l:part in split(a:filetype , '\.') + call extend(l:names_to_remove, get(a:config, l:part, [])) + endfor + + return l:names_to_remove + endif + + return [] +endfunction + +" Given a List of linter descriptions, exclude the linters to be ignored. +function! ale#engine#ignore#Exclude(filetype, all_linters, config) abort + let l:names_to_remove = ale#engine#ignore#GetList(a:filetype, a:config) + let l:filtered_linters = [] + + for l:linter in a:all_linters + let l:name_list = [l:linter.name] + l:linter.aliases + let l:should_include = 1 + + for l:name in l:name_list + if index(l:names_to_remove, l:name) >= 0 + let l:should_include = 0 + break + endif + endfor + + if l:should_include + call add(l:filtered_linters, l:linter) + endif + endfor + + return l:filtered_linters +endfunction diff --git a/autoload/ale/lsp_linter.vim b/autoload/ale/lsp_linter.vim index 4aef8ff5..781d04c4 100644 --- a/autoload/ale/lsp_linter.vim +++ b/autoload/ale/lsp_linter.vim @@ -8,11 +8,30 @@ if !has_key(s:, 'lsp_linter_map') let s:lsp_linter_map = {} endif +" Check if diagnostics for a particular linter should be ignored. +function! s:ShouldIgnore(buffer, linter_name) abort + let l:config = ale#Var(a:buffer, 'linters_ignore') + + " Don't load code for ignoring diagnostics if there's nothing to ignore. + if empty(l:config) + return 0 + endif + + let l:filetype = getbufvar(a:buffer, '&filetype') + let l:ignore_list = ale#engine#ignore#GetList(l:filetype, l:config) + + return index(l:ignore_list, a:linter_name) >= 0 +endfunction + function! s:HandleLSPDiagnostics(conn_id, response) abort let l:linter_name = s:lsp_linter_map[a:conn_id] let l:filename = ale#path#FromURI(a:response.params.uri) let l:buffer = bufnr(l:filename) + if s:ShouldIgnore(l:buffer, l:linter_name) + return + endif + if l:buffer <= 0 return endif @@ -23,6 +42,7 @@ function! s:HandleLSPDiagnostics(conn_id, response) abort endfunction function! s:HandleTSServerDiagnostics(response, error_type) abort + let l:linter_name = 'tsserver' let l:buffer = bufnr(a:response.body.file) let l:info = get(g:ale_buffer_info, l:buffer, {}) @@ -30,6 +50,10 @@ function! s:HandleTSServerDiagnostics(response, error_type) abort return endif + if s:ShouldIgnore(l:buffer, l:linter_name) + return + endif + let l:thislist = ale#lsp#response#ReadTSServerDiagnostics(a:response) " tsserver sends syntax and semantic errors in separate messages, so we @@ -44,7 +68,7 @@ function! s:HandleTSServerDiagnostics(response, error_type) abort let l:loclist = get(l:info, 'semantic_loclist', []) \ + get(l:info, 'syntax_loclist', []) - call ale#engine#HandleLoclist('tsserver', l:buffer, l:loclist) + call ale#engine#HandleLoclist(l:linter_name, l:buffer, l:loclist) endfunction function! s:HandleLSPErrorMessage(linter_name, response) abort diff --git a/doc/ale.txt b/doc/ale.txt index daec16b4..2f91c6d1 100644 --- a/doc/ale.txt +++ b/doc/ale.txt @@ -1231,6 +1231,32 @@ g:ale_linters_explicit *g:ale_linters_explicit* as possible, unless otherwise specified. +g:ale_linters_ignore *g:ale_linters_ignore* + *b:ale_linters_ignore* + + Type: |Dictionary| or |List| + Default: `{}` + + Linters to ignore. Commands for ignored linters will not be run, and + diagnostics for LSP linters will be ignored. (See |ale-lsp|) + + This setting can be set to a |Dictionary| mapping filetypes to linter names, + just like |g:ale_linters|, to list linters to ignore. Ignore lists will be + applied after everything else. > + + " Select flake8 and pylint, and ignore pylint, so only flake8 is run. + let g:ale_linters = {'python': ['flake8', 'pylint']} + let g:ale_linters_ignore = {'python': ['pylint']} +< + This setting can be set to simply a |List| of linter names, which is + especially more convenient when using the setting in ftplugin files for + particular buffers. > + + " The same as above, in a ftplugin/python.vim. + let b:ale_linters = ['flake8', 'pylint'] + let b:ale_linters_ignore = ['pylint'] +< + g:ale_list_vertical *g:ale_list_vertical* *b:ale_list_vertical* Type: |Number| diff --git a/test/test_ignoring_linters.vader b/test/test_ignoring_linters.vader new file mode 100644 index 00000000..af31fce3 --- /dev/null +++ b/test/test_ignoring_linters.vader @@ -0,0 +1,250 @@ +Execute(GetList should ignore some invalid values): + AssertEqual [], ale#engine#ignore#GetList('', 'foo') + AssertEqual [], ale#engine#ignore#GetList('', 0) + AssertEqual [], ale#engine#ignore#GetList('', v:null) + +Execute(GetList should handle Lists): + AssertEqual ['foo', 'bar'], ale#engine#ignore#GetList('', ['foo', 'bar']) + +Execute(GetList should handle Dictionaries): + AssertEqual + \ ['linter1', 'linter2'], + \ uniq(sort(ale#engine#ignore#GetList('x.y.z', { + \ 'x': ['linter1'], + \ 'abc': ['linter3'], + \ 'z': ['linter2'], + \ }))) + +Execute(Exclude should ignore some invalid values): + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ 'foo', + \ ) + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ 0, + \ ) + AssertEqual + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ v:null, + \ ) + +Execute(Exclude should handle Lists): + AssertEqual + \ [ + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ['linter1', 'alias1'], + \ ) + +Execute(Exclude should handle Dictionaries): + AssertEqual + \ [ + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ ale#engine#ignore#Exclude( + \ 'foo.bar', + \ [ + \ {'name': 'linter1', 'aliases': []}, + \ {'name': 'linter2', 'aliases': ['alias1']}, + \ {'name': 'linter3', 'aliases': []}, + \ ], + \ {'foo': ['linter1'], 'bar': ['alias1']}, + \ ) + +Before: + Save g:ale_linters_ignore + Save g:ale_buffer_info + + let g:linters = [] + let g:loclist = [] + let g:run_linters_called = 0 + + runtime autoload/ale/engine.vim + + " Mock the engine function so we can set it up. + function! ale#engine#RunLinters(buffer, linters, should_lint_file) abort + let g:linters = a:linters + let g:run_linters_called = 1 + endfunction + + function! ale#engine#HandleLoclist(linter_name, buffer, loclist) abort + let g:loclist = a:loclist + endfunction + + call ale#linter#Define('foobar', { + \ 'name': 'testlinter', + \ 'callback': 'TestCallback', + \ 'executable': has('win32') ? 'cmd' : 'true', + \ 'command': has('win32') ? 'echo' : 'true', + \}) + call ale#test#SetDirectory('/testplugin/test') + +After: + Restore + + unlet! b:ale_linted + unlet! b:ale_linters_ignore + unlet! b:ale_quitting + unlet! b:ale_save_event_fired + unlet! g:linters + unlet! g:loclist + unlet! g:lsp_message + + call ale#test#RestoreDirectory() + call ale#linter#Reset() + call ale#lsp_linter#ClearLSPData() + runtime autoload/ale/engine.vim + +Given foobar(An empty file): +Execute(Global ignore lists should be applied for linters): + ALELint + Assert g:run_linters_called, "The mock callback wasn't called" + AssertEqual ['testlinter'], map(g:linters, 'v:val.name') + + let g:ale_linters_ignore = ['testlinter'] + ALELint + AssertEqual [], g:linters + +Execute(buffer ignore lists should be applied for linters): + ALELint + Assert g:run_linters_called, "The mock callback wasn't called" + AssertEqual ['testlinter'], map(g:linters, 'v:val.name') + + let b:ale_linters_ignore = ['testlinter'] + ALELint + AssertEqual [], g:linters + +Execute(Buffer ignore lists should be applied for tsserver): + call ale#test#SetFilename('filename.ts') + call ale#engine#InitBufferInfo(bufnr('')) + + let g:lsp_message = { + \ 'seq': 0, + \ 'type': 'event', + \ 'event': 'syntaxDiag', + \ 'body': { + \ 'file': g:dir . '/filename.ts', + \ 'diagnostics':[ + \ { + \ 'start': { + \ 'line':2, + \ 'offset':14, + \ }, + \ 'end': { + \ 'line':2, + \ 'offset':15, + \ }, + \ 'text': ''','' expected.', + \ "code":1005 + \ }, + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 2, + \ 'col': 14, + \ 'nr': 1005, + \ 'type': 'E', + \ 'end_col': 15, + \ 'end_lnum': 2, + \ 'text': ''','' expected.', + \ }, + \ ], + \ g:loclist + + let g:loclist = [] + let b:ale_linters_ignore = ['tsserver'] + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist + +Execute(Buffer ignore lists should be applied for LSP linters): + call ale#test#SetFilename('filename.py') + call ale#engine#InitBufferInfo(bufnr('')) + call ale#lsp_linter#SetLSPLinterMap({'347': 'lsplinter'}) + + let g:lsp_message = { + \ 'jsonrpc': '2.0', + \ 'method': 'textDocument/publishDiagnostics', + \ 'params': { + \ 'uri': ale#path#ToURI(expand('%:p')), + \ 'diagnostics': [ + \ { + \ 'severity': 1, + \ 'message': 'x', + \ 'range': { + \ 'start': {'line': 0, 'character': 9}, + \ 'end': {'line': 0, 'character': 9}, + \ }, + \ } + \ ], + \ }, + \} + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual + \ [ + \ { + \ 'lnum': 1, + \ 'col': 10, + \ 'type': 'E', + \ 'end_col': 10, + \ 'end_lnum': 1, + \ 'text': 'x', + \ } + \ ], + \ g:loclist + + let b:ale_linters_ignore = ['lsplinter'] + let g:loclist = [] + + call ale#lsp_linter#HandleLSPResponse(347, g:lsp_message) + + AssertEqual [], g:loclist