Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add diagnostics feature to the language server VSCODE-375 #493

Merged
merged 11 commits into from
Mar 17, 2023
Merged
2 changes: 1 addition & 1 deletion .github/workflows/test-and-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
uses: actions/[email protected]
with:
# Version Spec of the version to use. Examples: 12.x, 10.15.1, >=10.15.0
node-version: ^14.17.5
node-version: ^16.16.0

- name: Run node-gyp bug workaround script
run: |
Expand Down
3 changes: 3 additions & 0 deletions src/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ enum EXTENSION_COMMANDS {
MDB_RUN_ALL_PLAYGROUND_BLOCKS = 'mdb.runAllPlaygroundBlocks',
MDB_RUN_ALL_OR_SELECTED_PLAYGROUND_BLOCKS = 'mdb.runPlayground',

MDB_FIX_THIS_INVALID_INTERACTIVE_SYNTAX = 'mdb.fixThisInvalidInteractiveSyntax',
MDB_FIX_ALL_INVALID_INTERACTIVE_SYNTAX = 'mdb.fixAllInvalidInteractiveSyntax',

MDB_EXPORT_TO_PYTHON = 'mdb.exportToPython',
MDB_EXPORT_TO_JAVA = 'mdb.exportToJava',
MDB_EXPORT_TO_CSHARP = 'mdb.exportToCsharp',
Expand Down
20 changes: 18 additions & 2 deletions src/editors/editorsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { EJSON } from 'bson';
import ActiveConnectionCodeLensProvider from './activeConnectionCodeLensProvider';
import ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider';
import PlaygroundSelectedCodeActionProvider from './playgroundSelectedCodeActionProvider';
import PlaygroundDiagnosticsCodeActionProvider from './playgroundDiagnosticsCodeActionProvider';
import ConnectionController from '../connectionController';
import CollectionDocumentsCodeLensProvider from './collectionDocumentsCodeLensProvider';
import CollectionDocumentsOperationsStore from './collectionDocumentsOperationsStore';
Expand Down Expand Up @@ -84,6 +85,7 @@ export function getViewCollectionDocumentsUri(
*/
export default class EditorsController {
_playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider;
_playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider;
_connectionController: ConnectionController;
_playgroundController: PlaygroundController;
_collectionDocumentsOperationsStore =
Expand All @@ -110,7 +112,8 @@ export default class EditorsController {
playgroundResultViewProvider: PlaygroundResultProvider,
activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider,
exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider,
codeActionProvider: PlaygroundSelectedCodeActionProvider,
playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider,
playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider,
editDocumentCodeLensProvider: EditDocumentCodeLensProvider
) {
this._connectionController = connectionController;
Expand Down Expand Up @@ -141,7 +144,10 @@ export default class EditorsController {
new CollectionDocumentsCodeLensProvider(
this._collectionDocumentsOperationsStore
);
this._playgroundSelectedCodeActionProvider = codeActionProvider;
this._playgroundSelectedCodeActionProvider =
playgroundSelectedCodeActionProvider;
this._playgroundDiagnosticsCodeActionProvider =
playgroundDiagnosticsCodeActionProvider;

vscode.workspace.onDidCloseTextDocument((e) => {
const uriParams = new URLSearchParams(e.uri.query);
Expand Down Expand Up @@ -436,6 +442,16 @@ export default class EditorsController {
}
)
);
this._context.subscriptions.push(
vscode.languages.registerCodeActionsProvider(
'javascript',
this._playgroundDiagnosticsCodeActionProvider,
{
providedCodeActionKinds:
PlaygroundDiagnosticsCodeActionProvider.providedCodeActionKinds,
}
)
);
}

deactivate(): void {
Expand Down
40 changes: 38 additions & 2 deletions src/editors/playgroundController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ import {
ExportToLanguageAddons,
ExportToLanguageNamespace,
ExportToLanguageMode,
ThisDiagnosticFix,
AllDiagnosticFixes,
} from '../types/playgroundType';
import PlaygroundResultProvider, {
PLAYGROUND_RESULT_SCHEME,
Expand Down Expand Up @@ -125,7 +127,7 @@ export default class PlaygroundController {
playgroundResultViewProvider: PlaygroundResultProvider,
activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider,
exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider,
codeActionProvider: PlaygroundSelectedCodeActionProvider,
playgroundSelectedCodeActionProvide: PlaygroundSelectedCodeActionProvider,
explorerController: ExplorerController
) {
this._connectionController = connectionController;
Expand All @@ -138,7 +140,8 @@ export default class PlaygroundController {
vscode.window.createOutputChannel('Playground output');
this._activeConnectionCodeLensProvider = activeConnectionCodeLensProvider;
this._exportToLanguageCodeLensProvider = exportToLanguageCodeLensProvider;
this._playgroundSelectedCodeActionProvider = codeActionProvider;
this._playgroundSelectedCodeActionProvider =
playgroundSelectedCodeActionProvide;
this._explorerController = explorerController;

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

// We count open untitled playground files to use this number as part of a new playground path.
const numberUntitledPlaygrounds = vscode.workspace.textDocuments.filter(
(doc) => isPlayground(doc.uri)
).length;

// We need a secondary `mongodb` extension otherwise VSCode will
// suggest playground-1.js name when saving playground to the disk.
// Users can open playgrounds from the disk
// and we need a way to distinguish this files from regular JS files.
const fileName = path.join(
filePath,
`playground-${numberUntitledPlaygrounds + 1}.mongodb.js`
Expand All @@ -293,6 +302,8 @@ export default class PlaygroundController {
await vscode.workspace.applyEdit(edit);

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

// Focus new text document.
Expand Down Expand Up @@ -639,6 +650,31 @@ export default class PlaygroundController {
return this._evaluatePlayground();
}

async fixThisInvalidInteractiveSyntax({
documentUri,
range,
fix,
}: ThisDiagnosticFix) {
const edit = new vscode.WorkspaceEdit();
edit.replace(documentUri, range, fix);
await vscode.workspace.applyEdit(edit);
return true;
}

async fixAllInvalidInteractiveSyntax({
documentUri,
diagnostics,
}: AllDiagnosticFixes) {
const edit = new vscode.WorkspaceEdit();

for (const { range, fix } of diagnostics) {
edit.replace(documentUri, range, fix);
}

await vscode.workspace.applyEdit(edit);
return true;
}

async openPlayground(filePath: string): Promise<boolean> {
try {
const document = await vscode.workspace.openTextDocument(filePath);
Expand Down
89 changes: 89 additions & 0 deletions src/editors/playgroundDiagnosticsCodeActionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import * as vscode from 'vscode';

import type { Diagnostic } from 'vscode-languageserver/node';

import EXTENSION_COMMANDS from '../commands';
import DIAGNOSTIC_CODES from './../language/diagnosticCodes';

export default class PlaygroundDiagnosticsCodeActionProvider
implements vscode.CodeActionProvider
{
_onDidChangeCodeCodeAction: vscode.EventEmitter<void> =
new vscode.EventEmitter<void>();

static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix];

constructor() {
vscode.workspace.onDidChangeConfiguration(() => {
this._onDidChangeCodeCodeAction.fire();
});
}

readonly onDidChangeCodeLenses: vscode.Event<void> =
this._onDidChangeCodeCodeAction.event;

provideCodeActions(
document: vscode.TextDocument,
_range: vscode.Range | vscode.Selection,
context: vscode.CodeActionContext
): vscode.ProviderResult<(vscode.CodeAction | vscode.Command)[]> {
const fixCodeActions: vscode.CodeAction[] = [];
const diagnostics = context.diagnostics as unknown as Diagnostic[];

for (const diagnostic of diagnostics) {
switch (diagnostic.code) {
case DIAGNOSTIC_CODES.invalidInteractiveSyntaxes:
{
const fix = new vscode.CodeAction(
'Fix this interactive syntax problem',
vscode.CodeActionKind.QuickFix
);
fix.command = {
command:
EXTENSION_COMMANDS.MDB_FIX_THIS_INVALID_INTERACTIVE_SYNTAX,
title: 'Fix invalid interactive syntax',
arguments: [
{
documentUri: document.uri,
range: diagnostic.range,
fix: diagnostic.data?.fix,
},
],
};
fixCodeActions.push(fix);
}
break;
default:
break;
}
}

const allDiagnostics = vscode.languages
.getDiagnostics(document.uri)
.filter((d) => d.code === DIAGNOSTIC_CODES.invalidInteractiveSyntaxes);

if (allDiagnostics.length > 1) {
const fix = new vscode.CodeAction(
'Fix all interactive syntax problems',
vscode.CodeActionKind.QuickFix
);

fix.command = {
command: EXTENSION_COMMANDS.MDB_FIX_ALL_INVALID_INTERACTIVE_SYNTAX,
title: 'Fix invalid interactive syntax',
arguments: [
{
documentUri: document.uri,
diagnostics: allDiagnostics.map((d) => ({
range: d.range,
fix: (<Diagnostic>d).data?.fix,
alenakhineika marked this conversation as resolved.
Show resolved Hide resolved
})),
},
],
};
fixCodeActions.push(fix);
}

return fixCodeActions;
}
}
5 changes: 5 additions & 0 deletions src/language/diagnosticCodes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum DIAGNOSTIC_CODES {
invalidInteractiveSyntaxes = 'playground.invalidInteractiveSyntaxes',
}

export default DIAGNOSTIC_CODES;
Loading