From 126d65eeb162efa18e0978055b1a8260e56b466b Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Tue, 14 Sep 2021 10:30:56 +0200 Subject: [PATCH 1/7] feat: export to language VSCODE-296 --- package-lock.json | 97 ++++++++++++++++--- package.json | 10 ++ src/commands/index.ts | 2 + src/editors/codeActionProvider.ts | 47 ++++++++- src/editors/playgroundController.ts | 53 +++++++++- src/editors/playgroundResultProvider.ts | 10 +- src/mdbExtensionController.ts | 3 + .../editors/playgroundController.test.ts | 54 ++++++++--- webpack.config.js | 10 +- 9 files changed, 247 insertions(+), 39 deletions(-) diff --git a/package-lock.json b/package-lock.json index 99434067e..3e6975a06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,9 +24,11 @@ "@mongosh/shell-api": "^1.0.4", "analytics-node": "^5.0.0", "bson": "^4.4.1", + "bson-transpilers": "^1.2.0", "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.2.0", + "ejson-shell-parser": "^1.1.1", "micromatch": "^4.0.4", "mongodb": "^4.1.3", "mongodb-cloud-info": "^1.1.2", @@ -76,6 +78,7 @@ "chai-json-schema": "^1.5.1", "chalk": "^4.1.2", "cli-ux": "^5.6.3", + "context-map-webpack-plugin": "^0.1.0", "cross-env": "^7.0.3", "css-loader": "^3.4.2", "depcheck": "^1.4.2", @@ -3106,7 +3109,6 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -3396,6 +3398,11 @@ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, + "node_modules/antlr4": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.7.2.tgz", + "integrity": "sha512-vZA1xYufXLe3LX+ja9rIVxjRmILb1x3k7KYZHltRbfJtXjJ1DlFIqt+CbPYmghx0EuzY9DajiDw+MdyEt1qAsQ==" + }, "node_modules/anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -3481,7 +3488,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "dependencies": { "sprintf-js": "~1.0.2" } @@ -4388,6 +4394,17 @@ "node": ">=6.9.0" } }, + "node_modules/bson-transpilers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bson-transpilers/-/bson-transpilers-1.2.0.tgz", + "integrity": "sha512-6JJWpPWbrJ4ebtpqie+AdTcT6owlWl/It5RELLbLlNKqGuzcp2H2kjjLCvDfUefob4zdLlTdm6cb330s2lX//w==", + "dependencies": { + "antlr4": "4.7.2", + "bson": "^4.4.1", + "context-eval": "^0.1.0", + "js-yaml": "^3.13.1" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -5758,6 +5775,20 @@ "node": ">= 0.6" } }, + "node_modules/context-eval": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/context-eval/-/context-eval-0.1.0.tgz", + "integrity": "sha1-P8pxfX3wI6l4XwjGWkGJo93wP6s=" + }, + "node_modules/context-map-webpack-plugin": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/context-map-webpack-plugin/-/context-map-webpack-plugin-0.1.0.tgz", + "integrity": "sha512-iq0u4ChgErhMk+l/3va5xJvjoVf14sA+5mcsBWcmWA3B8Rt31/F+PbbbAl5LteiIyWJWy2WxLPUxp08Yc0HxRg==", + "dev": true, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, "node_modules/convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -7152,6 +7183,17 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/ejson-shell-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ejson-shell-parser/-/ejson-shell-parser-1.1.1.tgz", + "integrity": "sha512-I+1FreeR4SmuU8aYtQEyQDQIBLu0xGIWEfwoKp7mX891i6QEjxQz/lLFw1eho3IPYBjfmbTES0Jogi2GE+D1rA==", + "dependencies": { + "acorn": "^7.4.0" + }, + "peerDependencies": { + "bson": "^4.0.3" + } + }, "node_modules/electron-to-chromium": { "version": "1.3.796", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.796.tgz", @@ -8258,7 +8300,6 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -13633,7 +13674,6 @@ "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -21124,8 +21164,7 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "node_modules/ssh2": { "version": "0.8.9", @@ -26868,8 +26907,7 @@ "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" }, "acorn-globals": { "version": "6.0.0", @@ -27130,6 +27168,11 @@ "integrity": "sha1-ZlWX3oap/+Oqm/vmyuXG6kJrSXk=", "dev": true }, + "antlr4": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/antlr4/-/antlr4-4.7.2.tgz", + "integrity": "sha512-vZA1xYufXLe3LX+ja9rIVxjRmILb1x3k7KYZHltRbfJtXjJ1DlFIqt+CbPYmghx0EuzY9DajiDw+MdyEt1qAsQ==" + }, "anymatch": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", @@ -27210,7 +27253,6 @@ "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, "requires": { "sprintf-js": "~1.0.2" } @@ -27997,6 +28039,17 @@ "buffer": "^5.6.0" } }, + "bson-transpilers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bson-transpilers/-/bson-transpilers-1.2.0.tgz", + "integrity": "sha512-6JJWpPWbrJ4ebtpqie+AdTcT6owlWl/It5RELLbLlNKqGuzcp2H2kjjLCvDfUefob4zdLlTdm6cb330s2lX//w==", + "requires": { + "antlr4": "4.7.2", + "bson": "^4.4.1", + "context-eval": "^0.1.0", + "js-yaml": "^3.13.1" + } + }, "buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -29108,6 +29161,17 @@ "safe-buffer": "5.1.2" } }, + "context-eval": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/context-eval/-/context-eval-0.1.0.tgz", + "integrity": "sha1-P8pxfX3wI6l4XwjGWkGJo93wP6s=" + }, + "context-map-webpack-plugin": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/context-map-webpack-plugin/-/context-map-webpack-plugin-0.1.0.tgz", + "integrity": "sha512-iq0u4ChgErhMk+l/3va5xJvjoVf14sA+5mcsBWcmWA3B8Rt31/F+PbbbAl5LteiIyWJWy2WxLPUxp08Yc0HxRg==", + "dev": true + }, "convert-source-map": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", @@ -30286,6 +30350,14 @@ "safer-buffer": "^2.1.0" } }, + "ejson-shell-parser": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ejson-shell-parser/-/ejson-shell-parser-1.1.1.tgz", + "integrity": "sha512-I+1FreeR4SmuU8aYtQEyQDQIBLu0xGIWEfwoKp7mX891i6QEjxQz/lLFw1eho3IPYBjfmbTES0Jogi2GE+D1rA==", + "requires": { + "acorn": "^7.4.0" + } + }, "electron-to-chromium": { "version": "1.3.796", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.796.tgz", @@ -31209,8 +31281,7 @@ "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" }, "esquery": { "version": "1.3.1", @@ -35696,7 +35767,6 @@ "version": "3.14.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, "requires": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -42011,8 +42081,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "ssh2": { "version": "0.8.9", diff --git a/package.json b/package.json index 471818545..dd3592d87 100644 --- a/package.json +++ b/package.json @@ -246,6 +246,10 @@ "dark": "images/dark/play.svg" } }, + { + "command": "mdb.exportToLanguage", + "title": "MongoDB: Export To Language" + }, { "command": "mdb.addConnection", "title": "Add MongoDB Connection", @@ -581,6 +585,10 @@ "command": "mdb.runAllPlaygroundBlocks", "when": "editorLangId == mongodb" }, + { + "command": "mdb.exportToLanguage", + "when": "false" + }, { "command": "mdb.refreshPlaygroundsFromTreeView", "when": "false" @@ -876,6 +884,7 @@ "@mongosh/shell-api": "^1.0.4", "analytics-node": "^5.0.0", "bson": "^4.4.1", + "bson-transpilers": "^1.2.0", "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.2.0", @@ -928,6 +937,7 @@ "chai-json-schema": "^1.5.1", "chalk": "^4.1.2", "cli-ux": "^5.6.3", + "context-map-webpack-plugin": "^0.1.0", "cross-env": "^7.0.3", "css-loader": "^3.4.2", "depcheck": "^1.4.2", diff --git a/src/commands/index.ts b/src/commands/index.ts index 61e21e752..d6520ba42 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -14,6 +14,8 @@ enum EXTENSION_COMMANDS { MDB_RUN_ALL_PLAYGROUND_BLOCKS = 'mdb.runAllPlaygroundBlocks', MDB_RUN_ALL_OR_SELECTED_PLAYGROUND_BLOCKS = 'mdb.runPlayground', + MDB_EXPORT_TO_LANGUAGE = 'mdb.exportToLanguage', + MDB_OPEN_MONGODB_DOCUMENT_FROM_CODE_LENS = 'mdb.openMongoDBDocumentFromCodeLens', MDB_OPEN_MONGODB_DOCUMENT_FROM_TREE = 'mdb.openMongoDBDocumentFromTree', MDB_SAVE_MONGODB_DOCUMENT = 'mdb.saveMongoDBDocument', diff --git a/src/editors/codeActionProvider.ts b/src/editors/codeActionProvider.ts index b209ddbce..638315a62 100644 --- a/src/editors/codeActionProvider.ts +++ b/src/editors/codeActionProvider.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; + import EXTENSION_COMMANDS from '../commands'; import PlaygroundController from './playgroundController'; @@ -16,14 +17,54 @@ export default class CodeActionProvider implements vscode.CodeActionProvider { return; } - const commandAction = new vscode.CodeAction('Run selected playground blocks', vscode.CodeActionKind.Empty); + const codeActions: vscode.CodeAction[] = []; - commandAction.command = { + const runSelectedPlaygroundBlockCommand = new vscode.CodeAction('Run selected playground blocks', vscode.CodeActionKind.Empty); + runSelectedPlaygroundBlockCommand.command = { command: EXTENSION_COMMANDS.MDB_RUN_SELECTED_PLAYGROUND_BLOCKS, title: 'Run selected playground blocks', tooltip: 'Run selected playground blocks' }; + codeActions.push(runSelectedPlaygroundBlockCommand); + + if (this._playgroundController._selectedText.trim().startsWith('[')) { + const exportToPythonCommand = new vscode.CodeAction('Export To Python 3', vscode.CodeActionKind.Empty); + exportToPythonCommand.command = { + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, + title: 'Export To Python 3', + tooltip: 'Export To Python 3', + arguments: ['python'] + }; + codeActions.push(exportToPythonCommand); + + const exportToJavaCommand = new vscode.CodeAction('Export To Java', vscode.CodeActionKind.Empty); + exportToJavaCommand.command = { + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, + title: 'Export To Java', + tooltip: 'Export To Java', + arguments: ['java'] + }; + codeActions.push(exportToJavaCommand); + + const exportToCsharpCommand = new vscode.CodeAction('Export To C#', vscode.CodeActionKind.Empty); + exportToCsharpCommand.command = { + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, + title: 'Export To C#', + tooltip: 'Export To C#', + arguments: ['csharp'] + }; + codeActions.push(exportToCsharpCommand); + + const exportToJSCommand = new vscode.CodeAction('Export To Node', vscode.CodeActionKind.Empty); + exportToJSCommand.command = { + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, + title: 'Export To Node', + tooltip: 'Export To Node', + arguments: ['javascript'] + }; + codeActions.push(exportToJSCommand); + } - return [commandAction]; + return codeActions; } } diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index d5d4bef82..918966ccf 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -1,5 +1,4 @@ import * as vscode from 'vscode'; -import { EJSON } from 'bson'; import ActiveConnectionCodeLensProvider from './activeConnectionCodeLensProvider'; import ConnectionController, { @@ -23,6 +22,7 @@ import { StatusView } from '../views'; import TelemetryService from '../telemetry/telemetryService'; import { ConnectionOptions } from '../types/connectionOptionsType'; +const transpiler = require('bson-transpilers'); const log = createLogger('playground controller'); const getSSLFilePathsFromConnectionModel = ( @@ -342,7 +342,29 @@ export default class PlaygroundController { } } - _getDocumentLanguage(content?: EJSON.SerializableTypes): string { + _getDocumentLanguage(playgroundResult: PlaygroundResult): string { + if (!playgroundResult) { + return 'plaintext'; + } + + const { type, content } = playgroundResult; + + if (type === 'python') { + return 'python'; + } + + if (type === 'java') { + return 'java'; + } + + if (type === 'csharp') { + return 'csharp'; + } + + if (type === 'javascript') { + return 'javascript'; + } + if (typeof content === 'object' && content !== null) { return 'json'; } @@ -364,7 +386,7 @@ export default class PlaygroundController { await this._showResultAsVirtualDocument(); if (this._playgroundResultTextDocument) { - const language = this._getDocumentLanguage(this._playgroundResult?.content); + const language = this._getDocumentLanguage(this._playgroundResult); await vscode.languages.setTextDocumentLanguage( this._playgroundResultTextDocument, @@ -533,6 +555,31 @@ export default class PlaygroundController { } } + async exportToLanguage(language: string): Promise { + log.info(`Start export to ${language} language`); + + try { + const compiledString = transpiler.shell[language].compile(this._selectedText); + + this._playgroundResult = { + namespace: null, + type: language, + content: compiledString + }; + log.info(`Export to ${language} language result`, this._playgroundResult); + await this._openPlaygroundResult(); + } catch (error) { + const printableError = error as { message: string }; + + log.error(`Export to ${language} language error`, error); + void vscode.window.showErrorMessage( + `Unable to export to ${language} language: ${printableError.message}` + ); + } + + return true; + } + deactivate(): void { this._connectionController.removeEventListener( DataServiceEventTypes.ACTIVE_CONNECTION_CHANGED, diff --git a/src/editors/playgroundResultProvider.ts b/src/editors/playgroundResultProvider.ts index 877c859e1..c661d14a4 100644 --- a/src/editors/playgroundResultProvider.ts +++ b/src/editors/playgroundResultProvider.ts @@ -53,8 +53,14 @@ implements vscode.TextDocumentContentProvider { return 'undefined'; } - if (type === 'string') { - return this._playgroundResult.content as string; + if ( + type === 'string' || + type === 'python' || + type === 'java' || + type === 'csharp' || + type === 'javascript' + ) { + return this._playgroundResult.content; } this._editDocumentCodeLensProvider?.updateCodeLensesForPlayground( diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index e7858c098..ab3a0e564 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -188,6 +188,9 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerCommand(EXTENSION_COMMANDS.MDB_REFRESH_PLAYGROUNDS, () => this._playgroundsExplorer.refresh() ); + this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, (language) => + this._playgroundController.exportToLanguage(language) + ); this.registerCommand( EXTENSION_COMMANDS.MDB_OPEN_MONGODB_DOCUMENT_FROM_CODE_LENS, (data: EditDocumentInfo) => { diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index f42cf4dbc..7edea4c02 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -472,7 +472,11 @@ suite('Playground Controller Test Suite', function () { .getConfiguration('mdb') .update('confirmRunAll', false); const language = testPlaygroundController._getDocumentLanguage({ - test: 'value' + namespace: null, + type: 'object', + content: { + test: 'value' + } }); expect(language).to.be.equal('json'); @@ -482,9 +486,13 @@ suite('Playground Controller Test Suite', function () { await vscode.workspace .getConfiguration('mdb') .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage([ - { test: 'value' } - ]); + const language = testPlaygroundController._getDocumentLanguage({ + namespace: null, + type: 'object', + content: [{ + test: 'value' + }] + }); expect(language).to.be.equal('json'); }); @@ -494,8 +502,12 @@ suite('Playground Controller Test Suite', function () { .getConfiguration('mdb') .update('confirmRunAll', false); const language = testPlaygroundController._getDocumentLanguage({ - _id: { - $oid: '5d973ae7443762aae72a160' + namespace: null, + type: 'object', + content: { + _id: { + $oid: '5d973ae7443762aae72a160' + } } }); @@ -506,9 +518,11 @@ suite('Playground Controller Test Suite', function () { await vscode.workspace .getConfiguration('mdb') .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage( - 'I am a string' - ); + const language = testPlaygroundController._getDocumentLanguage({ + namespace: null, + type: 'string', + content: 'I am a string' + }); expect(language).to.be.equal('plaintext'); }); @@ -517,7 +531,11 @@ suite('Playground Controller Test Suite', function () { await vscode.workspace .getConfiguration('mdb') .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage(12); + const language = testPlaygroundController._getDocumentLanguage({ + namespace: null, + type: 'number', + content: 12 + }); expect(language).to.be.equal('plaintext'); }); @@ -526,9 +544,11 @@ suite('Playground Controller Test Suite', function () { await vscode.workspace .getConfiguration('mdb') .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage( - undefined - ); + const language = testPlaygroundController._getDocumentLanguage({ + namespace: null, + type: null, + content: undefined + }); expect(language).to.be.equal('plaintext'); }); @@ -537,9 +557,11 @@ suite('Playground Controller Test Suite', function () { await vscode.workspace .getConfiguration('mdb') .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage( - undefined - ); + const language = testPlaygroundController._getDocumentLanguage({ + namespace: null, + type: null, + content: null + }); expect(language).to.be.equal('plaintext'); }); diff --git a/webpack.config.js b/webpack.config.js index 896bc6780..eb8514ed5 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -4,6 +4,8 @@ const path = require('path'); const autoprefixer = require('autoprefixer'); const outputPath = path.join(__dirname, 'dist'); +const ContextMapPlugin = require('context-map-webpack-plugin'); + const baseConfig = { devtool: 'source-map' // performance: { @@ -47,7 +49,13 @@ const extensionConfig = { loader: 'node-loader' } ] - } + }, + plugins: [ + new ContextMapPlugin( + 'node_modules/context-eval', + ['./lib/context-node'] + ) + ] }; const languageServerConfig = { From 65939e3f7e15fd6c8a130f1c7671923564721a44 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Fri, 29 Oct 2021 14:05:42 +0200 Subject: [PATCH 2/7] feat: include imports, driver syntax, and builders --- package-lock.json | 24 +-- package.json | 28 ++- src/commands/index.ts | 6 +- src/editors/codeActionProvider.ts | 46 +++-- src/editors/editorsController.ts | 12 ++ .../exportToLanguageCodeLensProvider.ts | 79 +++++++ src/editors/playgroundController.ts | 104 +++++++++- src/language/languageServerController.ts | 29 ++- src/language/mongoDBService.ts | 65 ++++-- src/language/server.ts | 12 +- src/language/serverCommands.ts | 4 +- src/language/visitor.ts | 97 +++++++-- src/mdbExtensionController.ts | 24 ++- .../suite/editors/codeActionProvider.test.ts | 195 +++++++++++++++++- .../exportToLanguageCodeLensProvider.test.ts | 82 ++++++++ .../editors/playgroundController.test.ts | 8 + .../language/languageServerController.test.ts | 6 + src/test/suite/stubs.ts | 10 +- src/types/playgroundType.ts | 29 +++ 19 files changed, 768 insertions(+), 92 deletions(-) create mode 100644 src/editors/exportToLanguageCodeLensProvider.ts create mode 100644 src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts diff --git a/package-lock.json b/package-lock.json index 3e6975a06..a7a0a4c01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,6 @@ "classnames": "^2.3.1", "debug": "^4.3.2", "dotenv": "^8.2.0", - "ejson-shell-parser": "^1.1.1", "micromatch": "^4.0.4", "mongodb": "^4.1.3", "mongodb-cloud-info": "^1.1.2", @@ -3109,6 +3108,7 @@ "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, "bin": { "acorn": "bin/acorn" }, @@ -7183,17 +7183,6 @@ "safer-buffer": "^2.1.0" } }, - "node_modules/ejson-shell-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ejson-shell-parser/-/ejson-shell-parser-1.1.1.tgz", - "integrity": "sha512-I+1FreeR4SmuU8aYtQEyQDQIBLu0xGIWEfwoKp7mX891i6QEjxQz/lLFw1eho3IPYBjfmbTES0Jogi2GE+D1rA==", - "dependencies": { - "acorn": "^7.4.0" - }, - "peerDependencies": { - "bson": "^4.0.3" - } - }, "node_modules/electron-to-chromium": { "version": "1.3.796", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.796.tgz", @@ -26907,7 +26896,8 @@ "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==" + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true }, "acorn-globals": { "version": "6.0.0", @@ -30350,14 +30340,6 @@ "safer-buffer": "^2.1.0" } }, - "ejson-shell-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ejson-shell-parser/-/ejson-shell-parser-1.1.1.tgz", - "integrity": "sha512-I+1FreeR4SmuU8aYtQEyQDQIBLu0xGIWEfwoKp7mX891i6QEjxQz/lLFw1eho3IPYBjfmbTES0Jogi2GE+D1rA==", - "requires": { - "acorn": "^7.4.0" - } - }, "electron-to-chromium": { "version": "1.3.796", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.796.tgz", diff --git a/package.json b/package.json index dd3592d87..51a2eaeb4 100644 --- a/package.json +++ b/package.json @@ -226,6 +226,10 @@ "command": "mdb.changeActiveConnection", "title": "MongoDB: Change Active Connection" }, + { + "command": "mdb.changeExportToLanguageAddons", + "title": "MongoDB: Change Export To Language Addons" + }, { "command": "mdb.runSelectedPlaygroundBlocks", "title": "MongoDB: Run Selected Lines From Playground" @@ -247,8 +251,20 @@ } }, { - "command": "mdb.exportToLanguage", - "title": "MongoDB: Export To Language" + "command": "mdb.exportToPython", + "title": "MongoDB: Export To Python 3" + }, + { + "command": "mdb.exportToJava", + "title": "MongoDB: Export To Java" + }, + { + "command": "mdb.exportToCsharp", + "title": "MongoDB: Export To C#" + }, + { + "command": "mdb.exportToNode", + "title": "MongoDB: Export To Node" }, { "command": "mdb.addConnection", @@ -585,10 +601,6 @@ "command": "mdb.runAllPlaygroundBlocks", "when": "editorLangId == mongodb" }, - { - "command": "mdb.exportToLanguage", - "when": "false" - }, { "command": "mdb.refreshPlaygroundsFromTreeView", "when": "false" @@ -621,6 +633,10 @@ "command": "mdb.changeActiveConnection", "when": "false" }, + { + "command": "mdb.changeExportToLanguageAddons", + "when": "false" + }, { "command": "mdb.copyConnectionString", "when": "false" diff --git a/src/commands/index.ts b/src/commands/index.ts index d6520ba42..60c06fee2 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -14,7 +14,11 @@ enum EXTENSION_COMMANDS { MDB_RUN_ALL_PLAYGROUND_BLOCKS = 'mdb.runAllPlaygroundBlocks', MDB_RUN_ALL_OR_SELECTED_PLAYGROUND_BLOCKS = 'mdb.runPlayground', - MDB_EXPORT_TO_LANGUAGE = 'mdb.exportToLanguage', + MDB_EXPORT_TO_PYTHON = 'mdb.exportToPython', + MDB_EXPORT_TO_JAVA = 'mdb.exportToJava', + MDB_EXPORT_TO_CSHARP = 'mdb.exportToCsharp', + MDB_EXPORT_TO_NODE = 'mdb.exportToNode', + MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS = 'mdb.changeExportToLanguageAddons', MDB_OPEN_MONGODB_DOCUMENT_FROM_CODE_LENS = 'mdb.openMongoDBDocumentFromCodeLens', MDB_OPEN_MONGODB_DOCUMENT_FROM_TREE = 'mdb.openMongoDBDocumentFromTree', diff --git a/src/editors/codeActionProvider.ts b/src/editors/codeActionProvider.ts index 638315a62..226e3038d 100644 --- a/src/editors/codeActionProvider.ts +++ b/src/editors/codeActionProvider.ts @@ -1,24 +1,36 @@ import * as vscode from 'vscode'; import EXTENSION_COMMANDS from '../commands'; -import PlaygroundController from './playgroundController'; +import { ExportToLanguageMode } from '../types/playgroundType'; export default class CodeActionProvider implements vscode.CodeActionProvider { - _playgroundController: PlaygroundController; + _onDidChangeCodeCodeAction: vscode.EventEmitter = new vscode.EventEmitter(); + selection?: vscode.Range; + mode?: string; static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix]; - constructor(playgroundController: PlaygroundController) { - this._playgroundController = playgroundController; + constructor() { + vscode.workspace.onDidChangeConfiguration(() => { + this._onDidChangeCodeCodeAction.fire(); + }); + } + + readonly onDidChangeCodeLenses: vscode.Event = this + ._onDidChangeCodeCodeAction.event; + + refresh({ selection, mode }: { selection?: vscode.Range, mode?: string }): void { + this.selection = selection; + this.mode = mode; + this._onDidChangeCodeCodeAction.fire(); } provideCodeActions(): vscode.CodeAction[] | undefined { - if (!this._playgroundController._selectedText) { + if (!this.selection) { return; } const codeActions: vscode.CodeAction[] = []; - const runSelectedPlaygroundBlockCommand = new vscode.CodeAction('Run selected playground blocks', vscode.CodeActionKind.Empty); runSelectedPlaygroundBlockCommand.command = { command: EXTENSION_COMMANDS.MDB_RUN_SELECTED_PLAYGROUND_BLOCKS, @@ -27,40 +39,36 @@ export default class CodeActionProvider implements vscode.CodeActionProvider { }; codeActions.push(runSelectedPlaygroundBlockCommand); - if (this._playgroundController._selectedText.trim().startsWith('[')) { + if (this.mode === ExportToLanguageMode.QUERY || this.mode === ExportToLanguageMode.AGGREGATION) { const exportToPythonCommand = new vscode.CodeAction('Export To Python 3', vscode.CodeActionKind.Empty); exportToPythonCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_PYTHON, title: 'Export To Python 3', - tooltip: 'Export To Python 3', - arguments: ['python'] + tooltip: 'Export To Python 3' }; codeActions.push(exportToPythonCommand); const exportToJavaCommand = new vscode.CodeAction('Export To Java', vscode.CodeActionKind.Empty); exportToJavaCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_JAVA, title: 'Export To Java', - tooltip: 'Export To Java', - arguments: ['java'] + tooltip: 'Export To Java' }; codeActions.push(exportToJavaCommand); const exportToCsharpCommand = new vscode.CodeAction('Export To C#', vscode.CodeActionKind.Empty); exportToCsharpCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_CSHARP, title: 'Export To C#', - tooltip: 'Export To C#', - arguments: ['csharp'] + tooltip: 'Export To C#' }; codeActions.push(exportToCsharpCommand); const exportToJSCommand = new vscode.CodeAction('Export To Node', vscode.CodeActionKind.Empty); exportToJSCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_NODE, title: 'Export To Node', - tooltip: 'Export To Node', - arguments: ['javascript'] + tooltip: 'Export To Node' }; codeActions.push(exportToJSCommand); } diff --git a/src/editors/editorsController.ts b/src/editors/editorsController.ts index 5669fe78d..5ec426067 100644 --- a/src/editors/editorsController.ts +++ b/src/editors/editorsController.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { EJSON } from 'bson'; import ActiveConnectionCodeLensProvider from './activeConnectionCodeLensProvider'; +import ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider'; import CodeActionProvider from './codeActionProvider'; import ConnectionController from '../connectionController'; import CollectionDocumentsCodeLensProvider from './collectionDocumentsCodeLensProvider'; @@ -95,6 +96,7 @@ export default class EditorsController { _telemetryService: TelemetryService; _playgroundResultViewProvider: PlaygroundResultProvider; _activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; + _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; _editDocumentCodeLensProvider: EditDocumentCodeLensProvider; _collectionDocumentsCodeLensProvider: CollectionDocumentsCodeLensProvider; @@ -106,6 +108,7 @@ export default class EditorsController { telemetryService: TelemetryService, playgroundResultViewProvider: PlaygroundResultProvider, activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider, + exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider, codeActionProvider: CodeActionProvider, editDocumentCodeLensProvider: EditDocumentCodeLensProvider ) { @@ -135,6 +138,7 @@ export default class EditorsController { ); this._playgroundResultViewProvider = playgroundResultViewProvider; this._activeConnectionCodeLensProvider = activeConnectionCodeLensProvider; + this._exportToLanguageCodeLensProvider = exportToLanguageCodeLensProvider; this._collectionDocumentsCodeLensProvider = new CollectionDocumentsCodeLensProvider( this._collectionDocumentsOperationsStore ); @@ -399,6 +403,14 @@ export default class EditorsController { this._activeConnectionCodeLensProvider ) ); + this._context.subscriptions.push( + vscode.languages.registerCodeLensProvider( + { + scheme: PLAYGROUND_RESULT_SCHEME + }, + this._exportToLanguageCodeLensProvider + ) + ); this._context.subscriptions.push( vscode.languages.registerCodeLensProvider( { diff --git a/src/editors/exportToLanguageCodeLensProvider.ts b/src/editors/exportToLanguageCodeLensProvider.ts new file mode 100644 index 000000000..cd36b3190 --- /dev/null +++ b/src/editors/exportToLanguageCodeLensProvider.ts @@ -0,0 +1,79 @@ +import * as vscode from 'vscode'; + +import EXTENSION_COMMANDS from '../commands'; +import { ExportToLanguageMode, ExportToLanguageAddons } from '../types/playgroundType'; + +export default class ExportToLanguageCodeLensProvider +implements vscode.CodeLensProvider { + _onDidChangeCodeLenses: vscode.EventEmitter = new vscode.EventEmitter(); + _exportToLanguageAddons: ExportToLanguageAddons; + + readonly onDidChangeCodeLenses: vscode.Event = this + ._onDidChangeCodeLenses.event; + + constructor() { + this._exportToLanguageAddons = { + importStatements: false, + driverSyntax: false, + builders: false, + language: 'shell' + }; + + vscode.workspace.onDidChangeConfiguration(() => { + this._onDidChangeCodeLenses.fire(); + }); + } + + refresh(exportToLanguageAddons: ExportToLanguageAddons): void { + this._exportToLanguageAddons = exportToLanguageAddons; + this._onDidChangeCodeLenses.fire(); + } + + createCodeLens(): vscode.CodeLens { + return new vscode.CodeLens(new vscode.Range(0, 0, 0, 0)); + } + + provideCodeLenses(): vscode.CodeLens[] { + const importStatementsCodeLens = this.createCodeLens(); + const driverSyntaxCodeLens = this.createCodeLens(); + const buildersCodeLens = this.createCodeLens(); + const exportToLanguageCodeLenses: vscode.CodeLens[] = []; + + importStatementsCodeLens.command = { + title: this._exportToLanguageAddons.importStatements ? 'Exclude Import Statements' : 'Include Import Statements', + command: EXTENSION_COMMANDS.MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS, + arguments: [{ + ...this._exportToLanguageAddons, + importStatements: !this._exportToLanguageAddons.importStatements + }] + }; + exportToLanguageCodeLenses.push(importStatementsCodeLens); + + driverSyntaxCodeLens.command = { + title: this._exportToLanguageAddons.driverSyntax ? 'Exclude Driver Syntax' : 'Include Driver Syntax', + command: EXTENSION_COMMANDS.MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS, + arguments: [{ + ...this._exportToLanguageAddons, + driverSyntax: !this._exportToLanguageAddons.driverSyntax + }] + }; + exportToLanguageCodeLenses.push(driverSyntaxCodeLens); + + if ( + this._exportToLanguageAddons.language === 'java' && + this._exportToLanguageAddons.mode === ExportToLanguageMode.QUERY + ) { + buildersCodeLens.command = { + title: this._exportToLanguageAddons.builders ? 'Use Raw Query' : 'Use Builders', + command: EXTENSION_COMMANDS.MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS, + arguments: [{ + ...this._exportToLanguageAddons, + builders: !this._exportToLanguageAddons.builders + }] + }; + exportToLanguageCodeLenses.push(buildersCodeLens); + } + + return exportToLanguageCodeLenses; + } +} diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 918966ccf..ce0066567 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -7,11 +7,18 @@ import ConnectionController, { import { createLogger } from '../logging'; import { ExplorerController, ConnectionTreeItem, DatabaseTreeItem } from '../explorer'; import { LanguageServerController } from '../language'; +import ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider'; import { OutputChannel, ProgressLocation, TextEditor } from 'vscode'; import playgroundCreateIndexTemplate from '../templates/playgroundCreateIndexTemplate'; import playgroundCreateCollectionTemplate from '../templates/playgroundCreateCollectionTemplate'; import playgroundCreateCollectionWithTSTemplate from '../templates/playgroundCreateCollectionWithTSTemplate'; -import type { PlaygroundResult, ShellExecuteAllResult } from '../types/playgroundType'; +import { + PlaygroundResult, + ShellExecuteAllResult, + ExportToLanguageAddons, + ExportToLanguageMode, + ExportToLanguageNamespace +} from '../types/playgroundType'; import PlaygroundResultProvider, { PLAYGROUND_RESULT_SCHEME, PLAYGROUND_RESULT_URI @@ -21,6 +28,7 @@ import playgroundTemplate from '../templates/playgroundTemplate'; import { StatusView } from '../views'; import TelemetryService from '../telemetry/telemetryService'; import { ConnectionOptions } from '../types/connectionOptionsType'; +import CodeActionProvider from './codeActionProvider'; const transpiler = require('bson-transpilers'); const log = createLogger('playground controller'); @@ -65,6 +73,8 @@ export default class PlaygroundController { _statusView: StatusView; _playgroundResultViewProvider: PlaygroundResultProvider; _explorerController: ExplorerController; + _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; + _codeActionProvider: CodeActionProvider; constructor( context: vscode.ExtensionContext, @@ -74,6 +84,8 @@ export default class PlaygroundController { statusView: StatusView, playgroundResultViewProvider: PlaygroundResultProvider, activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider, + exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider, + codeActionProvider: CodeActionProvider, explorerController: ExplorerController ) { this._context = context; @@ -87,6 +99,8 @@ export default class PlaygroundController { 'Playground output' ); this._activeConnectionCodeLensProvider = activeConnectionCodeLensProvider; + this._exportToLanguageCodeLensProvider = exportToLanguageCodeLensProvider; + this._codeActionProvider = codeActionProvider; this._explorerController = explorerController; this._connectionController.addEventListener( @@ -114,7 +128,7 @@ export default class PlaygroundController { onDidChangeActiveTextEditor(vscode.window.activeTextEditor); vscode.window.onDidChangeTextEditorSelection( - (changeEvent: vscode.TextEditorSelectionChangeEvent) => { + async (changeEvent: vscode.TextEditorSelectionChangeEvent) => { if ( changeEvent?.textEditor?.document?.languageId === 'mongodb' ) { @@ -125,6 +139,13 @@ export default class PlaygroundController { this._selectedText = sortedSelections .map((item) => this._getSelectedText(item)) .join('\n'); + + const mode = await this._languageServerController.getExportToLanguageMode({ + textFromEditor: this._getAllText(), + selection: sortedSelections[0] + }); + + this._codeActionProvider.refresh({ selection: sortedSelections[0], mode }); } } ); @@ -555,17 +576,90 @@ export default class PlaygroundController { } } + changeExportToLanguageAddons(exportToLanguageAddons: ExportToLanguageAddons): Promise { + this._exportToLanguageCodeLensProvider.refresh(exportToLanguageAddons); + + return this._transpile(); + } + async exportToLanguage(language: string): Promise { + this._exportToLanguageCodeLensProvider.refresh({ + ...this._exportToLanguageCodeLensProvider._exportToLanguageAddons, + textFromEditor: this._getAllText(), + selectedText: this._selectedText, + selection: this._codeActionProvider.selection, + language, + mode: this._codeActionProvider.mode + }); + + return this._transpile(); + } + + async _transpile(): Promise { + const { + textFromEditor, + selectedText, + selection, + importStatements, + driverSyntax, + builders, + language, + mode + } = this._exportToLanguageCodeLensProvider._exportToLanguageAddons; + log.info(`Start export to ${language} language`); + if (!textFromEditor || !selection) { + void vscode.window.showInformationMessage( + 'Please select one or more lines in the playground.' + ); + + return Promise.resolve(true); + } + try { - const compiledString = transpiler.shell[language].compile(this._selectedText); + const useBuilders = builders && language === 'java' && mode === ExportToLanguageMode.QUERY; + let transpiledExpression = ''; + let imports = ''; + let namespace: ExportToLanguageNamespace = { + databaseName: null, + collectionName: null + }; + + if (driverSyntax) { + namespace = await this._languageServerController.getNamespaceForSelection({ + textFromEditor, + selection: selection as vscode.Selection + }); + + const dataService = this._connectionController.getActiveDataService(); + const connectionDetails = dataService?.getConnectionOptions(); + const toCompile = { + aggregation: selectedText, + options: { + collection: namespace.collectionName, + database: namespace.databaseName, + uri: connectionDetails?.url + } + }; + + transpiledExpression = transpiler.shell[language].compileWithDriver(toCompile, useBuilders); + } else { + transpiledExpression = transpiler.shell[language].compile(selectedText, useBuilders, false); + } + + if (importStatements) { + imports = transpiler.shell[language].getImports(driverSyntax); + } this._playgroundResult = { - namespace: null, + namespace: namespace.databaseName && namespace.collectionName + ? `${namespace.databaseName}.${namespace.collectionName}` + : null, type: language, - content: compiledString + content: imports ? `${imports}\n\n${transpiledExpression}` : transpiledExpression }; + log.info(`Export to ${language} language result`, this._playgroundResult); await this._openPlaygroundResult(); } catch (error) { diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index 7ea0c8f96..ca5df4679 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -11,9 +11,14 @@ import WebSocket from 'ws'; import { workspace, ExtensionContext, OutputChannel } from 'vscode'; import { createLogger } from '../logging'; -import { PlaygroundExecuteParameters } from '../types/playgroundType'; +import { + PlaygroundExecuteParameters, + ShellExecuteAllResult, + ExportToLanguageMode, + ExportToLanguageNamespace, + PlaygroundSelection +} from '../types/playgroundType'; import { ServerCommands } from './serverCommands'; -import type { ShellExecuteAllResult } from '../types/playgroundType'; import { ConnectionOptions } from '../types/connectionOptionsType'; const log = createLogger('LanguageServerController'); @@ -165,6 +170,26 @@ export default class LanguageServerController { return result; } + async getExportToLanguageMode( + params: PlaygroundSelection + ): Promise { + await this._client.onReady(); + return this._client.sendRequest( + ServerCommands.GET_EXPORT_TO_LANGUAGE_MODE, + params + ); + } + + async getNamespaceForSelection( + params: PlaygroundSelection + ): Promise { + await this._client.onReady(); + return this._client.sendRequest( + ServerCommands.GET_NAMESPACE_FOR_SELECTION, + params + ); + } + async connectToServiceProvider(params: { connectionId: string, connectionString: string; diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index 723d96d8f..b6d9c9b2c 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -1,6 +1,13 @@ /* eslint-disable no-sync */ import * as util from 'util'; -import { CompletionItemKind, CancellationToken, Connection, CompletionItem, MarkupContent, MarkupKind } from 'vscode-languageserver/node'; +import { + CompletionItemKind, + CancellationToken, + Connection, + CompletionItem, + MarkupContent, + MarkupKind +} from 'vscode-languageserver/node'; import path from 'path'; import { signatures } from '@mongosh/shell-api'; import translator from '@mongosh/i18n'; @@ -9,7 +16,13 @@ import { Worker as WorkerThreads } from 'worker_threads'; import { CollectionItem } from '../types/collectionItemType'; import { ConnectionOptions } from '../types/connectionOptionsType'; import { ServerCommands } from './serverCommands'; -import { ShellExecuteAllResult, PlaygroundExecuteParameters } from '../types/playgroundType'; +import { + ShellExecuteAllResult, + PlaygroundExecuteParameters, + ExportToLanguageMode, + ExportToLanguageNamespace, + PlaygroundSelection +} from '../types/playgroundType'; import { Visitor } from './visitor'; export const languageServerWorkerFileName = 'languageServerWorker.js'; @@ -33,7 +46,7 @@ export default class MongoDBService { constructor(connection: Connection) { this._connection = connection; this._cachedShellSymbols = this._getShellCompletionItems(); - this._visitor = new Visitor(); + this._visitor = new Visitor(connection); } // ------ CONNECTION ------ // @@ -47,7 +60,7 @@ export default class MongoDBService { setExtensionPath(extensionPath: string): void { if (!extensionPath) { - this._connection.console.log('Set extensionPath error: extensionPath is undefined'); + this._connection.console.error('Set extensionPath error: extensionPath is undefined'); } else { this._extensionPath = extensionPath; } @@ -76,7 +89,7 @@ export default class MongoDBService { return Promise.resolve(true); } catch (error) { - this._connection.console.log( + this._connection.console.error( `MONGOSH connect error: ${util.inspect(error)}` ); @@ -100,7 +113,7 @@ export default class MongoDBService { return new Promise((resolve) => { if (!this._extensionPath) { - this._connection.console.log('MONGOSH execute all error: extensionPath is undefined'); + this._connection.console.error('MONGOSH execute all error: extensionPath is undefined'); return resolve(undefined); } @@ -150,7 +163,7 @@ export default class MongoDBService { if (error) { const printableError = error as { message: string }; - this._connection.console.log( + this._connection.console.error( `MONGOSH execute all error: ${util.inspect(error)}` ); this._connection.sendNotification( @@ -192,7 +205,7 @@ export default class MongoDBService { return resolve(undefined); }); } catch (error) { - this._connection.console.log( + this._connection.console.error( `MONGOSH execute all error: ${util.inspect(error)}` ); return resolve(undefined); @@ -203,7 +216,7 @@ export default class MongoDBService { // ------ GET DATA FOR COMPLETION ------ // _getDatabasesCompletionItems(): void { if (!this._extensionPath) { - this._connection.console.log('MONGOSH get list databases error: extensionPath is undefined'); + this._connection.console.error('MONGOSH get list databases error: extensionPath is undefined'); return; } @@ -226,7 +239,7 @@ export default class MongoDBService { const [error, result] = response; if (error) { - this._connection.console.log( + this._connection.console.error( `MONGOSH get list databases error: ${util.inspect(error)}` ); } @@ -237,7 +250,7 @@ export default class MongoDBService { }); }); } catch (error) { - this._connection.console.log( + this._connection.console.error( `MONGOSH get list databases error: ${util.inspect(error)}` ); } @@ -429,10 +442,36 @@ export default class MongoDBService { }); } + getExportToLanguageMode(params: PlaygroundSelection): ExportToLanguageMode { + const state = this._visitor.parseAST(params); + + if (state.isArray) { + return ExportToLanguageMode.AGGREGATION; + } + + if (state.isObject) { + return ExportToLanguageMode.QUERY; + } + + return ExportToLanguageMode.OTHER; + } + + getNamespaceForSelection(params: PlaygroundSelection): ExportToLanguageNamespace { + try { + const state = this._visitor.parseAST(params); + return { databaseName: state.databaseName, collectionName: state.collectionName }; + } catch (error) { + this._connection.console.error( + `Get namespace for selection error: ${util.inspect(error)}` + ); + return { databaseName: null, collectionName: null }; + } + } + // eslint-disable-next-line complexity async provideCompletionItems( textFromEditor: string, - position: { line: number; character: number } + position: { line: number, character: number } ): Promise { // eslint-disable-next-line complexity this._connection.console.log( @@ -442,7 +481,7 @@ export default class MongoDBService { `LS current symbol position: ${util.inspect(position)}` ); - const state = this._visitor.parseAST(textFromEditor, position); + const state = this._visitor.parseASTWithPlaceholder(textFromEditor, position); this._connection.console.log( `VISITOR completion state: ${util.inspect(state)}` diff --git a/src/language/server.ts b/src/language/server.ts index 9fc101af5..00a7a594d 100644 --- a/src/language/server.ts +++ b/src/language/server.ts @@ -14,7 +14,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import MongoDBService from './mongoDBService'; import { ServerCommands } from './serverCommands'; -import { PlaygroundExecuteParameters } from '../types/playgroundType'; +import { PlaygroundExecuteParameters, PlaygroundSelection } from '../types/playgroundType'; // Create a connection for the server. The connection uses Node's IPC as a transport. // Also include all preview / proposed LSP features. @@ -160,8 +160,16 @@ connection.onRequest(ServerCommands.DISCONNECT_TO_SERVICE_PROVIDER, () => { return mongoDBService.disconnectFromServiceProvider(); }); +connection.onRequest(ServerCommands.GET_EXPORT_TO_LANGUAGE_MODE, (params: PlaygroundSelection) => { + return mongoDBService.getExportToLanguageMode(params); +}); + +connection.onRequest(ServerCommands.GET_NAMESPACE_FOR_SELECTION, (params: PlaygroundSelection) => { + return mongoDBService.getNamespaceForSelection(params); +}); + // This handler provides the list of the completion items. -connection.onCompletion(async (params: TextDocumentPositionParams) => { +connection.onCompletion((params: TextDocumentPositionParams) => { const textFromEditor = documents.get(params.textDocument.uri)?.getText(); return mongoDBService.provideCompletionItems( diff --git a/src/language/serverCommands.ts b/src/language/serverCommands.ts index 3406def14..12a609251 100644 --- a/src/language/serverCommands.ts +++ b/src/language/serverCommands.ts @@ -8,7 +8,9 @@ export enum ServerCommands { GET_LIST_COLLECTIONS = 'GET_LIST_COLLECTIONS', SET_EXTENSION_PATH = 'SET_EXTENSION_PATH', SHOW_ERROR_MESSAGE = 'SHOW_ERROR_MESSAGE', - SHOW_INFO_MESSAGE = 'SHOW_INFO_MESSAGE' + SHOW_INFO_MESSAGE = 'SHOW_INFO_MESSAGE', + GET_NAMESPACE_FOR_SELECTION = 'GET_NAMESPACE_FOR_SELECTION', + GET_EXPORT_TO_LANGUAGE_MODE = 'GET_EXPORT_TO_LANGUAGE_MODE' } export type PlaygroundRunParameters = { diff --git a/src/language/visitor.ts b/src/language/visitor.ts index afa034984..ed8f66596 100644 --- a/src/language/visitor.ts +++ b/src/language/visitor.ts @@ -1,12 +1,19 @@ +import * as vscode from 'vscode'; import type * as babel from '@babel/core'; import * as parser from '@babel/parser'; import traverse from '@babel/traverse'; +import { Connection } from 'vscode-languageserver/node'; +import * as util from 'util'; + +import { PlaygroundSelection } from '../types/playgroundType'; const PLACEHOLDER = 'TRIGGER_CHARACTER'; -export type CompletionState = { +export interface CompletionState { databaseName: string | null; collectionName: string | null; + isObject: boolean; + isArray: boolean; isObjectKey: boolean; isShellMethod: boolean; isUseCallExpression: boolean; @@ -14,15 +21,17 @@ export type CompletionState = { isCollectionName: boolean; isAggregationCursor: boolean; isFindCursor: boolean; -}; +} export class Visitor { _state: CompletionState; - _position: { line: number; character: number }; + _selection: vscode.Selection; + _connection: Connection; - constructor() { + constructor(connection: Connection) { this._state = this._getDefaultNodesValues(); - this._position = { line: 0, character: 0 }; + this._selection = { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } as vscode.Selection; + this._connection = connection; } _visitCallExpression(node: babel.types.CallExpression): void { @@ -68,11 +77,21 @@ export class Visitor { } _visitObjectExpression(node: babel.types.ObjectExpression): void { + if (this._checkIsObjectSelection(node)) { + this._state.isObject = true; + } + if (this._checkIsObjectKey(node)) { this._state.isObjectKey = true; } } + _visitArrayExpression(node: babel.types.ArrayExpression): void { + if (this._checkIsArraySelection(node)) { + this._state.isArray = true; + } + } + _handleTriggerCharacter( textFromEditor: string, position: { line: number; character: number } @@ -97,25 +116,33 @@ export class Visitor { return textLines.join('\n'); } - parseAST( + parseASTWithPlaceholder( textFromEditor: string, position: { line: number; character: number } ): CompletionState { - this._state = this._getDefaultNodesValues(); - this._position = position; + const selection = { start: position, end: { line: 0, character: 0 } } as vscode.Selection; - const textWithPlaceholder = this._handleTriggerCharacter( + textFromEditor = this._handleTriggerCharacter( textFromEditor, position ); + + return this.parseAST({ textFromEditor, selection }); + } + + parseAST({ textFromEditor, selection }: PlaygroundSelection): CompletionState { let ast: any; + this._state = this._getDefaultNodesValues(); + this._selection = selection; + try { - ast = parser.parse(textWithPlaceholder, { + ast = parser.parse(textFromEditor, { // Parse in strict mode and allow module declarations sourceType: 'module' }); } catch (error) { + this._connection.console.error(`parseAST error: ${util.inspect(error)}`); return this._state; } @@ -134,6 +161,9 @@ export class Visitor { case 'ObjectExpression': this._visitObjectExpression(path.node); break; + case 'ArrayExpression': + this._visitArrayExpression(path.node); + break; default: break; } @@ -147,6 +177,8 @@ export class Visitor { return { databaseName: null, collectionName: null, + isObject: false, + isArray: false, isObjectKey: false, isShellMethod: false, isUseCallExpression: false, @@ -204,6 +236,45 @@ export class Visitor { return false; } + _checkIsWithinSelection(node: babel.types.Node): boolean { + if ( + node.loc?.start?.line && + node.loc.start.line - 1 === this._selection.start?.line && + node.loc?.start?.column && + node.loc.start.column === this._selection.start?.character && + node.loc?.end?.line && + node.loc.end.line - 1 === this._selection.end?.line && + node.loc?.end?.column && + node.loc.end.column === this._selection.end?.character + ) { + return true; + } + + return false; + } + + _checkIsObjectSelection(node: babel.types.ObjectExpression): boolean { + if ( + node.type === 'ObjectExpression' && + this._checkIsWithinSelection(node) + ) { + return true; + } + + return false; + } + + _checkIsArraySelection(node: babel.types.ArrayExpression): boolean { + if ( + node.type === 'ArrayExpression' && + this._checkIsWithinSelection(node) + ) { + return true; + } + + return false; + } + // eslint-disable-next-line complexity _checkIsCollectionName(node: babel.types.CallExpression | babel.types.MemberExpression): boolean { if ( @@ -262,9 +333,9 @@ export class Visitor { node.arguments.length === 1 && node.arguments[0].type === 'StringLiteral' && node.loc && - (this._position.line > node.loc.end.line - 1 || - (this._position.line === node.loc.end.line - 1 && - this._position.character >= node.loc.end.column)) + (this._selection.start.line > node.loc.end.line - 1 || + (this._selection.start.line === node.loc.end.line - 1 && + this._selection.start.character >= node.loc.end.column)) ) { return true; } diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index ab3a0e564..c352c04f9 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -26,6 +26,7 @@ import CodeActionProvider from './editors/codeActionProvider'; import EXTENSION_COMMANDS from './commands'; import FieldTreeItem from './explorer/fieldTreeItem'; import IndexListTreeItem from './explorer/indexListTreeItem'; +import ExportToLanguageCodeLensProvider from './editors/exportToLanguageCodeLensProvider'; import { LanguageServerController } from './language'; import launchMongoShell from './commands/launchMongoShell'; import SchemaTreeItem from './explorer/schemaTreeItem'; @@ -57,6 +58,7 @@ export default class MDBExtensionController implements vscode.Disposable { _playgroundResultViewProvider: PlaygroundResultProvider; _activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; _editDocumentCodeLensProvider: EditDocumentCodeLensProvider; + _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; constructor( context: vscode.ExtensionContext, @@ -91,6 +93,8 @@ export default class MDBExtensionController implements vscode.Disposable { this._activeConnectionCodeLensProvider = new ActiveConnectionCodeLensProvider( this._connectionController ); + this._exportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); + this._codeActionProvider = new CodeActionProvider(); this._playgroundController = new PlaygroundController( context, this._connectionController, @@ -99,9 +103,10 @@ export default class MDBExtensionController implements vscode.Disposable { this._statusView, this._playgroundResultViewProvider, this._activeConnectionCodeLensProvider, + this._exportToLanguageCodeLensProvider, + this._codeActionProvider, this._explorerController ); - this._codeActionProvider = new CodeActionProvider(this._playgroundController); this._editorsController = new EditorsController( context, this._connectionController, @@ -110,6 +115,7 @@ export default class MDBExtensionController implements vscode.Disposable { this._telemetryService, this._playgroundResultViewProvider, this._activeConnectionCodeLensProvider, + this._exportToLanguageCodeLensProvider, this._codeActionProvider, this._editDocumentCodeLensProvider ); @@ -188,8 +194,20 @@ export default class MDBExtensionController implements vscode.Disposable { this.registerCommand(EXTENSION_COMMANDS.MDB_REFRESH_PLAYGROUNDS, () => this._playgroundsExplorer.refresh() ); - this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_LANGUAGE, (language) => - this._playgroundController.exportToLanguage(language) + this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_PYTHON, () => + this._playgroundController.exportToLanguage('python') + ); + this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_JAVA, () => + this._playgroundController.exportToLanguage('java') + ); + this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_CSHARP, () => + this._playgroundController.exportToLanguage('csharp') + ); + this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_NODE, () => + this._playgroundController.exportToLanguage('javascript') + ); + this.registerCommand(EXTENSION_COMMANDS.MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS, (exportToLanguageAddons) => + this._playgroundController.changeExportToLanguageAddons(exportToLanguageAddons) ); this.registerCommand( EXTENSION_COMMANDS.MDB_OPEN_MONGODB_DOCUMENT_FROM_CODE_LENS, diff --git a/src/test/suite/editors/codeActionProvider.test.ts b/src/test/suite/editors/codeActionProvider.test.ts index 5c1154c55..cc264adfe 100644 --- a/src/test/suite/editors/codeActionProvider.test.ts +++ b/src/test/suite/editors/codeActionProvider.test.ts @@ -4,10 +4,12 @@ import chai from 'chai'; import sinon from 'sinon'; import ActiveDBCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; import CodeActionProvider from '../../../editors/codeActionProvider'; import { ExplorerController } from '../../../explorer'; import { LanguageServerController } from '../../../language'; import { PlaygroundController } from '../../../editors'; +import { PlaygroundResult } from '../../../types/playgroundType'; import { mdbTestExtension } from '../stubbableMdbExtension'; import { TestExtensionContext } from '../stubs'; @@ -43,6 +45,8 @@ suite('Code Action Provider Test Suite', function () { const testActiveDBCodeLensProvider = new ActiveDBCodeLensProvider( mdbTestExtension.testExtensionController._connectionController ); + const testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); + const testCodeActionProvider = new CodeActionProvider(); const testExplorerController = new ExplorerController( mdbTestExtension.testExtensionController._connectionController ); @@ -55,6 +59,8 @@ suite('Code Action Provider Test Suite', function () { mdbTestExtension.testExtensionController._statusView, mdbTestExtension.testExtensionController._playgroundResultViewProvider, testActiveDBCodeLensProvider, + testExportToLanguageCodeLensProvider, + testCodeActionProvider, testExplorerController ); @@ -82,17 +88,24 @@ suite('Code Action Provider Test Suite', function () { sinon.restore(); }); - test('expected provideCodeActions to return undefined when text is not selected', () => { - const testCodeActionProvider = new CodeActionProvider(mdbTestExtension.testExtensionController._playgroundController); + test('returns undefined when text is not selected', () => { + const testCodeActionProvider = new CodeActionProvider(); const codeActions = testCodeActionProvider.provideCodeActions(); expect(codeActions).to.be.undefined; }); - test('expected provideCodeActions to return a run selected playground blocks action', async () => { + test('returns a run selected playground blocks action', async () => { mdbTestExtension.testExtensionController._playgroundController._selectedText = '123'; - const testCodeActionProvider = new CodeActionProvider(mdbTestExtension.testExtensionController._playgroundController); + const selection = { + start: { line: 0, character: 0 }, + end: { line: 0, character: 4 } + } as vscode.Selection; + const testCodeActionProvider = new CodeActionProvider(); + + testCodeActionProvider.refresh({ selection, mode: 'OTHER' }); + const codeActions = testCodeActionProvider.provideCodeActions(); expect(codeActions).to.exist; @@ -100,7 +113,6 @@ suite('Code Action Provider Test Suite', function () { if (codeActions) { expect(codeActions.length).to.be.equal(1); const actionCommand = codeActions[0].command; - expect(codeActions).to.exist; if (actionCommand) { expect(actionCommand.command).to.be.equal('mdb.runSelectedPlaygroundBlocks'); @@ -114,4 +126,177 @@ suite('Code Action Provider Test Suite', function () { } } }); + + test('exports to java and includes builders', async () => { + const textFromEditor = "{ name: '22' }"; + const selection = { + start: { line: 0, character: 0 }, + end: { line: 0, character: 14 } + } as vscode.Selection; + const mode = 'QUERY'; + + mdbTestExtension.testExtensionController._playgroundController._selectedText = textFromEditor; + mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.selection = selection; + mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.mode = mode; + + const fakeGetAllText = sinon.fake.returns(textFromEditor); + sinon.replace( + mdbTestExtension.testExtensionController._playgroundController, + '_getAllText', + fakeGetAllText + ); + const testCodeActionProvider = new CodeActionProvider(); + + testCodeActionProvider.refresh({ selection, mode }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(5); + const actionCommand = codeActions[2].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal('mdb.exportToJava'); + expect(actionCommand.title).to.be.equal('Export To Java'); + + await vscode.commands.executeCommand(actionCommand.command); + + const expectedResult = { namespace: null, type: 'java', content: 'new Document("name", "22")' }; + + const codeLenses = mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses.length).to.be.equal(3); + + // Only java supports builders. + await vscode.commands.executeCommand('mdb.changeExportToLanguageAddons', { + ...mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider._exportToLanguageAddons, + builders: true + }); + + expectedResult.content = 'eq("name", "22")'; + expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); + } + } + }); + + test('exports to csharp and includes import statements', async () => { + const textFromEditor = "{ name: '22' }"; + const selection = { + start: { line: 0, character: 0 }, + end: { line: 0, character: 14 } + } as vscode.Selection; + const mode = 'QUERY'; + + mdbTestExtension.testExtensionController._playgroundController._selectedText = textFromEditor; + mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.selection = selection; + mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.mode = mode; + + const fakeGetAllText = sinon.fake.returns(textFromEditor); + sinon.replace( + mdbTestExtension.testExtensionController._playgroundController, + '_getAllText', + fakeGetAllText + ); + const testCodeActionProvider = new CodeActionProvider(); + + testCodeActionProvider.refresh({ selection, mode }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(5); + const actionCommand = codeActions[3].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal('mdb.exportToCsharp'); + expect(actionCommand.title).to.be.equal('Export To C#'); + + await vscode.commands.executeCommand(actionCommand.command); + + const expectedResult = { + namespace: null, + type: 'csharp', + content: 'new BsonDocument(\"name\", \"22\")' + }; + expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); + + const codeLenses = mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses.length).to.be.equal(2); + + // Csharp does not support driver syntax. + await vscode.commands.executeCommand('mdb.changeExportToLanguageAddons', { + ...mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider._exportToLanguageAddons, + importStatements: true + }); + + expectedResult.content = 'using MongoDB.Bson;\nusing MongoDB.Driver;\n\nnew BsonDocument(\"name\", \"22\")'; + expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); + } + } + }); + + test('exports to python and includes driver syntax', async () => { + const textFromEditor = "use('db'); db.coll.find({ name: '22' })"; + const selection = { + start: { line: 0, character: 24 }, + end: { line: 0, character: 38 } + } as vscode.Selection; + const mode = 'QUERY'; + + mdbTestExtension.testExtensionController._playgroundController._selectedText = "{ name: '22' }"; + mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.selection = selection; + mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.mode = mode; + + const fakeGetAllText = sinon.fake.returns(textFromEditor); + sinon.replace( + mdbTestExtension.testExtensionController._playgroundController, + '_getAllText', + fakeGetAllText + ); + const testCodeActionProvider = new CodeActionProvider(); + + testCodeActionProvider.refresh({ selection, mode }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(5); + const actionCommand = codeActions[1].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal('mdb.exportToPython'); + expect(actionCommand.title).to.be.equal('Export To Python 3'); + + await vscode.commands.executeCommand(actionCommand.command); + + let expectedResult: PlaygroundResult = { + namespace: null, + type: 'python', + content: "{\n 'name': '22'\n}" + }; + expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); + + const codeLenses = mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses.length).to.be.equal(2); + + await vscode.commands.executeCommand('mdb.changeExportToLanguageAddons', { + ...mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider._exportToLanguageAddons, + driverSyntax: true + }); + + expectedResult = { + namespace: 'db.coll', + type: 'python', + content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode+0.0.0-dev.0&directConnection=true&ssl=false')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})" + }; + + expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); + } + } + }); }); diff --git a/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts b/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts new file mode 100644 index 000000000..011c3eb47 --- /dev/null +++ b/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts @@ -0,0 +1,82 @@ +import { beforeEach } from 'mocha'; +import chai from 'chai'; + +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; + +const expect = chai.expect; + +suite('Export To Language Code Lens Provider Test Suite', function () { + const defaults = { + importStatements: false, + driverSyntax: false, + builders: false, + language: 'shell' + }; + let testExportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; + + beforeEach(() => { + testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); + }); + + test('has the include import statements code lens when importStatements is false', () => { + testExportToLanguageCodeLensProvider.refresh(defaults); + + const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); + + expect(codeLenses.length).to.be.equal(2); + expect(codeLenses[0].command?.title).to.be.equal('Include Import Statements'); + }); + + test('has the exclude import statements code lens when importStatements is true', () => { + testExportToLanguageCodeLensProvider.refresh({ ...defaults, importStatements: true }); + + const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); + + expect(codeLenses.length).to.be.equal(2); + expect(codeLenses[0].command?.title).to.be.equal('Exclude Import Statements'); + }); + + test('has the include import statements code lens when driverSyntax is false', () => { + testExportToLanguageCodeLensProvider.refresh(defaults); + + const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); + + expect(codeLenses.length).to.be.equal(2); + expect(codeLenses[1].command?.title).to.be.equal('Include Driver Syntax'); + }); + + test('has the exclude import statements code lens when driverSyntax is true', () => { + testExportToLanguageCodeLensProvider.refresh({ ...defaults, driverSyntax: true }); + + const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); + + expect(codeLenses.length).to.be.equal(2); + expect(codeLenses[1].command?.title).to.be.equal('Exclude Driver Syntax'); + }); + + test('has the use builders code lens when builders is false, language is java, and mode is query', () => { + testExportToLanguageCodeLensProvider.refresh({ ...defaults, mode: 'QUERY', language: 'java' }); + + const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); + + expect(codeLenses.length).to.be.equal(3); + expect(codeLenses[2].command?.title).to.be.equal('Use Builders'); + }); + + test('has the use raw query code lens when builders is true, language is java, and mode is query', () => { + testExportToLanguageCodeLensProvider.refresh({ ...defaults, builders: true, mode: 'QUERY', language: 'java' }); + + const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); + + expect(codeLenses.length).to.be.equal(3); + expect(codeLenses[2].command?.title).to.be.equal('Use Raw Query'); + }); + + test('does not have the use raw query code lens when builders is true, language is java, and mode is plain text', () => { + testExportToLanguageCodeLensProvider.refresh({ ...defaults, builders: true, mode: 'OTHER', language: 'java' }); + + const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); + + expect(codeLenses.length).to.be.equal(2); + }); +}); diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 7edea4c02..2975b4431 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -4,6 +4,7 @@ import chai from 'chai'; import sinon from 'sinon'; import ActiveDBCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; import ConnectionController from '../../../connectionController'; import { ConnectionModel } from '../../../types/connectionModelType'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; @@ -16,6 +17,7 @@ import { StorageController } from '../../../storage'; import TelemetryService from '../../../telemetry/telemetryService'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import { TestExtensionContext, MockLanguageServerController } from '../stubs'; +import CodeActionProvider from '../../../editors/codeActionProvider'; const expect = chai.expect; @@ -58,6 +60,8 @@ suite('Playground Controller Test Suite', function () { const testActiveDBCodeLensProvider = new ActiveDBCodeLensProvider( testConnectionController ); + const testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); + const testCodeActionProvider = new CodeActionProvider(); const testExplorerController = new ExplorerController( testConnectionController ); @@ -69,6 +73,8 @@ suite('Playground Controller Test Suite', function () { testStatusView, testPlaygroundResultProvider, testActiveDBCodeLensProvider, + testExportToLanguageCodeLensProvider, + testCodeActionProvider, testExplorerController ); const sandbox = sinon.createSandbox(); @@ -459,6 +465,8 @@ suite('Playground Controller Test Suite', function () { testStatusView, testPlaygroundResultProvider, testActiveDBCodeLensProvider, + testExportToLanguageCodeLensProvider, + testCodeActionProvider, testExplorerController ); diff --git a/src/test/suite/language/languageServerController.test.ts b/src/test/suite/language/languageServerController.test.ts index bf2333a13..8fe3ff418 100644 --- a/src/test/suite/language/languageServerController.test.ts +++ b/src/test/suite/language/languageServerController.test.ts @@ -5,6 +5,7 @@ import path from 'path'; import sinon from 'sinon'; import ActiveDBCodeLensProvider from '../../../editors/activeConnectionCodeLensProvider'; +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; import ConnectionController from '../../../connectionController'; import { DataServiceType } from '../../../types/dataServiceType'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; @@ -19,6 +20,7 @@ import { StorageController } from '../../../storage'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import TelemetryService from '../../../telemetry/telemetryService'; import { TestExtensionContext } from '../stubs'; +import CodeActionProvider from '../../../editors/codeActionProvider'; const expect = chai.expect; @@ -56,6 +58,8 @@ suite('Language Server Controller Test Suite', () => { const testExplorerController = new ExplorerController( testConnectionController ); + const testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider(); + const testCodeActionProvider = new CodeActionProvider(); const testPlaygroundController = new PlaygroundController( mockExtensionContext, testConnectionController, @@ -64,6 +68,8 @@ suite('Language Server Controller Test Suite', () => { testStatusView, testPlaygroundResultProvider, testActiveDBCodeLensProvider, + testExportToLanguageCodeLensProvider, + testCodeActionProvider, testExplorerController ); diff --git a/src/test/suite/stubs.ts b/src/test/suite/stubs.ts index 3bab3a3d2..6475c37a3 100644 --- a/src/test/suite/stubs.ts +++ b/src/test/suite/stubs.ts @@ -5,7 +5,7 @@ import path = require('path'); import { StorageController } from '../../storage'; -import type { ShellExecuteAllResult } from '../../types/playgroundType'; +import { ShellExecuteAllResult, ExportToLanguageMode, ExportToLanguageNamespace } from '../../types/playgroundType'; // Bare mock of the extension context for vscode. class TestExtensionContext implements vscode.ExtensionContext { @@ -248,6 +248,14 @@ class MockLanguageServerController { }); } + getExportToLanguageMode(/* codeToEvaluate: string*/): Promise { + return Promise.resolve(ExportToLanguageMode.OTHER); + } + + getNamespaceForSelection(/* codeToEvaluate: string*/): Promise { + return Promise.resolve({ databaseName: null, collectionName: null }); + } + connectToServiceProvider(/* params: { connectionString?: string; connectionOptions?: any; diff --git a/src/types/playgroundType.ts b/src/types/playgroundType.ts index 3affcac03..bcf794950 100644 --- a/src/types/playgroundType.ts +++ b/src/types/playgroundType.ts @@ -1,3 +1,5 @@ +import * as vscode from 'vscode'; + export type OutputItem = { namespace: string | null; type: string | null; @@ -17,3 +19,30 @@ export type PlaygroundExecuteParameters = { codeToEvaluate: string; connectionId: string; }; + +export interface ExportToLanguageAddons { + textFromEditor?: string; + selectedText?: string; + selection?: vscode.Range; + importStatements: boolean; + driverSyntax: boolean; + builders: boolean; + language: string; + mode?: string; +} + +export interface PlaygroundSelection { + textFromEditor: string; + selection: vscode.Selection; +} + +export enum ExportToLanguageMode { + QUERY = 'QUERY', + AGGREGATION = 'AGGREGATION', + OTHER = 'OTHER' +} + +export interface ExportToLanguageNamespace { + databaseName: string | null; + collectionName: string | null; +} From c83bd8c917b5dc932d2b715f68f4ac5fe90673a2 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Fri, 29 Oct 2021 14:51:35 +0200 Subject: [PATCH 3/7] refactor: clean up types --- src/editors/codeActionProvider.ts | 4 ++-- src/editors/playgroundController.ts | 2 +- src/language/languageServerController.ts | 6 +++--- src/language/mongoDBService.ts | 6 +++--- src/language/server.ts | 6 +++--- src/language/visitor.ts | 24 +++++++++++++++++------- src/test/suite/stubs.ts | 6 +++--- src/types/playgroundType.ts | 4 ++-- 8 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/editors/codeActionProvider.ts b/src/editors/codeActionProvider.ts index 226e3038d..49ed43469 100644 --- a/src/editors/codeActionProvider.ts +++ b/src/editors/codeActionProvider.ts @@ -5,7 +5,7 @@ import { ExportToLanguageMode } from '../types/playgroundType'; export default class CodeActionProvider implements vscode.CodeActionProvider { _onDidChangeCodeCodeAction: vscode.EventEmitter = new vscode.EventEmitter(); - selection?: vscode.Range; + selection?: vscode.Selection; mode?: string; static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix]; @@ -19,7 +19,7 @@ export default class CodeActionProvider implements vscode.CodeActionProvider { readonly onDidChangeCodeLenses: vscode.Event = this ._onDidChangeCodeCodeAction.event; - refresh({ selection, mode }: { selection?: vscode.Range, mode?: string }): void { + refresh({ selection, mode }: { selection?: vscode.Selection, mode?: string }): void { this.selection = selection; this.mode = mode; this._onDidChangeCodeCodeAction.fire(); diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index ce0066567..49ff4c483 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -629,7 +629,7 @@ export default class PlaygroundController { if (driverSyntax) { namespace = await this._languageServerController.getNamespaceForSelection({ textFromEditor, - selection: selection as vscode.Selection + selection }); const dataService = this._connectionController.getActiveDataService(); diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index ca5df4679..cf7f96152 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -16,7 +16,7 @@ import { ShellExecuteAllResult, ExportToLanguageMode, ExportToLanguageNamespace, - PlaygroundSelection + PlaygroundTextAndSelection } from '../types/playgroundType'; import { ServerCommands } from './serverCommands'; import { ConnectionOptions } from '../types/connectionOptionsType'; @@ -171,7 +171,7 @@ export default class LanguageServerController { } async getExportToLanguageMode( - params: PlaygroundSelection + params: PlaygroundTextAndSelection ): Promise { await this._client.onReady(); return this._client.sendRequest( @@ -181,7 +181,7 @@ export default class LanguageServerController { } async getNamespaceForSelection( - params: PlaygroundSelection + params: PlaygroundTextAndSelection ): Promise { await this._client.onReady(); return this._client.sendRequest( diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index b6d9c9b2c..bd4a9580b 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -21,7 +21,7 @@ import { PlaygroundExecuteParameters, ExportToLanguageMode, ExportToLanguageNamespace, - PlaygroundSelection + PlaygroundTextAndSelection } from '../types/playgroundType'; import { Visitor } from './visitor'; @@ -442,7 +442,7 @@ export default class MongoDBService { }); } - getExportToLanguageMode(params: PlaygroundSelection): ExportToLanguageMode { + getExportToLanguageMode(params: PlaygroundTextAndSelection): ExportToLanguageMode { const state = this._visitor.parseAST(params); if (state.isArray) { @@ -456,7 +456,7 @@ export default class MongoDBService { return ExportToLanguageMode.OTHER; } - getNamespaceForSelection(params: PlaygroundSelection): ExportToLanguageNamespace { + getNamespaceForSelection(params: PlaygroundTextAndSelection): ExportToLanguageNamespace { try { const state = this._visitor.parseAST(params); return { databaseName: state.databaseName, collectionName: state.collectionName }; diff --git a/src/language/server.ts b/src/language/server.ts index 00a7a594d..4f3f3adc1 100644 --- a/src/language/server.ts +++ b/src/language/server.ts @@ -14,7 +14,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import MongoDBService from './mongoDBService'; import { ServerCommands } from './serverCommands'; -import { PlaygroundExecuteParameters, PlaygroundSelection } from '../types/playgroundType'; +import { PlaygroundExecuteParameters, PlaygroundTextAndSelection } from '../types/playgroundType'; // Create a connection for the server. The connection uses Node's IPC as a transport. // Also include all preview / proposed LSP features. @@ -160,11 +160,11 @@ connection.onRequest(ServerCommands.DISCONNECT_TO_SERVICE_PROVIDER, () => { return mongoDBService.disconnectFromServiceProvider(); }); -connection.onRequest(ServerCommands.GET_EXPORT_TO_LANGUAGE_MODE, (params: PlaygroundSelection) => { +connection.onRequest(ServerCommands.GET_EXPORT_TO_LANGUAGE_MODE, (params: PlaygroundTextAndSelection) => { return mongoDBService.getExportToLanguageMode(params); }); -connection.onRequest(ServerCommands.GET_NAMESPACE_FOR_SELECTION, (params: PlaygroundSelection) => { +connection.onRequest(ServerCommands.GET_NAMESPACE_FOR_SELECTION, (params: PlaygroundTextAndSelection) => { return mongoDBService.getNamespaceForSelection(params); }); diff --git a/src/language/visitor.ts b/src/language/visitor.ts index ed8f66596..3cb7f98bd 100644 --- a/src/language/visitor.ts +++ b/src/language/visitor.ts @@ -1,14 +1,21 @@ -import * as vscode from 'vscode'; import type * as babel from '@babel/core'; import * as parser from '@babel/parser'; import traverse from '@babel/traverse'; import { Connection } from 'vscode-languageserver/node'; import * as util from 'util'; -import { PlaygroundSelection } from '../types/playgroundType'; - const PLACEHOLDER = 'TRIGGER_CHARACTER'; +export interface VisitorSelection { + start: { line: number, character: number }; + end: { line: number, character: number }; +} + +export interface VisitorTextAndSelection { + textFromEditor: string; + selection: VisitorSelection; +} + export interface CompletionState { databaseName: string | null; collectionName: string | null; @@ -25,12 +32,15 @@ export interface CompletionState { export class Visitor { _state: CompletionState; - _selection: vscode.Selection; + _selection: VisitorSelection; _connection: Connection; constructor(connection: Connection) { this._state = this._getDefaultNodesValues(); - this._selection = { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } } as vscode.Selection; + this._selection = { + start: { line: 0, character: 0 }, + end: { line: 0, character: 0 } + }; this._connection = connection; } @@ -120,7 +130,7 @@ export class Visitor { textFromEditor: string, position: { line: number; character: number } ): CompletionState { - const selection = { start: position, end: { line: 0, character: 0 } } as vscode.Selection; + const selection: VisitorSelection = { start: position, end: { line: 0, character: 0 } }; textFromEditor = this._handleTriggerCharacter( textFromEditor, @@ -130,7 +140,7 @@ export class Visitor { return this.parseAST({ textFromEditor, selection }); } - parseAST({ textFromEditor, selection }: PlaygroundSelection): CompletionState { + parseAST({ textFromEditor, selection }: VisitorTextAndSelection): CompletionState { let ast: any; this._state = this._getDefaultNodesValues(); diff --git a/src/test/suite/stubs.ts b/src/test/suite/stubs.ts index 6475c37a3..3895c45e3 100644 --- a/src/test/suite/stubs.ts +++ b/src/test/suite/stubs.ts @@ -241,18 +241,18 @@ class MockLanguageServerController { return; } - executeAll(/* codeToEvaluate: string*/): Promise { + executeAll(/* codeToEvaluate: string */): Promise { return Promise.resolve({ outputLines: [], result: { namespace: null, type: null, content: 'Result' } }); } - getExportToLanguageMode(/* codeToEvaluate: string*/): Promise { + getExportToLanguageMode(/* params: PlaygroundTextAndSelection */): Promise { return Promise.resolve(ExportToLanguageMode.OTHER); } - getNamespaceForSelection(/* codeToEvaluate: string*/): Promise { + getNamespaceForSelection(/* params: PlaygroundTextAndSelection */): Promise { return Promise.resolve({ databaseName: null, collectionName: null }); } diff --git a/src/types/playgroundType.ts b/src/types/playgroundType.ts index bcf794950..e95fa5f81 100644 --- a/src/types/playgroundType.ts +++ b/src/types/playgroundType.ts @@ -23,7 +23,7 @@ export type PlaygroundExecuteParameters = { export interface ExportToLanguageAddons { textFromEditor?: string; selectedText?: string; - selection?: vscode.Range; + selection?: vscode.Selection; importStatements: boolean; driverSyntax: boolean; builders: boolean; @@ -31,7 +31,7 @@ export interface ExportToLanguageAddons { mode?: string; } -export interface PlaygroundSelection { +export interface PlaygroundTextAndSelection { textFromEditor: string; selection: vscode.Selection; } From a01b7cf53a1580021e8a079c8b755eaa42a7bd30 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Fri, 29 Oct 2021 15:41:17 +0200 Subject: [PATCH 4/7] refactor: use enum for export to languages --- .../exportToLanguageCodeLensProvider.ts | 8 +++-- src/editors/playgroundController.ts | 31 ++++++------------- src/editors/playgroundResultProvider.ts | 10 ++---- src/mdbExtensionController.ts | 9 +++--- src/types/playgroundType.ts | 7 +++++ 5 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/editors/exportToLanguageCodeLensProvider.ts b/src/editors/exportToLanguageCodeLensProvider.ts index cd36b3190..5ae951138 100644 --- a/src/editors/exportToLanguageCodeLensProvider.ts +++ b/src/editors/exportToLanguageCodeLensProvider.ts @@ -1,7 +1,11 @@ import * as vscode from 'vscode'; import EXTENSION_COMMANDS from '../commands'; -import { ExportToLanguageMode, ExportToLanguageAddons } from '../types/playgroundType'; +import { + ExportToLanguageMode, + ExportToLanguageAddons, + ExportToLanguages +} from '../types/playgroundType'; export default class ExportToLanguageCodeLensProvider implements vscode.CodeLensProvider { @@ -60,7 +64,7 @@ implements vscode.CodeLensProvider { exportToLanguageCodeLenses.push(driverSyntaxCodeLens); if ( - this._exportToLanguageAddons.language === 'java' && + this._exportToLanguageAddons.language === ExportToLanguages.JAVA && this._exportToLanguageAddons.mode === ExportToLanguageMode.QUERY ) { buildersCodeLens.command = { diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 49ff4c483..ca3ee40d6 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -17,7 +17,8 @@ import { ShellExecuteAllResult, ExportToLanguageAddons, ExportToLanguageMode, - ExportToLanguageNamespace + ExportToLanguageNamespace, + ExportToLanguages } from '../types/playgroundType'; import PlaygroundResultProvider, { PLAYGROUND_RESULT_SCHEME, @@ -370,27 +371,11 @@ export default class PlaygroundController { const { type, content } = playgroundResult; - if (type === 'python') { - return 'python'; + if (type && type in ExportToLanguages) { + return type; } - if (type === 'java') { - return 'java'; - } - - if (type === 'csharp') { - return 'csharp'; - } - - if (type === 'javascript') { - return 'javascript'; - } - - if (typeof content === 'object' && content !== null) { - return 'json'; - } - - return 'plaintext'; + return (typeof content === 'object' && content !== null) ? 'json' : 'plaintext'; } async _openPlaygroundResult(): Promise { @@ -618,7 +603,11 @@ export default class PlaygroundController { } try { - const useBuilders = builders && language === 'java' && mode === ExportToLanguageMode.QUERY; + const useBuilders = ( + builders && + language === ExportToLanguages.JAVA && + mode === ExportToLanguageMode.QUERY + ); let transpiledExpression = ''; let imports = ''; let namespace: ExportToLanguageNamespace = { diff --git a/src/editors/playgroundResultProvider.ts b/src/editors/playgroundResultProvider.ts index c661d14a4..a7f3d3ae7 100644 --- a/src/editors/playgroundResultProvider.ts +++ b/src/editors/playgroundResultProvider.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import ConnectionController from '../connectionController'; import EditDocumentCodeLensProvider from './editDocumentCodeLensProvider'; -import type { PlaygroundResult } from '../types/playgroundType'; +import { PlaygroundResult, ExportToLanguages } from '../types/playgroundType'; export const PLAYGROUND_RESULT_SCHEME = 'PLAYGROUND_RESULT_SCHEME'; @@ -53,13 +53,7 @@ implements vscode.TextDocumentContentProvider { return 'undefined'; } - if ( - type === 'string' || - type === 'python' || - type === 'java' || - type === 'csharp' || - type === 'javascript' - ) { + if (type && (type === 'string' || type in ExportToLanguages)) { return this._playgroundResult.content; } diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index c352c04f9..c5dd66e45 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -36,6 +36,7 @@ import TelemetryService from './telemetry/telemetryService'; import PlaygroundsTreeItem from './explorer/playgroundsTreeItem'; import PlaygroundResultProvider from './editors/playgroundResultProvider'; import WebviewController from './views/webviewController'; +import { ExportToLanguages } from './types/playgroundType'; const log = createLogger('commands'); @@ -195,16 +196,16 @@ export default class MDBExtensionController implements vscode.Disposable { this._playgroundsExplorer.refresh() ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_PYTHON, () => - this._playgroundController.exportToLanguage('python') + this._playgroundController.exportToLanguage(ExportToLanguages.PYTHON) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_JAVA, () => - this._playgroundController.exportToLanguage('java') + this._playgroundController.exportToLanguage(ExportToLanguages.JAVA) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_CSHARP, () => - this._playgroundController.exportToLanguage('csharp') + this._playgroundController.exportToLanguage(ExportToLanguages.CSHARP) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_NODE, () => - this._playgroundController.exportToLanguage('javascript') + this._playgroundController.exportToLanguage(ExportToLanguages.JAVASCRIPT) ); this.registerCommand(EXTENSION_COMMANDS.MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS, (exportToLanguageAddons) => this._playgroundController.changeExportToLanguageAddons(exportToLanguageAddons) diff --git a/src/types/playgroundType.ts b/src/types/playgroundType.ts index e95fa5f81..71e0b7906 100644 --- a/src/types/playgroundType.ts +++ b/src/types/playgroundType.ts @@ -36,6 +36,13 @@ export interface PlaygroundTextAndSelection { selection: vscode.Selection; } +export enum ExportToLanguages { + PYTHON = 'python', + JAVA = 'java', + CSHARP = 'csharp', + JAVASCRIPT = 'javascript' +} + export enum ExportToLanguageMode { QUERY = 'QUERY', AGGREGATION = 'AGGREGATION', From 6e7adba914521a9cb38486da5cbf783a2a32f78a Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 17 Nov 2021 16:35:43 +0100 Subject: [PATCH 5/7] fix: separate evaluation type and results language --- src/editors/codeActionProvider.ts | 4 +- src/editors/playgroundController.ts | 23 +--- src/editors/playgroundResultProvider.ts | 7 +- src/language/worker.ts | 22 ++-- .../suite/editors/codeActionProvider.test.ts | 31 +++-- .../editDocumentCodeLensProvider.test.ts | 9 +- .../exportToLanguageCodeLensProvider.test.ts | 7 +- .../editors/playgroundController.test.ts | 99 -------------- .../editors/playgroundResultProvider.test.ts | 36 ++++-- .../suite/language/mongoDBService.test.ts | 121 ++++++++++++++++-- src/test/suite/mdbExtensionController.test.ts | 2 +- src/test/suite/stubs.ts | 2 +- .../suite/telemetry/telemetryService.test.ts | 26 ++-- src/types/playgroundType.ts | 3 +- 14 files changed, 202 insertions(+), 190 deletions(-) diff --git a/src/editors/codeActionProvider.ts b/src/editors/codeActionProvider.ts index 49ed43469..884fc202a 100644 --- a/src/editors/codeActionProvider.ts +++ b/src/editors/codeActionProvider.ts @@ -6,7 +6,7 @@ import { ExportToLanguageMode } from '../types/playgroundType'; export default class CodeActionProvider implements vscode.CodeActionProvider { _onDidChangeCodeCodeAction: vscode.EventEmitter = new vscode.EventEmitter(); selection?: vscode.Selection; - mode?: string; + mode?: ExportToLanguageMode; static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix]; @@ -19,7 +19,7 @@ export default class CodeActionProvider implements vscode.CodeActionProvider { readonly onDidChangeCodeLenses: vscode.Event = this ._onDidChangeCodeCodeAction.event; - refresh({ selection, mode }: { selection?: vscode.Selection, mode?: string }): void { + refresh({ selection, mode }: { selection?: vscode.Selection, mode?: ExportToLanguageMode }): void { this.selection = selection; this.mode = mode; this._onDidChangeCodeCodeAction.fire(); diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index ca3ee40d6..9318d7859 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -364,20 +364,6 @@ export default class PlaygroundController { } } - _getDocumentLanguage(playgroundResult: PlaygroundResult): string { - if (!playgroundResult) { - return 'plaintext'; - } - - const { type, content } = playgroundResult; - - if (type && type in ExportToLanguages) { - return type; - } - - return (typeof content === 'object' && content !== null) ? 'json' : 'plaintext'; - } - async _openPlaygroundResult(): Promise { this._playgroundResultViewProvider.setPlaygroundResult( this._playgroundResult @@ -392,7 +378,7 @@ export default class PlaygroundController { await this._showResultAsVirtualDocument(); if (this._playgroundResultTextDocument) { - const language = this._getDocumentLanguage(this._playgroundResult); + const language = this._playgroundResult?.language || 'plaintext'; await vscode.languages.setTextDocumentLanguage( this._playgroundResultTextDocument, @@ -599,7 +585,7 @@ export default class PlaygroundController { 'Please select one or more lines in the playground.' ); - return Promise.resolve(true); + return true; } try { @@ -645,8 +631,9 @@ export default class PlaygroundController { namespace: namespace.databaseName && namespace.collectionName ? `${namespace.databaseName}.${namespace.collectionName}` : null, - type: language, - content: imports ? `${imports}\n\n${transpiledExpression}` : transpiledExpression + type: null, + content: imports ? `${imports}\n\n${transpiledExpression}` : transpiledExpression, + language }; log.info(`Export to ${language} language result`, this._playgroundResult); diff --git a/src/editors/playgroundResultProvider.ts b/src/editors/playgroundResultProvider.ts index a7f3d3ae7..005482825 100644 --- a/src/editors/playgroundResultProvider.ts +++ b/src/editors/playgroundResultProvider.ts @@ -25,7 +25,8 @@ implements vscode.TextDocumentContentProvider { this._playgroundResult = { namespace: null, type: null, - content: undefined + content: undefined, + language: null }; } @@ -47,13 +48,13 @@ implements vscode.TextDocumentContentProvider { return 'undefined'; } - const { type, content } = this._playgroundResult; + const { type, content, language } = this._playgroundResult; if (type === 'undefined') { return 'undefined'; } - if (type && (type === 'string' || type in ExportToLanguages)) { + if (type === 'string' || (language && Object.values(ExportToLanguages).includes(language as ExportToLanguages))) { return this._playgroundResult.content; } diff --git a/src/language/worker.ts b/src/language/worker.ts index 627706113..aa2734615 100644 --- a/src/language/worker.ts +++ b/src/language/worker.ts @@ -42,7 +42,8 @@ const executeAll = async ( outputLines.push({ type, content: printable, - namespace: null + namespace: null, + language: null }); } } @@ -52,16 +53,21 @@ const executeAll = async ( source && source.namespace ? `${source.namespace.db}.${source.namespace.collection}` : null; - const content = - type === 'Cursor' || type === 'AggregationCursor' ? - JSON.parse(EJSON.stringify(printable.documents)) : - typeof printable === 'string' - ? printable - : JSON.parse(EJSON.stringify(printable)); + let content = ''; + + if (type === 'Cursor' || type === 'AggregationCursor') { + content = JSON.parse(EJSON.stringify(printable.documents)); + } else { + content = typeof printable === 'string' + ? printable + : JSON.parse(EJSON.stringify(printable)); + } + const result: PlaygroundResult = { namespace, type: type ? type : typeof printable, - content + content, + language: (typeof content === 'object' && content !== null) ? 'json' : 'plaintext' }; return [null, { outputLines, result }]; diff --git a/src/test/suite/editors/codeActionProvider.test.ts b/src/test/suite/editors/codeActionProvider.test.ts index cc264adfe..e8d4a6854 100644 --- a/src/test/suite/editors/codeActionProvider.test.ts +++ b/src/test/suite/editors/codeActionProvider.test.ts @@ -9,7 +9,7 @@ import CodeActionProvider from '../../../editors/codeActionProvider'; import { ExplorerController } from '../../../explorer'; import { LanguageServerController } from '../../../language'; import { PlaygroundController } from '../../../editors'; -import { PlaygroundResult } from '../../../types/playgroundType'; +import { PlaygroundResult, ExportToLanguageMode } from '../../../types/playgroundType'; import { mdbTestExtension } from '../stubbableMdbExtension'; import { TestExtensionContext } from '../stubs'; @@ -104,7 +104,7 @@ suite('Code Action Provider Test Suite', function () { } as vscode.Selection; const testCodeActionProvider = new CodeActionProvider(); - testCodeActionProvider.refresh({ selection, mode: 'OTHER' }); + testCodeActionProvider.refresh({ selection, mode: ExportToLanguageMode.OTHER }); const codeActions = testCodeActionProvider.provideCodeActions(); @@ -120,7 +120,7 @@ suite('Code Action Provider Test Suite', function () { await vscode.commands.executeCommand(actionCommand.command); - const expectedResult = { namespace: null, type: 'number', content: 123 }; + const expectedResult = { namespace: null, type: 'number', content: 123, language: 'plaintext' }; expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); expect(mdbTestExtension.testExtensionController._playgroundController._isPartialRun).to.be.equal(true); } @@ -133,7 +133,7 @@ suite('Code Action Provider Test Suite', function () { start: { line: 0, character: 0 }, end: { line: 0, character: 14 } } as vscode.Selection; - const mode = 'QUERY'; + const mode = ExportToLanguageMode.QUERY; mdbTestExtension.testExtensionController._playgroundController._selectedText = textFromEditor; mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.selection = selection; @@ -163,12 +163,12 @@ suite('Code Action Provider Test Suite', function () { await vscode.commands.executeCommand(actionCommand.command); - const expectedResult = { namespace: null, type: 'java', content: 'new Document("name", "22")' }; + const expectedResult = { namespace: null, type: null, content: 'new Document("name", "22")', language: 'java' }; const codeLenses = mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); expect(codeLenses.length).to.be.equal(3); - // Only java supports builders. + // Only java queries supports builders. await vscode.commands.executeCommand('mdb.changeExportToLanguageAddons', { ...mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider._exportToLanguageAddons, builders: true @@ -186,7 +186,7 @@ suite('Code Action Provider Test Suite', function () { start: { line: 0, character: 0 }, end: { line: 0, character: 14 } } as vscode.Selection; - const mode = 'QUERY'; + const mode = ExportToLanguageMode.QUERY; mdbTestExtension.testExtensionController._playgroundController._selectedText = textFromEditor; mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.selection = selection; @@ -218,8 +218,9 @@ suite('Code Action Provider Test Suite', function () { const expectedResult = { namespace: null, - type: 'csharp', - content: 'new BsonDocument(\"name\", \"22\")' + type: null, + content: 'new BsonDocument(\"name\", \"22\")', + language: 'csharp' }; expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); @@ -244,7 +245,7 @@ suite('Code Action Provider Test Suite', function () { start: { line: 0, character: 24 }, end: { line: 0, character: 38 } } as vscode.Selection; - const mode = 'QUERY'; + const mode = ExportToLanguageMode.QUERY; mdbTestExtension.testExtensionController._playgroundController._selectedText = "{ name: '22' }"; mdbTestExtension.testExtensionController._playgroundController._codeActionProvider.selection = selection; @@ -276,8 +277,9 @@ suite('Code Action Provider Test Suite', function () { let expectedResult: PlaygroundResult = { namespace: null, - type: 'python', - content: "{\n 'name': '22'\n}" + type: null, + content: "{\n 'name': '22'\n}", + language: 'python' }; expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); @@ -291,8 +293,9 @@ suite('Code Action Provider Test Suite', function () { expectedResult = { namespace: 'db.coll', - type: 'python', - content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode+0.0.0-dev.0&directConnection=true&ssl=false')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})" + type: null, + content: "# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27018/?readPreference=primary&appname=mongodb-vscode+0.0.0-dev.0&directConnection=true&ssl=false')\nresult = client['db']['coll'].aggregate({\n 'name': '22'\n})", + language: 'python' }; expect(mdbTestExtension.testExtensionController._playgroundController._playgroundResult).to.be.deep.equal(expectedResult); diff --git a/src/test/suite/editors/editDocumentCodeLensProvider.test.ts b/src/test/suite/editors/editDocumentCodeLensProvider.test.ts index 218ea1172..9d12b721a 100644 --- a/src/test/suite/editors/editDocumentCodeLensProvider.test.ts +++ b/src/test/suite/editors/editDocumentCodeLensProvider.test.ts @@ -123,7 +123,8 @@ suite('Edit Document Code Lens Provider Test Suite', () => { testCodeLensProvider.updateCodeLensesForPlayground({ namespace: 'db.coll', type: 'Document', - content: { _id: '93333a0d-83f6-4e6f-a575-af7ea6187a4a' } + content: { _id: '93333a0d-83f6-4e6f-a575-af7ea6187a4a' }, + language: 'json' }); const codeLens = testCodeLensProvider.provideCodeLenses(); @@ -164,7 +165,8 @@ suite('Edit Document Code Lens Provider Test Suite', () => { content: [ { _id: '93333a0d-83f6-4e6f-a575-af7ea6187a4a' }, { _id: '21333a0d-83f6-4e6f-a575-af7ea6187444' } - ] + ], + language: 'json' }); const codeLens = testCodeLensProvider.provideCodeLenses(); @@ -226,7 +228,8 @@ suite('Edit Document Code Lens Provider Test Suite', () => { $date: '2014-04-04T21:23:13.331Z' } } - ] + ], + language: 'json' }); const codeLens = testCodeLensProvider.provideCodeLenses(); diff --git a/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts b/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts index 011c3eb47..330a26285 100644 --- a/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts +++ b/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts @@ -2,6 +2,7 @@ import { beforeEach } from 'mocha'; import chai from 'chai'; import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; +import { ExportToLanguageMode } from '../../../types/playgroundType'; const expect = chai.expect; @@ -55,7 +56,7 @@ suite('Export To Language Code Lens Provider Test Suite', function () { }); test('has the use builders code lens when builders is false, language is java, and mode is query', () => { - testExportToLanguageCodeLensProvider.refresh({ ...defaults, mode: 'QUERY', language: 'java' }); + testExportToLanguageCodeLensProvider.refresh({ ...defaults, mode: ExportToLanguageMode.QUERY, language: 'java' }); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); @@ -64,7 +65,7 @@ suite('Export To Language Code Lens Provider Test Suite', function () { }); test('has the use raw query code lens when builders is true, language is java, and mode is query', () => { - testExportToLanguageCodeLensProvider.refresh({ ...defaults, builders: true, mode: 'QUERY', language: 'java' }); + testExportToLanguageCodeLensProvider.refresh({ ...defaults, builders: true, mode: ExportToLanguageMode.QUERY, language: 'java' }); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); @@ -73,7 +74,7 @@ suite('Export To Language Code Lens Provider Test Suite', function () { }); test('does not have the use raw query code lens when builders is true, language is java, and mode is plain text', () => { - testExportToLanguageCodeLensProvider.refresh({ ...defaults, builders: true, mode: 'OTHER', language: 'java' }); + testExportToLanguageCodeLensProvider.refresh({ ...defaults, builders: true, mode: ExportToLanguageMode.OTHER, language: 'java' }); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 2975b4431..349058cee 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -474,105 +474,6 @@ suite('Playground Controller Test Suite', function () { activeTestEditorMock ); }); - - test('getDocumentLanguage returns json if content is object', async () => { - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage({ - namespace: null, - type: 'object', - content: { - test: 'value' - } - }); - - expect(language).to.be.equal('json'); - }); - - test('getDocumentLanguage returns json if content is array', async () => { - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage({ - namespace: null, - type: 'object', - content: [{ - test: 'value' - }] - }); - - expect(language).to.be.equal('json'); - }); - - test('getDocumentLanguage returns json if content is object with BSON value', async () => { - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage({ - namespace: null, - type: 'object', - content: { - _id: { - $oid: '5d973ae7443762aae72a160' - } - } - }); - - expect(language).to.be.equal('json'); - }); - - test('getDocumentLanguage returns plaintext if content is string', async () => { - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage({ - namespace: null, - type: 'string', - content: 'I am a string' - }); - - expect(language).to.be.equal('plaintext'); - }); - - test('getDocumentLanguage returns plaintext if content is number', async () => { - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage({ - namespace: null, - type: 'number', - content: 12 - }); - - expect(language).to.be.equal('plaintext'); - }); - - test('getDocumentLanguage returns plaintext if content is undefined', async () => { - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage({ - namespace: null, - type: null, - content: undefined - }); - - expect(language).to.be.equal('plaintext'); - }); - - test('getDocumentLanguage returns plaintext if content is null', async () => { - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', false); - const language = testPlaygroundController._getDocumentLanguage({ - namespace: null, - type: null, - content: null - }); - - expect(language).to.be.equal('plaintext'); - }); }); }); }); diff --git a/src/test/suite/editors/playgroundResultProvider.test.ts b/src/test/suite/editors/playgroundResultProvider.test.ts index 5e78e7e4b..85e7ae57f 100644 --- a/src/test/suite/editors/playgroundResultProvider.test.ts +++ b/src/test/suite/editors/playgroundResultProvider.test.ts @@ -48,7 +48,8 @@ suite('Playground Result Provider Test Suite', () => { { namespace: null, type: null, - content: undefined + content: undefined, + language: null } ); }); @@ -64,7 +65,8 @@ suite('Playground Result Provider Test Suite', () => { content: { _id: '93333a0d-83f6-4e6f-a575-af7ea6187a4a', name: 'Berlin' - } + }, + language: 'json' }; testPlaygroundResultViewProvider.setPlaygroundResult(playgroundResult); @@ -83,7 +85,8 @@ suite('Playground Result Provider Test Suite', () => { testPlaygroundResultViewProvider._playgroundResult = { namespace: 'db.berlin', type: 'undefined', - content: null + content: null, + language: 'plaintext' }; const result = testPlaygroundResultViewProvider.provideTextDocumentContent(); @@ -100,7 +103,8 @@ suite('Playground Result Provider Test Suite', () => { testPlaygroundResultViewProvider._playgroundResult = { namespace: 'db.berlin', type: 'object', - content: null + content: null, + language: 'plaintext' }; const result = testPlaygroundResultViewProvider.provideTextDocumentContent(); @@ -117,7 +121,8 @@ suite('Playground Result Provider Test Suite', () => { testPlaygroundResultViewProvider._playgroundResult = { namespace: 'db.berlin', type: 'number', - content: 4 + content: 4, + language: 'plaintext' }; const result = testPlaygroundResultViewProvider.provideTextDocumentContent(); @@ -134,7 +139,8 @@ suite('Playground Result Provider Test Suite', () => { testPlaygroundResultViewProvider._playgroundResult = { namespace: 'db.berlin', type: 'object', - content: [] + content: [], + language: 'json' }; const result = testPlaygroundResultViewProvider.provideTextDocumentContent(); @@ -151,7 +157,8 @@ suite('Playground Result Provider Test Suite', () => { testPlaygroundResultViewProvider._playgroundResult = { namespace: 'db.berlin', type: 'object', - content: {} + content: {}, + language: 'json' }; const result = testPlaygroundResultViewProvider.provideTextDocumentContent(); @@ -168,7 +175,8 @@ suite('Playground Result Provider Test Suite', () => { testPlaygroundResultViewProvider._playgroundResult = { namespace: 'db.berlin', type: 'boolean', - content: true + content: true, + language: 'plaintext' }; const result = testPlaygroundResultViewProvider.provideTextDocumentContent(); @@ -185,7 +193,8 @@ suite('Playground Result Provider Test Suite', () => { testPlaygroundResultViewProvider._playgroundResult = { namespace: 'db.berlin', type: 'string', - content: 'Berlin' + content: 'Berlin', + language: 'plaintext' }; const result = testPlaygroundResultViewProvider.provideTextDocumentContent(); @@ -211,7 +220,8 @@ suite('Playground Result Provider Test Suite', () => { const playgroundResult = { namespace: 'db.berlin', type: 'Cursor', - content + content, + language: 'json' }; const mockRefresh: any = sinon.fake(); @@ -242,7 +252,8 @@ suite('Playground Result Provider Test Suite', () => { const playgroundResult = { namespace: 'db.berlin', type: 'Document', - content + content, + language: 'json' }; const mockRefresh: any = sinon.fake(); @@ -273,7 +284,8 @@ suite('Playground Result Provider Test Suite', () => { content: [ { _id: 1, item: 'abc', price: 10, quantity: 2, date: new Date('2014-03-01T08:00:00Z') }, { _id: 2, item: 'jkl', price: 20, quantity: 1, date: new Date('2014-03-01T09:00:00Z') } - ] + ], + language: 'json' }; const connectionId = '1c8c2b06-fbfb-40b7-bd8a-bd1f8333a487'; diff --git a/src/test/suite/language/mongoDBService.test.ts b/src/test/suite/language/mongoDBService.test.ts index 181b7ed17..ccf00a494 100644 --- a/src/test/suite/language/mongoDBService.test.ts +++ b/src/test/suite/language/mongoDBService.test.ts @@ -985,7 +985,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { outputLines: [], - result: { namespace: null, type: 'number', content: 2 } + result: { namespace: null, type: 'number', content: 2, language: 'plaintext' } }; expect(result).to.deep.equal(expectedResult); @@ -1015,7 +1015,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { outputLines: [], - result: { namespace: null, type: 'number', content: 3 } + result: { namespace: null, type: 'number', content: 3, language: 'plaintext' } }; expect(result).to.deep.equal(expectedResult); @@ -1032,7 +1032,7 @@ suite('MongoDBService Test Suite', () => { ); const firstRes = { outputLines: [], - result: { namespace: null, type: 'number', content: 2 } + result: { namespace: null, type: 'number', content: 2, language: 'plaintext' } }; expect(firstEvalResult).to.deep.equal(firstRes); @@ -1046,7 +1046,7 @@ suite('MongoDBService Test Suite', () => { ); const secondRes = { outputLines: [], - result: { namespace: null, type: 'number', content: 3 } + result: { namespace: null, type: 'number', content: 3, language: 'plaintext' } }; expect(secondEvalResult).to.deep.equal(secondRes); @@ -1072,7 +1072,102 @@ suite('MongoDBService Test Suite', () => { _id: { $oid: '5fb292760ece2dc9c0362075' } - } + }, + language: 'json' + } + }; + + expect(result).to.deep.equal(expectedResult); + }); + + test('evaluate returns an object', async () => { + const source = new CancellationTokenSource(); + const result = await testMongoDBService.executeAll( + { + connectionId: 'pineapple', + codeToEvaluate: `const obj = { name: "a short string" }; + obj` + }, + source.token + ); + const expectedResult = { + outputLines: [], + result: { + namespace: null, + type: 'object', + content: { + name: 'a short string' + }, + language: 'json' + } + }; + + expect(result).to.deep.equal(expectedResult); + }); + + test('evaluate returns an array', async () => { + const source = new CancellationTokenSource(); + const result = await testMongoDBService.executeAll( + { + connectionId: 'pineapple', + codeToEvaluate: `const arr = [{ name: "a short string" }]; + arr` + }, + source.token + ); + const expectedResult = { + outputLines: [], + result: { + namespace: null, + type: 'object', + content: [{ + name: 'a short string' + }], + language: 'json' + } + }; + + expect(result).to.deep.equal(expectedResult); + }); + + test('evaluate returns undefined', async () => { + const source = new CancellationTokenSource(); + const result = await testMongoDBService.executeAll( + { + connectionId: 'pineapple', + codeToEvaluate: 'undefined' + }, + source.token + ); + const expectedResult = { + outputLines: [], + result: { + namespace: null, + type: 'undefined', + content: null, + language: 'plaintext' + } + }; + + expect(result).to.deep.equal(expectedResult); + }); + + test('evaluate returns null', async () => { + const source = new CancellationTokenSource(); + const result = await testMongoDBService.executeAll( + { + connectionId: 'pineapple', + codeToEvaluate: 'null' + }, + source.token + ); + const expectedResult = { + outputLines: [], + result: { + namespace: null, + type: 'object', + content: null, + language: 'plaintext' } }; @@ -1094,7 +1189,8 @@ suite('MongoDBService Test Suite', () => { result: { namespace: null, type: 'string', - content: 'A single line string' + content: 'A single line string', + language: 'plaintext' } }; @@ -1120,7 +1216,8 @@ suite('MongoDBService Test Suite', () => { type: 'string', content: `vscode is - awesome` + awesome`, + language: 'plaintext' } }; @@ -1138,12 +1235,12 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { outputLines: [ - { namespace: null, type: null, content: 'Hello' }, - { namespace: null, type: null, content: 1 }, - { namespace: null, type: null, content: 2 }, - { namespace: null, type: null, content: 3 } + { namespace: null, type: null, content: 'Hello', language: null }, + { namespace: null, type: null, content: 1, language: null }, + { namespace: null, type: null, content: 2, language: null }, + { namespace: null, type: null, content: 3, language: null } ], - result: { namespace: null, type: 'number', content: 42 } + result: { namespace: null, type: 'number', content: 42, language: 'plaintext' } }; expect(result).to.deep.equal(expectedResult); diff --git a/src/test/suite/mdbExtensionController.test.ts b/src/test/suite/mdbExtensionController.test.ts index 0e340fc81..34792d197 100644 --- a/src/test/suite/mdbExtensionController.test.ts +++ b/src/test/suite/mdbExtensionController.test.ts @@ -1532,7 +1532,7 @@ suite('MDBExtensionController Test Suite', function () { ); }); - test('mdb.createIndexFromTreeView should create a MongoDB playground with search template', async () => { + test('mdb.createIndexFromTreeView should create a MongoDB playground with index template', async () => { const mockOpenTextDocument: any = sinon.fake.resolves('untitled'); sinon.replace(vscode.workspace, 'openTextDocument', mockOpenTextDocument); diff --git a/src/test/suite/stubs.ts b/src/test/suite/stubs.ts index 3895c45e3..1a34f62c7 100644 --- a/src/test/suite/stubs.ts +++ b/src/test/suite/stubs.ts @@ -244,7 +244,7 @@ class MockLanguageServerController { executeAll(/* codeToEvaluate: string */): Promise { return Promise.resolve({ outputLines: [], - result: { namespace: null, type: null, content: 'Result' } + result: { namespace: null, type: null, content: 'Result', language: 'plaintext' } }); } diff --git a/src/test/suite/telemetry/telemetryService.test.ts b/src/test/suite/telemetry/telemetryService.test.ts index 895270fb6..55db670bb 100644 --- a/src/test/suite/telemetry/telemetryService.test.ts +++ b/src/test/suite/telemetry/telemetryService.test.ts @@ -343,7 +343,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert AggregationCursor shellApiType to aggregation telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'AggregationCursor', content: '' } + result: { namespace: null, type: 'AggregationCursor', content: '', language: 'plaintext' }, }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -353,7 +353,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert BulkWriteResult shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'BulkWriteResult', content: '' } + result: { namespace: null, type: 'BulkWriteResult', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -363,7 +363,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert Collection shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'Collection', content: '' } + result: { namespace: null, type: 'Collection', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -373,7 +373,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert Cursor shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'Cursor', content: '' } + result: { namespace: null, type: 'Cursor', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -383,7 +383,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert Database shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'Database', content: '' } + result: { namespace: null, type: 'Database', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -393,7 +393,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert DeleteResult shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'DeleteResult', content: '' } + result: { namespace: null, type: 'DeleteResult', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -403,7 +403,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert InsertManyResult shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'InsertManyResult', content: '' } + result: { namespace: null, type: 'InsertManyResult', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -413,7 +413,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert InsertOneResult shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'InsertOneResult', content: '' } + result: { namespace: null, type: 'InsertOneResult', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -423,7 +423,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert ReplicaSet shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'ReplicaSet', content: '' } + result: { namespace: null, type: 'ReplicaSet', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -433,7 +433,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert Shard shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'Shard', content: '' } + result: { namespace: null, type: 'Shard', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -443,7 +443,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert ShellApi shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'ShellApi', content: '' } + result: { namespace: null, type: 'ShellApi', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -453,7 +453,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert UpdateResult shellApiType to other telemetry type', () => { const res = { outputLines: [], - result: { namespace: null, type: 'UpdateResult', content: '' } + result: { namespace: null, type: 'UpdateResult', content: '', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); @@ -463,7 +463,7 @@ suite('Telemetry Controller Test Suite', () => { test('return other telemetry type if evaluation returns a string', () => { const res = { outputLines: [], - result: { namespace: null, type: null, content: '2' } + result: { namespace: null, type: null, content: '2', language: 'plaintext' } }; const type = testTelemetryService.getPlaygroundResultType(res); diff --git a/src/types/playgroundType.ts b/src/types/playgroundType.ts index 71e0b7906..993391fa8 100644 --- a/src/types/playgroundType.ts +++ b/src/types/playgroundType.ts @@ -4,6 +4,7 @@ export type OutputItem = { namespace: string | null; type: string | null; content: any; + language: string | null; }; export type PlaygroundDebug = OutputItem[] | undefined; @@ -28,7 +29,7 @@ export interface ExportToLanguageAddons { driverSyntax: boolean; builders: boolean; language: string; - mode?: string; + mode?: ExportToLanguageMode; } export interface PlaygroundTextAndSelection { From 34425ad857528d17e939860ad544c8fe2fcb02aa Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Wed, 17 Nov 2021 21:55:07 +0100 Subject: [PATCH 6/7] refactor: replace temp variables with query functions --- src/language/worker.ts | 72 +++++++++++++++++++++++++----------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/language/worker.ts b/src/language/worker.ts index aa2734615..f23e6f834 100644 --- a/src/language/worker.ts +++ b/src/language/worker.ts @@ -3,21 +3,39 @@ import { MongoClientOptions } from '@mongosh/service-provider-server'; import { CompletionItemKind } from 'vscode-languageserver/node'; -import { EJSON } from 'bson'; +import { EJSON, Document } from 'bson'; import { ElectronRuntime } from '@mongosh/browser-runtime-electron'; import parseSchema = require('mongodb-schema'); import { parentPort, workerData } from 'worker_threads'; - -import type { PlaygroundResult, PlaygroundDebug, ShellExecuteAllResult } from '../types/playgroundType'; +import { PlaygroundResult, PlaygroundDebug, ShellExecuteAllResult } from '../types/playgroundType'; import { ServerCommands } from './serverCommands'; -type EvaluationResult = { +interface EvaluationResult { printable: any; type: string | null; -}; +} + type WorkerResult = ShellExecuteAllResult; type WorkerError = any | null; +const content = ({ type, printable }: EvaluationResult) => { + if (type === 'Cursor' || type === 'AggregationCursor') { + return JSON.parse(EJSON.stringify(printable.documents)); + } + + return typeof printable === 'string' + ? printable + : JSON.parse(EJSON.stringify(printable)); +}; + +const language = (evaluationResult: EvaluationResult) => { + if (typeof content(evaluationResult) === 'object' && content(evaluationResult) !== null) { + return 'json'; + } + + return 'plaintext'; +}; + const executeAll = async ( codeToEvaluate: string, connectionString: string, @@ -32,10 +50,11 @@ const executeAll = async ( connectionString, connectionOptions ); - const outputLines: PlaygroundDebug = []; + // Create a new instance of the runtime and evaluate code from a playground. const runtime: ElectronRuntime = new ElectronRuntime(serviceProvider); + runtime.setEvaluationListener({ onPrint(values: EvaluationResult[]) { for (const { type, printable } of values) { @@ -49,25 +68,14 @@ const executeAll = async ( } }); const { source, type, printable } = await runtime.evaluate(codeToEvaluate); - const namespace = - source && source.namespace - ? `${source.namespace.db}.${source.namespace.collection}` - : null; - let content = ''; - - if (type === 'Cursor' || type === 'AggregationCursor') { - content = JSON.parse(EJSON.stringify(printable.documents)); - } else { - content = typeof printable === 'string' - ? printable - : JSON.parse(EJSON.stringify(printable)); - } - + const namespace = (source && source.namespace) + ? `${source.namespace.db}.${source.namespace.collection}` + : null; const result: PlaygroundResult = { namespace, type: type ? type : typeof printable, - content, - language: (typeof content === 'object' && content !== null) ? 'json' : 'plaintext' + content: content({ type, printable }), + language: language({ type, printable }) }; return [null, { outputLines, result }]; @@ -133,6 +141,17 @@ const getFieldsFromSchema = async ( } }; +const prepareCompletionItems = (result: Document) => { + if (!result) { + return []; + } + + return result.databases.map((item) => ({ + label: item.name, + kind: CompletionItemKind.Value + })); +}; + const getListDatabases = async ( connectionString: string, connectionOptions: any @@ -145,14 +164,9 @@ const getListDatabases = async ( // TODO: There is a mistake in the service provider interface // Use `admin` as arguments to get list of dbs - // and remove it later when `mongosh` will merge a fix + // and remove it later when `mongosh` will merge a fix. const result = await serviceProvider.listDatabases('admin'); - const databases = result - ? result.databases.map((item) => ({ - label: item.name, - kind: CompletionItemKind.Value - })) - : []; + const databases = prepareCompletionItems(result); return [null, databases]; } catch (error) { From 543dc6e0cfa9ba4753373db490ca74d15eefb8c4 Mon Sep 17 00:00:00 2001 From: Alena Khineika Date: Thu, 18 Nov 2021 12:50:04 +0100 Subject: [PATCH 7/7] refactor: improve performance and coupling --- src/editors/playgroundController.ts | 16 ++++------------ src/language/mongoDBService.ts | 2 +- src/language/visitor.ts | 10 +++++----- src/language/worker.ts | 14 ++++++++------ src/test/suite/language/mongoDBService.test.ts | 2 +- 5 files changed, 19 insertions(+), 25 deletions(-) diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index 9318d7859..76585928d 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -16,9 +16,7 @@ import { PlaygroundResult, ShellExecuteAllResult, ExportToLanguageAddons, - ExportToLanguageMode, - ExportToLanguageNamespace, - ExportToLanguages + ExportToLanguageNamespace } from '../types/playgroundType'; import PlaygroundResultProvider, { PLAYGROUND_RESULT_SCHEME, @@ -574,8 +572,7 @@ export default class PlaygroundController { importStatements, driverSyntax, builders, - language, - mode + language } = this._exportToLanguageCodeLensProvider._exportToLanguageAddons; log.info(`Start export to ${language} language`); @@ -589,11 +586,6 @@ export default class PlaygroundController { } try { - const useBuilders = ( - builders && - language === ExportToLanguages.JAVA && - mode === ExportToLanguageMode.QUERY - ); let transpiledExpression = ''; let imports = ''; let namespace: ExportToLanguageNamespace = { @@ -618,9 +610,9 @@ export default class PlaygroundController { } }; - transpiledExpression = transpiler.shell[language].compileWithDriver(toCompile, useBuilders); + transpiledExpression = transpiler.shell[language].compileWithDriver(toCompile, builders); } else { - transpiledExpression = transpiler.shell[language].compile(selectedText, useBuilders, false); + transpiledExpression = transpiler.shell[language].compile(selectedText, builders, false); } if (importStatements) { diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index bd4a9580b..e12b5bc97 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -46,7 +46,7 @@ export default class MongoDBService { constructor(connection: Connection) { this._connection = connection; this._cachedShellSymbols = this._getShellCompletionItems(); - this._visitor = new Visitor(connection); + this._visitor = new Visitor(connection.console); } // ------ CONNECTION ------ // diff --git a/src/language/visitor.ts b/src/language/visitor.ts index 3cb7f98bd..e961d937a 100644 --- a/src/language/visitor.ts +++ b/src/language/visitor.ts @@ -1,7 +1,7 @@ import type * as babel from '@babel/core'; import * as parser from '@babel/parser'; import traverse from '@babel/traverse'; -import { Connection } from 'vscode-languageserver/node'; +import { RemoteConsole } from 'vscode-languageserver/node'; import * as util from 'util'; const PLACEHOLDER = 'TRIGGER_CHARACTER'; @@ -33,15 +33,15 @@ export interface CompletionState { export class Visitor { _state: CompletionState; _selection: VisitorSelection; - _connection: Connection; + _console: RemoteConsole; - constructor(connection: Connection) { + constructor(console: RemoteConsole) { this._state = this._getDefaultNodesValues(); this._selection = { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }; - this._connection = connection; + this._console = console; } _visitCallExpression(node: babel.types.CallExpression): void { @@ -152,7 +152,7 @@ export class Visitor { sourceType: 'module' }); } catch (error) { - this._connection.console.error(`parseAST error: ${util.inspect(error)}`); + this._console.error(`parseAST error: ${util.inspect(error)}`); return this._state; } diff --git a/src/language/worker.ts b/src/language/worker.ts index f23e6f834..62972af6d 100644 --- a/src/language/worker.ts +++ b/src/language/worker.ts @@ -18,18 +18,20 @@ interface EvaluationResult { type WorkerResult = ShellExecuteAllResult; type WorkerError = any | null; -const content = ({ type, printable }: EvaluationResult) => { +const getContent = ({ type, printable }: EvaluationResult) => { if (type === 'Cursor' || type === 'AggregationCursor') { return JSON.parse(EJSON.stringify(printable.documents)); } - return typeof printable === 'string' + return (typeof printable !== 'object' || printable === null) ? printable : JSON.parse(EJSON.stringify(printable)); }; -const language = (evaluationResult: EvaluationResult) => { - if (typeof content(evaluationResult) === 'object' && content(evaluationResult) !== null) { +const getLanguage = (evaluationResult: EvaluationResult) => { + const content = getContent(evaluationResult); + + if (typeof content === 'object' && content !== null) { return 'json'; } @@ -74,8 +76,8 @@ const executeAll = async ( const result: PlaygroundResult = { namespace, type: type ? type : typeof printable, - content: content({ type, printable }), - language: language({ type, printable }) + content: getContent({ type, printable }), + language: getLanguage({ type, printable }) }; return [null, { outputLines, result }]; diff --git a/src/test/suite/language/mongoDBService.test.ts b/src/test/suite/language/mongoDBService.test.ts index ccf00a494..3de1695be 100644 --- a/src/test/suite/language/mongoDBService.test.ts +++ b/src/test/suite/language/mongoDBService.test.ts @@ -1144,7 +1144,7 @@ suite('MongoDBService Test Suite', () => { result: { namespace: null, type: 'undefined', - content: null, + content: undefined, language: 'plaintext' } };