mirror of
https://github.com/dense-analysis/ale.git
synced 2026-01-10 13:32:34 +08:00
Add ALERename (tsserver & LSP), ALEOrganizeImports (tsserver) and auto import support (tsserver) (#2709)
This commit adds support for renaming symbols in tsserver and with LSP tools, and for organising imports with tsserver. Completion results for symbols that can be imported are now suggested if enabled for tsserver completion done via ALE.
This commit is contained in:
@@ -47,6 +47,14 @@ Before:
|
||||
AssertEqual a:expect_success, g:get_completions_called
|
||||
endfunction
|
||||
|
||||
let g:handle_code_action_called = 0
|
||||
function! MockHandleCodeAction() abort
|
||||
" delfunction! ale#code_action#HandleCodeAction
|
||||
function! ale#code_action#HandleCodeAction(action) abort
|
||||
let g:handle_code_action_called += 1
|
||||
endfunction
|
||||
endfunction
|
||||
|
||||
After:
|
||||
Restore
|
||||
|
||||
@@ -54,6 +62,7 @@ After:
|
||||
unlet! g:output
|
||||
unlet! g:fake_mode
|
||||
unlet! g:get_completions_called
|
||||
unlet! g:handle_code_action_called
|
||||
unlet! b:ale_old_omnifunc
|
||||
unlet! b:ale_old_completeopt
|
||||
unlet! b:ale_completion_info
|
||||
@@ -61,6 +70,8 @@ After:
|
||||
unlet! b:ale_complete_done_time
|
||||
|
||||
delfunction CheckCompletionCalled
|
||||
delfunction ale#code_action#HandleCodeAction
|
||||
delfunction MockHandleCodeAction
|
||||
|
||||
if exists('*CompleteCallback')
|
||||
delfunction CompleteCallback
|
||||
@@ -77,6 +88,7 @@ After:
|
||||
endfunction
|
||||
|
||||
runtime autoload/ale/completion.vim
|
||||
runtime autoload/ale/code_action.vim
|
||||
runtime autoload/ale/util.vim
|
||||
|
||||
Execute(ale#completion#GetCompletions should be called when the cursor position stays the same):
|
||||
@@ -385,3 +397,44 @@ Execute(Running the normal mode <Plug> keybind should reset the settings):
|
||||
AssertEqual 'menu', &l:completeopt
|
||||
Assert !has_key(b:, 'ale_old_omnifunc')
|
||||
Assert !has_key(b:, 'ale_old_completeopt')
|
||||
|
||||
Execute(HandleUserData should call ale#code_action#HandleCodeAction):
|
||||
let b:ale_completion_info = {'source': 'ale-manual'}
|
||||
call MockHandleCodeAction()
|
||||
|
||||
call ale#completion#HandleUserData({})
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
call ale#completion#HandleUserData({
|
||||
\ 'user_data': ''
|
||||
\})
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
call ale#completion#HandleUserData({
|
||||
\ 'user_data': '{}'
|
||||
\})
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
call ale#completion#HandleUserData({
|
||||
\ 'user_data': '{"codeActions": []}'
|
||||
\})
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
call ale#completion#HandleUserData({
|
||||
\ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}'
|
||||
\})
|
||||
AssertEqual g:handle_code_action_called, 1
|
||||
|
||||
let b:ale_completion_info = {'source': 'ale-automatic'}
|
||||
call ale#completion#HandleUserData({
|
||||
\ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}'
|
||||
\})
|
||||
AssertEqual g:handle_code_action_called, 2
|
||||
|
||||
Execute(ale#code_action#HandleCodeAction should not be called when when source is not ALE):
|
||||
call MockHandleCodeAction()
|
||||
let b:ale_completion_info = {'source': 'syntastic'}
|
||||
call ale#completion#HandleUserData({
|
||||
\ 'user_data': '{"codeActions": [{"description":"", "changes": []}]}'
|
||||
\})
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
@@ -116,7 +116,13 @@ Execute(The right message should be sent for the initial tsserver request):
|
||||
\ string(g:Callback)
|
||||
" We should send the right message.
|
||||
AssertEqual
|
||||
\ [[0, 'ts@completions', {'file': expand('%:p'), 'line': 1, 'offset': 3, 'prefix': 'fo'}]],
|
||||
\ [[0, 'ts@completions', {
|
||||
\ 'file': expand('%:p'),
|
||||
\ 'line': 1,
|
||||
\ 'offset': 3,
|
||||
\ 'prefix': 'fo',
|
||||
\ 'includeExternalModuleExports': g:ale_completion_tsserver_autoimport,
|
||||
\ }]],
|
||||
\ g:message_list
|
||||
" We should set up the completion info correctly.
|
||||
AssertEqual
|
||||
@@ -151,7 +157,7 @@ Execute(The right message sent to the tsserver LSP when the first completion mes
|
||||
\ 'body': [
|
||||
\ {'name': 'Baz'},
|
||||
\ {'name': 'dingDong'},
|
||||
\ {'name': 'Foo'},
|
||||
\ {'name': 'Foo', 'source': '/path/to/foo.ts'},
|
||||
\ {'name': 'FooBar'},
|
||||
\ {'name': 'frazzle'},
|
||||
\ {'name': 'FFS'},
|
||||
@@ -160,8 +166,16 @@ Execute(The right message sent to the tsserver LSP when the first completion mes
|
||||
|
||||
" We should save the names we got in the buffer, as TSServer doesn't return
|
||||
" details for every name.
|
||||
AssertEqual
|
||||
\ ['Foo', 'FooBar', 'frazzle'],
|
||||
AssertEqual [{
|
||||
\ 'word': 'Foo',
|
||||
\ 'source': '/path/to/foo.ts',
|
||||
\ }, {
|
||||
\ 'word': 'FooBar',
|
||||
\ 'source': '',
|
||||
\ }, {
|
||||
\ 'word': 'frazzle',
|
||||
\ 'source': '',
|
||||
\}],
|
||||
\ get(b:, 'ale_tsserver_completion_names', [])
|
||||
|
||||
" The entry details messages should have been sent.
|
||||
@@ -171,7 +185,16 @@ Execute(The right message sent to the tsserver LSP when the first completion mes
|
||||
\ 'ts@completionEntryDetails',
|
||||
\ {
|
||||
\ 'file': expand('%:p'),
|
||||
\ 'entryNames': ['Foo', 'FooBar', 'frazzle'],
|
||||
\ 'entryNames': [{
|
||||
\ 'name': 'Foo',
|
||||
\ 'source': '/path/to/foo.ts',
|
||||
\ }, {
|
||||
\ 'name': 'FooBar',
|
||||
\ 'source': '',
|
||||
\ }, {
|
||||
\ 'name': 'frazzle',
|
||||
\ 'source': '',
|
||||
\ }],
|
||||
\ 'offset': 1,
|
||||
\ 'line': 1,
|
||||
\ },
|
||||
|
||||
@@ -6,10 +6,24 @@ Execute(TypeScript completions responses should be parsed correctly):
|
||||
\ ale#completion#ParseTSServerCompletions({
|
||||
\ 'body': [],
|
||||
\})
|
||||
AssertEqual ['foo', 'bar', 'baz'],
|
||||
AssertEqual
|
||||
\ [
|
||||
\ {
|
||||
\ 'word': 'foo',
|
||||
\ 'source': '/path/to/foo.ts',
|
||||
\ },
|
||||
\ {
|
||||
\ 'word': 'bar',
|
||||
\ 'source': '',
|
||||
\ },
|
||||
\ {
|
||||
\ 'word': 'baz',
|
||||
\ 'source': '',
|
||||
\ }
|
||||
\ ],
|
||||
\ ale#completion#ParseTSServerCompletions({
|
||||
\ 'body': [
|
||||
\ {'name': 'foo'},
|
||||
\ {'name': 'foo', 'source': '/path/to/foo.ts'},
|
||||
\ {'name': 'bar'},
|
||||
\ {'name': 'baz'},
|
||||
\ ],
|
||||
@@ -24,6 +38,7 @@ Execute(TypeScript completion details responses should be parsed correctly):
|
||||
\ 'info': '',
|
||||
\ 'kind': 'f',
|
||||
\ 'icase': 1,
|
||||
\ 'dup': g:ale_completion_tsserver_autoimport,
|
||||
\ },
|
||||
\ {
|
||||
\ 'word': 'def',
|
||||
@@ -31,6 +46,7 @@ Execute(TypeScript completion details responses should be parsed correctly):
|
||||
\ 'info': 'foo bar baz',
|
||||
\ 'kind': 'f',
|
||||
\ 'icase': 1,
|
||||
\ 'dup': g:ale_completion_tsserver_autoimport,
|
||||
\ },
|
||||
\ {
|
||||
\ 'word': 'ghi',
|
||||
@@ -38,6 +54,7 @@ Execute(TypeScript completion details responses should be parsed correctly):
|
||||
\ 'info': '',
|
||||
\ 'kind': 'f',
|
||||
\ 'icase': 1,
|
||||
\ 'dup': g:ale_completion_tsserver_autoimport,
|
||||
\ },
|
||||
\ ],
|
||||
\ ale#completion#ParseTSServerCompletionEntryDetails({
|
||||
@@ -96,7 +113,10 @@ Execute(TypeScript completion details responses should be parsed correctly):
|
||||
\})
|
||||
|
||||
Execute(Entries without details should be included in the responses):
|
||||
let b:ale_tsserver_completion_names = ['xyz']
|
||||
let b:ale_tsserver_completion_names = [{
|
||||
\ 'word': 'xyz',
|
||||
\ 'source': '/path/to/xyz.ts',
|
||||
\ }]
|
||||
|
||||
AssertEqual
|
||||
\ [
|
||||
@@ -106,6 +126,13 @@ Execute(Entries without details should be included in the responses):
|
||||
\ 'info': '',
|
||||
\ 'kind': 'f',
|
||||
\ 'icase': 1,
|
||||
\ 'user_data': json_encode({
|
||||
\ 'codeActions': [{
|
||||
\ 'description': 'abc action',
|
||||
\ 'changes': [],
|
||||
\ }],
|
||||
\ }),
|
||||
\ 'dup': g:ale_completion_tsserver_autoimport,
|
||||
\ },
|
||||
\ {
|
||||
\ 'word': 'def',
|
||||
@@ -113,6 +140,7 @@ Execute(Entries without details should be included in the responses):
|
||||
\ 'info': 'foo bar baz',
|
||||
\ 'kind': 'f',
|
||||
\ 'icase': 1,
|
||||
\ 'dup': g:ale_completion_tsserver_autoimport,
|
||||
\ },
|
||||
\ {
|
||||
\ 'word': 'xyz',
|
||||
@@ -139,6 +167,10 @@ Execute(Entries without details should be included in the responses):
|
||||
\ {'text': ' '},
|
||||
\ {'text': 'number'},
|
||||
\ ],
|
||||
\ 'codeActions': [{
|
||||
\ 'description': 'abc action',
|
||||
\ 'changes': [],
|
||||
\ }],
|
||||
\ },
|
||||
\ {
|
||||
\ 'name': 'def',
|
||||
|
||||
@@ -275,9 +275,10 @@ Execute(ale#lsp#tsserver_message#Completions() should return correct messages):
|
||||
\ 'line': 347,
|
||||
\ 'offset': 12,
|
||||
\ 'prefix': 'abc',
|
||||
\ 'includeExternalModuleExports': 1,
|
||||
\ }
|
||||
\ ],
|
||||
\ ale#lsp#tsserver_message#Completions(bufnr(''), 347, 12, 'abc')
|
||||
\ ale#lsp#tsserver_message#Completions(bufnr(''), 347, 12, 'abc', 1)
|
||||
|
||||
Execute(ale#lsp#tsserver_message#CompletionEntryDetails() should return correct messages):
|
||||
AssertEqual
|
||||
|
||||
@@ -17,6 +17,7 @@ Before:
|
||||
\ 'init_queue': [],
|
||||
\ 'capabilities': {
|
||||
\ 'hover': 0,
|
||||
\ 'rename': 0,
|
||||
\ 'references': 0,
|
||||
\ 'completion': 0,
|
||||
\ 'completion_trigger_characters': [],
|
||||
@@ -100,6 +101,7 @@ Execute(Capabilities should bet set up correctly):
|
||||
\ 'hover': 1,
|
||||
\ 'definition': 1,
|
||||
\ 'symbol_search': 1,
|
||||
\ 'rename': 1,
|
||||
\ },
|
||||
\ b:conn.capabilities
|
||||
AssertEqual [[1, 'initialized', {}]], g:message_list
|
||||
@@ -110,7 +112,7 @@ Execute(Disabled capabilities should be recognised correctly):
|
||||
\ 'id': 1,
|
||||
\ 'result': {
|
||||
\ 'capabilities': {
|
||||
\ 'renameProvider': v:true,
|
||||
\ 'renameProvider': v:false,
|
||||
\ 'executeCommandProvider': {
|
||||
\ 'commands': [],
|
||||
\ },
|
||||
@@ -143,6 +145,7 @@ Execute(Disabled capabilities should be recognised correctly):
|
||||
\ 'hover': 0,
|
||||
\ 'definition': 0,
|
||||
\ 'symbol_search': 0,
|
||||
\ 'rename': 0,
|
||||
\ },
|
||||
\ b:conn.capabilities
|
||||
AssertEqual [[1, 'initialized', {}]], g:message_list
|
||||
|
||||
@@ -188,6 +188,10 @@ Execute (ALECleanupGroup should include the right commands):
|
||||
\], CheckAutocmd('ALECleanupGroup')
|
||||
endif
|
||||
|
||||
Execute(ALECompletionActions should always be set up):
|
||||
AssertEqual [
|
||||
\ 'CompleteDone * call ale#completion#HandleUserData(v:completed_item)',
|
||||
\], CheckAutocmd('ALECompletionActions')
|
||||
|
||||
Execute(Enabling completion should set up autocmd events correctly):
|
||||
let g:ale_completion_enabled = 0
|
||||
|
||||
334
test/test_code_action.vader
Normal file
334
test/test_code_action.vader
Normal file
@@ -0,0 +1,334 @@
|
||||
Before:
|
||||
runtime autoload/ale/code_action.vim
|
||||
runtime autoload/ale/util.vim
|
||||
|
||||
let g:file1 = tempname()
|
||||
let g:file2 = tempname()
|
||||
let g:test = {}
|
||||
|
||||
let g:test.create_change = {line, offset, end_line, end_offset, value ->
|
||||
\{
|
||||
\ 'changes': [{
|
||||
\ 'fileName': g:file1,
|
||||
\ 'textChanges': [{
|
||||
\ 'start': {
|
||||
\ 'line': line,
|
||||
\ 'offset': offset,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': end_line,
|
||||
\ 'offset': end_offset,
|
||||
\ },
|
||||
\ 'newText': value,
|
||||
\ }],
|
||||
\ }]
|
||||
\}}
|
||||
|
||||
function! WriteFileAndEdit() abort
|
||||
let g:test.text = [
|
||||
\ 'class Name {',
|
||||
\ ' value: string',
|
||||
\ '}',
|
||||
\]
|
||||
call writefile(g:test.text, g:file1, 'S')
|
||||
execute 'edit ' . g:file1
|
||||
endfunction!
|
||||
|
||||
After:
|
||||
" Close the extra buffers if we opened it.
|
||||
if bufnr(g:file1) != -1
|
||||
execute ':bp | :bd ' . bufnr(g:file1)
|
||||
endif
|
||||
if bufnr(g:file2) != -1
|
||||
execute ':bp | :bd ' . bufnr(g:file2)
|
||||
endif
|
||||
|
||||
if filereadable(g:file1)
|
||||
call delete(g:file1)
|
||||
endif
|
||||
if filereadable(g:file2)
|
||||
call delete(g:file2)
|
||||
endif
|
||||
|
||||
unlet g:file1
|
||||
unlet g:file2
|
||||
unlet g:test
|
||||
delfunction WriteFileAndEdit
|
||||
|
||||
runtime autoload/ale/code_action.vim
|
||||
runtime autoload/ale/util.vim
|
||||
|
||||
|
||||
Execute(It should modify and save multiple files):
|
||||
call writefile([
|
||||
\ 'class Name {',
|
||||
\ ' value: string',
|
||||
\ '}',
|
||||
\ '',
|
||||
\ 'class B {',
|
||||
\ ' constructor(readonly a: Name) {}',
|
||||
\ '}'
|
||||
\], g:file1, 'S')
|
||||
call writefile([
|
||||
\ 'import A from "A"',
|
||||
\ 'import {',
|
||||
\ ' B,',
|
||||
\ ' C,',
|
||||
\ '} from "module"',
|
||||
\ 'import D from "D"',
|
||||
\], g:file2, 'S')
|
||||
|
||||
call ale#code_action#HandleCodeAction({
|
||||
\ 'changes': [{
|
||||
\ 'fileName': g:file1,
|
||||
\ 'textChanges': [{
|
||||
\ 'start': {
|
||||
\ 'line': 1,
|
||||
\ 'offset': 7,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 1,
|
||||
\ 'offset': 11,
|
||||
\ },
|
||||
\ 'newText': 'Value',
|
||||
\ }, {
|
||||
\ 'start': {
|
||||
\ 'line': 6,
|
||||
\ 'offset': 27,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 6,
|
||||
\ 'offset': 31,
|
||||
\ },
|
||||
\ 'newText': 'Value',
|
||||
\ }],
|
||||
\ }, {
|
||||
\ 'fileName': g:file2,
|
||||
\ 'textChanges': [{
|
||||
\ 'start': {
|
||||
\ 'line': 2,
|
||||
\ 'offset': 1,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 6,
|
||||
\ 'offset': 1,
|
||||
\ },
|
||||
\ 'newText': "import {A, B} from 'module'\n\n",
|
||||
\ }]
|
||||
\ }],
|
||||
\})
|
||||
|
||||
AssertEqual [
|
||||
\ 'class Value {',
|
||||
\ ' value: string',
|
||||
\ '}',
|
||||
\ '',
|
||||
\ 'class B {',
|
||||
\ ' constructor(readonly a: Value) {}',
|
||||
\ '}',
|
||||
\ '',
|
||||
\], readfile(g:file1, 'b')
|
||||
|
||||
AssertEqual [
|
||||
\ 'import A from "A"',
|
||||
\ 'import {A, B} from ''module''',
|
||||
\ '',
|
||||
\ 'import D from "D"',
|
||||
\ '',
|
||||
\], readfile(g:file2, 'b')
|
||||
|
||||
|
||||
Execute(Beginning of file can be modified):
|
||||
let g:test.text = [
|
||||
\ 'class Name {',
|
||||
\ ' value: string',
|
||||
\ '}',
|
||||
\]
|
||||
call writefile(g:test.text, g:file1, 'S')
|
||||
|
||||
call ale#code_action#HandleCodeAction({
|
||||
\ 'changes': [{
|
||||
\ 'fileName': g:file1,
|
||||
\ 'textChanges': [{
|
||||
\ 'start': {
|
||||
\ 'line': 1,
|
||||
\ 'offset': 1,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 1,
|
||||
\ 'offset': 1,
|
||||
\ },
|
||||
\ 'newText': "type A: string\ntype B: number\n",
|
||||
\ }],
|
||||
\ }]
|
||||
\})
|
||||
|
||||
AssertEqual [
|
||||
\ 'type A: string',
|
||||
\ 'type B: number',
|
||||
\] + g:test.text + [''], readfile(g:file1, 'b')
|
||||
|
||||
|
||||
Execute(End of file can be modified):
|
||||
let g:test.text = [
|
||||
\ 'class Name {',
|
||||
\ ' value: string',
|
||||
\ '}',
|
||||
\]
|
||||
call writefile(g:test.text, g:file1, 'S')
|
||||
|
||||
call ale#code_action#HandleCodeAction({
|
||||
\ 'changes': [{
|
||||
\ 'fileName': g:file1,
|
||||
\ 'textChanges': [{
|
||||
\ 'start': {
|
||||
\ 'line': 4,
|
||||
\ 'offset': 1,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 4,
|
||||
\ 'offset': 1,
|
||||
\ },
|
||||
\ 'newText': "type A: string\ntype B: number\n",
|
||||
\ }],
|
||||
\ }]
|
||||
\})
|
||||
|
||||
AssertEqual g:test.text + [
|
||||
\ 'type A: string',
|
||||
\ 'type B: number',
|
||||
\ '',
|
||||
\], readfile(g:file1, 'b')
|
||||
|
||||
|
||||
Execute(Current buffer contents will be reloaded):
|
||||
let g:test.text = [
|
||||
\ 'class Name {',
|
||||
\ ' value: string',
|
||||
\ '}',
|
||||
\]
|
||||
call writefile(g:test.text, g:file1, 'S')
|
||||
|
||||
execute 'edit ' . g:file1
|
||||
let g:test.buffer = bufnr(g:file1)
|
||||
|
||||
call ale#code_action#HandleCodeAction({
|
||||
\ 'changes': [{
|
||||
\ 'fileName': g:file1,
|
||||
\ 'textChanges': [{
|
||||
\ 'start': {
|
||||
\ 'line': 1,
|
||||
\ 'offset': 1,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 1,
|
||||
\ 'offset': 1,
|
||||
\ },
|
||||
\ 'newText': "type A: string\ntype B: number\n",
|
||||
\ }],
|
||||
\ }]
|
||||
\})
|
||||
|
||||
AssertEqual [
|
||||
\ 'type A: string',
|
||||
\ 'type B: number',
|
||||
\] + g:test.text + [''], readfile(g:file1, 'b')
|
||||
|
||||
AssertEqual [
|
||||
\ 'type A: string',
|
||||
\ 'type B: number',
|
||||
\] + g:test.text, getbufline(g:test.buffer, 1, '$')
|
||||
|
||||
|
||||
# Tests for cursor repositioning. In comments `=` designates change range, and
|
||||
# `C` cursor position
|
||||
|
||||
# C ===
|
||||
Execute(Cursor will not move when it is before text change):
|
||||
call WriteFileAndEdit()
|
||||
let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2')
|
||||
|
||||
call setpos('.', [0, 1, 1, 0])
|
||||
call ale#code_action#HandleCodeAction(g:test.changes)
|
||||
AssertEqual [1, 1], getpos('.')[1:2]
|
||||
|
||||
call setpos('.', [0, 2, 2, 0])
|
||||
call ale#code_action#HandleCodeAction(g:test.changes)
|
||||
AssertEqual [2, 2], getpos('.')[1:2]
|
||||
|
||||
# ====C====
|
||||
Execute(Cursor column will move to the change end when cursor between start/end):
|
||||
let g:test.changes = g:test.create_change(2, 3, 2, 8, 'value2')
|
||||
|
||||
for r in range(3, 8)
|
||||
call WriteFileAndEdit()
|
||||
call setpos('.', [0, 2, r, 0])
|
||||
AssertEqual ' value: string', getline('.')
|
||||
call ale#code_action#HandleCodeAction(g:test.changes)
|
||||
AssertEqual ' value2: string', getline('.')
|
||||
AssertEqual [2, 9], getpos('.')[1:2]
|
||||
endfor
|
||||
|
||||
|
||||
# ====C
|
||||
Execute(Cursor column will move back when new text is shorter):
|
||||
call WriteFileAndEdit()
|
||||
call setpos('.', [0, 2, 8, 0])
|
||||
AssertEqual ' value: string', getline('.')
|
||||
call ale#code_action#HandleCodeAction(g:test.create_change(2, 3, 2, 8, 'val'))
|
||||
AssertEqual ' val: string', getline('.')
|
||||
AssertEqual [2, 6], getpos('.')[1:2]
|
||||
|
||||
|
||||
# ==== C
|
||||
Execute(Cursor column will move forward when new text is longer):
|
||||
call WriteFileAndEdit()
|
||||
|
||||
call setpos('.', [0, 2, 8, 0])
|
||||
AssertEqual ' value: string', getline('.')
|
||||
call ale#code_action#HandleCodeAction(g:test.create_change(2, 3, 2, 8, 'longValue'))
|
||||
AssertEqual ' longValue: string', getline('.')
|
||||
AssertEqual [2, 12], getpos('.')[1:2]
|
||||
|
||||
# =========
|
||||
# =
|
||||
# C
|
||||
Execute(Cursor line will move when updates are happening on lines above):
|
||||
call WriteFileAndEdit()
|
||||
call setpos('.', [0, 3, 1, 0])
|
||||
AssertEqual '}', getline('.')
|
||||
call ale#code_action#HandleCodeAction(g:test.create_change(1, 1, 2, 1, "test\ntest\n"))
|
||||
AssertEqual '}', getline('.')
|
||||
AssertEqual [4, 1], getpos('.')[1:2]
|
||||
|
||||
|
||||
# =========
|
||||
# =C
|
||||
Execute(Cursor line and column will move when change on lines above and just before cursor column):
|
||||
call WriteFileAndEdit()
|
||||
call setpos('.', [0, 2, 2, 0])
|
||||
AssertEqual ' value: string', getline('.')
|
||||
call ale#code_action#HandleCodeAction(g:test.create_change(1, 1, 2, 1, "test\ntest\n123"))
|
||||
AssertEqual '123 value: string', getline('.')
|
||||
AssertEqual [3, 5], getpos('.')[1:2]
|
||||
|
||||
# =========
|
||||
# ======C==
|
||||
# =
|
||||
Execute(Cursor line and column will move at the end of changes):
|
||||
call WriteFileAndEdit()
|
||||
call setpos('.', [0, 2, 10, 0])
|
||||
AssertEqual ' value: string', getline('.')
|
||||
call ale#code_action#HandleCodeAction(g:test.create_change(1, 1, 3, 1, "test\n"))
|
||||
AssertEqual '}', getline('.')
|
||||
AssertEqual [2, 1], getpos('.')[1:2]
|
||||
|
||||
# C ==
|
||||
# ===
|
||||
Execute(Cursor will not move when changes happening on lines >= cursor, but after cursor):
|
||||
call WriteFileAndEdit()
|
||||
call setpos('.', [0, 2, 3, 0])
|
||||
AssertEqual ' value: string', getline('.')
|
||||
call ale#code_action#HandleCodeAction(g:test.create_change(2, 10, 3, 1, "number\n"))
|
||||
AssertEqual ' value: number', getline('.')
|
||||
AssertEqual [2, 3], getpos('.')[1:2]
|
||||
171
test/test_organize_imports.vader
Normal file
171
test/test_organize_imports.vader
Normal file
@@ -0,0 +1,171 @@
|
||||
Before:
|
||||
call ale#test#SetDirectory('/testplugin/test')
|
||||
call ale#test#SetFilename('dummy.txt')
|
||||
|
||||
let g:old_filename = expand('%:p')
|
||||
let g:Callback = ''
|
||||
let g:expr_list = []
|
||||
let g:message_list = []
|
||||
let g:handle_code_action_called = 0
|
||||
let g:code_actions = []
|
||||
let g:options = {}
|
||||
let g:capability_checked = ''
|
||||
let g:conn_id = v:null
|
||||
let g:InitCallback = v:null
|
||||
|
||||
runtime autoload/ale/lsp_linter.vim
|
||||
runtime autoload/ale/lsp.vim
|
||||
runtime autoload/ale/util.vim
|
||||
runtime autoload/ale/organize_imports.vim
|
||||
runtime autoload/ale/code_action.vim
|
||||
|
||||
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)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
call ale#lsp#MarkConnectionAsTsserver(g:conn_id)
|
||||
endif
|
||||
|
||||
let l:details = {
|
||||
\ 'command': 'foobar',
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'connection_id': g:conn_id,
|
||||
\ 'project_root': '/foo/bar',
|
||||
\}
|
||||
|
||||
let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)}
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#HasCapability(conn_id, capability) abort
|
||||
let g:capability_checked = a:capability
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#RegisterCallback(conn_id, callback) abort
|
||||
let g:Callback = a:callback
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#Send(conn_id, message) abort
|
||||
call add(g:message_list, a:message)
|
||||
|
||||
return 42
|
||||
endfunction
|
||||
|
||||
function! ale#util#Execute(expr) abort
|
||||
call add(g:expr_list, a:expr)
|
||||
endfunction
|
||||
|
||||
function! ale#code_action#HandleCodeAction(code_action) abort
|
||||
let g:handle_code_action_called = 1
|
||||
call add(g:code_actions, a:code_action)
|
||||
endfunction
|
||||
|
||||
After:
|
||||
if g:conn_id isnot v:null
|
||||
call ale#lsp#RemoveConnectionWithID(g:conn_id)
|
||||
endif
|
||||
|
||||
call ale#references#SetMap({})
|
||||
call ale#test#RestoreDirectory()
|
||||
call ale#linter#Reset()
|
||||
|
||||
unlet! g:capability_checked
|
||||
unlet! g:InitCallback
|
||||
unlet! g:old_filename
|
||||
unlet! g:conn_id
|
||||
unlet! g:Callback
|
||||
unlet! g:message_list
|
||||
unlet! g:expr_list
|
||||
unlet! b:ale_linters
|
||||
unlet! g:options
|
||||
unlet! g:code_actions
|
||||
unlet! g:handle_code_action_called
|
||||
|
||||
runtime autoload/ale/lsp_linter.vim
|
||||
runtime autoload/ale/lsp.vim
|
||||
runtime autoload/ale/util.vim
|
||||
runtime autoload/ale/organize_imports.vim
|
||||
runtime autoload/ale/code_action.vim
|
||||
|
||||
Execute(Other messages for the tsserver handler should be ignored):
|
||||
call ale#organize_imports#HandleTSServerResponse(1, {'command': 'foo'})
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
Execute(Failed organizeImports responses should be handled correctly):
|
||||
call ale#organize_imports#HandleTSServerResponse(
|
||||
\ 1,
|
||||
\ {'command': 'organizeImports', 'request_seq': 3}
|
||||
\)
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
Execute(Code actions from tsserver should be handled):
|
||||
call ale#organize_imports#HandleTSServerResponse(1, {
|
||||
\ 'command': 'organizeImports',
|
||||
\ 'request_seq': 3,
|
||||
\ 'success': v:true,
|
||||
\ 'body': [],
|
||||
\})
|
||||
AssertEqual g:handle_code_action_called, 1
|
||||
AssertEqual [{
|
||||
\ 'description': 'Organize Imports',
|
||||
\ 'changes': [],
|
||||
\}], g:code_actions
|
||||
|
||||
Given typescript(Some typescript file):
|
||||
foo
|
||||
somelongerline
|
||||
bazxyzxyzxyz
|
||||
|
||||
Execute(tsserver organize imports requests should be sent):
|
||||
call ale#linter#Reset()
|
||||
runtime ale_linters/typescript/tsserver.vim
|
||||
|
||||
ALEOrganizeImports
|
||||
|
||||
" We shouldn't register the callback yet.
|
||||
AssertEqual '''''', string(g:Callback)
|
||||
|
||||
AssertEqual type(function('type')), type(g:InitCallback)
|
||||
call g:InitCallback()
|
||||
|
||||
AssertEqual
|
||||
\ 'function(''ale#organize_imports#HandleTSServerResponse'')',
|
||||
\ string(g:Callback)
|
||||
|
||||
AssertEqual
|
||||
\ [
|
||||
\ ale#lsp#tsserver_message#Change(bufnr('')),
|
||||
\ [0, 'ts@organizeImports', {
|
||||
\ 'scope': {
|
||||
\ 'type': 'file',
|
||||
\ 'args': {
|
||||
\ 'file': expand('%:p'),
|
||||
\ },
|
||||
\ },
|
||||
\ }]
|
||||
\ ],
|
||||
\ g:message_list
|
||||
|
||||
Given python(Some Python file):
|
||||
foo
|
||||
somelongerline
|
||||
bazxyzxyzxyz
|
||||
|
||||
Execute(Should result in error message):
|
||||
call ale#linter#Reset()
|
||||
runtime ale_linters/python/pyls.vim
|
||||
let b:ale_linters = ['pyls']
|
||||
|
||||
ALEOrganizeImports
|
||||
|
||||
" We shouldn't register the callback yet.
|
||||
AssertEqual '''''', string(g:Callback)
|
||||
|
||||
AssertEqual type(function('type')), type(g:InitCallback)
|
||||
call g:InitCallback()
|
||||
|
||||
AssertEqual [
|
||||
\ 'echom ''OrganizeImports currently only works with tsserver''',
|
||||
\], g:expr_list
|
||||
394
test/test_rename.vader
Normal file
394
test/test_rename.vader
Normal file
@@ -0,0 +1,394 @@
|
||||
Before:
|
||||
call ale#test#SetDirectory('/testplugin/test')
|
||||
call ale#test#SetFilename('dummy.txt')
|
||||
|
||||
let g:old_filename = expand('%:p')
|
||||
let g:Callback = ''
|
||||
let g:expr_list = []
|
||||
let g:message_list = []
|
||||
let g:handle_code_action_called = 0
|
||||
let g:code_actions = []
|
||||
let g:options = {}
|
||||
let g:capability_checked = ''
|
||||
let g:conn_id = v:null
|
||||
let g:InitCallback = v:null
|
||||
|
||||
runtime autoload/ale/lsp_linter.vim
|
||||
runtime autoload/ale/lsp.vim
|
||||
runtime autoload/ale/util.vim
|
||||
runtime autoload/ale/rename.vim
|
||||
runtime autoload/ale/code_action.vim
|
||||
|
||||
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)
|
||||
|
||||
if a:linter.lsp is# 'tsserver'
|
||||
call ale#lsp#MarkConnectionAsTsserver(g:conn_id)
|
||||
endif
|
||||
|
||||
let l:details = {
|
||||
\ 'command': 'foobar',
|
||||
\ 'buffer': a:buffer,
|
||||
\ 'connection_id': g:conn_id,
|
||||
\ 'project_root': '/foo/bar',
|
||||
\}
|
||||
|
||||
let g:InitCallback = {-> ale#lsp_linter#OnInit(a:linter, l:details, a:Callback)}
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#HasCapability(conn_id, capability) abort
|
||||
let g:capability_checked = a:capability
|
||||
|
||||
return 1
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#RegisterCallback(conn_id, callback) abort
|
||||
let g:Callback = a:callback
|
||||
endfunction
|
||||
|
||||
function! ale#lsp#Send(conn_id, message) abort
|
||||
call add(g:message_list, a:message)
|
||||
|
||||
return 42
|
||||
endfunction
|
||||
|
||||
function! ale#util#Execute(expr) abort
|
||||
call add(g:expr_list, a:expr)
|
||||
endfunction
|
||||
|
||||
function! ale#code_action#HandleCodeAction(code_action) abort
|
||||
let g:handle_code_action_called = 1
|
||||
call add(g:code_actions, a:code_action)
|
||||
endfunction
|
||||
|
||||
function! ale#util#Input(message, value) abort
|
||||
return 'a-new-name'
|
||||
endfunction
|
||||
|
||||
call ale#rename#SetMap({
|
||||
\ 3: {
|
||||
\ 'old_name': 'oldName',
|
||||
\ 'new_name': 'aNewName',
|
||||
\ },
|
||||
\})
|
||||
|
||||
After:
|
||||
if g:conn_id isnot v:null
|
||||
call ale#lsp#RemoveConnectionWithID(g:conn_id)
|
||||
endif
|
||||
|
||||
call ale#rename#SetMap({})
|
||||
call ale#test#RestoreDirectory()
|
||||
call ale#linter#Reset()
|
||||
|
||||
unlet! g:capability_checked
|
||||
unlet! g:InitCallback
|
||||
unlet! g:old_filename
|
||||
unlet! g:conn_id
|
||||
unlet! g:Callback
|
||||
unlet! g:message_list
|
||||
unlet! g:expr_list
|
||||
unlet! b:ale_linters
|
||||
unlet! g:options
|
||||
unlet! g:code_actions
|
||||
unlet! g:handle_code_action_called
|
||||
|
||||
runtime autoload/ale/lsp_linter.vim
|
||||
runtime autoload/ale/lsp.vim
|
||||
runtime autoload/ale/util.vim
|
||||
runtime autoload/ale/rename.vim
|
||||
runtime autoload/ale/code_action.vim
|
||||
|
||||
Execute(Other messages for the tsserver handler should be ignored):
|
||||
call ale#rename#HandleTSServerResponse(1, {'command': 'foo'})
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
Execute(Failed rename responses should be handled correctly):
|
||||
call ale#rename#SetMap({3: {'old_name': 'oldName', 'new_name': 'a-test'}})
|
||||
call ale#rename#HandleTSServerResponse(
|
||||
\ 1,
|
||||
\ {'command': 'rename', 'request_seq': 3}
|
||||
\)
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
Given typescript(Some typescript file):
|
||||
foo
|
||||
somelongerline
|
||||
bazxyzxyzxyz
|
||||
|
||||
Execute(Code actions from tsserver should be handled):
|
||||
call ale#rename#HandleTSServerResponse(1, {
|
||||
\ 'command': 'rename',
|
||||
\ 'request_seq': 3,
|
||||
\ 'success': v:true,
|
||||
\ 'body': {
|
||||
\ 'locs': [
|
||||
\ {
|
||||
\ 'file': '/foo/bar/file1.ts',
|
||||
\ 'locs': [
|
||||
\ {
|
||||
\ 'start': {
|
||||
\ 'line': 1,
|
||||
\ 'offset': 2,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 3,
|
||||
\ 'offset': 4,
|
||||
\ },
|
||||
\ },
|
||||
\ ],
|
||||
\ },
|
||||
\ {
|
||||
\ 'file': '/foo/bar/file2.ts',
|
||||
\ 'locs': [
|
||||
\ {
|
||||
\ 'start': {
|
||||
\ 'line': 10,
|
||||
\ 'offset': 20,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 30,
|
||||
\ 'offset': 40,
|
||||
\ },
|
||||
\ },
|
||||
\ ],
|
||||
\ },
|
||||
\ ]
|
||||
\ },
|
||||
\})
|
||||
|
||||
AssertEqual
|
||||
\ [
|
||||
\ {
|
||||
\ 'description': 'rename',
|
||||
\ 'changes': [
|
||||
\ {
|
||||
\ 'fileName': '/foo/bar/file1.ts',
|
||||
\ 'textChanges': [
|
||||
\ {
|
||||
\ 'start': {
|
||||
\ 'line': 1,
|
||||
\ 'offset': 2,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 3,
|
||||
\ 'offset': 4,
|
||||
\ },
|
||||
\ 'newText': 'aNewName',
|
||||
\ },
|
||||
\ ],
|
||||
\ },
|
||||
\ {
|
||||
\ 'fileName': '/foo/bar/file2.ts',
|
||||
\ 'textChanges': [
|
||||
\ {
|
||||
\ 'start': {
|
||||
\ 'line': 10,
|
||||
\ 'offset': 20,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 30,
|
||||
\ 'offset': 40,
|
||||
\ },
|
||||
\ 'newText': 'aNewName',
|
||||
\ },
|
||||
\ ],
|
||||
\ },
|
||||
\ ],
|
||||
\ }
|
||||
\ ],
|
||||
\ g:code_actions
|
||||
|
||||
Execute(HandleTSServerResponse does nothing when no data in rename_map):
|
||||
call ale#rename#HandleTSServerResponse(1, {
|
||||
\ 'command': 'rename',
|
||||
\ 'request_seq': -9,
|
||||
\ 'success': v:true,
|
||||
\ 'body': {}
|
||||
\})
|
||||
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
|
||||
Execute(Prints a tsserver error message when unsuccessful):
|
||||
call ale#rename#HandleTSServerResponse(1, {
|
||||
\ 'command': 'rename',
|
||||
\ 'request_seq': 3,
|
||||
\ 'success': v:false,
|
||||
\ 'message': 'This symbol cannot be renamed',
|
||||
\})
|
||||
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
AssertEqual ['echom ''Error renaming "oldName" to: "aNewName". ' .
|
||||
\ 'Reason: This symbol cannot be renamed'''], g:expr_list
|
||||
|
||||
Execute(Does nothing when no changes):
|
||||
call ale#rename#HandleTSServerResponse(1, {
|
||||
\ 'command': 'rename',
|
||||
\ 'request_seq': 3,
|
||||
\ 'success': v:true,
|
||||
\ 'body': {
|
||||
\ 'locs': []
|
||||
\ }
|
||||
\})
|
||||
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
AssertEqual ['echom ''Error renaming "oldName" to: "aNewName"'''], g:expr_list
|
||||
|
||||
Execute(tsserver rename requests should be sent):
|
||||
call ale#rename#SetMap({})
|
||||
call ale#linter#Reset()
|
||||
|
||||
runtime ale_linters/typescript/tsserver.vim
|
||||
call setpos('.', [bufnr(''), 2, 5, 0])
|
||||
|
||||
ALERename
|
||||
|
||||
" We shouldn't register the callback yet.
|
||||
AssertEqual '''''', string(g:Callback)
|
||||
|
||||
AssertEqual type(function('type')), type(g:InitCallback)
|
||||
call g:InitCallback()
|
||||
|
||||
AssertEqual 'rename', g:capability_checked
|
||||
AssertEqual
|
||||
\ 'function(''ale#rename#HandleTSServerResponse'')',
|
||||
\ string(g:Callback)
|
||||
AssertEqual
|
||||
\ [
|
||||
\ ale#lsp#tsserver_message#Change(bufnr('')),
|
||||
\ [0, 'ts@rename', {
|
||||
\ 'file': expand('%:p'),
|
||||
\ 'line': 2,
|
||||
\ 'offset': 5,
|
||||
\ 'arguments': {
|
||||
\ 'findInComments': g:ale_rename_tsserver_find_in_comments,
|
||||
\ 'findInStrings': g:ale_rename_tsserver_find_in_strings,
|
||||
\ },
|
||||
\ }]
|
||||
\ ],
|
||||
\ g:message_list
|
||||
AssertEqual {'42': {'old_name': 'somelongerline', 'new_name': 'a-new-name'}},
|
||||
\ ale#rename#GetMap()
|
||||
|
||||
Given python(Some Python file):
|
||||
foo
|
||||
somelongerline
|
||||
bazxyzxyzxyz
|
||||
|
||||
Execute(Code actions from LSP should be handled):
|
||||
call ale#rename#HandleLSPResponse(1, {
|
||||
\ 'id': 3,
|
||||
\ 'result': {
|
||||
\ 'changes': {
|
||||
\ 'file:///foo/bar/file1.ts': [
|
||||
\ {
|
||||
\ 'range': {
|
||||
\ 'start': {
|
||||
\ 'line': 1,
|
||||
\ 'character': 2,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 3,
|
||||
\ 'character': 4,
|
||||
\ },
|
||||
\ },
|
||||
\ 'newText': 'bla123'
|
||||
\ },
|
||||
\ ],
|
||||
\ },
|
||||
\ },
|
||||
\})
|
||||
|
||||
AssertEqual
|
||||
\ [
|
||||
\ {
|
||||
\ 'description': 'rename',
|
||||
\ 'changes': [
|
||||
\ {
|
||||
\ 'fileName': '/foo/bar/file1.ts',
|
||||
\ 'textChanges': [
|
||||
\ {
|
||||
\ 'start': {
|
||||
\ 'line': 2,
|
||||
\ 'offset': 3,
|
||||
\ },
|
||||
\ 'end': {
|
||||
\ 'line': 4,
|
||||
\ 'offset': 5,
|
||||
\ },
|
||||
\ 'newText': 'bla123',
|
||||
\ },
|
||||
\ ],
|
||||
\ },
|
||||
\ ],
|
||||
\ }
|
||||
\ ],
|
||||
\ g:code_actions
|
||||
|
||||
Execute(LSP should perform no action when no result):
|
||||
call ale#rename#HandleLSPResponse(1, {
|
||||
\ 'id': 3,
|
||||
\})
|
||||
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
AssertEqual ['echom ''No rename result received from server'''], g:expr_list
|
||||
|
||||
Execute(LSP should perform no action when no changes):
|
||||
call ale#rename#HandleLSPResponse(1, {
|
||||
\ 'id': 3,
|
||||
\ 'result': {},
|
||||
\})
|
||||
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
AssertEqual ['echom ''No changes received from server'''], g:expr_list
|
||||
|
||||
Execute(LSP should perform no action when changes is empty):
|
||||
call ale#rename#HandleLSPResponse(1, {
|
||||
\ 'id': 3,
|
||||
\ 'result': {
|
||||
\ 'changes': [],
|
||||
\ },
|
||||
\})
|
||||
|
||||
AssertEqual g:handle_code_action_called, 0
|
||||
AssertEqual ['echom ''No changes received from server'''], g:expr_list
|
||||
|
||||
Execute(LSP rename requests should be sent):
|
||||
call ale#rename#SetMap({})
|
||||
runtime ale_linters/python/pyls.vim
|
||||
let b:ale_linters = ['pyls']
|
||||
call setpos('.', [bufnr(''), 1, 5, 0])
|
||||
|
||||
ALERename
|
||||
|
||||
" We shouldn't register the callback yet.
|
||||
AssertEqual '''''', string(g:Callback)
|
||||
|
||||
AssertEqual type(function('type')), type(g:InitCallback)
|
||||
call g:InitCallback()
|
||||
|
||||
AssertEqual 'rename', g:capability_checked
|
||||
AssertEqual
|
||||
\ 'function(''ale#rename#HandleLSPResponse'')',
|
||||
\ string(g:Callback)
|
||||
|
||||
AssertEqual
|
||||
\ [
|
||||
\ [1, 'textDocument/didChange', {
|
||||
\ 'textDocument': {
|
||||
\ 'uri': ale#path#ToURI(expand('%:p')),
|
||||
\ 'version': g:ale_lsp_next_version_id - 1,
|
||||
\ },
|
||||
\ 'contentChanges': [{'text': join(getline(1, '$'), "\n") . "\n"}]
|
||||
\ }],
|
||||
\ [0, 'textDocument/rename', {
|
||||
\ 'textDocument': {'uri': ale#path#ToURI(expand('%:p'))},
|
||||
\ 'position': {'line': 0, 'character': 2},
|
||||
\ 'newName': 'a-new-name',
|
||||
\ }],
|
||||
\ ],
|
||||
\ g:message_list
|
||||
|
||||
AssertEqual {'42': {'old_name': 'foo', 'new_name': 'a-new-name'}},
|
||||
\ ale#rename#GetMap()
|
||||
Reference in New Issue
Block a user