From 33528905efb38b544db1eebf63aa3a6ea92ad964 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Wed, 19 Feb 2020 17:23:58 -0800 Subject: [PATCH 1/9] Added implementation --- src/client/interpreter/serviceRegistry.ts | 2 + .../language/quickFixes/fixLaunchJson.ts | 37 +++++++++++++++++++ src/client/language/quickFixes/main.ts | 21 +++++++++++ 3 files changed, 60 insertions(+) create mode 100644 src/client/language/quickFixes/fixLaunchJson.ts create mode 100644 src/client/language/quickFixes/main.ts diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index 275126c503a0..c9c951a67153 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -5,6 +5,7 @@ import { IExtensionActivationService, IExtensionSingleActivationService } from '../activation/types'; import { IServiceManager } from '../ioc/types'; +import { QuickFixService } from '../language/quickFixes/main'; import { PreWarmActivatedEnvironmentVariables } from './activation/preWarmVariables'; import { EnvironmentActivationService } from './activation/service'; import { TerminalEnvironmentActivationService } from './activation/terminalEnvironmentActivationService'; @@ -105,6 +106,7 @@ import { VirtualEnvironmentPrompt } from './virtualEnvs/virtualEnvPrompt'; */ // tslint:disable-next-line: max-func-body-length export function registerInterpreterTypes(serviceManager: IServiceManager) { + serviceManager.addSingleton(IExtensionSingleActivationService, QuickFixService); serviceManager.addSingleton( IKnownSearchPathsForInterpreters, KnownSearchPathsForInterpreters diff --git a/src/client/language/quickFixes/fixLaunchJson.ts b/src/client/language/quickFixes/fixLaunchJson.ts new file mode 100644 index 000000000000..83abbcf15d0c --- /dev/null +++ b/src/client/language/quickFixes/fixLaunchJson.ts @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { + CodeAction, + CodeActionContext, + CodeActionKind, + CodeActionProvider, + Diagnostic, + Range, + TextDocument, + WorkspaceEdit +} from 'vscode'; + +/** + * Provides code actions for launch.json + */ +export class QuickFixLaunchJson implements CodeActionProvider { + public provideCodeActions( + document: TextDocument, + _range: Range | Selection, + context: CodeActionContext + ): CodeAction[] { + // for each diagnostic entry that has the matching `code`, create a code action command + return context.diagnostics + .filter(diagnostic => diagnostic.message === 'Incorrect type. Expected "string".') + .map(diagnostic => this.createFix(document, diagnostic)); + } + + private createFix(document: TextDocument, diagnostic: Diagnostic): CodeAction { + const finalText = `"${document.getText(diagnostic.range)}"`; + const fix = new CodeAction(`Convert to ${finalText}`, CodeActionKind.QuickFix); + fix.edit = new WorkspaceEdit(); + fix.edit.replace(document.uri, new Range(diagnostic.range.start, diagnostic.range.end), finalText); + return fix; + } +} diff --git a/src/client/language/quickFixes/main.ts b/src/client/language/quickFixes/main.ts new file mode 100644 index 000000000000..1d088190e8c0 --- /dev/null +++ b/src/client/language/quickFixes/main.ts @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import { injectable } from 'inversify'; +import { CodeActionKind, DocumentFilter, languages } from 'vscode'; +import { IExtensionSingleActivationService } from '../../activation/types'; +import { QuickFixLaunchJson } from './fixLaunchJson'; + +@injectable() +export class QuickFixService implements IExtensionSingleActivationService { + public async activate(): Promise { + const documentSelector: DocumentFilter = { + scheme: 'file', + language: 'jsonc', + pattern: '**/launch.json' + }; + languages.registerCodeActionsProvider(documentSelector, new QuickFixLaunchJson(), { + providedCodeActionKinds: [CodeActionKind.QuickFix] + }); + } +} From 9bb5bb99fee0ec51a995d6116a1cae7364382b90 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 20 Feb 2020 15:48:30 -0800 Subject: [PATCH 2/9] Added tests --- src/client/language/quickFixes/main.ts | 10 ++-- .../language/quickFixes/main.unit.test.ts | 55 +++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/test/language/quickFixes/main.unit.test.ts diff --git a/src/client/language/quickFixes/main.ts b/src/client/language/quickFixes/main.ts index 1d088190e8c0..d3aa621dbee1 100644 --- a/src/client/language/quickFixes/main.ts +++ b/src/client/language/quickFixes/main.ts @@ -2,20 +2,22 @@ // Licensed under the MIT License. import { injectable } from 'inversify'; -import { CodeActionKind, DocumentFilter, languages } from 'vscode'; +import * as vscodeTypes from 'vscode'; import { IExtensionSingleActivationService } from '../../activation/types'; import { QuickFixLaunchJson } from './fixLaunchJson'; @injectable() export class QuickFixService implements IExtensionSingleActivationService { public async activate(): Promise { - const documentSelector: DocumentFilter = { + // tslint:disable-next-line:no-require-imports + const vscode = require('vscode') as typeof vscodeTypes; + const documentSelector: vscodeTypes.DocumentFilter = { scheme: 'file', language: 'jsonc', pattern: '**/launch.json' }; - languages.registerCodeActionsProvider(documentSelector, new QuickFixLaunchJson(), { - providedCodeActionKinds: [CodeActionKind.QuickFix] + vscode.languages.registerCodeActionsProvider(documentSelector, new QuickFixLaunchJson(), { + providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }); } } diff --git a/src/test/language/quickFixes/main.unit.test.ts b/src/test/language/quickFixes/main.unit.test.ts new file mode 100644 index 000000000000..919717721a35 --- /dev/null +++ b/src/test/language/quickFixes/main.unit.test.ts @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +// tslint:disable: match-default-export-name +import { assert, expect } from 'chai'; +import rewiremock from 'rewiremock'; +import { CodeActionProvider, CodeActionProviderMetadata, DocumentSelector } from 'vscode'; +import { QuickFixLaunchJson } from '../../../client/language/quickFixes/fixLaunchJson'; +import { QuickFixService } from '../../../client/language/quickFixes/main'; + +suite('Quick fix service', async () => { + setup(() => { + rewiremock.disable(); + }); + test('Code actions are registered correctly', async () => { + let selector: DocumentSelector; + let provider: CodeActionProvider; + let metadata: CodeActionProviderMetadata; + const vscodeMock = { + languages: { + registerCodeActionsProvider: ( + _selector: DocumentSelector, + _provider: CodeActionProvider, + _metadata: CodeActionProviderMetadata + ) => { + selector = _selector; + provider = _provider; + metadata = _metadata; + } + }, + CodeActionKind: { + QuickFix: 'CodeAction' + } + }; + rewiremock.enable(); + rewiremock('vscode').with(vscodeMock); + const quickFixService = new QuickFixService(); + + await quickFixService.activate(); + + // Ensure QuickFixLaunchJson is registered with correct arguments + assert.deepEqual(selector!, { + scheme: 'file', + language: 'jsonc', + pattern: '**/launch.json' + }); + assert.deepEqual(metadata!, { + // tslint:disable-next-line:no-any + providedCodeActionKinds: ['CodeAction' as any] + }); + expect(provider!).instanceOf(QuickFixLaunchJson); + }); +}); From b1affb2a83b517df1224bc856b8eaee5893058ca Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 20 Feb 2020 15:55:37 -0800 Subject: [PATCH 3/9] Moved into providers --- src/client/extension.ts | 2 +- src/client/interpreter/serviceRegistry.ts | 2 -- .../providers/{ => quickFixes}/codeActionsProvider.ts | 0 .../{language => providers}/quickFixes/fixLaunchJson.ts | 0 src/client/{language => providers}/quickFixes/main.ts | 0 src/client/providers/serviceRegistry.ts | 3 +++ src/test/providers/codeActionsProvider.test.ts | 4 ++-- .../{language => providers}/quickFixes/main.unit.test.ts | 4 ++-- src/test/providers/serviceRegistry.unit.test.ts | 8 ++++++++ 9 files changed, 16 insertions(+), 7 deletions(-) rename src/client/providers/{ => quickFixes}/codeActionsProvider.ts (100%) rename src/client/{language => providers}/quickFixes/fixLaunchJson.ts (100%) rename src/client/{language => providers}/quickFixes/main.ts (100%) rename src/test/{language => providers}/quickFixes/main.unit.test.ts (91%) diff --git a/src/client/extension.ts b/src/client/extension.ts index 03e6987da962..3018a05a772a 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -95,8 +95,8 @@ import { IServiceContainer, IServiceManager } from './ioc/types'; import { getLanguageConfiguration } from './language/languageConfiguration'; import { LinterCommands } from './linters/linterCommands'; import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; -import { PythonCodeActionProvider } from './providers/codeActionsProvider'; import { PythonFormattingEditProvider } from './providers/formatProvider'; +import { PythonCodeActionProvider } from './providers/quickFixes/codeActionsProvider'; import { ReplProvider } from './providers/replProvider'; import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry'; import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; diff --git a/src/client/interpreter/serviceRegistry.ts b/src/client/interpreter/serviceRegistry.ts index c9c951a67153..275126c503a0 100644 --- a/src/client/interpreter/serviceRegistry.ts +++ b/src/client/interpreter/serviceRegistry.ts @@ -5,7 +5,6 @@ import { IExtensionActivationService, IExtensionSingleActivationService } from '../activation/types'; import { IServiceManager } from '../ioc/types'; -import { QuickFixService } from '../language/quickFixes/main'; import { PreWarmActivatedEnvironmentVariables } from './activation/preWarmVariables'; import { EnvironmentActivationService } from './activation/service'; import { TerminalEnvironmentActivationService } from './activation/terminalEnvironmentActivationService'; @@ -106,7 +105,6 @@ import { VirtualEnvironmentPrompt } from './virtualEnvs/virtualEnvPrompt'; */ // tslint:disable-next-line: max-func-body-length export function registerInterpreterTypes(serviceManager: IServiceManager) { - serviceManager.addSingleton(IExtensionSingleActivationService, QuickFixService); serviceManager.addSingleton( IKnownSearchPathsForInterpreters, KnownSearchPathsForInterpreters diff --git a/src/client/providers/codeActionsProvider.ts b/src/client/providers/quickFixes/codeActionsProvider.ts similarity index 100% rename from src/client/providers/codeActionsProvider.ts rename to src/client/providers/quickFixes/codeActionsProvider.ts diff --git a/src/client/language/quickFixes/fixLaunchJson.ts b/src/client/providers/quickFixes/fixLaunchJson.ts similarity index 100% rename from src/client/language/quickFixes/fixLaunchJson.ts rename to src/client/providers/quickFixes/fixLaunchJson.ts diff --git a/src/client/language/quickFixes/main.ts b/src/client/providers/quickFixes/main.ts similarity index 100% rename from src/client/language/quickFixes/main.ts rename to src/client/providers/quickFixes/main.ts diff --git a/src/client/providers/serviceRegistry.ts b/src/client/providers/serviceRegistry.ts index 7418e0175e51..aacc5fc63664 100644 --- a/src/client/providers/serviceRegistry.ts +++ b/src/client/providers/serviceRegistry.ts @@ -3,10 +3,13 @@ 'use strict'; +import { IExtensionSingleActivationService } from '../activation/types'; import { IServiceManager } from '../ioc/types'; import { SortImportsEditingProvider } from './importSortProvider'; +import { QuickFixService } from './quickFixes/main'; import { ISortImportsEditingProvider } from './types'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(ISortImportsEditingProvider, SortImportsEditingProvider); + serviceManager.addSingleton(IExtensionSingleActivationService, QuickFixService); } diff --git a/src/test/providers/codeActionsProvider.test.ts b/src/test/providers/codeActionsProvider.test.ts index 8147063d649e..c44df213b880 100644 --- a/src/test/providers/codeActionsProvider.test.ts +++ b/src/test/providers/codeActionsProvider.test.ts @@ -6,9 +6,9 @@ import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode'; -import { PythonCodeActionProvider } from '../../client/providers/codeActionsProvider'; +import { PythonCodeActionProvider } from '../../client/providers/quickFixes/codeActionsProvider'; -suite('CodeAction Provider', () => { +suite('xCodeAction Provider', () => { let codeActionsProvider: PythonCodeActionProvider; let document: TypeMoq.IMock; let range: TypeMoq.IMock; diff --git a/src/test/language/quickFixes/main.unit.test.ts b/src/test/providers/quickFixes/main.unit.test.ts similarity index 91% rename from src/test/language/quickFixes/main.unit.test.ts rename to src/test/providers/quickFixes/main.unit.test.ts index 919717721a35..7b865e9dbba0 100644 --- a/src/test/language/quickFixes/main.unit.test.ts +++ b/src/test/providers/quickFixes/main.unit.test.ts @@ -7,8 +7,8 @@ import { assert, expect } from 'chai'; import rewiremock from 'rewiremock'; import { CodeActionProvider, CodeActionProviderMetadata, DocumentSelector } from 'vscode'; -import { QuickFixLaunchJson } from '../../../client/language/quickFixes/fixLaunchJson'; -import { QuickFixService } from '../../../client/language/quickFixes/main'; +import { QuickFixLaunchJson } from '../../../client/providers/quickFixes/fixLaunchJson'; +import { QuickFixService } from '../../../client/providers/quickFixes/main'; suite('Quick fix service', async () => { setup(() => { diff --git a/src/test/providers/serviceRegistry.unit.test.ts b/src/test/providers/serviceRegistry.unit.test.ts index d7e29720e3ff..d3ceda6007d4 100644 --- a/src/test/providers/serviceRegistry.unit.test.ts +++ b/src/test/providers/serviceRegistry.unit.test.ts @@ -4,9 +4,11 @@ 'use strict'; import { instance, mock, verify } from 'ts-mockito'; +import { IExtensionSingleActivationService } from '../../client/activation/types'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceManager } from '../../client/ioc/types'; import { SortImportsEditingProvider } from '../../client/providers/importSortProvider'; +import { QuickFixService } from '../../client/providers/quickFixes/main'; import { registerTypes } from '../../client/providers/serviceRegistry'; import { ISortImportsEditingProvider } from '../../client/providers/types'; @@ -25,5 +27,11 @@ suite('Common Providers Service Registry', () => { SortImportsEditingProvider ) ).once(); + verify( + serviceManager.addSingleton( + IExtensionSingleActivationService, + QuickFixService + ) + ).once(); }); }); From bfec2686965cf683e3fc36da8f49b7b6f849dca3 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 20 Feb 2020 17:01:00 -0800 Subject: [PATCH 4/9] Add code actions vscode mock and convert .test.ts into unit.test.ts --- src/test/mocks/vsc/index.ts | 13 +++++++++++++ ...der.test.ts => codeActionsProvider.unit.test.ts} | 2 +- src/test/vscode-mock.ts | 1 + 3 files changed, 15 insertions(+), 1 deletion(-) rename src/test/providers/{codeActionsProvider.test.ts => codeActionsProvider.unit.test.ts} (97%) diff --git a/src/test/mocks/vsc/index.ts b/src/test/mocks/vsc/index.ts index 0f86994533fe..b309c0a55e58 100644 --- a/src/test/mocks/vsc/index.ts +++ b/src/test/mocks/vsc/index.ts @@ -76,6 +76,19 @@ export namespace vscMock { } } + export class CodeAction { + public title: string; + public edit?: vscode.WorkspaceEdit; + public diagnostics?: vscode.Diagnostic[]; + public command?: vscode.Command; + public kind?: CodeActionKind; + public isPreferred?: boolean; + constructor(_title: string, _kind?: CodeActionKind) { + this.title = _title; + this.kind = _kind; + } + } + export enum CompletionItemKind { Text = 0, Method = 1, diff --git a/src/test/providers/codeActionsProvider.test.ts b/src/test/providers/codeActionsProvider.unit.test.ts similarity index 97% rename from src/test/providers/codeActionsProvider.test.ts rename to src/test/providers/codeActionsProvider.unit.test.ts index c44df213b880..e68f7c716c07 100644 --- a/src/test/providers/codeActionsProvider.test.ts +++ b/src/test/providers/codeActionsProvider.unit.test.ts @@ -8,7 +8,7 @@ import * as TypeMoq from 'typemoq'; import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode'; import { PythonCodeActionProvider } from '../../client/providers/quickFixes/codeActionsProvider'; -suite('xCodeAction Provider', () => { +suite('CodeAction Provider', () => { let codeActionsProvider: PythonCodeActionProvider; let document: TypeMoq.IMock; let range: TypeMoq.IMock; diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index f3105a9bd1fd..0712ce3810c6 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -50,6 +50,7 @@ export function initialize() { } mockedVSCode.Disposable = vscodeMocks.vscMock.Disposable as any; +mockedVSCode.CodeAction = vscodeMocks.vscMock.CodeAction; mockedVSCode.EventEmitter = vscodeMocks.vscMock.EventEmitter; mockedVSCode.CancellationTokenSource = vscodeMocks.vscMock.CancellationTokenSource; mockedVSCode.CompletionItemKind = vscodeMocks.vscMock.CompletionItemKind; From 33d0298a44eedec39f5cc7f42e50d14ec4fc2c53 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 20 Feb 2020 17:25:08 -0800 Subject: [PATCH 5/9] Rename folders --- src/client/extension.ts | 2 +- .../launchJsonCodeActionProvider.ts} | 9 +--- .../main.ts | 4 +- .../pythonCodeActionProvider.ts} | 0 src/client/providers/serviceRegistry.ts | 2 +- .../fixLaunchJson.unit.test.ts} | 2 +- .../main.unit.test.ts | 6 +-- .../pythonCodeActionsProvider.unit.test.ts | 44 +++++++++++++++++++ .../providers/serviceRegistry.unit.test.ts | 2 +- 9 files changed, 55 insertions(+), 16 deletions(-) rename src/client/providers/{quickFixes/fixLaunchJson.ts => codeActionProvider/launchJsonCodeActionProvider.ts} (74%) rename src/client/providers/{quickFixes => codeActionProvider}/main.ts (87%) rename src/client/providers/{quickFixes/codeActionsProvider.ts => codeActionProvider/pythonCodeActionProvider.ts} (100%) rename src/test/providers/{codeActionsProvider.unit.test.ts => codeActionProvider/fixLaunchJson.unit.test.ts} (93%) rename src/test/providers/{quickFixes => codeActionProvider}/main.unit.test.ts (86%) create mode 100644 src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts diff --git a/src/client/extension.ts b/src/client/extension.ts index 3018a05a772a..38f491259d21 100644 --- a/src/client/extension.ts +++ b/src/client/extension.ts @@ -95,8 +95,8 @@ import { IServiceContainer, IServiceManager } from './ioc/types'; import { getLanguageConfiguration } from './language/languageConfiguration'; import { LinterCommands } from './linters/linterCommands'; import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry'; +import { PythonCodeActionProvider } from './providers/codeActionProvider/pythonCodeActionProvider'; import { PythonFormattingEditProvider } from './providers/formatProvider'; -import { PythonCodeActionProvider } from './providers/quickFixes/codeActionsProvider'; import { ReplProvider } from './providers/replProvider'; import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry'; import { activateSimplePythonRefactorProvider } from './providers/simpleRefactorProvider'; diff --git a/src/client/providers/quickFixes/fixLaunchJson.ts b/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts similarity index 74% rename from src/client/providers/quickFixes/fixLaunchJson.ts rename to src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts index 83abbcf15d0c..85112d15bb2f 100644 --- a/src/client/providers/quickFixes/fixLaunchJson.ts +++ b/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts @@ -15,13 +15,8 @@ import { /** * Provides code actions for launch.json */ -export class QuickFixLaunchJson implements CodeActionProvider { - public provideCodeActions( - document: TextDocument, - _range: Range | Selection, - context: CodeActionContext - ): CodeAction[] { - // for each diagnostic entry that has the matching `code`, create a code action command +export class LaunchJsonCodeActionProvider implements CodeActionProvider { + public provideCodeActions(document: TextDocument, _: Range, context: CodeActionContext): CodeAction[] { return context.diagnostics .filter(diagnostic => diagnostic.message === 'Incorrect type. Expected "string".') .map(diagnostic => this.createFix(document, diagnostic)); diff --git a/src/client/providers/quickFixes/main.ts b/src/client/providers/codeActionProvider/main.ts similarity index 87% rename from src/client/providers/quickFixes/main.ts rename to src/client/providers/codeActionProvider/main.ts index d3aa621dbee1..05d4c43856df 100644 --- a/src/client/providers/quickFixes/main.ts +++ b/src/client/providers/codeActionProvider/main.ts @@ -4,7 +4,7 @@ import { injectable } from 'inversify'; import * as vscodeTypes from 'vscode'; import { IExtensionSingleActivationService } from '../../activation/types'; -import { QuickFixLaunchJson } from './fixLaunchJson'; +import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider'; @injectable() export class QuickFixService implements IExtensionSingleActivationService { @@ -16,7 +16,7 @@ export class QuickFixService implements IExtensionSingleActivationService { language: 'jsonc', pattern: '**/launch.json' }; - vscode.languages.registerCodeActionsProvider(documentSelector, new QuickFixLaunchJson(), { + vscode.languages.registerCodeActionsProvider(documentSelector, new LaunchJsonCodeActionProvider(), { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }); } diff --git a/src/client/providers/quickFixes/codeActionsProvider.ts b/src/client/providers/codeActionProvider/pythonCodeActionProvider.ts similarity index 100% rename from src/client/providers/quickFixes/codeActionsProvider.ts rename to src/client/providers/codeActionProvider/pythonCodeActionProvider.ts diff --git a/src/client/providers/serviceRegistry.ts b/src/client/providers/serviceRegistry.ts index aacc5fc63664..8a4d4bc93a9a 100644 --- a/src/client/providers/serviceRegistry.ts +++ b/src/client/providers/serviceRegistry.ts @@ -5,8 +5,8 @@ import { IExtensionSingleActivationService } from '../activation/types'; import { IServiceManager } from '../ioc/types'; +import { QuickFixService } from './codeActionProvider/main'; import { SortImportsEditingProvider } from './importSortProvider'; -import { QuickFixService } from './quickFixes/main'; import { ISortImportsEditingProvider } from './types'; export function registerTypes(serviceManager: IServiceManager) { diff --git a/src/test/providers/codeActionsProvider.unit.test.ts b/src/test/providers/codeActionProvider/fixLaunchJson.unit.test.ts similarity index 93% rename from src/test/providers/codeActionsProvider.unit.test.ts rename to src/test/providers/codeActionProvider/fixLaunchJson.unit.test.ts index e68f7c716c07..372ecf839852 100644 --- a/src/test/providers/codeActionsProvider.unit.test.ts +++ b/src/test/providers/codeActionProvider/fixLaunchJson.unit.test.ts @@ -6,7 +6,7 @@ import { expect } from 'chai'; import * as TypeMoq from 'typemoq'; import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode'; -import { PythonCodeActionProvider } from '../../client/providers/quickFixes/codeActionsProvider'; +import { PythonCodeActionProvider } from '../../../client/providers/codeActionProvider/pythonCodeActionProvider'; suite('CodeAction Provider', () => { let codeActionsProvider: PythonCodeActionProvider; diff --git a/src/test/providers/quickFixes/main.unit.test.ts b/src/test/providers/codeActionProvider/main.unit.test.ts similarity index 86% rename from src/test/providers/quickFixes/main.unit.test.ts rename to src/test/providers/codeActionProvider/main.unit.test.ts index 7b865e9dbba0..76db948d829e 100644 --- a/src/test/providers/quickFixes/main.unit.test.ts +++ b/src/test/providers/codeActionProvider/main.unit.test.ts @@ -7,8 +7,8 @@ import { assert, expect } from 'chai'; import rewiremock from 'rewiremock'; import { CodeActionProvider, CodeActionProviderMetadata, DocumentSelector } from 'vscode'; -import { QuickFixLaunchJson } from '../../../client/providers/quickFixes/fixLaunchJson'; -import { QuickFixService } from '../../../client/providers/quickFixes/main'; +import { LaunchJsonCodeActionProvider } from '../../../client/providers/codeActionProvider/launchJsonCodeActionProvider'; +import { QuickFixService } from '../../../client/providers/codeActionProvider/main'; suite('Quick fix service', async () => { setup(() => { @@ -50,6 +50,6 @@ suite('Quick fix service', async () => { // tslint:disable-next-line:no-any providedCodeActionKinds: ['CodeAction' as any] }); - expect(provider!).instanceOf(QuickFixLaunchJson); + expect(provider!).instanceOf(LaunchJsonCodeActionProvider); }); }); diff --git a/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts b/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts new file mode 100644 index 000000000000..372ecf839852 --- /dev/null +++ b/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +'use strict'; + +import { expect } from 'chai'; +import * as TypeMoq from 'typemoq'; +import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode'; +import { PythonCodeActionProvider } from '../../../client/providers/codeActionProvider/pythonCodeActionProvider'; + +suite('CodeAction Provider', () => { + let codeActionsProvider: PythonCodeActionProvider; + let document: TypeMoq.IMock; + let range: TypeMoq.IMock; + let context: TypeMoq.IMock; + let token: TypeMoq.IMock; + + setup(() => { + codeActionsProvider = new PythonCodeActionProvider(); + document = TypeMoq.Mock.ofType(); + range = TypeMoq.Mock.ofType(); + context = TypeMoq.Mock.ofType(); + token = TypeMoq.Mock.ofType(); + }); + + test('Ensure it always returns a source.organizeImports CodeAction', async () => { + const codeActions = await codeActionsProvider.provideCodeActions( + document.object, + range.object, + context.object, + token.object + ); + + if (!codeActions) { + throw Error(`codeActionsProvider.provideCodeActions did not return an array (it returned ${codeActions})`); + } + + const organizeImportsCodeAction = codeActions.filter( + codeAction => codeAction.kind === CodeActionKind.SourceOrganizeImports + ); + expect(organizeImportsCodeAction).to.have.length(1); + expect(organizeImportsCodeAction[0].kind).to.eq(CodeActionKind.SourceOrganizeImports); + }); +}); diff --git a/src/test/providers/serviceRegistry.unit.test.ts b/src/test/providers/serviceRegistry.unit.test.ts index d3ceda6007d4..32ae1a9bb0b5 100644 --- a/src/test/providers/serviceRegistry.unit.test.ts +++ b/src/test/providers/serviceRegistry.unit.test.ts @@ -7,8 +7,8 @@ import { instance, mock, verify } from 'ts-mockito'; import { IExtensionSingleActivationService } from '../../client/activation/types'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceManager } from '../../client/ioc/types'; +import { QuickFixService } from '../../client/providers/codeActionProvider/main'; import { SortImportsEditingProvider } from '../../client/providers/importSortProvider'; -import { QuickFixService } from '../../client/providers/quickFixes/main'; import { registerTypes } from '../../client/providers/serviceRegistry'; import { ISortImportsEditingProvider } from '../../client/providers/types'; From 1209b44e933aba3781a3f151b5f8ee038245690f Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 20 Feb 2020 20:59:37 -0800 Subject: [PATCH 6/9] Dispose registered services --- src/client/providers/codeActionProvider/main.ts | 12 ++++++++---- ....ts => launchJsonCodeActionProvider.unit.test.ts} | 0 .../providers/codeActionProvider/main.unit.test.ts | 4 +++- .../pythonCodeActionsProvider.unit.test.ts | 2 +- 4 files changed, 12 insertions(+), 6 deletions(-) rename src/test/providers/codeActionProvider/{fixLaunchJson.unit.test.ts => launchJsonCodeActionProvider.unit.test.ts} (100%) diff --git a/src/client/providers/codeActionProvider/main.ts b/src/client/providers/codeActionProvider/main.ts index 05d4c43856df..fdafc448178e 100644 --- a/src/client/providers/codeActionProvider/main.ts +++ b/src/client/providers/codeActionProvider/main.ts @@ -1,13 +1,15 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import { injectable } from 'inversify'; +import { inject, injectable } from 'inversify'; import * as vscodeTypes from 'vscode'; import { IExtensionSingleActivationService } from '../../activation/types'; +import { IDisposableRegistry } from '../../common/types'; import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider'; @injectable() export class QuickFixService implements IExtensionSingleActivationService { + constructor(@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry) {} public async activate(): Promise { // tslint:disable-next-line:no-require-imports const vscode = require('vscode') as typeof vscodeTypes; @@ -16,8 +18,10 @@ export class QuickFixService implements IExtensionSingleActivationService { language: 'jsonc', pattern: '**/launch.json' }; - vscode.languages.registerCodeActionsProvider(documentSelector, new LaunchJsonCodeActionProvider(), { - providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] - }); + this.disposableRegistry.push( + vscode.languages.registerCodeActionsProvider(documentSelector, new LaunchJsonCodeActionProvider(), { + providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] + }) + ); } } diff --git a/src/test/providers/codeActionProvider/fixLaunchJson.unit.test.ts b/src/test/providers/codeActionProvider/launchJsonCodeActionProvider.unit.test.ts similarity index 100% rename from src/test/providers/codeActionProvider/fixLaunchJson.unit.test.ts rename to src/test/providers/codeActionProvider/launchJsonCodeActionProvider.unit.test.ts diff --git a/src/test/providers/codeActionProvider/main.unit.test.ts b/src/test/providers/codeActionProvider/main.unit.test.ts index 76db948d829e..c9ffe4b30cc0 100644 --- a/src/test/providers/codeActionProvider/main.unit.test.ts +++ b/src/test/providers/codeActionProvider/main.unit.test.ts @@ -6,7 +6,9 @@ // tslint:disable: match-default-export-name import { assert, expect } from 'chai'; import rewiremock from 'rewiremock'; +import * as typemoq from 'typemoq'; import { CodeActionProvider, CodeActionProviderMetadata, DocumentSelector } from 'vscode'; +import { IDisposableRegistry } from '../../../client/common/types'; import { LaunchJsonCodeActionProvider } from '../../../client/providers/codeActionProvider/launchJsonCodeActionProvider'; import { QuickFixService } from '../../../client/providers/codeActionProvider/main'; @@ -36,7 +38,7 @@ suite('Quick fix service', async () => { }; rewiremock.enable(); rewiremock('vscode').with(vscodeMock); - const quickFixService = new QuickFixService(); + const quickFixService = new QuickFixService(typemoq.Mock.ofType().object); await quickFixService.activate(); diff --git a/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts b/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts index 372ecf839852..454dd2bc27ad 100644 --- a/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts +++ b/src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts @@ -8,7 +8,7 @@ import * as TypeMoq from 'typemoq'; import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode'; import { PythonCodeActionProvider } from '../../../client/providers/codeActionProvider/pythonCodeActionProvider'; -suite('CodeAction Provider', () => { +suite('Python CodeAction Provider', () => { let codeActionsProvider: PythonCodeActionProvider; let document: TypeMoq.IMock; let range: TypeMoq.IMock; From 4c618828d0261da068c191451e0a73f34bf28403 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Thu, 20 Feb 2020 21:21:10 -0800 Subject: [PATCH 7/9] News entry --- news/1 Enhancements/10245.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 news/1 Enhancements/10245.md diff --git a/news/1 Enhancements/10245.md b/news/1 Enhancements/10245.md new file mode 100644 index 000000000000..e59965b96e63 --- /dev/null +++ b/news/1 Enhancements/10245.md @@ -0,0 +1 @@ +Show quickfixes for launch.json From cfcf142513b862e63165c6bc8524af8bb4a2627e Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 21 Feb 2020 01:32:56 -0800 Subject: [PATCH 8/9] Added unit tests --- .../launchJsonCodeActionProvider.ts | 2 +- .../launchJsonCodeActionProvider.unit.test.ts | 65 ++++++++++++------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts b/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts index 85112d15bb2f..e3cb8f90ec60 100644 --- a/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts +++ b/src/client/providers/codeActionProvider/launchJsonCodeActionProvider.ts @@ -26,7 +26,7 @@ export class LaunchJsonCodeActionProvider implements CodeActionProvider { const finalText = `"${document.getText(diagnostic.range)}"`; const fix = new CodeAction(`Convert to ${finalText}`, CodeActionKind.QuickFix); fix.edit = new WorkspaceEdit(); - fix.edit.replace(document.uri, new Range(diagnostic.range.start, diagnostic.range.end), finalText); + fix.edit.replace(document.uri, diagnostic.range, finalText); return fix; } } diff --git a/src/test/providers/codeActionProvider/launchJsonCodeActionProvider.unit.test.ts b/src/test/providers/codeActionProvider/launchJsonCodeActionProvider.unit.test.ts index 372ecf839852..ceae05d4bbd0 100644 --- a/src/test/providers/codeActionProvider/launchJsonCodeActionProvider.unit.test.ts +++ b/src/test/providers/codeActionProvider/launchJsonCodeActionProvider.unit.test.ts @@ -3,42 +3,57 @@ 'use strict'; -import { expect } from 'chai'; +import { assert, expect } from 'chai'; import * as TypeMoq from 'typemoq'; -import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode'; -import { PythonCodeActionProvider } from '../../../client/providers/codeActionProvider/pythonCodeActionProvider'; +import { CodeActionContext, CodeActionKind, Diagnostic, Range, TextDocument, Uri } from 'vscode'; +import { LaunchJsonCodeActionProvider } from '../../../client/providers/codeActionProvider/launchJsonCodeActionProvider'; -suite('CodeAction Provider', () => { - let codeActionsProvider: PythonCodeActionProvider; +suite('LaunchJson CodeAction Provider', () => { + const documentUri = Uri.parse('a'); let document: TypeMoq.IMock; let range: TypeMoq.IMock; let context: TypeMoq.IMock; - let token: TypeMoq.IMock; + let diagnostic: TypeMoq.IMock; + let codeActionsProvider: LaunchJsonCodeActionProvider; setup(() => { - codeActionsProvider = new PythonCodeActionProvider(); + codeActionsProvider = new LaunchJsonCodeActionProvider(); document = TypeMoq.Mock.ofType(); range = TypeMoq.Mock.ofType(); context = TypeMoq.Mock.ofType(); - token = TypeMoq.Mock.ofType(); + diagnostic = TypeMoq.Mock.ofType(); + document.setup(d => d.getText(TypeMoq.It.isAny())).returns(() => 'Diagnostic text'); + document.setup(d => d.uri).returns(() => documentUri); + context.setup(c => c.diagnostics).returns(() => [diagnostic.object]); }); - test('Ensure it always returns a source.organizeImports CodeAction', async () => { - const codeActions = await codeActionsProvider.provideCodeActions( - document.object, - range.object, - context.object, - token.object - ); - - if (!codeActions) { - throw Error(`codeActionsProvider.provideCodeActions did not return an array (it returned ${codeActions})`); - } - - const organizeImportsCodeAction = codeActions.filter( - codeAction => codeAction.kind === CodeActionKind.SourceOrganizeImports - ); - expect(organizeImportsCodeAction).to.have.length(1); - expect(organizeImportsCodeAction[0].kind).to.eq(CodeActionKind.SourceOrganizeImports); + test('Ensure correct code action is returned if diagnostic message equals `Incorrect type. Expected "string".`', async () => { + diagnostic.setup(d => d.message).returns(() => 'Incorrect type. Expected "string".'); + diagnostic.setup(d => d.range).returns(() => new Range(2, 0, 7, 8)); + + const codeActions = codeActionsProvider.provideCodeActions(document.object, range.object, context.object); + + // Now ensure that the code action object is as expected + expect(codeActions).to.have.length(1); + expect(codeActions[0].kind).to.eq(CodeActionKind.QuickFix); + expect(codeActions[0].title).to.equal('Convert to "Diagnostic text"'); + + // Ensure the correct TextEdit is provided + const entries = codeActions[0].edit!.entries(); + // Edits the correct document is edited + assert.deepEqual(entries[0][0], documentUri); + const edit = entries[0][1][0]; + // Final text is as expected + expect(edit.newText).to.equal('"Diagnostic text"'); + // Text edit range is as expected + expect(edit.range.isEqual(new Range(2, 0, 7, 8))).to.equal(true, 'Text edit range not as expected'); + }); + + test('Ensure no code action is returned if diagnostic message does not equal `Incorrect type. Expected "string".`', async () => { + diagnostic.setup(d => d.message).returns(() => 'Random diagnostic message'); + + const codeActions = codeActionsProvider.provideCodeActions(document.object, range.object, context.object); + + expect(codeActions).to.have.length(0); }); }); From 5cb64f2aaf7c2ec5d57d58d84b752930f7503bb4 Mon Sep 17 00:00:00 2001 From: Kartik Raj Date: Fri, 21 Feb 2020 01:37:15 -0800 Subject: [PATCH 9/9] Rename core service --- src/client/providers/codeActionProvider/main.ts | 2 +- src/client/providers/serviceRegistry.ts | 7 +++++-- src/test/providers/codeActionProvider/main.unit.test.ts | 6 +++--- src/test/providers/serviceRegistry.unit.test.ts | 4 ++-- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/client/providers/codeActionProvider/main.ts b/src/client/providers/codeActionProvider/main.ts index fdafc448178e..375c9986d949 100644 --- a/src/client/providers/codeActionProvider/main.ts +++ b/src/client/providers/codeActionProvider/main.ts @@ -8,7 +8,7 @@ import { IDisposableRegistry } from '../../common/types'; import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider'; @injectable() -export class QuickFixService implements IExtensionSingleActivationService { +export class CodeActionProviderService implements IExtensionSingleActivationService { constructor(@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry) {} public async activate(): Promise { // tslint:disable-next-line:no-require-imports diff --git a/src/client/providers/serviceRegistry.ts b/src/client/providers/serviceRegistry.ts index 8a4d4bc93a9a..66640455f08a 100644 --- a/src/client/providers/serviceRegistry.ts +++ b/src/client/providers/serviceRegistry.ts @@ -5,11 +5,14 @@ import { IExtensionSingleActivationService } from '../activation/types'; import { IServiceManager } from '../ioc/types'; -import { QuickFixService } from './codeActionProvider/main'; +import { CodeActionProviderService } from './codeActionProvider/main'; import { SortImportsEditingProvider } from './importSortProvider'; import { ISortImportsEditingProvider } from './types'; export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(ISortImportsEditingProvider, SortImportsEditingProvider); - serviceManager.addSingleton(IExtensionSingleActivationService, QuickFixService); + serviceManager.addSingleton( + IExtensionSingleActivationService, + CodeActionProviderService + ); } diff --git a/src/test/providers/codeActionProvider/main.unit.test.ts b/src/test/providers/codeActionProvider/main.unit.test.ts index c9ffe4b30cc0..a29b78fcd4fb 100644 --- a/src/test/providers/codeActionProvider/main.unit.test.ts +++ b/src/test/providers/codeActionProvider/main.unit.test.ts @@ -10,9 +10,9 @@ import * as typemoq from 'typemoq'; import { CodeActionProvider, CodeActionProviderMetadata, DocumentSelector } from 'vscode'; import { IDisposableRegistry } from '../../../client/common/types'; import { LaunchJsonCodeActionProvider } from '../../../client/providers/codeActionProvider/launchJsonCodeActionProvider'; -import { QuickFixService } from '../../../client/providers/codeActionProvider/main'; +import { CodeActionProviderService } from '../../../client/providers/codeActionProvider/main'; -suite('Quick fix service', async () => { +suite('Code Action Provider service', async () => { setup(() => { rewiremock.disable(); }); @@ -38,7 +38,7 @@ suite('Quick fix service', async () => { }; rewiremock.enable(); rewiremock('vscode').with(vscodeMock); - const quickFixService = new QuickFixService(typemoq.Mock.ofType().object); + const quickFixService = new CodeActionProviderService(typemoq.Mock.ofType().object); await quickFixService.activate(); diff --git a/src/test/providers/serviceRegistry.unit.test.ts b/src/test/providers/serviceRegistry.unit.test.ts index 32ae1a9bb0b5..fe4da7a03b12 100644 --- a/src/test/providers/serviceRegistry.unit.test.ts +++ b/src/test/providers/serviceRegistry.unit.test.ts @@ -7,7 +7,7 @@ import { instance, mock, verify } from 'ts-mockito'; import { IExtensionSingleActivationService } from '../../client/activation/types'; import { ServiceManager } from '../../client/ioc/serviceManager'; import { IServiceManager } from '../../client/ioc/types'; -import { QuickFixService } from '../../client/providers/codeActionProvider/main'; +import { CodeActionProviderService } from '../../client/providers/codeActionProvider/main'; import { SortImportsEditingProvider } from '../../client/providers/importSortProvider'; import { registerTypes } from '../../client/providers/serviceRegistry'; import { ISortImportsEditingProvider } from '../../client/providers/types'; @@ -30,7 +30,7 @@ suite('Common Providers Service Registry', () => { verify( serviceManager.addSingleton( IExtensionSingleActivationService, - QuickFixService + CodeActionProviderService ) ).once(); });