Skip to content

Commit e60bdc1

Browse files
author
Kartik Raj
authored
Show quickfixes for launch.json (#10245)
* Added implementation * Added tests * Moved into providers * Add code actions vscode mock and convert .test.ts into unit.test.ts * Rename folders * Dispose registered services * News entry * Added unit tests * Rename core service
1 parent 7d4995d commit e60bdc1

File tree

12 files changed

+207
-3
lines changed

12 files changed

+207
-3
lines changed

news/1 Enhancements/10245.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Show quickfixes for launch.json

src/client/extension.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ import { IServiceContainer, IServiceManager } from './ioc/types';
9595
import { getLanguageConfiguration } from './language/languageConfiguration';
9696
import { LinterCommands } from './linters/linterCommands';
9797
import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry';
98-
import { PythonCodeActionProvider } from './providers/codeActionsProvider';
98+
import { PythonCodeActionProvider } from './providers/codeActionProvider/pythonCodeActionProvider';
9999
import { PythonFormattingEditProvider } from './providers/formatProvider';
100100
import { ReplProvider } from './providers/replProvider';
101101
import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import {
5+
CodeAction,
6+
CodeActionContext,
7+
CodeActionKind,
8+
CodeActionProvider,
9+
Diagnostic,
10+
Range,
11+
TextDocument,
12+
WorkspaceEdit
13+
} from 'vscode';
14+
15+
/**
16+
* Provides code actions for launch.json
17+
*/
18+
export class LaunchJsonCodeActionProvider implements CodeActionProvider {
19+
public provideCodeActions(document: TextDocument, _: Range, context: CodeActionContext): CodeAction[] {
20+
return context.diagnostics
21+
.filter(diagnostic => diagnostic.message === 'Incorrect type. Expected "string".')
22+
.map(diagnostic => this.createFix(document, diagnostic));
23+
}
24+
25+
private createFix(document: TextDocument, diagnostic: Diagnostic): CodeAction {
26+
const finalText = `"${document.getText(diagnostic.range)}"`;
27+
const fix = new CodeAction(`Convert to ${finalText}`, CodeActionKind.QuickFix);
28+
fix.edit = new WorkspaceEdit();
29+
fix.edit.replace(document.uri, diagnostic.range, finalText);
30+
return fix;
31+
}
32+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
import { inject, injectable } from 'inversify';
5+
import * as vscodeTypes from 'vscode';
6+
import { IExtensionSingleActivationService } from '../../activation/types';
7+
import { IDisposableRegistry } from '../../common/types';
8+
import { LaunchJsonCodeActionProvider } from './launchJsonCodeActionProvider';
9+
10+
@injectable()
11+
export class CodeActionProviderService implements IExtensionSingleActivationService {
12+
constructor(@inject(IDisposableRegistry) private disposableRegistry: IDisposableRegistry) {}
13+
public async activate(): Promise<void> {
14+
// tslint:disable-next-line:no-require-imports
15+
const vscode = require('vscode') as typeof vscodeTypes;
16+
const documentSelector: vscodeTypes.DocumentFilter = {
17+
scheme: 'file',
18+
language: 'jsonc',
19+
pattern: '**/launch.json'
20+
};
21+
this.disposableRegistry.push(
22+
vscode.languages.registerCodeActionsProvider(documentSelector, new LaunchJsonCodeActionProvider(), {
23+
providedCodeActionKinds: [vscode.CodeActionKind.QuickFix]
24+
})
25+
);
26+
}
27+
}

src/client/providers/serviceRegistry.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@
33

44
'use strict';
55

6+
import { IExtensionSingleActivationService } from '../activation/types';
67
import { IServiceManager } from '../ioc/types';
8+
import { CodeActionProviderService } from './codeActionProvider/main';
79
import { SortImportsEditingProvider } from './importSortProvider';
810
import { ISortImportsEditingProvider } from './types';
911

1012
export function registerTypes(serviceManager: IServiceManager) {
1113
serviceManager.addSingleton<ISortImportsEditingProvider>(ISortImportsEditingProvider, SortImportsEditingProvider);
14+
serviceManager.addSingleton<IExtensionSingleActivationService>(
15+
IExtensionSingleActivationService,
16+
CodeActionProviderService
17+
);
1218
}

src/test/mocks/vsc/index.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ export namespace vscMock {
7676
}
7777
}
7878

79+
export class CodeAction {
80+
public title: string;
81+
public edit?: vscode.WorkspaceEdit;
82+
public diagnostics?: vscode.Diagnostic[];
83+
public command?: vscode.Command;
84+
public kind?: CodeActionKind;
85+
public isPreferred?: boolean;
86+
constructor(_title: string, _kind?: CodeActionKind) {
87+
this.title = _title;
88+
this.kind = _kind;
89+
}
90+
}
91+
7992
export enum CompletionItemKind {
8093
Text = 0,
8194
Method = 1,
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
import { assert, expect } from 'chai';
7+
import * as TypeMoq from 'typemoq';
8+
import { CodeActionContext, CodeActionKind, Diagnostic, Range, TextDocument, Uri } from 'vscode';
9+
import { LaunchJsonCodeActionProvider } from '../../../client/providers/codeActionProvider/launchJsonCodeActionProvider';
10+
11+
suite('LaunchJson CodeAction Provider', () => {
12+
const documentUri = Uri.parse('a');
13+
let document: TypeMoq.IMock<TextDocument>;
14+
let range: TypeMoq.IMock<Range>;
15+
let context: TypeMoq.IMock<CodeActionContext>;
16+
let diagnostic: TypeMoq.IMock<Diagnostic>;
17+
let codeActionsProvider: LaunchJsonCodeActionProvider;
18+
19+
setup(() => {
20+
codeActionsProvider = new LaunchJsonCodeActionProvider();
21+
document = TypeMoq.Mock.ofType<TextDocument>();
22+
range = TypeMoq.Mock.ofType<Range>();
23+
context = TypeMoq.Mock.ofType<CodeActionContext>();
24+
diagnostic = TypeMoq.Mock.ofType<Diagnostic>();
25+
document.setup(d => d.getText(TypeMoq.It.isAny())).returns(() => 'Diagnostic text');
26+
document.setup(d => d.uri).returns(() => documentUri);
27+
context.setup(c => c.diagnostics).returns(() => [diagnostic.object]);
28+
});
29+
30+
test('Ensure correct code action is returned if diagnostic message equals `Incorrect type. Expected "string".`', async () => {
31+
diagnostic.setup(d => d.message).returns(() => 'Incorrect type. Expected "string".');
32+
diagnostic.setup(d => d.range).returns(() => new Range(2, 0, 7, 8));
33+
34+
const codeActions = codeActionsProvider.provideCodeActions(document.object, range.object, context.object);
35+
36+
// Now ensure that the code action object is as expected
37+
expect(codeActions).to.have.length(1);
38+
expect(codeActions[0].kind).to.eq(CodeActionKind.QuickFix);
39+
expect(codeActions[0].title).to.equal('Convert to "Diagnostic text"');
40+
41+
// Ensure the correct TextEdit is provided
42+
const entries = codeActions[0].edit!.entries();
43+
// Edits the correct document is edited
44+
assert.deepEqual(entries[0][0], documentUri);
45+
const edit = entries[0][1][0];
46+
// Final text is as expected
47+
expect(edit.newText).to.equal('"Diagnostic text"');
48+
// Text edit range is as expected
49+
expect(edit.range.isEqual(new Range(2, 0, 7, 8))).to.equal(true, 'Text edit range not as expected');
50+
});
51+
52+
test('Ensure no code action is returned if diagnostic message does not equal `Incorrect type. Expected "string".`', async () => {
53+
diagnostic.setup(d => d.message).returns(() => 'Random diagnostic message');
54+
55+
const codeActions = codeActionsProvider.provideCodeActions(document.object, range.object, context.object);
56+
57+
expect(codeActions).to.have.length(0);
58+
});
59+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the MIT License.
3+
4+
'use strict';
5+
6+
// tslint:disable: match-default-export-name
7+
import { assert, expect } from 'chai';
8+
import rewiremock from 'rewiremock';
9+
import * as typemoq from 'typemoq';
10+
import { CodeActionProvider, CodeActionProviderMetadata, DocumentSelector } from 'vscode';
11+
import { IDisposableRegistry } from '../../../client/common/types';
12+
import { LaunchJsonCodeActionProvider } from '../../../client/providers/codeActionProvider/launchJsonCodeActionProvider';
13+
import { CodeActionProviderService } from '../../../client/providers/codeActionProvider/main';
14+
15+
suite('Code Action Provider service', async () => {
16+
setup(() => {
17+
rewiremock.disable();
18+
});
19+
test('Code actions are registered correctly', async () => {
20+
let selector: DocumentSelector;
21+
let provider: CodeActionProvider;
22+
let metadata: CodeActionProviderMetadata;
23+
const vscodeMock = {
24+
languages: {
25+
registerCodeActionsProvider: (
26+
_selector: DocumentSelector,
27+
_provider: CodeActionProvider,
28+
_metadata: CodeActionProviderMetadata
29+
) => {
30+
selector = _selector;
31+
provider = _provider;
32+
metadata = _metadata;
33+
}
34+
},
35+
CodeActionKind: {
36+
QuickFix: 'CodeAction'
37+
}
38+
};
39+
rewiremock.enable();
40+
rewiremock('vscode').with(vscodeMock);
41+
const quickFixService = new CodeActionProviderService(typemoq.Mock.ofType<IDisposableRegistry>().object);
42+
43+
await quickFixService.activate();
44+
45+
// Ensure QuickFixLaunchJson is registered with correct arguments
46+
assert.deepEqual(selector!, {
47+
scheme: 'file',
48+
language: 'jsonc',
49+
pattern: '**/launch.json'
50+
});
51+
assert.deepEqual(metadata!, {
52+
// tslint:disable-next-line:no-any
53+
providedCodeActionKinds: ['CodeAction' as any]
54+
});
55+
expect(provider!).instanceOf(LaunchJsonCodeActionProvider);
56+
});
57+
});

src/test/providers/codeActionsProvider.test.ts renamed to src/test/providers/codeActionProvider/pythonCodeActionsProvider.unit.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
import { expect } from 'chai';
77
import * as TypeMoq from 'typemoq';
88
import { CancellationToken, CodeActionContext, CodeActionKind, Range, TextDocument } from 'vscode';
9-
import { PythonCodeActionProvider } from '../../client/providers/codeActionsProvider';
9+
import { PythonCodeActionProvider } from '../../../client/providers/codeActionProvider/pythonCodeActionProvider';
1010

11-
suite('CodeAction Provider', () => {
11+
suite('Python CodeAction Provider', () => {
1212
let codeActionsProvider: PythonCodeActionProvider;
1313
let document: TypeMoq.IMock<TextDocument>;
1414
let range: TypeMoq.IMock<Range>;

src/test/providers/serviceRegistry.unit.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
'use strict';
55

66
import { instance, mock, verify } from 'ts-mockito';
7+
import { IExtensionSingleActivationService } from '../../client/activation/types';
78
import { ServiceManager } from '../../client/ioc/serviceManager';
89
import { IServiceManager } from '../../client/ioc/types';
10+
import { CodeActionProviderService } from '../../client/providers/codeActionProvider/main';
911
import { SortImportsEditingProvider } from '../../client/providers/importSortProvider';
1012
import { registerTypes } from '../../client/providers/serviceRegistry';
1113
import { ISortImportsEditingProvider } from '../../client/providers/types';
@@ -25,5 +27,11 @@ suite('Common Providers Service Registry', () => {
2527
SortImportsEditingProvider
2628
)
2729
).once();
30+
verify(
31+
serviceManager.addSingleton<IExtensionSingleActivationService>(
32+
IExtensionSingleActivationService,
33+
CodeActionProviderService
34+
)
35+
).once();
2836
});
2937
});

src/test/vscode-mock.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export function initialize() {
5050
}
5151

5252
mockedVSCode.Disposable = vscodeMocks.vscMock.Disposable as any;
53+
mockedVSCode.CodeAction = vscodeMocks.vscMock.CodeAction;
5354
mockedVSCode.EventEmitter = vscodeMocks.vscMock.EventEmitter;
5455
mockedVSCode.CancellationTokenSource = vscodeMocks.vscMock.CancellationTokenSource;
5556
mockedVSCode.CompletionItemKind = vscodeMocks.vscMock.CompletionItemKind;

0 commit comments

Comments
 (0)