All patches and comments are welcome. Please squash your changes to logical
commits before using git-format-patch and git-send-email to
patches@git.madduck.net.
If you'd read over the Git project's submission guidelines and adhered to them,
I'd be especially grateful.
2 call ale#test#SetDirectory('/testplugin/test')
3 call ale#test#SetFilename('dummy.txt')
6 let g:ale_buffer_info = {}
8 let g:old_filename = expand('%:p')
11 let g:message_list = []
12 let g:handle_code_action_called = 0
13 let g:code_actions = []
15 let g:capability_checked = ''
16 let g:conn_id = v:null
17 let g:InitCallback = v:null
19 runtime autoload/ale/lsp_linter.vim
20 runtime autoload/ale/lsp.vim
21 runtime autoload/ale/util.vim
22 runtime autoload/ale/codefix.vim
23 runtime autoload/ale/code_action.vim
25 function! ale#lsp_linter#StartLSP(buffer, linter, Callback) abort
26 let g:conn_id = ale#lsp#Register('executable', '/foo/bar', '', {})
27 call ale#lsp#MarkDocumentAsOpen(g:conn_id, a:buffer)
29 if a:linter.lsp is# 'tsserver'
30 call ale#lsp#MarkConnectionAsTsserver(g:conn_id)
34 \ 'command': 'foobar',
36 \ 'connection_id': g:conn_id,
37 \ 'project_root': '/foo/bar',
40 let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)}
43 function! ale#lsp#HasCapability(conn_id, capability) abort
44 let g:capability_checked = a:capability
49 function! ale#lsp#RegisterCallback(conn_id, callback) abort
50 let g:Callback = a:callback
53 function! ale#lsp#Send(conn_id, message) abort
54 call add(g:message_list, a:message)
59 function! ale#util#Execute(expr) abort
60 call add(g:expr_list, a:expr)
63 function! ale#code_action#HandleCodeAction(code_action, options) abort
64 let g:handle_code_action_called = 1
65 Assert !get(a:options, 'should_save')
66 call add(g:code_actions, a:code_action)
69 function! ale#util#Input(message, value) abort
76 if g:conn_id isnot v:null
77 call ale#lsp#RemoveConnectionWithID(g:conn_id)
80 call ale#test#RestoreDirectory()
81 call ale#linter#Reset()
83 unlet! g:capability_checked
93 unlet! g:handle_code_action_called
95 runtime autoload/ale/lsp_linter.vim
96 runtime autoload/ale/lsp.vim
97 runtime autoload/ale/util.vim
98 runtime autoload/ale/codefix.vim
99 runtime autoload/ale/code_action.vim
101 Execute(Failed codefix responses should be handled correctly):
102 call ale#codefix#HandleTSServerResponse(
104 \ {'command': 'getCodeFixes', 'request_seq': 3}
106 AssertEqual g:handle_code_action_called, 0
108 Given typescript(Some typescript file):
113 Execute(getCodeFixes from tsserver should be handled):
114 call ale#codefix#SetMap({3: {}})
115 call ale#codefix#HandleTSServerResponse(1, {
116 \ 'command': 'getCodeFixes',
119 \ 'type': 'response',
122 \ 'description': 'Import default "x" from module "./z"',
123 \ 'fixName': 'import',
126 \ 'fileName': "/foo/bar/file1.ts",
133 \ 'newText': 'import x from "./z";^@',
146 AssertEqual g:handle_code_action_called, 1
150 \ 'description': 'codefix',
153 \ 'fileName': "/foo/bar/file1.ts",
160 \ 'newText': 'import x from "./z";^@',
173 Execute(getCodeFixes from tsserver should be handled with user input if there are more than one action):
174 call ale#codefix#SetMap({3: {}})
175 call ale#codefix#HandleTSServerResponse(1, {
176 \ 'command': 'getCodeFixes',
179 \ 'type': 'response',
182 \ 'description': 'Import default "x" from module "./z"',
183 \ 'fixName': 'import',
186 \ 'fileName': "/foo/bar/file1.ts",
193 \ 'newText': 'import x from "./z";^@',
204 \ 'description': 'Import default "x" from module "./y"',
205 \ 'fixName': 'import',
208 \ 'fileName': "/foo/bar/file1.ts",
215 \ 'newText': 'import x from "./y";^@',
228 AssertEqual g:handle_code_action_called, 1
232 \ 'description': 'codefix',
235 \ 'fileName': "/foo/bar/file1.ts",
242 \ 'newText': 'import x from "./y";^@',
255 Execute(Prints a tsserver error message when getCodeFixes unsuccessful):
256 call ale#codefix#SetMap({3: {}})
257 call ale#codefix#HandleTSServerResponse(1, {
258 \ 'command': 'getCodeFixes',
260 \ 'success': v:false,
261 \ 'message': 'something is wrong',
264 AssertEqual g:handle_code_action_called, 0
265 AssertEqual ['echom ''Error while getting code fixes. Reason: something is wrong'''], g:expr_list
267 Execute(Does nothing when where are no code fixes):
268 call ale#codefix#SetMap({3: {}})
269 call ale#codefix#HandleTSServerResponse(1, {
270 \ 'command': 'getCodeFixes',
276 AssertEqual g:handle_code_action_called, 0
277 AssertEqual ['echom ''No code fixes available.'''], g:expr_list
279 Execute(tsserver codefix requests should be sent):
280 call ale#linter#Reset()
282 runtime ale_linters/typescript/tsserver.vim
283 let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'code': 2304, 'linter_name': 'tsserver'}]}}
284 call setpos('.', [bufnr(''), 2, 16, 0])
287 call ale#codefix#Execute(0)
289 " We shouldn't register the callback yet.
290 AssertEqual '''''', string(g:Callback)
292 AssertEqual type(function('type')), type(g:InitCallback)
293 call g:InitCallback()
295 AssertEqual 'code_actions', g:capability_checked
297 \ 'function(''ale#codefix#HandleTSServerResponse'')',
301 \ ale#lsp#tsserver_message#Change(bufnr('')),
302 \ [0, 'ts@getCodeFixes', {
307 \ 'file': expand('%:p'),
308 \ 'errorCodes': [2304],
313 Execute(tsserver codefix requests should be sent only for error with code):
314 call ale#linter#Reset()
316 runtime ale_linters/typescript/tsserver.vim
317 let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 16, 'linter_name': 'tsserver'}, {'lnum': 2, 'col': 16, 'code': 2304, 'linter_name': 'tsserver'}]}}
318 call setpos('.', [bufnr(''), 2, 16, 0])
321 call ale#codefix#Execute(0)
323 " We shouldn't register the callback yet.
324 AssertEqual '''''', string(g:Callback)
326 AssertEqual type(function('type')), type(g:InitCallback)
327 call g:InitCallback()
329 AssertEqual 'code_actions', g:capability_checked
331 \ 'function(''ale#codefix#HandleTSServerResponse'')',
335 \ ale#lsp#tsserver_message#Change(bufnr('')),
336 \ [0, 'ts@getCodeFixes', {
341 \ 'file': expand('%:p'),
342 \ 'errorCodes': [2304],
347 Execute(getApplicableRefactors from tsserver should be handled):
348 call ale#codefix#SetMap({3: {
349 \ 'buffer': expand('%:p'),
354 \ 'connection_id': 0,
356 call ale#codefix#HandleTSServerResponse(1,
357 \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': [{'actions': [{'description': 'Extract to constant in enclosing scope', 'name': 'constant_scope_0'}], 'description': 'Extract constant', 'name': 'Extract Symbol'}, {'actions': [{'description': 'Extract to function in module scope', 'name': 'function_scope_1'}], 'description': 'Extract function', 'name': 'Extract Symbol'}], 'command': 'getApplicableRefactors'})
361 \ [0, 'ts@getEditsForRefactor', {
366 \ 'file': expand('%:p'),
367 \ 'refactor': 'Extract Symbol',
368 \ 'action': 'function_scope_1',
373 Execute(getApplicableRefactors should print error on failure):
374 call ale#codefix#SetMap({3: {
375 \ 'buffer': expand('%:p'),
380 \ 'connection_id': 0,
382 call ale#codefix#HandleTSServerResponse(1,
383 \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:false, 'message': 'oops', 'command': 'getApplicableRefactors'})
385 AssertEqual ['echom ''Error while getting applicable refactors. Reason: oops'''], g:expr_list
387 Execute(getApplicableRefactors should do nothing if there are no refactors):
388 call ale#codefix#SetMap({3: {
389 \ 'buffer': expand('%:p'),
394 \ 'connection_id': 0,
396 call ale#codefix#HandleTSServerResponse(1,
397 \ {'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': [], 'command': 'getApplicableRefactors'})
399 AssertEqual ['echom ''No applicable refactors available.'''], g:expr_list
401 Execute(getEditsForRefactor from tsserver should be handled):
402 call ale#codefix#SetMap({3: {}})
403 call ale#codefix#HandleTSServerResponse(1,
404 \{'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:true, 'body': {'edits': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 35, 'line': 9}, 'newText': 'newFunction(app);', 'start': {'offset': 3, 'line': 8}}, {'end': {'offset': 4, 'line': 19}, 'newText': '^@function newFunction(app: Router) {^@ app.use(booExpressCsrf());^@ app.use(booExpressRequireHttps);^@}^@', 'start': {'offset': 4, 'line': 19}}]}], 'renameLocation': {'offset': 3, 'line': 8}, 'renameFilename': '/foo/bar/file.ts'}, 'command': 'getEditsForRefactor' }
407 AssertEqual g:handle_code_action_called, 1
411 \ 'description': 'editsForRefactor',
412 \ 'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 35, 'line': 9}, 'newText': 'newFunction(app);', 'start': {'offset': 3, 'line': 8}}, {'end': {'offset': 4, 'line': 19}, 'newText': '^@function newFunction(app: Router) {^@ app.use(booExpressCsrf());^@ app.use(booExpressRequireHttps);^@}^@', 'start': {'offset': 4, 'line': 19}}]}],
417 Execute(getEditsForRefactor should print error on failure):
418 call ale#codefix#SetMap({3: {}})
419 call ale#codefix#HandleTSServerResponse(1,
420 \{'seq': 0, 'request_seq': 3, 'type': 'response', 'success': v:false, 'message': 'oops', 'command': 'getEditsForRefactor' }
423 AssertEqual ['echom ''Error while getting edits for refactor. Reason: oops'''], g:expr_list
425 Execute(Failed LSP responses should be handled correctly):
426 call ale#codefix#HandleLSPResponse(
428 \ {'method': 'workspace/applyEdit', 'request_seq': 3}
430 AssertEqual g:handle_code_action_called, 0
432 Given python(Some python file):
437 Execute("workspace/applyEdit" from LSP should be handled):
438 call ale#codefix#SetMap({3: {}})
439 call ale#codefix#HandleLSPResponse(1,
440 \ {'id': 0, 'jsonrpc': '2.0', 'method': 'workspace/applyEdit', 'params': {'edit': {'changes': {'file:///foo/bar/file.ts': [{'range': {'end': {'character': 27, 'line': 7}, 'start': {'character': 27, 'line': 7}}, 'newText': ', Config'}, {'range': {'end': {'character': 12, 'line': 96}, 'start': {'character': 2, 'line': 94}}, 'newText': 'await newFunction(redis, imageKey, cover, config);'}, {'range': {'end': {'character': 2, 'line': 99}, 'start': {'character': 2, 'line': 99}}, 'newText': '^@async function newFunction(redis: IRedis, imageKey: string, cover: Buffer, config: Config) {^@ try {^@ await redis.set(imageKey, cover, ''ex'', parseInt(config.coverKeyTTL, 10));^@ }^@ catch { }^@}^@'}]}}}})
442 AssertEqual g:handle_code_action_called, 1
444 \ [{'description': 'applyEdit', 'changes': [{'fileName': '/foo/bar/file.ts', 'textChanges': [{'end': {'offset': 28, 'line': 8}, 'newText': ', Config', 'start': {'offset': 28, 'line': 8}}, {'end': {'offset': 13, 'line': 97}, 'newText': 'await newFunction(redis, imageKey, cover, config);', 'start': {'offset': 3, 'line': 95}}, {'end': {'offset': 3, 'line': 100}, 'newText': '^@async function newFunction(redis: IRedis, imageKey: string, cover: Buffer, config: Config) {^@ try {^@ await redis.set(imageKey, cover, ''ex'', parseInt(config.coverKeyTTL, 10));^@ }^@ catch { }^@}^@', 'start': {'offset': 3, 'line': 100}}]}]}],
447 Execute(Code Actions from LSP should be handled when returned with documentChanges):
448 call ale#codefix#SetMap({2: {}})
449 call ale#codefix#HandleLSPResponse(1,
450 \ {'id': 2, 'jsonrpc': '2.0', 'result': [{'diagnostics': v:null, 'edit': {'changes': v:null, 'documentChanges': [{'edits': [{'range': {'end': {'character': 4, 'line': 2}, 'start': {'character': 4, 'line': 1}}, 'newText': ''}, {'range': {'end': {'character': 9, 'line': 2}, 'start': {'character': 8, 'line': 2}}, 'newText': '(1)'}], 'textDocument': {'uri': 'file:///foo/bar/test.py', 'version': v:null}}]}, 'kind': 'refactor.inline', 'title': 'Inline variable', 'command': v:null}, {'diagnostics': v:null, 'edit': {'changes': v:null, 'documentChanges': [{'edits': [{'range': {'end': {'character': 0, 'line': 0}, 'start': {'character': 0, 'line': 0}}, 'newText': 'def func_bomdjnxh():^@ a = 1return a^@^@^@'}, {'range': {'end': {'character': 9, 'line': 1}, 'start': {'character': 8, 'line': 1}}, 'newText': 'func_bomdjnxh()^@'}], 'textDocument': {'uri': 'file:///foo/bar/test.py', 'version': v:null}}]}, 'kind': 'refactor.extract', 'title': 'Extract expression into function ''func_bomdjnxh''', 'command': v:null}]})
452 AssertEqual g:handle_code_action_called, 1
454 \ [{'description': 'codeaction', 'changes': [{'fileName': '/foo/bar/test.py', 'textChanges': [{'end': {'offset': 1, 'line': 1}, 'newText': 'def func_bomdjnxh():^@ a = 1return a^@^@^@', 'start': {'offset': 1, 'line': 1}}, {'end': {'offset': 10, 'line': 2}, 'newText': 'func_bomdjnxh()^@', 'start': {'offset': 9, 'line': 2}}]}]}],
457 Execute(LSP Code Actions handles CodeAction responses):
458 call ale#codefix#SetMap({3: {
459 \ 'connection_id': 0,
461 call ale#codefix#HandleLSPResponse(1,
462 \ {'id': 3, 'jsonrpc': '2.0', 'result': [{'kind': 'refactor', 'title': 'Extract to inner function in function ''getVideo''', 'command': {'arguments': [{'file': '/foo/bar/file.ts', 'endOffset': 0, 'action': 'function_scope_0', 'startOffset': 1, 'startLine': 65, 'refactor': 'Extract Symbol', 'endLine': 68}], 'title': 'Extract to inner function in function ''getVideo''', 'command': '_typescript.applyRefactoring'}}, {'kind': 'refactor', 'title': 'Extract to function in module scope', 'command': {'arguments': [{'file': '/foo/bar/file.ts', 'endOffset': 0, 'action': 'function_scope_1', 'startOffset': 1, 'startLine': 65, 'refactor': 'Extract Symbol', 'endLine': 68}], 'title': 'Extract to function in module scope', 'command': '_typescript.applyRefactoring'}}]})
465 \ [[0, 'workspace/executeCommand', {'arguments': [{'file': '/foo/bar/file.ts', 'action': 'function_scope_1', 'endOffset': 0, 'refactor': 'Extract Symbol', 'endLine': 68, 'startLine': 65, 'startOffset': 1}], 'command': '_typescript.applyRefactoring'}]],
468 Execute(LSP Code Actions handles Command responses):
469 call ale#codefix#SetMap({2: {
470 \ 'connection_id': 2,
472 call ale#codefix#HandleLSPResponse(1,
473 \ {'id': 2, 'jsonrpc': '2.0', 'result': [{'title': 'fake for testing'}, {'arguments': [{'documentChanges': [{'edits': [{'range': {'end': {'character': 31, 'line': 2}, 'start': {'character': 31, 'line': 2}}, 'newText': ', createVideo'}], 'textDocument': {'uri': 'file:///foo/bar/file.ts', 'version': 1}}]}], 'title': 'Add ''createVideo'' to existing import declaration from "./video"', 'command': '_typescript.applyWorkspaceEdit'}]})
476 \ [[0, 'workspace/executeCommand', {'arguments': [{'documentChanges': [{'edits': [{'range': {'end': {'character': 31, 'line': 2}, 'start': {'character': 31, 'line': 2}}, 'newText': ', createVideo'}], 'textDocument': {'uri': 'file:///foo/bar/file.ts', 'version': 1}}]}], 'command': '_typescript.applyWorkspaceEdit'}]],
479 Execute(Prints message when LSP code action returns no results):
480 call ale#codefix#SetMap({3: {}})
481 call ale#codefix#HandleLSPResponse(1,
482 \ {'id': 3, 'jsonrpc': '2.0', 'result': []})
484 AssertEqual g:handle_code_action_called, 0
485 AssertEqual ['echom ''No code actions received from server'''], g:expr_list
487 Execute(LSP code action requests should be sent):
488 call ale#linter#Reset()
490 runtime ale_linters/python/jedils.vim
491 let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'end_lnum': 2, 'end_col': 6, 'code': 2304, 'text': 'oops'}]}}
492 call setpos('.', [bufnr(''), 2, 5, 0])
495 call ale#codefix#Execute(0)
497 " We shouldn't register the callback yet.
498 AssertEqual '''''', string(g:Callback)
500 AssertEqual type(function('type')), type(g:InitCallback)
501 call g:InitCallback()
503 AssertEqual 'code_actions', g:capability_checked
505 \ 'function(''ale#codefix#HandleLSPResponse'')',
509 \ [0, 'textDocument/codeAction', {
511 \ 'diagnostics': [{'range': {'end': {'character': 6, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}]
513 \ 'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}},
514 \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}
517 \ g:message_list[-1:]
519 Execute(LSP code action requests should be sent only for error with code):
520 call ale#linter#Reset()
522 runtime ale_linters/python/jedils.vim
523 let g:ale_buffer_info = {bufnr(''): {'loclist': [{'lnum': 2, 'col': 5, 'end_lnum': 2, 'end_col': 6, 'code': 2304, 'text': 'oops'}]}}
524 call setpos('.', [bufnr(''), 2, 5, 0])
527 call ale#codefix#Execute(0)
529 " We shouldn't register the callback yet.
530 AssertEqual '''''', string(g:Callback)
532 AssertEqual type(function('type')), type(g:InitCallback)
533 call g:InitCallback()
535 AssertEqual 'code_actions', g:capability_checked
537 \ 'function(''ale#codefix#HandleLSPResponse'')',
541 \ [0, 'textDocument/codeAction', {
543 \ 'diagnostics': [{'range': {'end': {'character': 6, 'line': 1}, 'start': {'character': 4, 'line': 1}}, 'code': 2304, 'message': 'oops'}]
545 \ 'range': {'end': {'character': 5, 'line': 1}, 'start': {'character': 4, 'line': 1}},
546 \ 'textDocument': {'uri': ale#path#ToFileURI(expand('%:p'))}
549 \ g:message_list[-1:]