Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
import assert from 'assert';
6
import { CancellationToken } from '../../../../../base/common/cancellation.js';
7
import { HierarchicalKind } from '../../../../../base/common/hierarchicalKind.js';
8
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
9
import { URI } from '../../../../../base/common/uri.js';
10
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
11
import { Range } from '../../../../common/core/range.js';
12
import { LanguageFeatureRegistry } from '../../../../common/languageFeatureRegistry.js';
13
import * as languages from '../../../../common/languages.js';
14
import { TextModel } from '../../../../common/model/textModel.js';
15
import { getCodeActions } from '../../browser/codeAction.js';
16
import { CodeActionItem, CodeActionKind, CodeActionTriggerSource } from '../../common/types.js';
17
import { createTextModel } from '../../../../test/common/testTextModel.js';
18
import { IMarkerData, MarkerSeverity } from '../../../../../platform/markers/common/markers.js';
19
import { Progress } from '../../../../../platform/progress/common/progress.js';
20
21
function staticCodeActionProvider(...actions: languages.CodeAction[]): languages.CodeActionProvider {
22
return new class implements languages.CodeActionProvider {
23
provideCodeActions(): languages.CodeActionList {
24
return {
25
actions: actions,
26
dispose: () => { }
27
};
28
}
29
};
30
}
31
32
33
suite('CodeAction', () => {
34
35
const langId = 'fooLang';
36
const uri = URI.parse('untitled:path');
37
let model: TextModel;
38
let registry: LanguageFeatureRegistry<languages.CodeActionProvider>;
39
const disposables = new DisposableStore();
40
const testData = {
41
diagnostics: {
42
abc: {
43
title: 'bTitle',
44
diagnostics: [{
45
startLineNumber: 1,
46
startColumn: 1,
47
endLineNumber: 2,
48
endColumn: 1,
49
severity: MarkerSeverity.Error,
50
message: 'abc'
51
}]
52
},
53
bcd: {
54
title: 'aTitle',
55
diagnostics: [{
56
startLineNumber: 1,
57
startColumn: 1,
58
endLineNumber: 2,
59
endColumn: 1,
60
severity: MarkerSeverity.Error,
61
message: 'bcd'
62
}]
63
}
64
},
65
command: {
66
abc: {
67
command: new class implements languages.Command {
68
id!: '1';
69
title!: 'abc';
70
},
71
title: 'Extract to inner function in function "test"'
72
}
73
},
74
spelling: {
75
bcd: {
76
diagnostics: <IMarkerData[]>[],
77
edit: new class implements languages.WorkspaceEdit {
78
edits!: languages.IWorkspaceTextEdit[];
79
},
80
title: 'abc'
81
}
82
},
83
tsLint: {
84
abc: {
85
$ident: 'funny' + 57,
86
arguments: <IMarkerData[]>[],
87
id: '_internal_command_delegation',
88
title: 'abc'
89
},
90
bcd: {
91
$ident: 'funny' + 47,
92
arguments: <IMarkerData[]>[],
93
id: '_internal_command_delegation',
94
title: 'bcd'
95
}
96
}
97
};
98
99
setup(() => {
100
registry = new LanguageFeatureRegistry();
101
disposables.clear();
102
model = createTextModel('test1\ntest2\ntest3', langId, undefined, uri);
103
disposables.add(model);
104
});
105
106
teardown(() => {
107
disposables.clear();
108
});
109
110
ensureNoDisposablesAreLeakedInTestSuite();
111
112
test('CodeActions are sorted by type, #38623', async () => {
113
114
const provider = staticCodeActionProvider(
115
testData.command.abc,
116
testData.diagnostics.bcd,
117
testData.spelling.bcd,
118
testData.tsLint.bcd,
119
testData.tsLint.abc,
120
testData.diagnostics.abc
121
);
122
123
disposables.add(registry.register('fooLang', provider));
124
125
const expected = [
126
// CodeActions with a diagnostics array are shown first without further sorting
127
new CodeActionItem(testData.diagnostics.bcd, provider),
128
new CodeActionItem(testData.diagnostics.abc, provider),
129
130
// CodeActions without diagnostics are shown in the given order without any further sorting
131
new CodeActionItem(testData.command.abc, provider),
132
new CodeActionItem(testData.spelling.bcd, provider),
133
new CodeActionItem(testData.tsLint.bcd, provider),
134
new CodeActionItem(testData.tsLint.abc, provider)
135
];
136
137
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Invoke, triggerAction: CodeActionTriggerSource.Default }, Progress.None, CancellationToken.None));
138
assert.strictEqual(actions.length, 6);
139
assert.deepStrictEqual(actions, expected);
140
});
141
142
test('getCodeActions should filter by scope', async () => {
143
const provider = staticCodeActionProvider(
144
{ title: 'a', kind: 'a' },
145
{ title: 'b', kind: 'b' },
146
{ title: 'a.b', kind: 'a.b' }
147
);
148
149
disposables.add(registry.register('fooLang', provider));
150
151
{
152
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new HierarchicalKind('a') } }, Progress.None, CancellationToken.None));
153
assert.strictEqual(actions.length, 2);
154
assert.strictEqual(actions[0].action.title, 'a');
155
assert.strictEqual(actions[1].action.title, 'a.b');
156
}
157
158
{
159
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new HierarchicalKind('a.b') } }, Progress.None, CancellationToken.None));
160
assert.strictEqual(actions.length, 1);
161
assert.strictEqual(actions[0].action.title, 'a.b');
162
}
163
164
{
165
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new HierarchicalKind('a.b.c') } }, Progress.None, CancellationToken.None));
166
assert.strictEqual(actions.length, 0);
167
}
168
});
169
170
test('getCodeActions should forward requested scope to providers', async () => {
171
const provider = new class implements languages.CodeActionProvider {
172
provideCodeActions(_model: any, _range: Range, context: languages.CodeActionContext, _token: any): languages.CodeActionList {
173
return {
174
actions: [
175
{ title: context.only || '', kind: context.only }
176
],
177
dispose: () => { }
178
};
179
}
180
};
181
182
disposables.add(registry.register('fooLang', provider));
183
184
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: new HierarchicalKind('a') } }, Progress.None, CancellationToken.None));
185
assert.strictEqual(actions.length, 1);
186
assert.strictEqual(actions[0].action.title, 'a');
187
});
188
189
test('getCodeActions should not return source code action by default', async () => {
190
const provider = staticCodeActionProvider(
191
{ title: 'a', kind: CodeActionKind.Source.value },
192
{ title: 'b', kind: 'b' }
193
);
194
195
disposables.add(registry.register('fooLang', provider));
196
197
{
198
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.SourceAction }, Progress.None, CancellationToken.None));
199
assert.strictEqual(actions.length, 1);
200
assert.strictEqual(actions[0].action.title, 'b');
201
}
202
203
{
204
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), { type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Default, filter: { include: CodeActionKind.Source, includeSourceActions: true } }, Progress.None, CancellationToken.None));
205
assert.strictEqual(actions.length, 1);
206
assert.strictEqual(actions[0].action.title, 'a');
207
}
208
});
209
210
test('getCodeActions should support filtering out some requested source code actions #84602', async () => {
211
const provider = staticCodeActionProvider(
212
{ title: 'a', kind: CodeActionKind.Source.value },
213
{ title: 'b', kind: CodeActionKind.Source.append('test').value },
214
{ title: 'c', kind: 'c' }
215
);
216
217
disposables.add(registry.register('fooLang', provider));
218
219
{
220
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), {
221
type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.SourceAction, filter: {
222
include: CodeActionKind.Source.append('test'),
223
excludes: [CodeActionKind.Source],
224
includeSourceActions: true,
225
}
226
}, Progress.None, CancellationToken.None));
227
assert.strictEqual(actions.length, 1);
228
assert.strictEqual(actions[0].action.title, 'b');
229
}
230
});
231
232
test('getCodeActions no invoke a provider that has been excluded #84602', async () => {
233
const baseType = CodeActionKind.Refactor;
234
const subType = CodeActionKind.Refactor.append('sub');
235
236
disposables.add(registry.register('fooLang', staticCodeActionProvider(
237
{ title: 'a', kind: baseType.value }
238
)));
239
240
let didInvoke = false;
241
disposables.add(registry.register('fooLang', new class implements languages.CodeActionProvider {
242
243
providedCodeActionKinds = [subType.value];
244
245
provideCodeActions(): languages.ProviderResult<languages.CodeActionList> {
246
didInvoke = true;
247
return {
248
actions: [
249
{ title: 'x', kind: subType.value }
250
],
251
dispose: () => { }
252
};
253
}
254
}));
255
256
{
257
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), {
258
type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Refactor, filter: {
259
include: baseType,
260
excludes: [subType],
261
}
262
}, Progress.None, CancellationToken.None));
263
assert.strictEqual(didInvoke, false);
264
assert.strictEqual(actions.length, 1);
265
assert.strictEqual(actions[0].action.title, 'a');
266
}
267
});
268
269
test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async () => {
270
let wasInvoked = false;
271
const provider = new class implements languages.CodeActionProvider {
272
provideCodeActions(): languages.CodeActionList {
273
wasInvoked = true;
274
return { actions: [], dispose: () => { } };
275
}
276
277
providedCodeActionKinds = [CodeActionKind.Refactor.value];
278
};
279
280
disposables.add(registry.register('fooLang', provider));
281
282
const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), {
283
type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Refactor,
284
filter: {
285
include: CodeActionKind.QuickFix
286
}
287
}, Progress.None, CancellationToken.None));
288
assert.strictEqual(actions.length, 0);
289
assert.strictEqual(wasInvoked, false);
290
});
291
});
292
293