1
0
mirror of https://github.com/amix/vimrc synced 2025-06-16 09:35:01 +08:00

Update Ale.

This commit is contained in:
Kurtis Moxley
2022-05-19 21:16:38 +08:00
parent 0071859401
commit dd26bc4697
1324 changed files with 56041 additions and 437 deletions

View File

@ -0,0 +1,176 @@
Before:
runtime autoload/ale/lsp.vim
let g:message_list = []
function! MarkAllConnectionsInitialized() abort
for l:conn in values(ale#lsp#GetConnections())
let l:conn.initialized = 1
endfor
endfunction
function! MarkDocumentOpened() abort
for l:conn in values(ale#lsp#GetConnections())
let l:conn.open_documents[bufnr('')] = 1
endfor
endfunction
function! ale#lsp#Send(conn_id, message) abort
let l:connections = ale#lsp#GetConnections()
if !l:connections[a:conn_id].initialized
throw 'LSP server not initialized yet!'
endif
call add(g:message_list, [a:conn_id] + a:message)
endfunction
call ale#lsp#ResetConnections()
After:
unlet! g:message_list
delfunction MarkAllConnectionsInitialized
delfunction MarkDocumentOpened
call ale#lsp#ResetConnections()
runtime autoload/ale/lsp.vim
Execute(No errors should be thrown if the connection is not initialized):
call ale#lsp#Register('command', '/foo', {})
call MarkDocumentOpened()
call ale#engine#Cleanup(bufnr(''))
AssertEqual [], g:message_list
Execute(No messages should be sent if the document wasn't opened):
call ale#lsp#Register('command', '/foo', {})
call MarkAllConnectionsInitialized()
call ale#engine#Cleanup(bufnr(''))
AssertEqual [], g:message_list
Execute(A message should be sent if the document was opened):
call ale#lsp#Register('command', '/foo', {})
call MarkAllConnectionsInitialized()
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang')
call ale#engine#Cleanup(bufnr(''))
" We should only send the message once.
call ale#engine#Cleanup(bufnr(''))
AssertEqual
\ [
\ ['command:/foo', 1, 'textDocument/didOpen', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'version': g:ale_lsp_next_version_id - 1,
\ 'languageId': 'lang',
\ 'text': "\n",
\ },
\ }],
\ ['command:/foo', 1, 'textDocument/didClose', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ },
\ }],
\ ],
\ g:message_list
Execute(A message should be sent if the document was opened for tsserver):
call ale#lsp#Register('command', '/foo', {})
call ale#lsp#MarkConnectionAsTsserver('command:/foo')
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang')
call ale#engine#Cleanup(bufnr(''))
" We should only send the message once.
call ale#engine#Cleanup(bufnr(''))
AssertEqual
\ [
\ ['command:/foo', 1, 'ts@open', {'file': expand('%:p')}],
\ ['command:/foo', 1, 'ts@close', {'file': expand('%:p')}],
\ ],
\ g:message_list
Execute(Re-opening and closing the documents should work):
call ale#lsp#Register('command', '/foo', {})
call MarkAllConnectionsInitialized()
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang')
call ale#engine#Cleanup(bufnr(''))
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang')
call ale#engine#Cleanup(bufnr(''))
AssertEqual
\ [
\ ['command:/foo', 1, 'textDocument/didOpen', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'version': g:ale_lsp_next_version_id - 2,
\ 'languageId': 'lang',
\ 'text': "\n",
\ },
\ }],
\ ['command:/foo', 1, 'textDocument/didClose', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ },
\ }],
\ ['command:/foo', 1, 'textDocument/didOpen', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'version': g:ale_lsp_next_version_id - 1,
\ 'languageId': 'lang',
\ 'text': "\n",
\ },
\ }],
\ ['command:/foo', 1, 'textDocument/didClose', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ },
\ }],
\ ],
\ g:message_list
Execute(Messages for closing documents should be sent to each server):
call ale#lsp#Register('command', '/foo', {})
call ale#lsp#Register('command', '/bar', {})
call MarkAllConnectionsInitialized()
call ale#lsp#OpenDocument('command:/foo', bufnr(''), 'lang')
call ale#lsp#OpenDocument('command:/bar', bufnr(''), 'lang')
call ale#engine#Cleanup(bufnr(''))
" We should only send the message once.
call ale#engine#Cleanup(bufnr(''))
AssertEqual
\ [
\ ['command:/foo', 1, 'textDocument/didOpen', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'version': g:ale_lsp_next_version_id - 2,
\ 'languageId': 'lang',
\ 'text': "\n",
\ },
\ }],
\ ['command:/bar', 1, 'textDocument/didOpen', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'version': g:ale_lsp_next_version_id - 1,
\ 'languageId': 'lang',
\ 'text': "\n",
\ },
\ }],
\ ['command:/bar', 1, 'textDocument/didClose', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ },
\ }],
\ ['command:/foo', 1, 'textDocument/didClose', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ },
\ }],
\ ],
\ g:message_list

View File

@ -0,0 +1,147 @@
Before:
Save g:ale_lint_on_save
Save g:ale_enabled
Save g:ale_linters
Save g:ale_run_synchronously
Save g:ale_disable_lsp
call ale#test#SetDirectory('/testplugin/test/completion')
call ale#test#SetFilename('dummy.txt')
runtime autoload/ale/lsp.vim
runtime autoload/ale/lsp_linter.vim
let g:ale_disable_lsp = 0
unlet! b:ale_disable_lsp
let g:ale_lint_on_save = 1
let b:ale_enabled = 1
let g:ale_lsp_next_message_id = 1
let g:ale_run_synchronously = 1
let g:conn_id = v:null
let g:message_list = []
function! LanguageCallback() abort
return 'foobar'
endfunction
function! ProjectRootCallback() abort
return expand('.')
endfunction
call ale#linter#Define('foobar', {
\ 'name': 'dummy_linter',
\ 'lsp': 'stdio',
\ 'command': 'cat - > /dev/null',
\ 'executable': has('win32') ? 'cmd' : 'echo',
\ 'language': function('LanguageCallback'),
\ 'project_root': function('ProjectRootCallback'),
\ })
let g:ale_linters = {'foobar': ['dummy_linter']}
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
let l:details = {
\ 'command': 'foobar',
\ 'buffer': a:buffer,
\ 'connection_id': g:conn_id,
\ 'project_root': '/foo/bar',
\}
call a:Callback(a:linter, l:details)
return 1
endfunction
" Replace the Send function for LSP, so we can monitor calls to it.
function! ale#lsp#Send(conn_id, message) abort
call add(g:message_list, a:message)
endfunction
After:
Restore
if g:conn_id isnot v:null
call ale#lsp#RemoveConnectionWithID(g:conn_id)
endif
unlet! b:ale_enabled
unlet! b:ale_linters
unlet! g:message_list
unlet! b:ale_save_event_fired
delfunction LanguageCallback
delfunction ProjectRootCallback
call ale#test#RestoreDirectory()
call ale#linter#Reset()
" Stop any timers we left behind.
" This stops the tests from failing randomly.
call ale#completion#StopTimer()
runtime autoload/ale/completion.vim
runtime autoload/ale/lsp.vim
runtime autoload/ale/lsp_linter.vim
Given foobar (Some imaginary filetype):
<contents>
Execute(Server should be notified on save):
call ale#events#SaveEvent(bufnr(''))
AssertEqual
\ [
\ [1, 'textDocument/didChange', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'version': g:ale_lsp_next_version_id - 1,
\ },
\ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}],
\ }],
\ ],
\ g:message_list
Execute(Server should be notified on save with didSave is supported by server):
" Replace has capability function to simulate didSave server capability
function! ale#lsp#HasCapability(conn_id, capability) abort
if a:capability == 'did_save'
return 1
endif
return 0
endfunction
call ale#events#SaveEvent(bufnr(''))
AssertEqual
\ [
\ [1, 'textDocument/didChange', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'version': g:ale_lsp_next_version_id - 1,
\ },
\ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}],
\ }],
\ [1, 'textDocument/didSave', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ },
\ }],
\ ],
\ g:message_list
Execute(Server should be notified on change):
call ale#events#FileChangedEvent(bufnr(''))
AssertEqual
\ [
\ [1, 'textDocument/didChange', {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'version': g:ale_lsp_next_version_id - 1,
\ },
\ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}],
\ }],
\ ],
\ g:message_list

View File

@ -0,0 +1,428 @@
Before:
Save g:ale_set_lists_synchronously
Save g:ale_buffer_info
Save g:ale_lsp_error_messages
Save g:ale_set_loclist
Save g:ale_set_signs
Save g:ale_set_quickfix
Save g:ale_set_highlights
Save g:ale_echo_cursor
Save g:ale_disable_lsp
Save g:ale_history_enabled
Save g:ale_history_log_output
let g:ale_disable_lsp = 0
let g:ale_set_lists_synchronously = 1
let g:ale_buffer_info = {}
let g:ale_set_loclist = 1
" Disable features we don't need for these tests.
let g:ale_set_signs = 0
let g:ale_set_quickfix = 0
let g:ale_set_highlights = 0
let g:ale_echo_cursor = 0
let g:ale_history_enabled = 1
let g:ale_history_log_output = 1
unlet! g:ale_lsp_error_messages
unlet! b:ale_linters
unlet! b:ale_disable_lsp
call ale#linter#Reset()
call ale#test#SetDirectory('/testplugin/test')
call setloclist(0, [])
After:
Restore
unlet! b:ale_linters
call setloclist(0, [])
call ale#test#RestoreDirectory()
call ale#linter#Reset()
call ale#lsp_linter#ClearLSPData()
Given foobar(An empty file):
Execute(tsserver syntax error responses should be handled correctly):
runtime ale_linters/typescript/tsserver.vim
if has('win32')
call ale#test#SetFilename('filename,[]^$.ts')
else
call ale#test#SetFilename('filename*?,{}[]^$.ts')
endif
call ale#engine#InitBufferInfo(bufnr(''))
if has('win32')
AssertEqual 'filename,[]^$.ts', expand('%:p:t')
else
AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t')
endif
" When we get syntax errors and no semantic errors, we should keep the
" syntax errors.
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'syntaxDiag',
\ 'body': {
\ 'file': expand('%:p'),
\ 'diagnostics':[
\ {
\ 'start': {
\ 'line':2,
\ 'offset':14,
\ },
\ 'end': {
\ 'line':2,
\ 'offset':15,
\ },
\ 'text': ''','' expected.',
\ "code":1005
\ },
\ ],
\ },
\})
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'semanticDiag',
\ 'body': {
\ 'file': expand('%:p'),
\ 'diagnostics':[
\ ],
\ },
\})
AssertEqual
\ [
\ {
\ 'lnum': 1,
\ 'bufnr': bufnr(''),
\ 'col': 14,
\ 'vcol': 0,
\ 'nr': 1005,
\ 'type': 'E',
\ 'text': '1005: '','' expected.',
\ 'valid': 1,
\ 'pattern': '',
\ },
\ ],
\ ale#test#GetLoclistWithoutNewerKeys()
" After we get empty syntax errors, we should clear them.
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'syntaxDiag',
\ 'body': {
\ 'file': expand('%:p'),
\ 'diagnostics':[
\ ],
\ },
\})
AssertEqual
\ [
\ ],
\ ale#test#GetLoclistWithoutNewerKeys()
" Syntax errors on the project root should not populate the LocList.
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'syntaxDiag',
\ 'body': {
\ 'file': g:dir,
\ 'diagnostics':[
\ {
\ 'start': {
\ 'line':2,
\ 'offset':14,
\ },
\ 'end': {
\ 'line':2,
\ 'offset':15,
\ },
\ 'text': ''','' expected.',
\ "code":1005
\ },
\ ],
\ },
\})
AssertEqual
\ [
\ ],
\ ale#test#GetLoclistWithoutNewerKeys()
Execute(tsserver semantic error responses should be handled correctly):
runtime ale_linters/typescript/tsserver.vim
if has('win32')
call ale#test#SetFilename('filename,[]^$.ts')
else
call ale#test#SetFilename('filename*?,{}[]^$.ts')
endif
call ale#engine#InitBufferInfo(bufnr(''))
if has('win32')
AssertEqual 'filename,[]^$.ts', expand('%:p:t')
else
AssertEqual 'filename*?,{}[]^$.ts', expand('%:p:t')
endif
" When we get syntax errors and no semantic errors, we should keep the
" syntax errors.
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'syntaxDiag',
\ 'body': {
\ 'file': expand('%:p'),
\ 'diagnostics':[
\ ],
\ },
\})
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'semanticDiag',
\ 'body': {
\ 'file': expand('%:p'),
\ 'diagnostics':[
\ {
\ 'start': {
\ 'line':2,
\ 'offset':14,
\ },
\ 'end': {
\ 'line':2,
\ 'offset':15,
\ },
\ 'text': 'Some semantic error',
\ "code":1005
\ },
\ ],
\ },
\})
AssertEqual
\ [
\ {
\ 'lnum': 1,
\ 'bufnr': bufnr(''),
\ 'col': 14,
\ 'vcol': 0,
\ 'nr': 1005,
\ 'type': 'E',
\ 'text': '1005: Some semantic error',
\ 'valid': 1,
\ 'pattern': '',
\ },
\ ],
\ ale#test#GetLoclistWithoutNewerKeys()
" After we get empty syntax errors, we should clear them.
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'semanticDiag',
\ 'body': {
\ 'file': expand('%:p'),
\ 'diagnostics':[
\ ],
\ },
\})
AssertEqual
\ [
\ ],
\ ale#test#GetLoclistWithoutNewerKeys()
" Semantic errors on the project root should not populate the LocList.
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'semanticDiag',
\ 'body': {
\ 'file': g:dir,
\ 'diagnostics':[
\ {
\ 'start': {
\ 'line':2,
\ 'offset':14,
\ },
\ 'end': {
\ 'line':2,
\ 'offset':15,
\ },
\ 'text': 'Some semantic error',
\ "code":1005
\ },
\ ],
\ },
\})
AssertEqual
\ [
\ ],
\ ale#test#GetLoclistWithoutNewerKeys()
Execute(tsserver errors should mark tsserver no longer active):
let b:ale_linters = ['tsserver']
runtime ale_linters/typescript/tsserver.vim
call ale#test#SetFilename('filename.ts')
call ale#engine#InitBufferInfo(bufnr(''))
let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('typescript')
Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list)
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'seq': 0,
\ 'type': 'event',
\ 'event': 'semanticDiag',
\ 'body': {
\ 'file': g:dir . '/filename.ts',
\ 'diagnostics':[],
\ },
\})
AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list
Execute(LSP diagnostics responses should be handled correctly):
let b:ale_linters = ['eclipselsp']
runtime ale_linters/java/eclipselsp.vim
if has('win32')
call ale#test#SetFilename('filename,[]^$.ts')
else
call ale#test#SetFilename('filename*?,{}[]^$.java')
endif
call ale#engine#InitBufferInfo(bufnr(''))
call ale#lsp_linter#SetLSPLinterMap({'1': 'eclipselsp'})
if has('win32')
AssertEqual 'filename,[]^$.ts', expand('%:p:t')
else
AssertEqual 'filename*?,{}[]^$.java', expand('%:p:t')
endif
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'jsonrpc':'2.0',
\ 'method':'textDocument/publishDiagnostics',
\ 'params': {
\ 'uri': ale#path#ToFileURI(expand('%:p')),
\ 'diagnostics': [
\ {
\ 'range': {
\ 'start': {
\ 'line': 0,
\ 'character':0
\ },
\ 'end': {
\ 'line': 0,
\ 'character':0
\ }
\ },
\ 'severity': 2,
\ 'code': "",
\ 'source': 'Java',
\ 'message': 'Missing JRE 1-8'
\ }
\ ]
\ }
\})
AssertEqual
\ [
\ {
\ 'lnum': 1,
\ 'bufnr': bufnr(''),
\ 'col': 1,
\ 'pattern': '',
\ 'valid': 1,
\ 'vcol': 0,
\ 'nr': -1,
\ 'type': 'W',
\ 'text': 'Missing JRE 1-8'
\ }
\ ],
\ ale#test#GetLoclistWithoutNewerKeys()
Execute(LSP diagnostics responses on project root should not populate loclist):
let b:ale_linters = ['eclipselsp']
runtime ale_linters/java/eclipselsp.vim
call ale#test#SetFilename('filename.java')
call ale#engine#InitBufferInfo(bufnr(''))
call ale#lsp_linter#SetLSPLinterMap({'1': 'eclipselsp'})
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'jsonrpc':'2.0',
\ 'method':'textDocument/publishDiagnostics',
\ 'params': {
\ 'uri':'file://' . g:dir,
\ 'diagnostics': [
\ {
\ 'range': {
\ 'start': {
\ 'line': 0,
\ 'character':0
\ },
\ 'end': {
\ 'line': 0,
\ 'character':0
\ }
\ },
\ 'severity': 2,
\ 'code': "",
\ 'source': 'Java',
\ 'message': 'Missing JRE 1-8'
\ }
\ ]
\ }
\})
AssertEqual
\ [
\ ],
\ ale#test#GetLoclistWithoutNewerKeys()
Execute(LSP errors should mark linters no longer active):
let b:ale_linters = ['pylsp']
runtime ale_linters/python/pylsp.vim
call ale#test#SetFilename('filename.py')
call ale#engine#InitBufferInfo(bufnr(''))
call ale#lsp_linter#SetLSPLinterMap({1: 'pylsp'})
let g:ale_buffer_info[bufnr('')].active_linter_list = ale#linter#Get('python')
Assert !empty(g:ale_buffer_info[bufnr('')].active_linter_list)
call ale#lsp_linter#HandleLSPResponse(1, {
\ 'method': 'textDocument/publishDiagnostics',
\ 'params': {
\ 'uri': ale#path#ToFileURI(g:dir . '/filename.py'),
\ 'diagnostics': [],
\ },
\})
AssertEqual [], g:ale_buffer_info[bufnr('')].active_linter_list
Execute(LSP errors should be logged in the history):
call ale#lsp_linter#SetLSPLinterMap({'347': 'foobar'})
call ale#lsp_linter#HandleLSPResponse(347, {
\ 'jsonrpc': '2.0',
\ 'error': {
\ 'code': -32602,
\ 'message': 'xyz',
\ 'data': {
\ 'traceback': ['123', '456'],
\ },
\ },
\})
AssertEqual
\ {'foobar': ["xyz\n123\n456"]},
\ get(g:, 'ale_lsp_error_messages', {})

View File

@ -0,0 +1,94 @@
Before:
let g:expr_list = []
let g:linter_name = 'some_linter'
let g:format = '%severity%:%linter%: %s'
" Get the default value to restore it
let g:default_severity = g:ale_lsp_show_message_severity
let g:ale_lsp_show_message_severity = 'information'
function! ale#util#ShowMessage(expr) abort
call add(g:expr_list, a:expr)
endfunction
After:
unlet! g:expr_list
unlet! g:linter_name
unlet! g:format
let g:ale_lsp_show_message_severity = g:default_severity
unlet! g:default_severity
Execute(ale#lsp_window#HandleShowMessage() should only show errors when severity is set to "error"):
let g:ale_lsp_show_message_severity = 'error'
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'})
AssertEqual ['Error:some_linter: an error'], g:expr_list
Execute(ale#lsp_window#HandleShowMessage() should only show errors and warnings when severity is set to "warning"):
let g:ale_lsp_show_message_severity = 'warning'
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'})
AssertEqual ['Error:some_linter: an error', 'Warning:some_linter: a warning'], g:expr_list
Execute(ale#lsp_window#HandleShowMessage() should only show errors, warnings and infos when severity is set to "information"):
let g:ale_lsp_show_message_severity = 'information'
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'})
AssertEqual [
\ 'Error:some_linter: an error',
\ 'Warning:some_linter: a warning',
\ 'Info:some_linter: an info'],
\ g:expr_list
Execute(ale#lsp_window#HandleShowMessage() should only show errors, warnings and infos when severity is set to "info"):
let g:ale_lsp_show_message_severity = 'info'
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'})
AssertEqual [
\ 'Error:some_linter: an error',
\ 'Warning:some_linter: a warning',
\ 'Info:some_linter: an info'],
\ g:expr_list
Execute(ale#lsp_window#HandleShowMessage() should show all messages is severity is set to "log"):
let g:ale_lsp_show_message_severity = 'log'
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'})
AssertEqual [
\ 'Error:some_linter: an error',
\ 'Warning:some_linter: a warning',
\ 'Info:some_linter: an info',
\ 'Log:some_linter: a log'],
\ g:expr_list
Execute(ale#lsp_window#HandleShowMessage() should not show anything if severity is configured as disabled):
let g:ale_lsp_show_message_severity = 'disabled'
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'})
AssertEqual [], g:expr_list
Execute(ale#lsp_window#HandleShowMessage() should use "warning" when severity is set to an invalid value):
let g:ale_lsp_show_message_severity = 'foo'
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':1,'message':'an error'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':2,'message':'a warning'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':'an info'})
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':4,'message':'a log'})
AssertEqual [
\ 'Error:some_linter: an error',
\ 'Warning:some_linter: a warning'],
\ g:expr_list
Execute(ale#lsp_window#HandleShowMessage() should escape quotes on messages):
call ale#lsp_window#HandleShowMessage(g:linter_name, g:format, {'type':3,'message':"this is an 'info'"})
AssertEqual ['Info:some_linter: this is an ''info'''], g:expr_list

View File

@ -0,0 +1,389 @@
Before:
let g:ale_lsp_next_version_id = 1
call ale#test#SetDirectory('/testplugin/test/lsp')
call ale#test#SetFilename('foo/bar.ts')
After:
call ale#test#RestoreDirectory()
Execute(ale#lsp#message#Initialize() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'initialize',
\ {
\ 'processId': getpid(),
\ 'rootPath': '/foo/bar',
\ 'capabilities': {},
\ 'initializationOptions': {'foo': 'bar'},
\ 'rootUri': 'file:///foo/bar',
\ }
\ ],
\ ale#lsp#message#Initialize('/foo/bar', {'foo': 'bar'}, {})
Execute(ale#lsp#message#Initialized() should return correct messages):
AssertEqual [1, 'initialized', {}], ale#lsp#message#Initialized()
Execute(ale#lsp#message#Shutdown() should return correct messages):
AssertEqual [0, 'shutdown'], ale#lsp#message#Shutdown()
Execute(ale#lsp#message#Exit() should return correct messages):
AssertEqual [1, 'exit'], ale#lsp#message#Exit(),
Given typescript(A TypeScript file with 3 lines):
foo()
bar()
baz()
Execute(ale#lsp#message#DidOpen() should return correct messages):
let g:ale_lsp_next_version_id = 12
AssertEqual
\ [
\ 1,
\ 'textDocument/didOpen',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ 'languageId': 'typescript',
\ 'version': 12,
\ 'text': "foo()\nbar()\nbaz()\n",
\ },
\ }
\ ],
\ ale#lsp#message#DidOpen(bufnr(''), 'typescript')
Execute(ale#lsp#message#DidChange() should return correct messages):
let g:ale_lsp_next_version_id = 34
AssertEqual
\ [
\ 1,
\ 'textDocument/didChange',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ 'version': 34,
\ },
\ 'contentChanges': [{'text': "foo()\nbar()\nbaz()\n"}],
\ }
\ ],
\ ale#lsp#message#DidChange(bufnr(''))
" The version numbers should increment.
AssertEqual
\ 35,
\ ale#lsp#message#DidChange(bufnr(''))[2].textDocument.version
AssertEqual
\ 36,
\ ale#lsp#message#DidChange(bufnr(''))[2].textDocument.version
Execute(ale#lsp#message#DidSave() should return correct messages):
AssertEqual
\ [
\ 1,
\ 'textDocument/didSave',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ }
\ ],
\ ale#lsp#message#DidSave(bufnr(''), v:false)
Execute(ale#lsp#message#DidSave() should return correct message with includeText capability):
AssertEqual
\ [
\ 1,
\ 'textDocument/didSave',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ 'version': 1,
\ },
\ 'text': ale#util#GetBufferContents(bufnr('')),
\ }
\ ],
\ ale#lsp#message#DidSave(bufnr(''), v:true)
Execute(ale#lsp#message#DidClose() should return correct messages):
AssertEqual
\ [
\ 1,
\ 'textDocument/didClose',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ }
\ ],
\ ale#lsp#message#DidClose(bufnr(''))
Execute(ale#lsp#message#Completion() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'textDocument/completion',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ 'position': {'line': 11, 'character': 33},
\ }
\ ],
\ ale#lsp#message#Completion(bufnr(''), 12, 34, '')
Execute(ale#lsp#message#Completion() should return correct messages with a trigger charaacter):
AssertEqual
\ [
\ 0,
\ 'textDocument/completion',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ 'position': {'line': 11, 'character': 33},
\ 'context': {'triggerKind': 2, 'triggerCharacter': '.'},
\ }
\ ],
\ ale#lsp#message#Completion(bufnr(''), 12, 34, '.')
\
Execute(ale#lsp#message#Definition() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'textDocument/definition',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ 'position': {'line': 11, 'character': 33},
\ }
\ ],
\ ale#lsp#message#Definition(bufnr(''), 12, 34)
Execute(ale#lsp#message#TypeDefinition() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'textDocument/typeDefinition',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ 'position': {'line': 11, 'character': 33},
\ }
\ ],
\ ale#lsp#message#TypeDefinition(bufnr(''), 12, 34)
Execute(ale#lsp#message#Implementation() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'textDocument/implementation',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ 'position': {'line': 11, 'character': 33},
\ }
\ ],
\ ale#lsp#message#Implementation(bufnr(''), 12, 34)
Execute(ale#lsp#message#References() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'textDocument/references',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ 'position': {'line': 11, 'character': 33},
\ 'context': {'includeDeclaration': v:false},
\ }
\ ],
\ ale#lsp#message#References(bufnr(''), 12, 34)
Execute(ale#lsp#message#Symbol() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'workspace/symbol',
\ {
\ 'query': 'foobar',
\ }
\ ],
\ ale#lsp#message#Symbol('foobar')
Execute(ale#lsp#message#Hover() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'textDocument/hover',
\ {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(g:dir . '/foo/bar.ts'),
\ },
\ 'position': {'line': 11, 'character': 33},
\ }
\ ],
\ ale#lsp#message#Hover(bufnr(''), 12, 34)
Execute(ale#lsp#message#DidChangeConfiguration() should return correct messages):
let g:ale_lsp_configuration = {
\ 'foo': 'bar'
\ }
AssertEqual
\ [
\ 1,
\ 'workspace/didChangeConfiguration',
\ {
\ 'settings': {
\ 'foo': 'bar',
\ }
\ }
\ ],
\ ale#lsp#message#DidChangeConfiguration(bufnr(''), g:ale_lsp_configuration)
Execute(ale#lsp#tsserver_message#Open() should return correct messages):
AssertEqual
\ [
\ 1,
\ 'ts@open',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ }
\ ],
\ ale#lsp#tsserver_message#Open(bufnr(''))
Execute(ale#lsp#tsserver_message#Close() should return correct messages):
AssertEqual
\ [
\ 1,
\ 'ts@close',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ }
\ ],
\ ale#lsp#tsserver_message#Close(bufnr(''))
Execute(ale#lsp#tsserver_message#Change() should return correct messages):
AssertEqual
\ [
\ 1,
\ 'ts@change',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ 'line': 1,
\ 'offset': 1,
\ 'endLine': 1073741824,
\ 'endOffset': 1,
\ 'insertString': "foo()\nbar()\nbaz()\n",
\ }
\ ],
\ ale#lsp#tsserver_message#Change(bufnr(''))
Execute(ale#lsp#tsserver_message#Geterr() should return correct messages):
AssertEqual
\ [
\ 1,
\ 'ts@geterr',
\ {
\ 'files': [ale#path#Simplify(g:dir . '/foo/bar.ts')],
\ }
\ ],
\ ale#lsp#tsserver_message#Geterr(bufnr(''))
Execute(ale#lsp#tsserver_message#Completions() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'ts@completions',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ 'line': 347,
\ 'offset': 12,
\ 'prefix': 'abc',
\ 'includeExternalModuleExports': 1,
\ }
\ ],
\ ale#lsp#tsserver_message#Completions(bufnr(''), 347, 12, 'abc', 1)
Execute(ale#lsp#tsserver_message#CompletionEntryDetails() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'ts@completionEntryDetails',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ 'line': 347,
\ 'offset': 12,
\ 'entryNames': ['foo', 'bar'],
\ }
\ ],
\ ale#lsp#tsserver_message#CompletionEntryDetails(bufnr(''), 347, 12, ['foo', 'bar'])
Execute(ale#lsp#tsserver_message#Definition() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'ts@definition',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ 'line': 347,
\ 'offset': 12,
\ }
\ ],
\ ale#lsp#tsserver_message#Definition(bufnr(''), 347, 12)
Execute(ale#lsp#tsserver_message#TypeDefinition() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'ts@typeDefinition',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ 'line': 347,
\ 'offset': 12,
\ }
\ ],
\ ale#lsp#tsserver_message#TypeDefinition(bufnr(''), 347, 12)
Execute(ale#lsp#tsserver_message#Implementation() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'ts@implementation',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ 'line': 347,
\ 'offset': 12,
\ }
\ ],
\ ale#lsp#tsserver_message#Implementation(bufnr(''), 347, 12)
Execute(ale#lsp#tsserver_message#References() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'ts@references',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ 'line': 347,
\ 'offset': 12,
\ }
\ ],
\ ale#lsp#tsserver_message#References(bufnr(''), 347, 12)
Execute(ale#lsp#tsserver_message#Quickinfo() should return correct messages):
AssertEqual
\ [
\ 0,
\ 'ts@quickinfo',
\ {
\ 'file': ale#path#Simplify(g:dir . '/foo/bar.ts'),
\ 'line': 347,
\ 'offset': 12,
\ }
\ ],
\ ale#lsp#tsserver_message#Quickinfo(bufnr(''), 347, 12)

View File

@ -0,0 +1,44 @@
Before:
Save g:ale_command_wrapper
runtime autoload/ale/lsp.vim
let g:ale_command_wrapper = ''
let g:args = []
" Mock the StartProgram function so we can just capture the arguments.
function! ale#lsp#StartProgram(...) abort
let g:args = a:000[1:]
endfunction
After:
Restore
unlet! g:args
runtime autoload/ale/lsp.vim
Execute(Command formatting should be applied correctly for LSP linters):
call ale#lsp_linter#StartLSP(
\ bufnr(''),
\ {
\ 'name': 'linter',
\ 'language': {-> 'x'},
\ 'project_root': {-> '/foo/bar'},
\ 'lsp': 'stdio',
\ 'executable': has('win32') ? 'cmd': 'true',
\ 'command': '%e --foo',
\ },
\ {-> 0}
\)
if has('win32')
AssertEqual
\ ['cmd', 'cmd /s/c "cmd --foo"'],
\ g:args
else
AssertEqual
\ ['true', [&shell, '-c', '''true'' --foo']],
\ g:args
endif

View File

@ -0,0 +1,227 @@
Before:
let g:ale_lsp_next_message_id = 1
After:
if exists('b:conn') && has_key(b:conn, 'id')
call ale#lsp#RemoveConnectionWithID(b:conn.id)
endif
unlet! b:data
unlet! b:conn
Execute(GetNextMessageID() should increment appropriately):
" We should get the initial ID, and increment a bit.
AssertEqual 1, ale#lsp#GetNextMessageID()
AssertEqual 2, ale#lsp#GetNextMessageID()
AssertEqual 3, ale#lsp#GetNextMessageID()
" Set the maximum ID.
let g:ale_lsp_next_message_id = 9223372036854775807
" When we hit the maximum ID, the next ID afterwards should be 1.
AssertEqual 9223372036854775807, ale#lsp#GetNextMessageID()
AssertEqual 1, ale#lsp#GetNextMessageID()
Execute(ale#lsp#CreateMessageData() should create an appropriate message):
" NeoVim outputs JSON with spaces, so the output is a little different.
if has('nvim')
" 79 is the size in bytes for UTF-8, not the number of characters.
AssertEqual
\ [
\ 1,
\ "Content-Length: 79\r\n\r\n"
\ . '{"method": "someMethod", "jsonrpc": "2.0", "id": 1, "params": {"foo": "barÜ"}}',
\ ],
\ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}])
" Check again to ensure that we use the next ID.
AssertEqual
\ [
\ 2,
\ "Content-Length: 79\r\n\r\n"
\ . '{"method": "someMethod", "jsonrpc": "2.0", "id": 2, "params": {"foo": "barÜ"}}',
\ ],
\ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}])
else
AssertEqual
\ [
\ 1,
\ "Content-Length: 71\r\n\r\n"
\ . '{"method":"someMethod","jsonrpc":"2.0","id":1,"params":{"foo":"barÜ"}}',
\ ],
\ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}])
AssertEqual
\ [
\ 2,
\ "Content-Length: 71\r\n\r\n"
\ . '{"method":"someMethod","jsonrpc":"2.0","id":2,"params":{"foo":"barÜ"}}',
\ ],
\ ale#lsp#CreateMessageData([0, 'someMethod', {'foo': 'barÜ'}])
endif
Execute(ale#lsp#CreateMessageData() should create messages without params):
if has('nvim')
AssertEqual
\ [
\ 1,
\ "Content-Length: 56\r\n\r\n"
\ . '{"method": "someOtherMethod", "jsonrpc": "2.0", "id": 1}',
\ ],
\ ale#lsp#CreateMessageData([0, 'someOtherMethod'])
else
AssertEqual
\ [
\ 1,
\ "Content-Length: 51\r\n\r\n"
\ . '{"method":"someOtherMethod","jsonrpc":"2.0","id":1}',
\ ],
\ ale#lsp#CreateMessageData([0, 'someOtherMethod'])
endif
Execute(ale#lsp#CreateMessageData() should create notifications):
if has('nvim')
AssertEqual
\ [
\ 0,
\ "Content-Length: 48\r\n\r\n"
\ . '{"method": "someNotification", "jsonrpc": "2.0"}',
\ ],
\ ale#lsp#CreateMessageData([1, 'someNotification'])
AssertEqual
\ [
\ 0,
\ "Content-Length: 74\r\n\r\n"
\ . '{"method": "someNotification", "jsonrpc": "2.0", "params": {"foo": "bar"}}',
\ ],
\ ale#lsp#CreateMessageData([1, 'someNotification', {'foo': 'bar'}])
else
AssertEqual
\ [
\ 0,
\ "Content-Length: 45\r\n\r\n"
\ . '{"method":"someNotification","jsonrpc":"2.0"}',
\ ],
\ ale#lsp#CreateMessageData([1, 'someNotification'])
AssertEqual
\ [
\ 0,
\ "Content-Length: 68\r\n\r\n"
\ . '{"method":"someNotification","jsonrpc":"2.0","params":{"foo":"bar"}}',
\ ],
\ ale#lsp#CreateMessageData([1, 'someNotification', {'foo': 'bar'}])
endif
Execute(ale#lsp#CreateMessageData() should create tsserver notification messages):
if has('nvim')
AssertEqual
\ [
\ 0,
\ '{"seq": null, "type": "request", "command": "someNotification"}'
\ . "\n",
\ ],
\ ale#lsp#CreateMessageData([1, 'ts@someNotification'])
AssertEqual
\ [
\ 0,
\ '{"seq": null, "arguments": {"foo": "bar"}, "type": "request", "command": "someNotification"}'
\ . "\n",
\ ],
\ ale#lsp#CreateMessageData([1, 'ts@someNotification', {'foo': 'bar'}])
else
AssertEqual
\ [
\ 0,
\ '{"seq":null,"type":"request","command":"someNotification"}'
\ . "\n",
\ ],
\ ale#lsp#CreateMessageData([1, 'ts@someNotification'])
AssertEqual
\ [
\ 0,
\ '{"seq":null,"arguments":{"foo":"bar"},"type":"request","command":"someNotification"}'
\ . "\n",
\ ],
\ ale#lsp#CreateMessageData([1, 'ts@someNotification', {'foo': 'bar'}])
endif
Execute(ale#lsp#CreateMessageData() should create tsserver messages expecting responses):
if has('nvim')
AssertEqual
\ [
\ 1,
\ '{"seq": 1, "type": "request", "command": "someMessage"}'
\ . "\n",
\ ],
\ ale#lsp#CreateMessageData([0, 'ts@someMessage'])
AssertEqual
\ [
\ 2,
\ '{"seq": 2, "arguments": {"foo": "bar"}, "type": "request", "command": "someMessage"}'
\ . "\n",
\ ],
\ ale#lsp#CreateMessageData([0, 'ts@someMessage', {'foo': 'bar'}])
else
AssertEqual
\ [
\ 1,
\ '{"seq":1,"type":"request","command":"someMessage"}'
\ . "\n",
\ ],
\ ale#lsp#CreateMessageData([0, 'ts@someMessage'])
AssertEqual
\ [
\ 2,
\ '{"seq":2,"arguments":{"foo":"bar"},"type":"request","command":"someMessage"}'
\ . "\n",
\ ],
\ ale#lsp#CreateMessageData([0, 'ts@someMessage', {'foo': 'bar'}])
endif
Execute(ale#lsp#ReadMessageData() should read single whole messages):
AssertEqual
\ ['', [{'id': 2, 'jsonrpc': '2.0', 'result': {'foo': 'barÜ'}}]],
\ ale#lsp#ReadMessageData(
\ "Content-Length: 49\r\n\r\n"
\ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}'
\ )
Execute(ale#lsp#ReadMessageData() should ignore other headers):
AssertEqual
\ ['', [{'id': 2, 'jsonrpc': '2.0', 'result': {'foo': 'barÜ'}}]],
\ ale#lsp#ReadMessageData(
\ "First-Header: 49\r\n"
\ . "Content-Length: 49\r\n"
\ . "Other-Header: 49\r\n"
\ . "\r\n"
\ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}'
\ )
Execute(ale#lsp#ReadMessageData() should handle partial messages):
let b:data = "Content-Length: 49\r\n\r\n" . '{"id":2,"jsonrpc":"2.0","result":'
AssertEqual [b:data, []], ale#lsp#ReadMessageData(b:data)
Execute(ale#lsp#ReadMessageData() should handle multiple messages):
AssertEqual
\ ['', [
\ {'id': 2, 'jsonrpc': '2.0', 'result': {'foo': 'barÜ'}},
\ {'id': 2, 'jsonrpc': '2.0', 'result': {'foo123': 'barÜ'}},
\ ]],
\ ale#lsp#ReadMessageData(
\ "Content-Length: 49\r\n\r\n"
\ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}'
\ . "Content-Length: 52\r\n\r\n"
\ . '{"id":2,"jsonrpc":"2.0","result":{"foo123":"barÜ"}}'
\ )
Execute(ale#lsp#ReadMessageData() should handle a message with part of a second message):
let b:data = "Content-Length: 52\r\n\r\n" . '{"id":2,"jsonrpc":"2.'
AssertEqual
\ [b:data, [
\ {'id': 2, 'jsonrpc': '2.0', 'result': {'foo': 'barÜ'}},
\ ]],
\ ale#lsp#ReadMessageData(
\ "Content-Length: 49\r\n\r\n"
\ . '{"id":2,"jsonrpc":"2.0","result":{"foo":"barÜ"}}'
\ . b:data
\ )

View File

@ -0,0 +1,158 @@
Before:
runtime autoload/ale/linter.vim
runtime autoload/ale/lsp.vim
runtime autoload/ale/lsp_linter.vim
let g:address = 'ccls_address'
let g:conn_id = -1
let g:executable = 'ccls'
let g:executable_or_address = ''
let g:linter_name = 'ccls'
let g:magic_number = 42
let g:no_result = 0
let g:message_list = []
let g:message_id = 1
let g:method = '$ccls/call'
let g:parameters = {}
let g:project_root = '/project/root'
let g:response = ''
let g:return_value = -1
let g:linter_list = [{
\ 'output_stream': 'stdout',
\ 'lint_file': 0,
\ 'language': 'cpp',
\ 'name': g:linter_name,
\ 'project_root': {b -> g:project_root},
\ 'aliases': [],
\ 'read_buffer': 1,
\ 'command': '%e'
\ }]
let g:callback_result = g:no_result
" Encode dictionary to jsonrpc
function! Encode(obj) abort
let l:body = json_encode(a:obj)
return 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
endfunction
" Replace the StartLSP function to mock an LSP linter
function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
let g:conn_id = ale#lsp#Register(g:executable_or_address, g:project_root, {})
call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
call ale#lsp#HandleMessage(g:conn_id, Encode({'method': 'initialize'}))
let l:details = {
\ 'command': g:executable,
\ 'buffer': a:buffer,
\ 'connection_id': g:conn_id,
\ 'project_root': g:project_root,
\}
call ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)
endfunction
" Dummy callback
function! Callback(response) abort
let g:callback_result = a:response.result.value
endfunction
" Replace the GetAll function to mock an LSP linter
function! ale#linter#GetAll(filetype) abort
return g:linter_list
endfunction
" Replace the Send function to mock an LSP linter
function! ale#lsp#Send(conn_id, message) abort
call add(g:message_list, a:message)
return g:message_id
endfunction
" Code for a test case
function! TestCase(is_notification) abort
" Test sending a custom request
let g:return_value = ale#lsp_linter#SendRequest(
\ bufnr('%'),
\ g:linter_name,
\ [a:is_notification, g:method, g:parameters],
\ function('Callback'))
Assert index(g:message_list, [a:is_notification, g:method, g:parameters]) >= 0
" Mock an incoming response to the request
let g:response = Encode({
\ 'id': g:message_id,
\ 'jsonrpc': '2.0',
\ 'result': {'value': g:magic_number}
\ })
call ale#lsp#HandleMessage(g:conn_id, g:response)
AssertEqual
\ a:is_notification ? g:no_result : g:magic_number,
\ g:callback_result
endfunction
After:
if g:conn_id isnot v:null
call ale#lsp#RemoveConnectionWithID(g:conn_id)
endif
unlet! g:callback_result
unlet! g:conn_id
unlet! g:executable
unlet! g:is_notification
unlet! g:linter_name
unlet! g:magic_number
unlet! g:message_list
unlet! g:message_id
unlet! g:method
unlet! g:no_result
unlet! g:parameters
unlet! g:project_root
unlet! g:response
unlet! g:return_value
delfunction Encode
delfunction Callback
delfunction TestCase
runtime autoload/ale/linter.vim
runtime autoload/ale/lsp.vim
runtime autoload/ale/lsp_linter.vim
Given cpp(Empty cpp file):
Execute(Test custom request to server identified by executable):
let g:executable_or_address = g:executable
let g:linter_list[0].executable = {b -> g:executable}
let g:linter_list[0].lsp = 'stdio'
let g:is_notification = 0
call TestCase(g:is_notification)
Given cpp(Empty cpp file):
Execute(Test custom notification to server identified by executable):
let g:executable_or_address = g:executable
let g:linter_list[0].executable = {b -> g:executable}
let g:linter_list[0].lsp = 'stdio'
let g:is_notification = 1
call TestCase(g:is_notification)
Given cpp(Empty cpp file):
Execute(Test custom request to server identified by address):
let g:executable_or_address = g:address
let g:linter_list[0].address = {b -> g:address}
let g:linter_list[0].lsp = 'socket'
let g:is_notification = 0
call TestCase(g:is_notification)
Given cpp(Empty cpp file):
Execute(Test custom notification to server identified by address):
let g:executable_or_address = g:address
let g:linter_list[0].address = {b -> g:address}
let g:linter_list[0].lsp = 'socket'
let g:is_notification = 1
call TestCase(g:is_notification)

View File

@ -0,0 +1,74 @@
Execute(Invalid responses should be handled):
AssertEqual '', ale#lsp#response#GetErrorMessage({})
AssertEqual '', ale#lsp#response#GetErrorMessage({'error': 0})
AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {}})
AssertEqual '', ale#lsp#response#GetErrorMessage({
\ 'error': {
\ 'code': 0,
\ 'message': 'x',
\ },
\})
AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {'code': -32602}})
AssertEqual '', ale#lsp#response#GetErrorMessage({'error': {'code': -32603}})
Execute(Messages without tracebacks should be handled):
AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({
\ 'error': {
\ 'code': -32602,
\ 'message': 'xyz',
\ },
\})
AssertEqual 'abc', ale#lsp#response#GetErrorMessage({
\ 'error': {
\ 'code': -32603,
\ 'message': 'abc',
\ },
\})
Execute(Invalid traceback data should be tolerated):
AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({
\ 'error': {
\ 'code': -32602,
\ 'message': 'xyz',
\ 'data': {
\ },
\ },
\})
AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({
\ 'error': {
\ 'code': -32602,
\ 'message': 'xyz',
\ 'data': {
\ 'traceback': 0,
\ },
\ },
\})
AssertEqual 'xyz', ale#lsp#response#GetErrorMessage({
\ 'error': {
\ 'code': -32602,
\ 'message': 'xyz',
\ 'data': {
\ 'traceback': [],
\ },
\ },
\})
Execute(Messages with tracebacks should be handled):
AssertEqual "xyz\n123\n456", ale#lsp#response#GetErrorMessage({
\ 'error': {
\ 'code': -32602,
\ 'message': 'xyz',
\ 'data': {
\ 'traceback': ['123', '456'],
\ },
\ },
\})
Execute(Messages with string data should be handled):
AssertEqual "xyz\nUncaught Exception", ale#lsp#response#GetErrorMessage({
\ 'error': {
\ 'code': -32602,
\ 'message': 'xyz',
\ 'data': 'Uncaught Exception',
\ },
\})

View File

@ -0,0 +1,90 @@
Before:
Save g:ale_lsp_root
Save g:ale_root
Save b:ale_lsp_root
Save b:ale_root
unlet! g:ale_lsp_root
let g:ale_root = {}
call ale#assert#SetUpLinterTest('c', 'clangd')
function! Hook1(buffer)
return 'abc123'
endfunction
After:
Restore
delfunction Hook1
call ale#assert#TearDownLinterTest()
Execute(The buffer-specific variable can be a string):
let b:ale_root = '/some/path'
call ale#test#SetFilename('other-file.c')
AssertLSPProject '/some/path'
Execute(The buffer-specific variable can be a dictionary):
let b:ale_root = {'clangd': '/some/path', 'golangserver': '/other/path'}
call ale#test#SetFilename('other-file.c')
AssertLSPProject '/some/path'
Execute(The buffer-specific variable can have funcrefs):
let b:ale_root = {'clangd': function('Hook1'), 'golangserver': '/path'}
call ale#test#SetFilename('other-file.c')
AssertLSPProject 'abc123'
Execute(The buffer-specific variable can be the old ale_lsp_root setting):
let b:ale_lsp_root = '/some/path'
call ale#test#SetFilename('other-file.c')
AssertLSPProject '/some/path'
Execute(The global variable can be a dictionary):
let g:ale_root = {'clangd': '/some/path', 'golangserver': '/other/path'}
call ale#test#SetFilename('other-file.c')
AssertLSPProject '/some/path'
Execute(The global variable can have funcrefs):
let g:ale_root = {'clangd': function('Hook1'), 'golangserver': '/path'}
call ale#test#SetFilename('other-file.c')
AssertLSPProject 'abc123'
Execute(The buffer-specific variable overrides the global variable):
let b:ale_root = {'clangd': '/some/path', 'golangserver': '/other/path'}
let g:ale_root = {'clangd': '/not/this/path', 'golangserver': '/elsewhere'}
call ale#test#SetFilename('other-file.c')
AssertLSPProject '/some/path'
Execute(The global variable is queried if the buffer-specific has no value):
let b:ale_root = {'golangserver': '/other/path'}
let g:ale_root = {'clangd': '/some/path', 'golangserver': '/elsewhere'}
call ale#test#SetFilename('other-file.c')
AssertLSPProject '/some/path'
Execute(The global variable can be the old ale_lsp_root setting):
let g:ale_root = {}
let g:ale_lsp_root = {'clangd': '/some/path', 'golangserver': '/other/path'}
call ale#test#SetFilename('other-file.c')
AssertLSPProject '/some/path'
Execute(A non-empty ale_root setting should replace the old ale_lsp_root):
let g:ale_root = {'clangd': '/some/path', 'golangserver': '/other/path'}
let g:ale_lsp_root = {'clangd': '/xxx', 'golangserver': '/xxx'}
call ale#test#SetFilename('other-file.c')
AssertLSPProject '/some/path'
Execute(No path should be returned by default):
call ale#test#SetFilename(tempname() . '/other-file.c')
AssertLSPProject ''

View File

@ -0,0 +1,492 @@
Before:
Save g:ale_run_synchronously
let g:ale_run_synchronously = 1
unlet! g:ale_run_synchronously_callbacks
unlet! g:ale_run_synchronously_emulate_commands
runtime autoload/ale/lsp.vim
runtime autoload/ale/lsp_linter.vim
runtime autoload/ale/engine.vim
runtime autoload/ale/job.vim
runtime autoload/ale/socket.vim
let g:job_map = {}
let g:emulate_job_failure = 0
let g:next_job_id = 1
let g:lsp_started = 0
let g:socket_map = {}
let g:emulate_socket_failure = 0
let g:next_channel_id = 0
let g:message_buffer = ''
let g:calls = []
function! ale#engine#IsExecutable(buffer, executable) abort
return !empty(a:executable)
endfunction
function! ale#job#HasOpenChannel(job_id) abort
return has_key(g:job_map, a:job_id)
endfunction
function! ale#job#Stop(job_id) abort
if has_key(g:job_map, a:job_id)
call remove(g:job_map, a:job_id)
endif
endfunction
function! ale#job#Start(command, options) abort
if g:emulate_job_failure
return 0
endif
let l:job_id = g:next_job_id
let g:next_job_id += 1
let g:job_map[l:job_id] = [a:command, a:options]
return l:job_id
endfunction
function! ale#job#SendRaw(job_id, data) abort
let g:message_buffer .= a:data
endfunction
function! ale#socket#IsOpen(channel_id) abort
return has_key(g:socket_map, a:channel_id)
endfunction
function! ale#socket#Close(channel_id) abort
if has_key(g:socket_map, a:channel_id)
call remove(g:socket_map, a:channel_id)
endif
endfunction
function! ale#socket#Open(address, options) abort
if g:emulate_socket_failure
return -1
endif
let l:channel_id = g:next_channel_id
let g:next_channel_id += 1
let g:socket_map[l:channel_id] = [a:address, a:options]
return l:channel_id
endfunction
function! ale#socket#Send(channel_id, data) abort
let g:message_buffer .= a:data
endfunction
function! PopMessages() abort
let l:message_list = []
for l:line in split(g:message_buffer, '\(\r\|\n\|Content-Length\)\+')
if l:line[:0] is '{'
let l:data = json_decode(l:line)
call add(l:message_list, l:data)
endif
endfor
let g:message_buffer = ''
return l:message_list
endfunction
function! SendMessage(message) abort
let l:conn_id = keys(ale#lsp#GetConnections())[0]
let l:body = json_encode(a:message)
let l:data = 'Content-Length: ' . strlen(l:body) . "\r\n\r\n" . l:body
call ale#lsp#HandleMessage(l:conn_id, l:data)
endfunction
function! Start(buffer) abort
let l:linter = values(ale#linter#GetLintersLoaded())[0][0]
return ale#lsp_linter#StartLSP(
\ a:buffer,
\ l:linter,
\ {linter, details -> add(g:calls, [linter.name, details])},
\)
endfunction
function! AssertInitSuccess(linter_name, conn_prefix, language, root, command, buffer) abort
let l:messages = PopMessages()
if a:linter_name is# 'tsserver'
AssertEqual
\ [
\ {
\ 'seq': v:null,
\ 'arguments': {
\ 'file': expand('#' . a:buffer . ':p'),
\ },
\ 'type': 'request',
\ 'command': 'open',
\ },
\ ],
\ l:messages
else
AssertEqual
\ [
\ {
\ 'method': 'initialize',
\ 'jsonrpc': '2.0',
\ 'id': 1,
\ 'params': {
\ 'initializationOptions': {},
\ 'rootUri': ale#path#ToFileURI(a:root),
\ 'rootPath': a:root,
\ 'processId': getpid(),
\ 'capabilities': {
\ 'workspace': {
\ 'applyEdit': v:false,
\ 'didChangeConfiguration': {
\ 'dynamicRegistration': v:false,
\ },
\ 'symbol': {
\ 'dynamicRegistration': v:false,
\ },
\ 'workspaceFolders': v:false,
\ 'configuration': v:false,
\ },
\ 'textDocument': {
\ 'synchronization': {
\ 'dynamicRegistration': v:false,
\ 'willSave': v:false,
\ 'willSaveWaitUntil': v:false,
\ 'didSave': v:true,
\ },
\ 'completion': {
\ 'dynamicRegistration': v:false,
\ 'completionItem': {
\ 'snippetSupport': v:false,
\ 'commitCharactersSupport': v:false,
\ 'documentationFormat': ['plaintext'],
\ 'deprecatedSupport': v:false,
\ 'preselectSupport': v:false,
\ },
\ 'contextSupport': v:false,
\ },
\ 'hover': {
\ 'dynamicRegistration': v:false,
\ 'contentFormat': ['plaintext'],
\ },
\ 'references': {
\ 'dynamicRegistration': v:false,
\ },
\ 'documentSymbol': {
\ 'dynamicRegistration': v:false,
\ 'hierarchicalDocumentSymbolSupport': v:false,
\ },
\ 'definition': {
\ 'dynamicRegistration': v:false,
\ 'linkSupport': v:false,
\ },
\ 'typeDefinition': {
\ 'dynamicRegistration': v:false,
\ },
\ 'implementation': {
\ 'dynamicRegistration': v:false,
\ 'linkSupport': v:false,
\ },
\ 'publishDiagnostics': {
\ 'relatedInformation': v:true,
\ },
\ 'codeAction': {
\ 'dynamicRegistration': v:false,
\ 'codeActionLiteralSupport': {
\ 'codeActionKind': {
\ 'valueSet': []
\ }
\ }
\ },
\ 'rename': {
\ 'dynamicRegistration': v:false,
\ },
\ },
\ },
\ },
\ },
\ ],
\ l:messages
call SendMessage({
\ 'jsonrpc': '2.0',
\ 'id': 1,
\ 'result': {
\ 'capabilities': {
\ 'renameProvider': v:true,
\ 'executeCommandProvider': {
\ 'commands': [],
\ },
\ 'hoverProvider': v:true,
\ 'documentSymbolProvider': v:true,
\ 'documentRangeFormattingProvider': v:true,
\ 'codeLensProvider': {
\ 'resolveProvider': v:false
\ },
\ 'referencesProvider': v:true,
\ 'textDocumentSync': 2,
\ 'documentFormattingProvider': v:true,
\ 'codeActionProvider': v:true,
\ 'signatureHelpProvider': {
\ 'triggerCharacters': ['(', ','],
\ },
\ 'completionProvider': {
\ 'triggerCharacters': ['.'],
\ 'resolveProvider': v:false
\ },
\ 'definitionProvider': v:true,
\ 'experimental': {},
\ 'documentHighlightProvider': v:true,
\ 'workspaceSymbolProvider': v:true,
\ },
\ },
\})
let l:messages = PopMessages()
AssertEqual
\ [
\ {
\ 'method': 'initialized',
\ 'jsonrpc': '2.0',
\ 'params': {},
\ },
\ {
\ 'method': 'textDocument/didOpen',
\ 'jsonrpc': '2.0',
\ 'params': {
\ 'textDocument': {
\ 'uri': ale#path#ToFileURI(expand('#' . a:buffer . ':p')),
\ 'version': ale#lsp#message#GetNextVersionID() - 1,
\ 'languageId': a:language,
\ 'text': "\n",
\ },
\ },
\ },
\ ],
\ l:messages
endif
AssertEqual
\ [
\ [
\ a:linter_name,
\ {
\ 'connection_id': a:conn_prefix . ':' . a:root,
\ 'project_root': a:root,
\ 'buffer': a:buffer,
\ 'command': !empty(a:command) ? ale#job#PrepareCommand(a:buffer, a:command) : '',
\ },
\ ],
\ ],
\ g:calls
endfunction
function! AssertInitFailure() abort
let l:messages = PopMessages()
AssertEqual [], l:messages
AssertEqual [], g:calls
endfunction
call ale#linter#Reset()
After:
Restore
call ale#linter#Reset()
call ale#lsp#ResetConnections()
unlet! g:ale_run_synchronously_callbacks
unlet! g:job_map
unlet! g:emulate_job_failure
unlet! g:next_job_id
unlet! g:lsp_started
unlet! g:socket_map
unlet! g:emulate_socket_failure
unlet! g:next_channel_id
unlet! g:message_buffer
unlet! g:calls
augroup VaderTest
autocmd!
augroup END
augroup! VaderTest
delfunction PopMessages
delfunction Start
delfunction AssertInitSuccess
delfunction AssertInitFailure
runtime autoload/ale/engine.vim
runtime autoload/ale/job.vim
runtime autoload/ale/socket.vim
Execute(tsserver should be started correctly):
runtime ale_linters/typescript/tsserver.vim
Assert Start(bufnr(''))
call AssertInitSuccess('tsserver', 'tsserver', '', '', ale#Escape('tsserver'), bufnr(''))
Execute(tsserver failures should be handled appropriately):
runtime ale_linters/typescript/tsserver.vim
let g:emulate_job_failure = 1
Assert !Start(bufnr(''))
call AssertInitFailure()
Execute(LSP jobs should start correctly):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'stdio',
\ 'executable': 'foo',
\ 'command': 'foo',
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
Assert Start(bufnr(''))
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', 'foo', bufnr(''))
Execute(LSP job failures should be handled):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'stdio',
\ 'executable': 'foo',
\ 'command': 'foo',
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
let g:emulate_job_failure = 1
Assert !Start(bufnr(''))
call AssertInitFailure()
Execute(LSP TCP connections should start correctly):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address': 'foo',
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
Assert Start(bufnr(''))
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', bufnr(''))
Execute(LSP TCP connection failures should be handled):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address': 'foo',
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
let g:emulate_socket_failure = 1
Assert !Start(bufnr(''))
call AssertInitFailure()
Execute(Deferred executables should be handled correctly):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'stdio',
\ 'executable': {b -> ale#command#Run(b, 'echo', {-> 'foo'})},
\ 'command': '%e -c',
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
Assert Start(bufnr(''))
call ale#test#FlushJobs()
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c', bufnr(''))
Execute(Deferred commands should be handled correctly):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'stdio',
\ 'executable': 'foo',
\ 'command': {b -> ale#command#Run(b, 'echo', {-> '%e -c'})},
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
Assert Start(bufnr(''))
call ale#test#FlushJobs()
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', ale#Escape('foo') . ' -c', bufnr(''))
Execute(Deferred addresses should be handled correctly):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address': {b -> ale#command#Run(b, 'echo', {-> 'localhost:1234'})},
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
Assert Start(bufnr(''))
call ale#test#FlushJobs()
call AssertInitSuccess('foo', 'localhost:1234', 'foobar', '/foo/bar', '', bufnr(''))
Execute(Servers that have crashed should be restarted):
call ale#lsp#Register('foo', '/foo/bar', {})
call extend(ale#lsp#GetConnections()['foo:/foo/bar'], {'initialized': 1})
" Starting the program again should reset initialized to `0`.
call ale#lsp#StartProgram('foo:/foo/bar', 'foobar', 'foobar --start')
AssertEqual 0, ale#lsp#GetConnections()['foo:/foo/bar']['initialized']
AssertEqual ['initialize'], map(PopMessages(), 'v:val[''method'']')
Execute(Current LSP buffer should receive ALELSPStarted):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address': 'foo',
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
augroup VaderTest
autocmd!
autocmd User ALELSPStarted let g:lsp_started = 1
augroup END
Assert Start(bufnr(''))
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', bufnr(''))
AssertEqual g:lsp_started, 1
Execute(Target LSP buffer should receive ALELSPStarted):
call ale#linter#Define('foobar', {
\ 'name': 'foo',
\ 'lsp': 'socket',
\ 'address': 'foo',
\ 'project_root': '/foo/bar',
\ 'initialization_options': {},
\})
augroup VaderTest
autocmd!
autocmd User ALELSPStarted let g:lsp_started = 1
augroup END
let buffer = bufnr('')
enew!
Assert Start(buffer)
call AssertInitSuccess('foo', 'foo', 'foobar', '/foo/bar', '', buffer)
execute 'buffer' . buffer
AssertEqual g:lsp_started, 1

View File

@ -0,0 +1,216 @@
Before:
runtime autoload/ale/lsp.vim
let g:message_list = []
let b:conn = {
\ 'id': 1,
\ 'is_tsserver': 0,
\ 'data': '',
\ 'root': '/foo/bar',
\ 'open_documents': {},
\ 'initialized': 0,
\ 'init_request_id': 0,
\ 'init_options': {},
\ 'config': {},
\ 'callback_list': [],
\ 'message_queue': [],
\ 'init_queue': [],
\ 'capabilities': {
\ 'hover': 0,
\ 'rename': 0,
\ 'references': 0,
\ 'completion': 0,
\ 'completion_trigger_characters': [],
\ 'definition': 0,
\ 'symbol_search': 0,
\ 'code_actions': 0,
\ },
\}
function! ale#lsp#Send(conn_id, message) abort
call add(g:message_list, a:message)
return 42
endfunction
After:
unlet! b:conn
unlet! g:message_list
runtime autoload/ale/lsp.vim
Execute(Messages with no method and capabilities should initialize projects):
call ale#lsp#HandleInitResponse(b:conn, {
\ 'result': {'capabilities': {}},
\})
AssertEqual 1, b:conn.initialized
AssertEqual [[1, 'initialized', {}]], g:message_list
Execute(Other messages should not initialize projects):
call ale#lsp#HandleInitResponse(b:conn, {'method': 'lolwat'})
AssertEqual 0, b:conn.initialized
AssertEqual [], g:message_list
call ale#lsp#HandleInitResponse(b:conn, {'result': {'x': {}}})
AssertEqual 0, b:conn.initialized
AssertEqual [], g:message_list
Execute(Capabilities should bet set up correctly):
call ale#lsp#HandleInitResponse(b:conn, {
\ 'jsonrpc': '2.0',
\ 'id': 1,
\ 'result': {
\ 'capabilities': {
\ 'renameProvider': v:true,
\ 'executeCommandProvider': {
\ 'commands': [],
\ },
\ 'hoverProvider': v:true,
\ 'documentSymbolProvider': v:true,
\ 'documentRangeFormattingProvider': v:true,
\ 'codeLensProvider': {
\ 'resolveProvider': v:false
\ },
\ 'referencesProvider': v:true,
\ 'textDocumentSync': 2,
\ 'documentFormattingProvider': v:true,
\ 'codeActionProvider': v:true,
\ 'signatureHelpProvider': {
\ 'triggerCharacters': ['(', ','],
\ },
\ 'completionProvider': {
\ 'triggerCharacters': ['.'],
\ 'resolveProvider': v:false
\ },
\ 'definitionProvider': v:true,
\ 'experimental': {},
\ 'documentHighlightProvider': v:true,
\ 'workspaceSymbolProvider': v:true
\ },
\ },
\})
AssertEqual 1, b:conn.initialized
AssertEqual
\ {
\ 'completion_trigger_characters': ['.'],
\ 'completion': 1,
\ 'references': 1,
\ 'hover': 1,
\ 'definition': 1,
\ 'symbol_search': 1,
\ 'rename': 1,
\ 'code_actions': 1,
\ },
\ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list
Execute(Disabled capabilities should be recognised correctly):
call ale#lsp#HandleInitResponse(b:conn, {
\ 'jsonrpc': '2.0',
\ 'id': 1,
\ 'result': {
\ 'capabilities': {
\ 'renameProvider': v:false,
\ 'executeCommandProvider': {
\ 'commands': [],
\ },
\ 'hoverProvider': v:false,
\ 'documentSymbolProvider': v:true,
\ 'documentRangeFormattingProvider': v:true,
\ 'codeLensProvider': {
\ 'resolveProvider': v:false
\ },
\ 'referencesProvider': v:false,
\ 'textDocumentSync': 2,
\ 'documentFormattingProvider': v:true,
\ 'codeActionProvider': v:false,
\ 'signatureHelpProvider': {
\ 'triggerCharacters': ['(', ','],
\ },
\ 'definitionProvider': v:false,
\ 'experimental': {},
\ 'documentHighlightProvider': v:true,
\ },
\ },
\})
AssertEqual 1, b:conn.initialized
AssertEqual
\ {
\ 'completion_trigger_characters': [],
\ 'completion': 0,
\ 'references': 0,
\ 'hover': 0,
\ 'definition': 0,
\ 'symbol_search': 0,
\ 'rename': 0,
\ 'code_actions': 0,
\ },
\ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list
Execute(Capabilities should be enabled when send as Dictionaries):
call ale#lsp#HandleInitResponse(b:conn, {
\ 'jsonrpc': '2.0',
\ 'id': 1,
\ 'result': {
\ 'capabilities': {
\ 'renameProvider': {},
\ 'executeCommandProvider': {
\ 'commands': [],
\ },
\ 'hoverProvider': {},
\ 'documentSymbolProvider': v:true,
\ 'documentRangeFormattingProvider': v:true,
\ 'codeLensProvider': {
\ 'resolveProvider': v:false
\ },
\ 'completionProvider': {
\ 'triggerCharacters': ['.'],
\ 'resolveProvider': v:false
\ },
\ 'referencesProvider': {},
\ 'textDocumentSync': 2,
\ 'documentFormattingProvider': v:true,
\ 'codeActionProvider': v:true,
\ 'signatureHelpProvider': {
\ 'triggerCharacters': ['(', ','],
\ },
\ 'definitionProvider': {},
\ 'typeDefinitionProvider': {},
\ 'implementationProvider': {},
\ 'experimental': {},
\ 'documentHighlightProvider': v:true,
\ 'workspaceSymbolProvider': {}
\ },
\ },
\})
AssertEqual 1, b:conn.initialized
AssertEqual
\ {
\ 'completion_trigger_characters': ['.'],
\ 'completion': 1,
\ 'references': 1,
\ 'hover': 1,
\ 'definition': 1,
\ 'typeDefinition': 1,
\ 'implementation': 1,
\ 'symbol_search': 1,
\ 'rename': 1,
\ 'code_actions': 1,
\ },
\ b:conn.capabilities
AssertEqual [[1, 'initialized', {}]], g:message_list
Execute(Results that are not dictionaries should be handled correctly):
call ale#lsp#HandleInitResponse(b:conn, {
\ 'jsonrpc': '2.0',
\ 'id': 1,
\ 'result': v:null,
\})
AssertEqual [], g:message_list

View File

@ -0,0 +1,257 @@
Before:
function Range(start_line, start_char, end_line, end_char) abort
return {
\ 'start': {'line': a:start_line, 'character': a:start_char},
\ 'end': {'line': a:end_line, 'character': a:end_char},
\}
endfunction
After:
delfunction Range
Execute(ale#lsp#response#ReadDiagnostics() should handle errors):
AssertEqual [
\ {
\ 'type': 'E',
\ 'text': 'Something went wrong!',
\ 'lnum': 3,
\ 'col': 11,
\ 'end_lnum': 5,
\ 'end_col': 15,
\ 'code': 'some-error',
\ }
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'severity': 1,
\ 'range': Range(2, 10, 4, 15),
\ 'code': 'some-error',
\ 'message': 'Something went wrong!',
\ },
\ ]}})
Execute(ale#lsp#response#ReadDiagnostics() should handle warnings):
AssertEqual [
\ {
\ 'type': 'W',
\ 'text': 'Something went wrong!',
\ 'lnum': 2,
\ 'col': 4,
\ 'end_lnum': 2,
\ 'end_col': 3,
\ 'code': 'some-warning',
\ }
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'severity': 2,
\ 'range': Range(1, 3, 1, 3),
\ 'code': 'some-warning',
\ 'message': 'Something went wrong!',
\ },
\ ]}})
Execute(ale#lsp#response#ReadDiagnostics() should treat messages with missing severity as errors):
AssertEqual [
\ {
\ 'type': 'E',
\ 'text': 'Something went wrong!',
\ 'lnum': 3,
\ 'col': 11,
\ 'end_lnum': 5,
\ 'end_col': 15,
\ 'code': 'some-error',
\ }
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'range': Range(2, 10, 4, 15),
\ 'code': 'some-error',
\ 'message': 'Something went wrong!',
\ },
\ ]}})
Execute(ale#lsp#response#ReadDiagnostics() should handle messages without codes):
AssertEqual [
\ {
\ 'type': 'E',
\ 'text': 'Something went wrong!',
\ 'lnum': 3,
\ 'col': 11,
\ 'end_lnum': 5,
\ 'end_col': 15,
\ }
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'range': Range(2, 10, 4, 15),
\ 'message': 'Something went wrong!',
\ },
\ ]}})
Execute(ale#lsp#response#ReadDiagnostics() should include sources in detail):
AssertEqual [
\ {
\ 'type': 'E',
\ 'text': 'Something went wrong!',
\ 'detail': '[tslint] Something went wrong!',
\ 'lnum': 10,
\ 'col': 15,
\ 'end_lnum': 12,
\ 'end_col': 22,
\ }
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'range': Range(9, 14, 11, 22),
\ 'message': 'Something went wrong!',
\ 'source': 'tslint',
\ }
\ ]}})
Execute(ale#lsp#response#ReadDiagnostics() should keep detail with line breaks but replace with spaces in text):
AssertEqual [
\ {
\ 'type': 'E',
\ 'text': 'cannot borrow `cap` as mutable more than once at a time mutable borrow starts here in previous iteration of loop',
\ 'detail': "[rustc] cannot borrow `cap` as mutable\r\nmore than once at a time\n\nmutable borrow starts here\rin previous iteration of loop",
\ 'lnum': 10,
\ 'col': 15,
\ 'end_lnum': 12,
\ 'end_col': 22,
\ }
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'range': Range(9, 14, 11, 22),
\ 'message': "cannot borrow `cap` as mutable\r\nmore than once at a time\n\nmutable borrow starts here\rin previous iteration of loop",
\ 'source': 'rustc',
\ }
\ ]}})
Execute(ale#lsp#response#ReadDiagnostics() should consider -1 to be a meaningless code):
AssertEqual [
\ {
\ 'type': 'E',
\ 'text': 'Something went wrong!',
\ 'lnum': 3,
\ 'col': 11,
\ 'end_lnum': 5,
\ 'end_col': 15,
\ }
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'range': Range(2, 10, 4, 15),
\ 'message': 'Something went wrong!',
\ 'code': -1,
\ },
\ ]}})
Execute(ale#lsp#response#ReadDiagnostics() should handle multiple messages):
AssertEqual [
\ {
\ 'type': 'E',
\ 'text': 'Something went wrong!',
\ 'lnum': 1,
\ 'col': 3,
\ 'end_lnum': 1,
\ 'end_col': 2,
\ },
\ {
\ 'type': 'W',
\ 'text': 'A warning',
\ 'lnum': 2,
\ 'col': 5,
\ 'end_lnum': 2,
\ 'end_col': 4,
\ },
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'range': Range(0, 2, 0, 2),
\ 'message': 'Something went wrong!',
\ },
\ {
\ 'severity': 2,
\ 'range': Range(1, 4, 1, 4),
\ 'message': 'A warning',
\ },
\ ]}})
Execute(ale#lsp#response#ReadDiagnostics() should use relatedInformation for detail):
AssertEqual [
\ {
\ 'type': 'E',
\ 'text': 'Something went wrong!',
\ 'lnum': 1,
\ 'col': 3,
\ 'end_lnum': 1,
\ 'end_col': 2,
\ 'detail': "Something went wrong!\n/tmp/someotherfile.txt:43:80:\n\tmight be this"
\ }
\ ],
\ ale#lsp#response#ReadDiagnostics({'params': {'uri': 'filename.ts', 'diagnostics': [
\ {
\ 'range': Range(0, 2, 0, 2),
\ 'message': 'Something went wrong!',
\ 'relatedInformation': [{
\ 'message': 'might be this',
\ 'location': {
\ 'uri': 'file:///tmp/someotherfile.txt',
\ 'range': {
\ 'start': { 'line': 42, 'character': 79 },
\ 'end': { 'line': 142, 'character': 179},
\ }
\ }
\ }]
\ }
\ ]}})
Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle tsserver responses):
AssertEqual
\ [
\ {
\ 'type': 'E',
\ 'nr': 2365,
\ 'code': '2365',
\ 'text': 'Operator ''''+'''' cannot be applied to types ''''3'''' and ''''{}''''.',
\ 'lnum': 1,
\ 'col': 11,
\ 'end_lnum': 1,
\ 'end_col': 16,
\ },
\ ],
\ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"/bar/foo.ts","diagnostics":[{"start":{"line":1,"offset":11},"end":{"line":1,"offset":17},"text":"Operator ''+'' cannot be applied to types ''3'' and ''{}''.","code":2365}]}})
Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle warnings from tsserver):
AssertEqual
\ [
\ {
\ 'lnum': 27,
\ 'col': 3,
\ 'nr': 2515,
\ 'code': '2515',
\ 'end_lnum': 27,
\ 'type': 'W',
\ 'end_col': 13,
\ 'text': 'Calls to ''console.log'' are not allowed. (no-console)',
\ }
\ ],
\ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"<removed>","diagnostics":[{"start":{"line":27,"offset":3},"end":{"line":27,"offset":14},"text":"Calls to 'console.log' are not allowed. (no-console)","code":2515,"category":"warning","source":"tslint"}]}})
Execute(ale#lsp#response#ReadTSServerDiagnostics() should handle suggestions from tsserver):
AssertEqual
\ [
\ {
\ 'lnum': 27,
\ 'col': 3,
\ 'nr': 2515,
\ 'code': '2515',
\ 'end_lnum': 27,
\ 'type': 'I',
\ 'end_col': 13,
\ 'text': 'Some info',
\ }
\ ],
\ ale#lsp#response#ReadTSServerDiagnostics({"seq":0,"type":"event","event":"semanticDiag","body":{"file":"<removed>","diagnostics":[{"start":{"line":27,"offset":3},"end":{"line":27,"offset":14},"text":"Some info","code":2515,"category":"suggestion","source":"tslint"}]}})

View File

@ -0,0 +1,98 @@
Before:
Save g:ale_enabled
Save g:ale_set_signs
Save g:ale_set_quickfix
Save g:ale_set_loclist
Save g:ale_set_highlights
Save g:ale_echo_cursor
let g:ale_enabled = 0
let g:ale_set_signs = 0
let g:ale_set_quickfix = 0
let g:ale_set_loclist = 0
let g:ale_set_highlights = 0
let g:ale_echo_cursor = 0
function EmptyString() abort
return ''
endfunction
call ale#engine#InitBufferInfo(bufnr(''))
" Call this function first, so we can be sure the module is loaded before we
" check if it exists.
call ale#lsp_linter#ClearLSPData()
call ale#linter#Define('testft', {
\ 'name': 'lsplinter',
\ 'lsp': 'tsserver',
\ 'executable': function('EmptyString'),
\ 'command': function('EmptyString'),
\ 'project_root': function('EmptyString'),
\ 'language': function('EmptyString'),
\})
call ale#linter#Define('testft', {
\ 'name': 'otherlinter',
\ 'callback': 'TestCallback',
\ 'executable': has('win32') ? 'cmd': 'true',
\ 'command': 'true',
\ 'read_buffer': 0,
\})
After:
Restore
unlet! b:ale_save_event_fired
delfunction EmptyString
call ale#linter#Reset()
Given testft(Some file with an imaginary filetype):
Execute(ALEStopAllLSPs should clear the loclist):
let g:ale_buffer_info[bufnr('')].loclist = [
\ {
\ 'text': 'a',
\ 'lnum': 10,
\ 'col': 0,
\ 'bufnr': bufnr(''),
\ 'vcol': 0,
\ 'type': 'E',
\ 'nr': -1,
\ 'linter_name': 'lsplinter',
\ },
\ {
\ 'text': 'a',
\ 'lnum': 10,
\ 'col': 0,
\ 'bufnr': bufnr(''),
\ 'vcol': 0,
\ 'type': 'E',
\ 'nr': -1,
\ 'linter_name': 'otherlinter',
\ },
\]
let g:ale_buffer_info[bufnr('')].active_linter_list = [
\ {'name': 'lsplinter'},
\ {'name': 'otherlinter'},
\]
ALEStopAllLSPs
" The loclist should be updated.
AssertEqual g:ale_buffer_info[bufnr('')].loclist, [
\ {
\ 'text': 'a',
\ 'lnum': 10,
\ 'col': 0,
\ 'bufnr': bufnr(''),
\ 'vcol': 0,
\ 'type': 'E',
\ 'nr': -1,
\ 'linter_name': 'otherlinter',
\ },
\]
" The LSP linter should be removed from the active linter list.
AssertEqual
\ ['otherlinter'],
\ map(copy(g:ale_buffer_info[bufnr('')].active_linter_list), 'v:val.name')

View File

@ -0,0 +1,21 @@
Before:
runtime autoload/ale/lsp.vim
let g:conn_id = ale#lsp#Register('executable', '/foo/bar', {})
" Stub out this function, so we test updating configs.
function! ale#lsp#Send(conn_id, message) abort
endfunction
After:
Restore
unlet! g:conn_id
runtime autoload/ale/lsp.vim
Execute(Only send updates when the configuration dictionary changes):
AssertEqual 0, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {})
AssertEqual 1, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {'a': 1})
AssertEqual 0, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {'a': 1})
AssertEqual 1, ale#lsp#UpdateConfig(g:conn_id, bufnr(''), {})