Skip to content

Commit

Permalink
#2132 - Make most foo_callback options work as foo
Browse files Browse the repository at this point in the history
  • Loading branch information
w0rp committed Feb 22, 2019
1 parent ffa45fa commit f8aeb5c
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 40 deletions.
46 changes: 33 additions & 13 deletions autoload/ale/linter.vim
Expand Up @@ -195,17 +195,24 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif

if !l:needs_address
if has_key(a:linter, 'address_callback')
throw '`address_callback` cannot be used when lsp != ''socket'''
if has_key(a:linter, 'address') || has_key(a:linter, 'address_callback')
throw '`address` or `address_callback` cannot be used when lsp != ''socket'''
endif
elseif has_key(a:linter, 'address')
if type(a:linter.address) isnot v:t_string
\&& type(a:linter.address) isnot v:t_func
throw '`address` must be a String or Function if defined'
endif

let l:obj.address = a:linter.address
elseif has_key(a:linter, 'address_callback')
let l:obj.address_callback = a:linter.address_callback

if !s:IsCallback(l:obj.address_callback)
throw '`address_callback` must be a callback if defined'
endif
else
throw '`address_callback` must be defined for getting the LSP address'
throw '`address` or `address_callback` must be defined for getting the LSP address'
endif

if l:needs_lsp_details
Expand All @@ -222,14 +229,17 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
else
" Default to using the filetype as the language.
let l:obj.language = get(a:linter, 'language', a:filetype)

if type(l:obj.language) isnot v:t_string
throw '`language` must be a string'
let l:Language = get(a:linter, 'language', a:filetype)

if type(l:Language) is v:t_string
" Make 'language_callback' return the 'language' value.
let l:obj.language = l:Language
let l:obj.language_callback = function('s:LanguageGetter')
elseif type(l:Language) is v:t_func
let l:obj.language_callback = l:Language
else
throw '`language` must be a String or Funcref'
endif

" Make 'language_callback' return the 'language' value.
let l:obj.language_callback = function('s:LanguageGetter')
endif

let l:obj.project_root_callback = get(a:linter, 'project_root_callback')
Expand Down Expand Up @@ -259,6 +269,11 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
elseif has_key(a:linter, 'initialization_options')
let l:obj.initialization_options = a:linter.initialization_options

if type(l:obj.initialization_options) isnot v:t_dict
\&& type(l:obj.initialization_options) isnot v:t_func
throw '`initialization_options` must be a String or Function if defined'
endif
endif

if has_key(a:linter, 'lsp_config_callback')
Expand All @@ -273,7 +288,8 @@ function! ale#linter#PreProcess(filetype, linter) abort
endif
elseif has_key(a:linter, 'lsp_config')
if type(a:linter.lsp_config) isnot v:t_dict
throw '`lsp_config` must be a Dictionary'
\&& type(a:linter.lsp_config) isnot v:t_func
throw '`lsp_config` must be a Dictionary or Function if defined'
endif

let l:obj.lsp_config = a:linter.lsp_config
Expand Down Expand Up @@ -501,7 +517,11 @@ endfunction

" Given a buffer and linter, get the address for connecting to the server.
function! ale#linter#GetAddress(buffer, linter) abort
return has_key(a:linter, 'address_callback')
\ ? ale#util#GetFunction(a:linter.address_callback)(a:buffer)
let l:Address = has_key(a:linter, 'address_callback')
\ ? function(a:linter.address_callback)
\ : a:linter.address

return type(l:Address) is v:t_func
\ ? l:Address(a:buffer)
\ : l:Address
endfunction
36 changes: 24 additions & 12 deletions autoload/ale/lsp_linter.vim
Expand Up @@ -129,27 +129,39 @@ function! ale#lsp_linter#HandleLSPResponse(conn_id, response) abort
endfunction

function! ale#lsp_linter#GetOptions(buffer, linter) abort
let l:initialization_options = {}

if has_key(a:linter, 'initialization_options_callback')
let l:initialization_options = ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer)
elseif has_key(a:linter, 'initialization_options')
let l:initialization_options = a:linter.initialization_options
return ale#util#GetFunction(a:linter.initialization_options_callback)(a:buffer)
endif

if has_key(a:linter, 'initialization_options')
let l:Options = a:linter.initialization_options

if type(l:Options) is v:t_func
let l:Options = l:Options(a:buffer)
endif

return l:Options
endif

return l:initialization_options
return {}
endfunction

function! ale#lsp_linter#GetConfig(buffer, linter) abort
let l:config = {}

if has_key(a:linter, 'lsp_config_callback')
let l:config = ale#util#GetFunction(a:linter.lsp_config_callback)(a:buffer)
elseif has_key(a:linter, 'lsp_config')
let l:config = a:linter.lsp_config
return ale#util#GetFunction(a:linter.lsp_config_callback)(a:buffer)
endif

if has_key(a:linter, 'lsp_config')
let l:Config = a:linter.lsp_config

if type(l:Config) is v:t_func
let l:Config = l:Config(a:buffer)
endif

return l:Config
endif

return l:config
return {}
endfunction

function! ale#lsp_linter#FindProjectRoot(buffer, linter) abort
Expand Down
30 changes: 23 additions & 7 deletions doc/ale.txt
Expand Up @@ -3231,8 +3231,9 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*

When this argument is set to `'socket'`, then the
linter will be defined as an LSP linter via a TCP
socket connection. `address_callback` must be set
with a callback returning an address to connect to.
socket connection. Either `address` or
`address_callback` must be set.

ALE will not start a server automatically.

When this argument is not empty
Expand All @@ -3255,6 +3256,13 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
An optional `lsp_config` or `lsp_config_callback` may
be defined to pass configuration settings to the LSP.

`address` A |String| representing an address to connect to,
or a |Funcref| accepting a buffer number and
returning the |String|.

This argument must only be set if the `lsp` argument
is set to `'socket'`.

`address_callback` A |String| or |Funcref| for a callback function
accepting a buffer number. A |String| should be
returned with an address to connect to.
Expand All @@ -3273,8 +3281,10 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
is also set to a non-empty string.

`language` A |String| representing the name of the language
being checked. This string will be sent to the LSP to
tell it what type of language is being checked.
being checked, or a |Funcref| accepting a buffer
number and returning the |String|. This string will
be sent to the LSP to tell it what type of language
is being checked.

If this or `language_callback` isn't set, the
language will default to the value of the filetype
Expand Down Expand Up @@ -3304,7 +3314,10 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
setting can make it easier to guess the linter name
by offering a few alternatives.

`initialization_options` A |Dictionary| of initialization options for LSPs.
`initialization_options` A |Dictionary| of initialization options for LSPs,
or a |Funcref| for a callback function accepting
a buffer number and returning the |Dictionary|.

This will be fed (as JSON) to the LSP in the
initialize command.

Expand All @@ -3315,11 +3328,14 @@ ale#linter#Define(filetype, linter) *ale#linter#Define()*
This can be used in place of `initialization_options`
when more complicated processing is needed.

`lsp_config` A |Dictionary| of configuration settings for LSPs.
`lsp_config` A |Dictionary| for configuring a language server,
or a |Funcref| for a callback function accepting
a buffer number and returning the |Dictionary|.

This will be fed (as JSON) to the LSP in the
workspace/didChangeConfiguration command.

`lsp_config_callback` A |String| or |Funcref| for a callback function
`lsp_config_callback` A |String| or |Funcref| for a callback function
accepting a buffer number. A |Dictionary| should be
returned for configuration settings to pass the LSP.
This can be used in place of `lsp_config` when more
Expand Down
125 changes: 117 additions & 8 deletions test/test_linter_defintion_processing.vader
Expand Up @@ -461,6 +461,18 @@ Execute(PreProcess should complain about using language and language_callback to
AssertThrows call ale#linter#PreProcess('testft', g:linter)
AssertEqual 'Only one of `language` or `language_callback` should be set', g:vader_exception

Execute(PreProcess should complain about invalid language values):
let g:linter = {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language': 0,
\ 'project_root_callback': 'x',
\}

AssertThrows call ale#linter#PreProcess('testft', g:linter)
AssertEqual '`language` must be a String or Funcref', g:vader_exception

Execute(PreProcess should use the filetype as the language string by default):
let g:linter = {
\ 'name': 'x',
Expand All @@ -471,14 +483,25 @@ Execute(PreProcess should use the filetype as the language string by default):

AssertEqual 'testft', ale#linter#PreProcess('testft', g:linter).language_callback(0)

Execute(PreProcess should allow language to be set to a callback):
let g:linter = {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language': {-> 'foo'},
\ 'project_root_callback': 'x',
\}

AssertEqual 'foo', ale#linter#PreProcess('testft', g:linter).language_callback(0)

Execute(PreProcess should require an address_callback for LSP socket configurations):
let g:linter = {
\ 'name': 'x',
\ 'lsp': 'socket',
\}

AssertThrows call ale#linter#PreProcess('testft', g:linter)
AssertEqual '`address_callback` must be defined for getting the LSP address', g:vader_exception
AssertEqual '`address` or `address_callback` must be defined for getting the LSP address', g:vader_exception

Execute(PreProcess should complain about address_callback for non-LSP linters):
let g:linter = {
Expand All @@ -490,7 +513,50 @@ Execute(PreProcess should complain about address_callback for non-LSP linters):
\}

AssertThrows call ale#linter#PreProcess('testft', g:linter)
AssertEqual '`address_callback` cannot be used when lsp != ''socket''', g:vader_exception
AssertEqual '`address` or `address_callback` cannot be used when lsp != ''socket''', g:vader_exception

Execute(PreProcess accept valid address_callback values):
let g:linter = ale#linter#PreProcess('testft', {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address_callback': {-> 'foo:123'},
\ 'language': 'x',
\ 'project_root_callback': 'x',
\})

AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter)

Execute(PreProcess accept address as a String):
let g:linter = ale#linter#PreProcess('testft', {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address': 'foo:123',
\ 'language': 'x',
\ 'project_root_callback': 'x',
\})

AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter)

Execute(PreProcess accept address as a Function):
let g:linter = ale#linter#PreProcess('testft', {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address': {-> 'foo:123'},
\ 'language': 'x',
\ 'project_root_callback': 'x',
\})

AssertEqual 'foo:123', ale#linter#GetAddress(0, g:linter)

Execute(PreProcess should complain about invalid address values):
AssertThrows call ale#linter#PreProcess('testft', {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address': 0,
\ 'language': 'x',
\ 'project_root_callback': 'x',
\})
AssertEqual '`address` must be a String or Function if defined', g:vader_exception

Execute(PreProcess should complain about using initialization_options and initialization_options_callback together):
let g:linter = {
Expand All @@ -517,6 +583,41 @@ Execute(PreProcess should throw when initialization_options_callback is not a ca
\})
AssertEqual '`initialization_options_callback` must be a callback if defined', g:vader_exception

Execute(PreProcess should throw when initialization_options is not a Dictionary or callback):
AssertThrows call ale#linter#PreProcess('testft', {
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language': 'x',
\ 'project_root_callback': 'x',
\ 'initialization_options': 0,
\})
AssertEqual '`initialization_options` must be a String or Function if defined', g:vader_exception

Execute(PreProcess should accept initialization_options as a Dictionary):
let g:linter = ale#linter#PreProcess('testft', {
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language': 'x',
\ 'project_root_callback': 'x',
\ 'initialization_options': {'foo': v:true},
\})

AssertEqual {'foo': v:true}, ale#lsp_linter#GetOptions(0, g:linter)

Execute(PreProcess should accept initialization_options as a Funcref):
let g:linter = ale#linter#PreProcess('testft', {
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language': 'x',
\ 'project_root_callback': 'x',
\ 'initialization_options': {-> {'foo': v:true}},
\})

AssertEqual {'foo': v:true}, ale#lsp_linter#GetOptions(0, g:linter)

Execute(PreProcess should complain about using lsp_config and lsp_config_callback together):
let g:linter = {
\ 'name': 'x',
Expand All @@ -543,22 +644,30 @@ Execute(PreProcess should throw when lsp_config_callback is not a callback):
AssertEqual '`lsp_config_callback` must be a callback if defined', g:vader_exception

Execute(PreProcess should accept LSP configuration options via lsp_config):
let g:ale_lsp_configuration = {
\ 'foo': 'bar'
let g:linter = {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language_callback': 'x',
\ 'project_root_callback': 'x',
\ 'lsp_config': {'foo': 'bar'},
\}

AssertEqual {'foo': 'bar'}, ale#lsp_linter#GetConfig(0, g:linter)

Execute(PreProcess should accept LSP configuration options via lsp_config as a function):
let g:linter = {
\ 'name': 'x',
\ 'lsp': 'socket',
\ 'address_callback': 'X',
\ 'language_callback': 'x',
\ 'project_root_callback': 'x',
\ 'lsp_config': g:ale_lsp_configuration,
\ 'lsp_config': {-> {'foo': 'bar'}},
\}

AssertEqual {'foo': 'bar'}, ale#linter#PreProcess('testft', g:linter).lsp_config
AssertEqual {'foo': 'bar'}, ale#lsp_linter#GetConfig(0, g:linter)

Execute(PreProcess should throw when lsp_config is not a Dictionary):
Execute(PreProcess should throw when lsp_config is not a Dictionary or Function):
AssertThrows call ale#linter#PreProcess('testft', {
\ 'name': 'foo',
\ 'lsp': 'socket',
Expand All @@ -567,4 +676,4 @@ Execute(PreProcess should throw when lsp_config is not a Dictionary):
\ 'project_root_callback': 'x',
\ 'lsp_config': 'x',
\})
AssertEqual '`lsp_config` must be a Dictionary', g:vader_exception
AssertEqual '`lsp_config` must be a Dictionary or Function if defined', g:vader_exception

0 comments on commit f8aeb5c

Please sign in to comment.