Skip to content

Commit c31a9c5

Browse files
feat: add diagnostics feature to the language server VSCODE-375 (#493)
* feat: add diagnostics feature to the language server VSCODE-375 * test: add diagnostic suite and hopefully fix telemetry suit * test: try to replace property with sandbox * test: simplify the agg exported telemetry test * refactor: provide fix for new dbs and address other pr comments * refactor: remove unused function * fix: do not highlight use in the middle of the string * refactor: remove trim * fix: remove extra space * fix: do not find use diagnostic issue when use in the middle of other command * refactor: checking for multiple conditions with startsWith
1 parent 3467e40 commit c31a9c5

14 files changed

+888
-252
lines changed

.github/workflows/test-and-build.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ jobs:
4343
uses: actions/[email protected]
4444
with:
4545
# Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0
46-
node-version: ^14.17.5
46+
node-version: ^16.16.0
4747

4848
- name: Run node-gyp bug workaround script
4949
run: |

src/commands/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ enum EXTENSION_COMMANDS {
1414
MDB_RUN_ALL_PLAYGROUND_BLOCKS = 'mdb.runAllPlaygroundBlocks',
1515
MDB_RUN_ALL_OR_SELECTED_PLAYGROUND_BLOCKS = 'mdb.runPlayground',
1616

17+
MDB_FIX_THIS_INVALID_INTERACTIVE_SYNTAX = 'mdb.fixThisInvalidInteractiveSyntax',
18+
MDB_FIX_ALL_INVALID_INTERACTIVE_SYNTAX = 'mdb.fixAllInvalidInteractiveSyntax',
19+
1720
MDB_EXPORT_TO_PYTHON = 'mdb.exportToPython',
1821
MDB_EXPORT_TO_JAVA = 'mdb.exportToJava',
1922
MDB_EXPORT_TO_CSHARP = 'mdb.exportToCsharp',

src/editors/editorsController.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { EJSON } from 'bson';
44
import ActiveConnectionCodeLensProvider from './activeConnectionCodeLensProvider';
55
import ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider';
66
import PlaygroundSelectedCodeActionProvider from './playgroundSelectedCodeActionProvider';
7+
import PlaygroundDiagnosticsCodeActionProvider from './playgroundDiagnosticsCodeActionProvider';
78
import ConnectionController from '../connectionController';
89
import CollectionDocumentsCodeLensProvider from './collectionDocumentsCodeLensProvider';
910
import CollectionDocumentsOperationsStore from './collectionDocumentsOperationsStore';
@@ -84,6 +85,7 @@ export function getViewCollectionDocumentsUri(
8485
*/
8586
export default class EditorsController {
8687
_playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider;
88+
_playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider;
8789
_connectionController: ConnectionController;
8890
_playgroundController: PlaygroundController;
8991
_collectionDocumentsOperationsStore =
@@ -110,7 +112,8 @@ export default class EditorsController {
110112
playgroundResultViewProvider: PlaygroundResultProvider,
111113
activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider,
112114
exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider,
113-
codeActionProvider: PlaygroundSelectedCodeActionProvider,
115+
playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider,
116+
playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider,
114117
editDocumentCodeLensProvider: EditDocumentCodeLensProvider
115118
) {
116119
this._connectionController = connectionController;
@@ -141,7 +144,10 @@ export default class EditorsController {
141144
new CollectionDocumentsCodeLensProvider(
142145
this._collectionDocumentsOperationsStore
143146
);
144-
this._playgroundSelectedCodeActionProvider = codeActionProvider;
147+
this._playgroundSelectedCodeActionProvider =
148+
playgroundSelectedCodeActionProvider;
149+
this._playgroundDiagnosticsCodeActionProvider =
150+
playgroundDiagnosticsCodeActionProvider;
145151

146152
vscode.workspace.onDidCloseTextDocument((e) => {
147153
const uriParams = new URLSearchParams(e.uri.query);
@@ -436,6 +442,16 @@ export default class EditorsController {
436442
}
437443
)
438444
);
445+
this._context.subscriptions.push(
446+
vscode.languages.registerCodeActionsProvider(
447+
'javascript',
448+
this._playgroundDiagnosticsCodeActionProvider,
449+
{
450+
providedCodeActionKinds:
451+
PlaygroundDiagnosticsCodeActionProvider.providedCodeActionKinds,
452+
}
453+
)
454+
);
439455
}
440456

441457
deactivate(): void {

src/editors/playgroundController.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import {
2828
ExportToLanguageAddons,
2929
ExportToLanguageNamespace,
3030
ExportToLanguageMode,
31+
ThisDiagnosticFix,
32+
AllDiagnosticFixes,
3133
} from '../types/playgroundType';
3234
import PlaygroundResultProvider, {
3335
PLAYGROUND_RESULT_SCHEME,
@@ -125,7 +127,7 @@ export default class PlaygroundController {
125127
playgroundResultViewProvider: PlaygroundResultProvider,
126128
activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider,
127129
exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider,
128-
codeActionProvider: PlaygroundSelectedCodeActionProvider,
130+
playgroundSelectedCodeActionProvide: PlaygroundSelectedCodeActionProvider,
129131
explorerController: ExplorerController
130132
) {
131133
this._connectionController = connectionController;
@@ -138,7 +140,8 @@ export default class PlaygroundController {
138140
vscode.window.createOutputChannel('Playground output');
139141
this._activeConnectionCodeLensProvider = activeConnectionCodeLensProvider;
140142
this._exportToLanguageCodeLensProvider = exportToLanguageCodeLensProvider;
141-
this._playgroundSelectedCodeActionProvider = codeActionProvider;
143+
this._playgroundSelectedCodeActionProvider =
144+
playgroundSelectedCodeActionProvide;
142145
this._explorerController = explorerController;
143146

144147
this._connectionController.addEventListener(
@@ -272,9 +275,15 @@ export default class PlaygroundController {
272275
const workspaceFolder = vscode.workspace.workspaceFolders?.[0];
273276
const filePath = workspaceFolder?.uri.fsPath || os.homedir();
274277

278+
// We count open untitled playground files to use this number as part of a new playground path.
275279
const numberUntitledPlaygrounds = vscode.workspace.textDocuments.filter(
276280
(doc) => isPlayground(doc.uri)
277281
).length;
282+
283+
// We need a secondary `mongodb` extension otherwise VSCode will
284+
// suggest playground-1.js name when saving playground to the disk.
285+
// Users can open playgrounds from the disk
286+
// and we need a way to distinguish this files from regular JS files.
278287
const fileName = path.join(
279288
filePath,
280289
`playground-${numberUntitledPlaygrounds + 1}.mongodb.js`
@@ -293,6 +302,8 @@ export default class PlaygroundController {
293302
await vscode.workspace.applyEdit(edit);
294303

295304
// Actually show the editor.
305+
// We open playgrounds by URI to use the secondary `mongodb` extension
306+
// as an identifier that distinguishes them from regular JS files.
296307
const document = await vscode.workspace.openTextDocument(documentUri);
297308

298309
// Focus new text document.
@@ -639,6 +650,31 @@ export default class PlaygroundController {
639650
return this._evaluatePlayground();
640651
}
641652

653+
async fixThisInvalidInteractiveSyntax({
654+
documentUri,
655+
range,
656+
fix,
657+
}: ThisDiagnosticFix) {
658+
const edit = new vscode.WorkspaceEdit();
659+
edit.replace(documentUri, range, fix);
660+
await vscode.workspace.applyEdit(edit);
661+
return true;
662+
}
663+
664+
async fixAllInvalidInteractiveSyntax({
665+
documentUri,
666+
diagnostics,
667+
}: AllDiagnosticFixes) {
668+
const edit = new vscode.WorkspaceEdit();
669+
670+
for (const { range, fix } of diagnostics) {
671+
edit.replace(documentUri, range, fix);
672+
}
673+
674+
await vscode.workspace.applyEdit(edit);
675+
return true;
676+
}
677+
642678
async openPlayground(filePath: string): Promise<boolean> {
643679
try {
644680
const document = await vscode.workspace.openTextDocument(filePath);
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as vscode from 'vscode';
2+
3+
import type { Diagnostic } from 'vscode-languageserver/node';
4+
5+
import EXTENSION_COMMANDS from '../commands';
6+
import DIAGNOSTIC_CODES from './../language/diagnosticCodes';
7+
8+
export default class PlaygroundDiagnosticsCodeActionProvider
9+
implements vscode.CodeActionProvider
10+
{
11+
_onDidChangeCodeCodeAction: vscode.EventEmitter<void> =
12+
new vscode.EventEmitter<void>();
13+
14+
static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix];
15+
16+
constructor() {
17+
vscode.workspace.onDidChangeConfiguration(() => {
18+
this._onDidChangeCodeCodeAction.fire();
19+
});
20+
}
21+
22+
readonly onDidChangeCodeLenses: vscode.Event<void> =
23+
this._onDidChangeCodeCodeAction.event;
24+
25+
provideCodeActions(
26+
document: vscode.TextDocument,
27+
_range: vscode.Range | vscode.Selection,
28+
context: vscode.CodeActionContext
29+
): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
30+
const fixCodeActions: vscode.CodeAction[] = [];
31+
const diagnostics = context.diagnostics as unknown as Diagnostic[];
32+
33+
for (const diagnostic of diagnostics) {
34+
switch (diagnostic.code) {
35+
case DIAGNOSTIC_CODES.invalidInteractiveSyntaxes:
36+
{
37+
const fix = new vscode.CodeAction(
38+
'Fix this interactive syntax problem',
39+
vscode.CodeActionKind.QuickFix
40+
);
41+
fix.command = {
42+
command:
43+
EXTENSION_COMMANDS.MDB_FIX_THIS_INVALID_INTERACTIVE_SYNTAX,
44+
title: 'Fix invalid interactive syntax',
45+
arguments: [
46+
{
47+
documentUri: document.uri,
48+
range: diagnostic.range,
49+
fix: diagnostic.data?.fix,
50+
},
51+
],
52+
};
53+
fixCodeActions.push(fix);
54+
}
55+
break;
56+
default:
57+
break;
58+
}
59+
}
60+
61+
const allDiagnostics = vscode.languages
62+
.getDiagnostics(document.uri)
63+
.filter((d) => d.code === DIAGNOSTIC_CODES.invalidInteractiveSyntaxes);
64+
65+
if (allDiagnostics.length > 1) {
66+
const fix = new vscode.CodeAction(
67+
'Fix all interactive syntax problems',
68+
vscode.CodeActionKind.QuickFix
69+
);
70+
71+
fix.command = {
72+
command: EXTENSION_COMMANDS.MDB_FIX_ALL_INVALID_INTERACTIVE_SYNTAX,
73+
title: 'Fix invalid interactive syntax',
74+
arguments: [
75+
{
76+
documentUri: document.uri,
77+
diagnostics: allDiagnostics.map((d) => ({
78+
range: d.range,
79+
fix: (d as Diagnostic).data?.fix,
80+
})),
81+
},
82+
],
83+
};
84+
fixCodeActions.push(fix);
85+
}
86+
87+
return fixCodeActions;
88+
}
89+
}

src/language/diagnosticCodes.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
enum DIAGNOSTIC_CODES {
2+
invalidInteractiveSyntaxes = 'playground.invalidInteractiveSyntaxes',
3+
}
4+
5+
export default DIAGNOSTIC_CODES;

0 commit comments

Comments
 (0)