Path: blob/main/src/vs/editor/contrib/codeAction/test/browser/codeAction.test.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import assert from 'assert';5import { CancellationToken } from '../../../../../base/common/cancellation.js';6import { HierarchicalKind } from '../../../../../base/common/hierarchicalKind.js';7import { DisposableStore } from '../../../../../base/common/lifecycle.js';8import { URI } from '../../../../../base/common/uri.js';9import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';10import { Range } from '../../../../common/core/range.js';11import { LanguageFeatureRegistry } from '../../../../common/languageFeatureRegistry.js';12import * as languages from '../../../../common/languages.js';13import { TextModel } from '../../../../common/model/textModel.js';14import { getCodeActions } from '../../browser/codeAction.js';15import { CodeActionItem, CodeActionKind, CodeActionTriggerSource } from '../../common/types.js';16import { createTextModel } from '../../../../test/common/testTextModel.js';17import { IMarkerData, MarkerSeverity } from '../../../../../platform/markers/common/markers.js';18import { Progress } from '../../../../../platform/progress/common/progress.js';1920function staticCodeActionProvider(...actions: languages.CodeAction[]): languages.CodeActionProvider {21return new class implements languages.CodeActionProvider {22provideCodeActions(): languages.CodeActionList {23return {24actions: actions,25dispose: () => { }26};27}28};29}303132suite('CodeAction', () => {3334const langId = 'fooLang';35const uri = URI.parse('untitled:path');36let model: TextModel;37let registry: LanguageFeatureRegistry<languages.CodeActionProvider>;38const disposables = new DisposableStore();39const testData = {40diagnostics: {41abc: {42title: 'bTitle',43diagnostics: [{44startLineNumber: 1,45startColumn: 1,46endLineNumber: 2,47endColumn: 1,48severity: MarkerSeverity.Error,49message: 'abc'50}]51},52bcd: {53title: 'aTitle',54diagnostics: [{55startLineNumber: 1,56startColumn: 1,57endLineNumber: 2,58endColumn: 1,59severity: MarkerSeverity.Error,60message: 'bcd'61}]62}63},64command: {65abc: {66command: new class implements languages.Command {67id!: '1';68title!: 'abc';69},70title: 'Extract to inner function in function "test"'71}72},73spelling: {74bcd: {75diagnostics: <IMarkerData[]>[],76edit: new class implements languages.WorkspaceEdit {77edits!: languages.IWorkspaceTextEdit[];78},79title: 'abc'80}81},82tsLint: {83abc: {84$ident: 'funny' + 57,85arguments: <IMarkerData[]>[],86id: '_internal_command_delegation',87title: 'abc'88},89bcd: {90$ident: 'funny' + 47,91arguments: <IMarkerData[]>[],92id: '_internal_command_delegation',93title: 'bcd'94}95}96};9798setup(() => {99registry = new LanguageFeatureRegistry();100disposables.clear();101model = createTextModel('test1\ntest2\ntest3', langId, undefined, uri);102disposables.add(model);103});104105teardown(() => {106disposables.clear();107});108109ensureNoDisposablesAreLeakedInTestSuite();110111test('CodeActions are sorted by type, #38623', async () => {112113const provider = staticCodeActionProvider(114testData.command.abc,115testData.diagnostics.bcd,116testData.spelling.bcd,117testData.tsLint.bcd,118testData.tsLint.abc,119testData.diagnostics.abc120);121122disposables.add(registry.register('fooLang', provider));123124const expected = [125// CodeActions with a diagnostics array are shown first without further sorting126new CodeActionItem(testData.diagnostics.bcd, provider),127new CodeActionItem(testData.diagnostics.abc, provider),128129// CodeActions without diagnostics are shown in the given order without any further sorting130new CodeActionItem(testData.command.abc, provider),131new CodeActionItem(testData.spelling.bcd, provider),132new CodeActionItem(testData.tsLint.bcd, provider),133new CodeActionItem(testData.tsLint.abc, provider)134];135136const { 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));137assert.strictEqual(actions.length, 6);138assert.deepStrictEqual(actions, expected);139});140141test('getCodeActions should filter by scope', async () => {142const provider = staticCodeActionProvider(143{ title: 'a', kind: 'a' },144{ title: 'b', kind: 'b' },145{ title: 'a.b', kind: 'a.b' }146);147148disposables.add(registry.register('fooLang', provider));149150{151const { 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));152assert.strictEqual(actions.length, 2);153assert.strictEqual(actions[0].action.title, 'a');154assert.strictEqual(actions[1].action.title, 'a.b');155}156157{158const { 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));159assert.strictEqual(actions.length, 1);160assert.strictEqual(actions[0].action.title, 'a.b');161}162163{164const { 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));165assert.strictEqual(actions.length, 0);166}167});168169test('getCodeActions should forward requested scope to providers', async () => {170const provider = new class implements languages.CodeActionProvider {171provideCodeActions(_model: any, _range: Range, context: languages.CodeActionContext, _token: any): languages.CodeActionList {172return {173actions: [174{ title: context.only || '', kind: context.only }175],176dispose: () => { }177};178}179};180181disposables.add(registry.register('fooLang', provider));182183const { 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));184assert.strictEqual(actions.length, 1);185assert.strictEqual(actions[0].action.title, 'a');186});187188test('getCodeActions should not return source code action by default', async () => {189const provider = staticCodeActionProvider(190{ title: 'a', kind: CodeActionKind.Source.value },191{ title: 'b', kind: 'b' }192);193194disposables.add(registry.register('fooLang', provider));195196{197const { 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));198assert.strictEqual(actions.length, 1);199assert.strictEqual(actions[0].action.title, 'b');200}201202{203const { 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));204assert.strictEqual(actions.length, 1);205assert.strictEqual(actions[0].action.title, 'a');206}207});208209test('getCodeActions should support filtering out some requested source code actions #84602', async () => {210const provider = staticCodeActionProvider(211{ title: 'a', kind: CodeActionKind.Source.value },212{ title: 'b', kind: CodeActionKind.Source.append('test').value },213{ title: 'c', kind: 'c' }214);215216disposables.add(registry.register('fooLang', provider));217218{219const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), {220type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.SourceAction, filter: {221include: CodeActionKind.Source.append('test'),222excludes: [CodeActionKind.Source],223includeSourceActions: true,224}225}, Progress.None, CancellationToken.None));226assert.strictEqual(actions.length, 1);227assert.strictEqual(actions[0].action.title, 'b');228}229});230231test('getCodeActions no invoke a provider that has been excluded #84602', async () => {232const baseType = CodeActionKind.Refactor;233const subType = CodeActionKind.Refactor.append('sub');234235disposables.add(registry.register('fooLang', staticCodeActionProvider(236{ title: 'a', kind: baseType.value }237)));238239let didInvoke = false;240disposables.add(registry.register('fooLang', new class implements languages.CodeActionProvider {241242providedCodeActionKinds = [subType.value];243244provideCodeActions(): languages.ProviderResult<languages.CodeActionList> {245didInvoke = true;246return {247actions: [248{ title: 'x', kind: subType.value }249],250dispose: () => { }251};252}253}));254255{256const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), {257type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Refactor, filter: {258include: baseType,259excludes: [subType],260}261}, Progress.None, CancellationToken.None));262assert.strictEqual(didInvoke, false);263assert.strictEqual(actions.length, 1);264assert.strictEqual(actions[0].action.title, 'a');265}266});267268test('getCodeActions should not invoke code action providers filtered out by providedCodeActionKinds', async () => {269let wasInvoked = false;270const provider = new class implements languages.CodeActionProvider {271provideCodeActions(): languages.CodeActionList {272wasInvoked = true;273return { actions: [], dispose: () => { } };274}275276providedCodeActionKinds = [CodeActionKind.Refactor.value];277};278279disposables.add(registry.register('fooLang', provider));280281const { validActions: actions } = disposables.add(await getCodeActions(registry, model, new Range(1, 1, 2, 1), {282type: languages.CodeActionTriggerType.Auto, triggerAction: CodeActionTriggerSource.Refactor,283filter: {284include: CodeActionKind.QuickFix285}286}, Progress.None, CancellationToken.None));287assert.strictEqual(actions.length, 0);288assert.strictEqual(wasInvoked, false);289});290});291292293