diff --git a/.github/dependabot.yml b/.github/dependabot.yml index b0b452f6a..2d2a1e881 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -22,7 +22,6 @@ updates: patterns: - "@mongodb-js/compass-*" - mongodb-data-service - - bson-transpilers - "@mongodb-js/connection-form" mongosh: patterns: diff --git a/package-lock.json b/package-lock.json index 0836f53e8..08157fd15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,6 @@ "@mongosh/shell-api": "^2.3.3", "@segment/analytics-node": "^1.3.0", "bson": "^6.8.0", - "bson-transpilers": "^2.2.0", "debug": "^4.3.7", "dotenv": "^16.4.5", "ejson-shell-parser": "^2.0.1", @@ -6783,11 +6782,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "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.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -6811,6 +6805,7 @@ "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" } @@ -7505,50 +7500,6 @@ "node": ">=16.20.1" } }, - "node_modules/bson-transpilers": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/bson-transpilers/-/bson-transpilers-2.2.0.tgz", - "integrity": "sha512-hiRRIwxQ0CYmmhBcfC2I7tS+kS7YFz+vXZ0GrRTiMzitSwOsMZwOYs6c31C1r85/rm1O+mcVV2UCwhhmgcooag==", - "dependencies": { - "antlr4": "4.7.2", - "bson": "^4.4.1", - "js-yaml": "^3.13.1" - } - }, - "node_modules/bson-transpilers/node_modules/bson": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", - "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", - "dependencies": { - "buffer": "^5.6.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/bson-transpilers/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -13262,6 +13213,7 @@ "version": "3.14.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -18040,7 +17992,8 @@ "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true }, "node_modules/ssh2": { "version": "1.15.0", diff --git a/package.json b/package.json index 629ce65f2..3d222e5af 100644 --- a/package.json +++ b/package.json @@ -259,8 +259,8 @@ "title": "MongoDB: Change Active Connection" }, { - "command": "mdb.changeExportToLanguageAddons", - "title": "MongoDB: Change Export To Language Addons" + "command": "mdb.changeDriverSyntax", + "title": "MongoDB: Change Export To Language Driver Syntax" }, { "command": "mdb.runSelectedPlaygroundBlocks", @@ -753,7 +753,8 @@ ], "mdb.copilot": [ { - "command": "mdb.exportCodeToPlayground" + "command": "mdb.exportCodeToPlayground", + "when": "mdb.isPlayground == false" } ], "editor/context": [ @@ -872,7 +873,7 @@ "when": "mdb.isPlayground" }, { - "command": "mdb.changeExportToLanguageAddons", + "command": "mdb.changeDriverSyntax", "when": "false" }, { @@ -1216,7 +1217,6 @@ "@mongosh/shell-api": "^2.3.3", "@segment/analytics-node": "^1.3.0", "bson": "^6.8.0", - "bson-transpilers": "^2.2.0", "debug": "^4.3.7", "dotenv": "^16.4.5", "ejson-shell-parser": "^2.0.1", diff --git a/src/commands/index.ts b/src/commands/index.ts index 209990b10..9164d0a28 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -28,7 +28,7 @@ enum EXTENSION_COMMANDS { MDB_EXPORT_TO_GO = 'mdb.exportToGo', MDB_EXPORT_TO_RUST = 'mdb.exportToRust', MDB_EXPORT_TO_PHP = 'mdb.exportToPHP', - MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS = 'mdb.changeExportToLanguageAddons', + MDB_CHANGE_DRIVER_SYNTAX = 'mdb.changeDriverSyntax', MDB_OPEN_MONGODB_DOCUMENT_FROM_CODE_LENS = 'mdb.openMongoDBDocumentFromCodeLens', MDB_OPEN_MONGODB_DOCUMENT_FROM_TREE = 'mdb.openMongoDBDocumentFromTree', @@ -84,6 +84,7 @@ enum EXTENSION_COMMANDS { SELECT_DATABASE_WITH_PARTICIPANT = 'mdb.selectDatabaseWithParticipant', SELECT_COLLECTION_WITH_PARTICIPANT = 'mdb.selectCollectionWithParticipant', PARTICIPANT_OPEN_RAW_SCHEMA_OUTPUT = 'mdb.participantViewRawSchemaOutput', + SHOW_EXPORT_TO_LANGUAGE_RESULT = 'mdb.showExportToLanguageResult', } export default EXTENSION_COMMANDS; diff --git a/src/commands/launchMongoShell.ts b/src/commands/launchMongoShell.ts index e8fce2013..2da599c7e 100644 --- a/src/commands/launchMongoShell.ts +++ b/src/commands/launchMongoShell.ts @@ -52,7 +52,6 @@ const openMongoDBShell = ( void vscode.window.showErrorMessage( 'You need to be connected before launching the MongoDB Shell.' ); - return Promise.resolve(false); } @@ -63,9 +62,8 @@ const openMongoDBShell = ( if (!userShell) { void vscode.window.showErrorMessage( - 'Error: No shell found, please set your default shell environment in vscode.' + 'No shell found, please set your default shell environment in vscode.' ); - return Promise.resolve(false); } diff --git a/src/editors/editDocumentCodeLensProvider.ts b/src/editors/editDocumentCodeLensProvider.ts index c380c95ec..ea541dbfc 100644 --- a/src/editors/editDocumentCodeLensProvider.ts +++ b/src/editors/editDocumentCodeLensProvider.ts @@ -7,7 +7,7 @@ import { DocumentSource } from '../documentSource'; import type { EditDocumentInfo } from '../types/editDocumentInfoType'; import EXTENSION_COMMANDS from '../commands'; import { PLAYGROUND_RESULT_URI } from './playgroundResultProvider'; -import type { PlaygroundResult } from '../types/playgroundType'; +import type { PlaygroundRunResult } from '../types/playgroundType'; export default class EditDocumentCodeLensProvider implements vscode.CodeLensProvider @@ -31,7 +31,7 @@ export default class EditDocumentCodeLensProvider updateCodeLensesForCollection(data: { content: Document; - namespace: string | null; + namespace?: string; uri: vscode.Uri; }): void { let resultCodeLensesInfo: EditDocumentInfo[] = []; @@ -44,7 +44,7 @@ export default class EditDocumentCodeLensProvider this._codeLensesInfo[data.uri.toString()] = resultCodeLensesInfo; } - updateCodeLensesForPlayground(playgroundResult: PlaygroundResult): void { + updateCodeLensesForPlayground(playgroundResult: PlaygroundRunResult): void { const source = DocumentSource.DOCUMENT_SOURCE_PLAYGROUND; let resultCodeLensesInfo: EditDocumentInfo[] = []; @@ -71,7 +71,7 @@ export default class EditDocumentCodeLensProvider _updateCodeLensesForCursor(data: { content: any; - namespace: string | null; + namespace?: string; source: DocumentSource; }): EditDocumentInfo[] { const resultCodeLensesInfo: EditDocumentInfo[] = []; @@ -109,7 +109,7 @@ export default class EditDocumentCodeLensProvider _updateCodeLensesForDocument(data: { content: any; - namespace: string | null; + namespace?: string; source: DocumentSource; }): EditDocumentInfo[] { const { content, namespace, source } = data; diff --git a/src/editors/editorsController.ts b/src/editors/editorsController.ts index 51c111f82..eb4306812 100644 --- a/src/editors/editorsController.ts +++ b/src/editors/editorsController.ts @@ -4,7 +4,7 @@ import type { Document } from 'bson'; import type ActiveConnectionCodeLensProvider from './activeConnectionCodeLensProvider'; import type ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider'; -import PlaygroundSelectedCodeActionProvider from './playgroundSelectedCodeActionProvider'; +import PlaygroundSelectionCodeActionProvider from './playgroundSelectionCodeActionProvider'; import PlaygroundDiagnosticsCodeActionProvider from './playgroundDiagnosticsCodeActionProvider'; import type ConnectionController from '../connectionController'; import CollectionDocumentsCodeLensProvider from './collectionDocumentsCodeLensProvider'; @@ -38,7 +38,7 @@ const log = createLogger('editors controller'); export function getFileDisplayNameForDocument( documentId: any, namespace: string -) { +): string { let displayName = `${namespace}:${EJSON.stringify(documentId)}`; // Encode special file uri characters to ensure VSCode handles @@ -84,7 +84,7 @@ export function getViewCollectionDocumentsUri( * new editors and the data they need. It also manages active editors. */ export default class EditorsController { - _playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider; + _playgroundSelectionCodeActionProvider: PlaygroundSelectionCodeActionProvider; _playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider; _connectionController: ConnectionController; _playgroundController: PlaygroundController; @@ -97,7 +97,7 @@ export default class EditorsController { _documentIdStore: DocumentIdStore; _mongoDBDocumentService: MongoDBDocumentService; _telemetryService: TelemetryService; - _playgroundResultViewProvider: PlaygroundResultProvider; + _playgroundResultProvider: PlaygroundResultProvider; _activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; _editDocumentCodeLensProvider: EditDocumentCodeLensProvider; @@ -109,10 +109,10 @@ export default class EditorsController { playgroundController, statusView, telemetryService, - playgroundResultViewProvider, + playgroundResultProvider, activeConnectionCodeLensProvider, exportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider, + playgroundSelectionCodeActionProvider, playgroundDiagnosticsCodeActionProvider, editDocumentCodeLensProvider, }: { @@ -121,10 +121,10 @@ export default class EditorsController { playgroundController: PlaygroundController; statusView: StatusView; telemetryService: TelemetryService; - playgroundResultViewProvider: PlaygroundResultProvider; + playgroundResultProvider: PlaygroundResultProvider; activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; - playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider; + playgroundSelectionCodeActionProvider: PlaygroundSelectionCodeActionProvider; playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider; editDocumentCodeLensProvider: EditDocumentCodeLensProvider; }) { @@ -149,15 +149,15 @@ export default class EditorsController { statusView: new StatusView(context), editDocumentCodeLensProvider: this._editDocumentCodeLensProvider, }); - this._playgroundResultViewProvider = playgroundResultViewProvider; + this._playgroundResultProvider = playgroundResultProvider; this._activeConnectionCodeLensProvider = activeConnectionCodeLensProvider; this._exportToLanguageCodeLensProvider = exportToLanguageCodeLensProvider; this._collectionDocumentsCodeLensProvider = new CollectionDocumentsCodeLensProvider( this._collectionDocumentsOperationsStore ); - this._playgroundSelectedCodeActionProvider = - playgroundSelectedCodeActionProvider; + this._playgroundSelectionCodeActionProvider = + playgroundSelectionCodeActionProvider; this._playgroundDiagnosticsCodeActionProvider = playgroundDiagnosticsCodeActionProvider; @@ -218,15 +218,14 @@ export default class EditorsController { } async saveMongoDBDocument(): Promise { - const activeEditor = vscode.window.activeTextEditor; + const editor = vscode.window.activeTextEditor; - if (!activeEditor) { + if (!editor) { await vscode.commands.executeCommand('workbench.action.files.save'); - return false; } - const uriParams = new URLSearchParams(activeEditor.document.uri.query); + const uriParams = new URLSearchParams(editor.document.uri.query); const namespace = uriParams.get(NAMESPACE_URI_IDENTIFIER); const connectionId = uriParams.get(CONNECTION_ID_URI_IDENTIFIER); const documentIdReference = uriParams.get(DOCUMENT_ID_URI_IDENTIFIER) || ''; @@ -236,7 +235,7 @@ export default class EditorsController { ) as DocumentSource; if ( - activeEditor.document.uri.scheme !== 'VIEW_DOCUMENT_SCHEME' || + editor.document.uri.scheme !== 'VIEW_DOCUMENT_SCHEME' || !namespace || !connectionId || // A valid documentId can be false. @@ -244,13 +243,13 @@ export default class EditorsController { documentId === undefined ) { void vscode.window.showErrorMessage( - `The current file can not be saved as a MongoDB document. Invalid URL: ${activeEditor.document.uri.toString()}` + `The current file can not be saved as a MongoDB document. Invalid URL: ${editor.document.uri.toString()}` ); return false; } try { - const newDocument = EJSON.parse(activeEditor.document.getText() || ''); + const newDocument = EJSON.parse(editor.document.getText() || ''); await this._mongoDBDocumentService.replaceDocument({ namespace, @@ -261,7 +260,7 @@ export default class EditorsController { }); // Save document changes to active editor. - await activeEditor?.document.save(); + await editor?.document.save(); void vscode.window.showInformationMessage( `The document was saved successfully to '${namespace}'` @@ -317,7 +316,6 @@ export default class EditorsController { .isCurrentlyFetchingMoreDocuments ) { void vscode.window.showErrorMessage('Already fetching more documents...'); - return Promise.resolve(false); } @@ -330,7 +328,6 @@ export default class EditorsController { void vscode.window.showErrorMessage( `Unable to view more documents: no longer connected to ${oldConnectionName}` ); - return Promise.resolve(false); } @@ -399,7 +396,7 @@ export default class EditorsController { this._context.subscriptions.push( vscode.workspace.registerTextDocumentContentProvider( PLAYGROUND_RESULT_SCHEME, - this._playgroundResultViewProvider + this._playgroundResultProvider ) ); // REGISTER CODE LENSES PROVIDERS. @@ -447,10 +444,10 @@ export default class EditorsController { this._context.subscriptions.push( vscode.languages.registerCodeActionsProvider( 'javascript', - this._playgroundSelectedCodeActionProvider, + this._playgroundSelectionCodeActionProvider, { providedCodeActionKinds: - PlaygroundSelectedCodeActionProvider.providedCodeActionKinds, + PlaygroundSelectionCodeActionProvider.providedCodeActionKinds, } ) ); diff --git a/src/editors/exportToLanguageCodeLensProvider.ts b/src/editors/exportToLanguageCodeLensProvider.ts index 97ff7468f..09f071cbf 100644 --- a/src/editors/exportToLanguageCodeLensProvider.ts +++ b/src/editors/exportToLanguageCodeLensProvider.ts @@ -1,103 +1,66 @@ import * as vscode from 'vscode'; import EXTENSION_COMMANDS from '../commands'; -import type { ExportToLanguageAddons } from '../types/playgroundType'; import { - ExportToLanguageMode, - ExportToLanguages, + ExportToLanguage, + isExportToLanguageResult, } from '../types/playgroundType'; +import type PlaygroundResultProvider from './playgroundResultProvider'; export default class ExportToLanguageCodeLensProvider implements vscode.CodeLensProvider { + _playgroundResultProvider: PlaygroundResultProvider; _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', - }; - + constructor(playgroundResultProvider: PlaygroundResultProvider) { + this._playgroundResultProvider = playgroundResultProvider; 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(); + provideCodeLenses(): vscode.CodeLens[] | undefined { const driverSyntaxCodeLens = this.createCodeLens(); - const buildersCodeLens = this.createCodeLens(); const exportToLanguageCodeLenses: vscode.CodeLens[] = []; - if (['json', 'plaintext'].includes(this._exportToLanguageAddons.language)) { - return []; + if ( + !this._playgroundResultProvider._playgroundResult?.language || + ['json', 'plaintext'].includes( + this._playgroundResultProvider._playgroundResult?.language + ) || + !isExportToLanguageResult( + this._playgroundResultProvider._playgroundResult + ) + ) { + return; } - 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); - - if (this._exportToLanguageAddons.language !== ExportToLanguages.CSHARP) { + if ( + this._playgroundResultProvider._playgroundResult?.language !== + ExportToLanguage.CSHARP + ) { driverSyntaxCodeLens.command = { - title: this._exportToLanguageAddons.driverSyntax + title: this._playgroundResultProvider._playgroundResult + .includeDriverSyntax ? 'Exclude Driver Syntax' : 'Include Driver Syntax', - command: EXTENSION_COMMANDS.MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS, + command: EXTENSION_COMMANDS.MDB_CHANGE_DRIVER_SYNTAX, arguments: [ - { - ...this._exportToLanguageAddons, - driverSyntax: !this._exportToLanguageAddons.driverSyntax, - }, + !this._playgroundResultProvider._playgroundResult.includeDriverSyntax, ], }; exportToLanguageCodeLenses.push(driverSyntaxCodeLens); } - if ( - this._exportToLanguageAddons.language === ExportToLanguages.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 cd69213c4..da4632294 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -1,18 +1,14 @@ import * as vscode from 'vscode'; import path from 'path'; -import type { TextEditor } from 'vscode'; import { ProgressLocation } from 'vscode'; -import vm from 'vm'; import os from 'os'; -import transpiler from 'bson-transpilers'; -import type PlaygroundSelectedCodeActionProvider from './playgroundSelectedCodeActionProvider'; +import type PlaygroundSelectionCodeActionProvider from './playgroundSelectionCodeActionProvider'; import type ConnectionController from '../connectionController'; import { DataServiceEventTypes } from '../connectionController'; import { createLogger } from '../logging'; import type { ConnectionTreeItem } from '../explorer'; import { DatabaseTreeItem } from '../explorer'; -import type ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider'; import formatError from '../utils/formatError'; import type { LanguageServerController } from '../language'; import playgroundBasicTextTemplate from '../templates/playgroundBasicTextTemplate'; @@ -22,15 +18,13 @@ import playgroundCloneDocumentTemplate from '../templates/playgroundCloneDocumen import playgroundInsertDocumentTemplate from '../templates/playgroundInsertDocumentTemplate'; import playgroundStreamsTemplate from '../templates/playgroundStreamsTemplate'; import playgroundCreateStreamProcessorTemplate from '../templates/playgroundCreateStreamProcessorTemplate'; -import type { - PlaygroundResult, - ShellEvaluateResult, - ExportToLanguageAddons, - ExportToLanguageNamespace, - ThisDiagnosticFix, - AllDiagnosticFixes, +import { + type ShellEvaluateResult, + type ThisDiagnosticFix, + type AllDiagnosticFixes, + type PlaygroundRunResult, + type ExportToLanguageResult, } from '../types/playgroundType'; -import { ExportToLanguageMode } from '../types/playgroundType'; import type PlaygroundResultProvider from './playgroundResultProvider'; import { PLAYGROUND_RESULT_SCHEME, @@ -42,80 +36,18 @@ import type { StatusView } from '../views'; import type TelemetryService from '../telemetry/telemetryService'; import { isPlayground, + getSelectedText, + getAllText, getPlaygroundExtensionForTelemetry, } from '../utils/playground'; +import type ExportToLanguageCodeLensProvider from './exportToLanguageCodeLensProvider'; const log = createLogger('playground controller'); -interface ToCompile { - filter?: string; - aggregation?: string; - options: { - collection: string | null; - database: string | null; - uri?: string; - }; -} - -let dummySandbox; - function getActiveEditorFilePath(): string | undefined { return vscode.window.activeTextEditor?.document.uri.fsPath; } -// TODO: this function was copied from the compass-export-to-language module -// https://github.com/mongodb-js/compass/blob/7c4bc0789a7b66c01bb7ba63955b3b11ed40c094/packages/compass-export-to-language/src/modules/count-aggregation-stages-in-string.js -// and should be updated as well when the better solution for the problem will be found. -const countAggregationStagesInString = (str: string): number => { - if (!dummySandbox) { - dummySandbox = vm.createContext(Object.create(null), { - codeGeneration: { strings: false, wasm: false }, - microtaskMode: 'afterEvaluate', - }); - vm.runInContext( - [ - 'BSONRegExp', - 'DBRef', - 'Decimal128', - 'Double', - 'Int32', - 'Long', - 'Int64', - 'MaxKey', - 'MinKey', - 'ObjectID', - 'ObjectId', - 'BSONSymbol', - 'Timestamp', - 'Code', - 'Buffer', - 'Binary', - ] - .map((name) => `function ${name}() {}`) - .join('\n'), - dummySandbox - ); - } - - return vm.runInContext('(' + str + ')', dummySandbox, { timeout: 100 }) - .length; -}; - -enum TranspilerExportMode { - PIPELINE = 'Pipeline', - QUERY = 'Query', - DELETE_QUERY = 'Delete Query', - UPDATE_QUERY = 'Update Query', -} -const exportModeMapping: Record< - ExportToLanguageMode, - TranspilerExportMode | undefined -> = { - [ExportToLanguageMode.AGGREGATION]: TranspilerExportMode.PIPELINE, - [ExportToLanguageMode.QUERY]: TranspilerExportMode.QUERY, - [ExportToLanguageMode.OTHER]: undefined, -}; - const connectBeforeRunningMessage = 'Please connect to a database before running a playground.'; @@ -124,20 +56,18 @@ const connectBeforeRunningMessage = */ export default class PlaygroundController { _connectionController: ConnectionController; - _activeTextEditor?: TextEditor; - _playgroundResult?: PlaygroundResult; + _playgroundResult?: PlaygroundRunResult | ExportToLanguageResult; _languageServerController: LanguageServerController; - _selectedText?: string; - _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; - _playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider; + _playgroundSelectionCodeActionProvider: PlaygroundSelectionCodeActionProvider; _telemetryService: TelemetryService; + _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; _isPartialRun = false; private _playgroundResultViewColumn?: vscode.ViewColumn; private _playgroundResultTextDocument?: vscode.TextDocument; private _statusView: StatusView; - private _playgroundResultViewProvider: PlaygroundResultProvider; + private _playgroundResultProvider: PlaygroundResultProvider; private _activeConnectionChangedHandler: () => void; constructor({ @@ -145,27 +75,26 @@ export default class PlaygroundController { languageServerController, telemetryService, statusView, - playgroundResultViewProvider, + playgroundResultProvider, + playgroundSelectionCodeActionProvider, exportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider, }: { connectionController: ConnectionController; languageServerController: LanguageServerController; telemetryService: TelemetryService; statusView: StatusView; - playgroundResultViewProvider: PlaygroundResultProvider; + playgroundResultProvider: PlaygroundResultProvider; + playgroundSelectionCodeActionProvider: PlaygroundSelectionCodeActionProvider; exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; - playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider; }) { this._connectionController = connectionController; - this._activeTextEditor = vscode.window.activeTextEditor; this._languageServerController = languageServerController; this._telemetryService = telemetryService; this._statusView = statusView; - this._playgroundResultViewProvider = playgroundResultViewProvider; + this._playgroundResultProvider = playgroundResultProvider; + this._playgroundSelectionCodeActionProvider = + playgroundSelectionCodeActionProvider; this._exportToLanguageCodeLensProvider = exportToLanguageCodeLensProvider; - this._playgroundSelectedCodeActionProvider = - playgroundSelectedCodeActionProvider; this._activeConnectionChangedHandler = (): void => { void this._activeConnectionChanged(); @@ -183,23 +112,11 @@ export default class PlaygroundController { this._playgroundResultTextDocument = editor?.document; } const isPlaygroundEditor = isPlayground(editor?.document.uri); - void vscode.commands.executeCommand( 'setContext', 'mdb.isPlayground', isPlaygroundEditor ); - - if (isPlaygroundEditor) { - this._activeTextEditor = editor; - this._playgroundSelectedCodeActionProvider.setActiveTextEditor( - this._activeTextEditor - ); - log.info('Active editor', { - documentPath: editor?.document.uri?.path, - documentLanguageId: editor?.document.languageId, - }); - } }; vscode.workspace.textDocuments.forEach((document) => { @@ -229,40 +146,6 @@ export default class PlaygroundController { ); } }); - - vscode.window.onDidChangeTextEditorSelection( - async (changeEvent: vscode.TextEditorSelectionChangeEvent) => { - if (!isPlayground(changeEvent?.textEditor?.document?.uri)) { - return; - } - - // Sort lines selected as the may be mis-ordered from alt+click. - const sortedSelections = ( - changeEvent.selections as Array - ).sort((a, b) => (a.start.line > b.start.line ? 1 : -1)); - - const selectedText = sortedSelections - .map((item) => this._getSelectedText(item)) - .join('\n'); - - if (selectedText === this._selectedText) { - return; - } - - this._selectedText = selectedText; - - const mode = - await this._languageServerController.getExportToLanguageMode({ - textFromEditor: this._getAllText(), - selection: sortedSelections[0], - }); - - this._playgroundSelectedCodeActionProvider.refresh({ - selection: sortedSelections[0], - mode, - }); - } - ); } async _activeConnectionChanged(): Promise { @@ -491,14 +374,6 @@ export default class PlaygroundController { return result; } - _getAllText(): string { - return this._activeTextEditor?.document.getText().trim() || ''; - } - - _getSelectedText(selection: vscode.Range): string { - return this._activeTextEditor?.document.getText(selection) || ''; - } - async _evaluateWithCancelModal({ codeToEvaluate, filePath, @@ -528,8 +403,10 @@ export default class PlaygroundController { ); } - async _openInResultPane(result: PlaygroundResult): Promise { - this._playgroundResultViewProvider.setPlaygroundResult(result); + async _openInResultPane( + result: PlaygroundRunResult | ExportToLanguageResult + ): Promise { + this._playgroundResultProvider.setPlaygroundResult(result); if (!this._playgroundResultTextDocument) { await this._openResultAsVirtualDocument(); @@ -546,16 +423,11 @@ export default class PlaygroundController { this._playgroundResultTextDocument, language ); - - this._exportToLanguageCodeLensProvider.refresh({ - ...this._exportToLanguageCodeLensProvider._exportToLanguageAddons, - language, - }); } } _refreshResultAsVirtualDocument(): void { - this._playgroundResultViewProvider.refresh(); + this._playgroundResultProvider.refresh(); } async _showResultAsVirtualDocument(): Promise { @@ -619,6 +491,13 @@ export default class PlaygroundController { return true; } + async showExportToLanguageResult( + result: ExportToLanguageResult + ): Promise { + await this._openInResultPane(result); + return true; + } + async _evaluatePlayground({ codeToEvaluate, filePath, @@ -666,63 +545,56 @@ export default class PlaygroundController { } runSelectedPlaygroundBlocks(): Promise { - if (!this._selectedText) { + const editor = vscode.window.activeTextEditor; + const selectedText = getSelectedText(); + + if (!isPlayground(editor?.document.uri) || !getSelectedText()) { void vscode.window.showInformationMessage( 'Please select one or more lines in the playground.' ); - - return Promise.resolve(true); + return Promise.resolve(false); } this._isPartialRun = true; return this._evaluatePlayground({ - codeToEvaluate: this._selectedText || '', + codeToEvaluate: selectedText || '', filePath: getActiveEditorFilePath(), }); } runAllPlaygroundBlocks(): Promise { - if ( - !this._activeTextEditor || - !isPlayground(this._activeTextEditor.document.uri) - ) { + const codeToEvaluate = getAllText(); + + if (!codeToEvaluate) { void vscode.window.showErrorMessage( 'Please open a MongoDB playground file before running it.' ); - return Promise.resolve(false); } this._isPartialRun = false; return this._evaluatePlayground({ - codeToEvaluate: this._getAllText(), + codeToEvaluate, filePath: getActiveEditorFilePath(), }); } runAllOrSelectedPlaygroundBlocks(): Promise { - if ( - !this._activeTextEditor || - !isPlayground(this._activeTextEditor.document.uri) - ) { + const editor = vscode.window.activeTextEditor; + const selectedText = getSelectedText(); + const codeToEvaluate = selectedText || getAllText(); + + this._isPartialRun = !!selectedText; + + if (!isPlayground(editor?.document.uri)) { void vscode.window.showErrorMessage( 'Please open a MongoDB playground file before running it.' ); - return Promise.resolve(false); } - let codeToEvaluate = ''; - if (!this._selectedText) { - this._isPartialRun = false; - codeToEvaluate = this._getAllText(); - } else { - this._isPartialRun = true; - codeToEvaluate = this._selectedText; - } - return this._evaluatePlayground({ codeToEvaluate, filePath: getActiveEditorFilePath(), @@ -770,187 +642,6 @@ 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._playgroundSelectedCodeActionProvider.selection, - language, - mode: this._playgroundSelectedCodeActionProvider.mode, - }); - - return this._transpile(); - } - - async getTranspiledContent(): Promise< - { namespace: ExportToLanguageNamespace; expression: string } | undefined - > { - const { - textFromEditor, - selectedText, - selection, - driverSyntax, - builders, - language, - } = this._exportToLanguageCodeLensProvider._exportToLanguageAddons; - let namespace: ExportToLanguageNamespace = { - databaseName: 'DATABASE_NAME', - collectionName: 'COLLECTION_NAME', - }; - let expression = ''; - - if (!textFromEditor || !selection) { - return; - } - - if (driverSyntax) { - const connectionId = this._connectionController.getActiveConnectionId(); - let driverUrl = 'mongodb://localhost:27017'; - - if (connectionId) { - namespace = - await this._languageServerController.getNamespaceForSelection({ - textFromEditor, - selection, - }); - - const mongoClientOptions = - this._connectionController.getMongoClientConnectionOptions(); - driverUrl = mongoClientOptions?.url || ''; - } - - const toCompile: ToCompile = { - options: { - collection: namespace.collectionName, - database: namespace.databaseName, - uri: driverUrl, - }, - }; - - if ( - this._playgroundSelectedCodeActionProvider.mode === - ExportToLanguageMode.AGGREGATION - ) { - toCompile.aggregation = selectedText; - } else if ( - this._playgroundSelectedCodeActionProvider.mode === - ExportToLanguageMode.QUERY - ) { - toCompile.filter = selectedText; - } - - expression = transpiler.shell[language].compileWithDriver( - toCompile, - builders - ); - } else { - expression = transpiler.shell[language].compile( - selectedText, - builders, - false - ); - } - - return { namespace, expression }; - } - - async _transpile(): Promise { - const { selectedText, importStatements, driverSyntax, builders, language } = - this._exportToLanguageCodeLensProvider._exportToLanguageAddons; - - log.info(`Exporting to the '${language}' language...`); - - try { - const transpiledContent = await this.getTranspiledContent(); - - if (!transpiledContent) { - void vscode.window.showInformationMessage( - 'Please select one or more lines in the playground.' - ); - return true; - } - - const { namespace, expression } = transpiledContent; - - let imports = ''; - - if (importStatements) { - const exportMode = this._playgroundSelectedCodeActionProvider.mode - ? exportModeMapping[this._playgroundSelectedCodeActionProvider.mode] - : undefined; - imports = transpiler.shell[language].getImports( - exportMode, - driverSyntax - ); - } - - this._playgroundResult = { - namespace: - namespace.databaseName && namespace.collectionName - ? `${namespace.databaseName}.${namespace.collectionName}` - : null, - type: null, - content: imports ? `${imports}\n\n${expression}` : expression, - language, - }; - - log.info( - `Exported to the '${language}' language`, - this._playgroundResult - ); - - /* eslint-disable camelcase */ - if ( - this._playgroundSelectedCodeActionProvider.mode === - ExportToLanguageMode.AGGREGATION - ) { - const aggExportedProps = { - language, - num_stages: selectedText - ? countAggregationStagesInString(selectedText) - : null, - with_import_statements: importStatements, - with_builders: builders, - with_driver_syntax: driverSyntax, - }; - - this._telemetryService.trackAggregationExported(aggExportedProps); - } else if ( - this._playgroundSelectedCodeActionProvider.mode === - ExportToLanguageMode.QUERY - ) { - const queryExportedProps = { - language, - with_import_statements: importStatements, - with_builders: builders, - with_driver_syntax: driverSyntax, - }; - - this._telemetryService.trackQueryExported(queryExportedProps); - } - /* eslint-enable camelcase */ - - await this._openInResultPane(this._playgroundResult); - } catch (error) { - log.error(`Export to the '${language}' language failed`, error); - const printableError = formatError(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 4fa7be2bc..fbc412809 100644 --- a/src/editors/playgroundResultProvider.ts +++ b/src/editors/playgroundResultProvider.ts @@ -2,8 +2,11 @@ import * as vscode from 'vscode'; import type ConnectionController from '../connectionController'; import type EditDocumentCodeLensProvider from './editDocumentCodeLensProvider'; -import type { PlaygroundResult } from '../types/playgroundType'; -import { ExportToLanguages } from '../types/playgroundType'; +import { + type PlaygroundRunResult, + type ExportToLanguageResult, +} from '../types/playgroundType'; +import { isExportToLanguageResult } from '../types/playgroundType'; export const PLAYGROUND_RESULT_SCHEME = 'PLAYGROUND_RESULT_SCHEME'; @@ -16,7 +19,7 @@ export default class PlaygroundResultProvider { _connectionController: ConnectionController; _editDocumentCodeLensProvider: EditDocumentCodeLensProvider; - _playgroundResult: PlaygroundResult; + _playgroundResult?: PlaygroundRunResult | ExportToLanguageResult; constructor( connectionController: ConnectionController, @@ -24,18 +27,14 @@ export default class PlaygroundResultProvider ) { this._connectionController = connectionController; this._editDocumentCodeLensProvider = editDocumentCodeLensProvider; - this._playgroundResult = { - namespace: null, - type: null, - content: undefined, - language: null, - }; } onDidChangeEmitter = new vscode.EventEmitter(); onDidChange = this.onDidChangeEmitter.event; - setPlaygroundResult(playgroundResult?: PlaygroundResult): void { + setPlaygroundResult( + playgroundResult?: PlaygroundRunResult | ExportToLanguageResult + ): void { if (playgroundResult) { this._playgroundResult = playgroundResult; } @@ -50,26 +49,21 @@ export default class PlaygroundResultProvider return 'undefined'; } - const { type, content, language } = this._playgroundResult; - - if (type === 'undefined') { - return 'undefined'; - } - if ( - type === 'string' || - (language && - Object.values(ExportToLanguages).includes( - language as ExportToLanguages - )) + isExportToLanguageResult(this._playgroundResult) || + this._playgroundResult.type === 'string' ) { return this._playgroundResult.content; } + if (this._playgroundResult.type === 'undefined') { + return 'undefined'; + } + this._editDocumentCodeLensProvider?.updateCodeLensesForPlayground( this._playgroundResult ); - return JSON.stringify(content, null, 2); + return JSON.stringify(this._playgroundResult.content, null, 2); } } diff --git a/src/editors/playgroundSelectedCodeActionProvider.ts b/src/editors/playgroundSelectedCodeActionProvider.ts deleted file mode 100644 index f12d4de45..000000000 --- a/src/editors/playgroundSelectedCodeActionProvider.ts +++ /dev/null @@ -1,162 +0,0 @@ -import * as vscode from 'vscode'; -import type { TextEditor } from 'vscode'; - -import EXTENSION_COMMANDS from '../commands'; -import { ExportToLanguageMode } from '../types/playgroundType'; -import { isPlayground } from '../utils/playground'; - -export default class PlaygroundSelectedCodeActionProvider - implements vscode.CodeActionProvider -{ - _onDidChangeCodeCodeAction: vscode.EventEmitter = - new vscode.EventEmitter(); - selection?: vscode.Selection; - mode?: ExportToLanguageMode; - activeTextEditor?: TextEditor; - - static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix]; - - constructor() { - this.activeTextEditor = vscode.window.activeTextEditor; - vscode.workspace.onDidChangeConfiguration(() => { - this._onDidChangeCodeCodeAction.fire(); - }); - } - - readonly onDidChangeCodeLenses: vscode.Event = - this._onDidChangeCodeCodeAction.event; - - setActiveTextEditor(activeTextEditor?: TextEditor) { - this.activeTextEditor = activeTextEditor; - this._onDidChangeCodeCodeAction.fire(); - } - - refresh({ - selection, - mode, - }: { - selection?: vscode.Selection; - mode?: ExportToLanguageMode; - }): void { - this.selection = selection; - this.mode = mode; - this._onDidChangeCodeCodeAction.fire(); - } - - isPlayground(): boolean { - return isPlayground(this.activeTextEditor?.document.uri); - } - - provideCodeActions(): vscode.CodeAction[] | undefined { - if (!this.selection || !this.isPlayground()) { - 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, - title: 'Run selected playground blocks', - tooltip: 'Run selected playground blocks', - }; - codeActions.push(runSelectedPlaygroundBlockCommand); - - 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_PYTHON, - title: 'Export To Python 3', - 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_JAVA, - title: 'Export To 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_CSHARP, - title: 'Export To C#', - tooltip: 'Export To C#', - }; - codeActions.push(exportToCsharpCommand); - - const exportToJSCommand = new vscode.CodeAction( - 'Export To Node.js', - vscode.CodeActionKind.Empty - ); - exportToJSCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_NODE, - title: 'Export To Node.js', - tooltip: 'Export To Node.js', - }; - codeActions.push(exportToJSCommand); - - const exportToRubyCommand = new vscode.CodeAction( - 'Export To Ruby', - vscode.CodeActionKind.Empty - ); - exportToRubyCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_RUBY, - title: 'Export To Ruby', - tooltip: 'Export To Ruby', - }; - codeActions.push(exportToRubyCommand); - - const exportToGoCommand = new vscode.CodeAction( - 'Export To Go', - vscode.CodeActionKind.Empty - ); - exportToGoCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_GO, - title: 'Export To Go', - tooltip: 'Export To Go', - }; - codeActions.push(exportToGoCommand); - - const exportToRustCommand = new vscode.CodeAction( - 'Export To Rust', - vscode.CodeActionKind.Empty - ); - exportToRustCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_RUST, - title: 'Export To Rust', - tooltip: 'Export To Rust', - }; - codeActions.push(exportToRustCommand); - - const exportToPHPCommand = new vscode.CodeAction( - 'Export To PHP', - vscode.CodeActionKind.Empty - ); - exportToPHPCommand.command = { - command: EXTENSION_COMMANDS.MDB_EXPORT_TO_PHP, - title: 'Export To PHP', - tooltip: 'Export To PHP', - }; - codeActions.push(exportToPHPCommand); - } - - return codeActions; - } -} diff --git a/src/editors/playgroundSelectionCodeActionProvider.ts b/src/editors/playgroundSelectionCodeActionProvider.ts new file mode 100644 index 000000000..592bac441 --- /dev/null +++ b/src/editors/playgroundSelectionCodeActionProvider.ts @@ -0,0 +1,111 @@ +import * as vscode from 'vscode'; + +import EXTENSION_COMMANDS from '../commands'; +import { isPlayground, getSelectedText } from '../utils/playground'; + +const selectionCommands = [ + { + name: 'Run selected playground blocks', + command: EXTENSION_COMMANDS.MDB_RUN_SELECTED_PLAYGROUND_BLOCKS, + }, + { + name: 'Export To Python 3', + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_PYTHON, + isCopilotRequired: true, + }, + { + name: 'Export To Java', + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_JAVA, + isCopilotRequired: true, + }, + { + name: 'Export To C#', + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_CSHARP, + isCopilotRequired: true, + }, + { + name: 'Export To Node.js', + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_NODE, + isCopilotRequired: true, + }, + { + name: 'Export To Ruby', + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_RUBY, + isCopilotRequired: true, + }, + { + name: 'Export To Go', + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_GO, + isCopilotRequired: true, + }, + { + name: 'Export To Rust', + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_RUST, + isCopilotRequired: true, + }, + { + name: 'Export To PHP', + command: EXTENSION_COMMANDS.MDB_EXPORT_TO_PHP, + isCopilotRequired: true, + }, +]; + +export default class PlaygroundSelectionCodeActionProvider + implements vscode.CodeActionProvider +{ + _onDidChangeCodeCodeAction: vscode.EventEmitter = + new vscode.EventEmitter(); + + static readonly providedCodeActionKinds = [vscode.CodeActionKind.QuickFix]; + + constructor() { + vscode.workspace.onDidChangeConfiguration(() => { + this._onDidChangeCodeCodeAction.fire(); + }); + } + + readonly onDidChangeCodeLenses: vscode.Event = + this._onDidChangeCodeCodeAction.event; + + createCodeAction({ + codeActionName, + codeActionCommand, + }: { + codeActionName: string; + codeActionCommand: string; + }): vscode.CodeAction { + const codeAction = new vscode.CodeAction( + codeActionName, + vscode.CodeActionKind.Empty + ); + codeAction.command = { + command: codeActionCommand, + title: codeActionName, + tooltip: codeActionName, + }; + return codeAction; + } + + provideCodeActions(): vscode.CodeAction[] | undefined { + const editor = vscode.window.activeTextEditor; + const codeActions: vscode.CodeAction[] = []; + const copilot = vscode.extensions.getExtension('github.copilot-chat'); + + if (!isPlayground(editor?.document.uri) || !getSelectedText()) { + return; + } + + for (const { name, command, isCopilotRequired } of selectionCommands) { + if (!isCopilotRequired || copilot?.isActive) { + codeActions.push( + this.createCodeAction({ + codeActionName: name, + codeActionCommand: command, + }) + ); + } + } + + return codeActions; + } +} diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index cd95afb7b..17109140c 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -14,9 +14,6 @@ import { createLogger } from '../logging'; import type { PlaygroundEvaluateParams, ShellEvaluateResult, - ExportToLanguageMode, - ExportToLanguageNamespace, - PlaygroundTextAndSelection, } from '../types/playgroundType'; import type { ClearCompletionsCache } from '../types/completionsCache'; import { ServerCommands } from './serverCommands'; @@ -134,19 +131,16 @@ export default class LanguageServerController { }); }); - this._client.onNotification( - ServerCommands.SHOW_INFO_MESSAGE, - (messsage) => { - log.info('The info message shown to a user', messsage); - void vscode.window.showInformationMessage(messsage); - } - ); + this._client.onNotification(ServerCommands.SHOW_INFO_MESSAGE, (message) => { + log.info('The info message shown to a user', message); + void vscode.window.showInformationMessage(message); + }); this._client.onNotification( ServerCommands.SHOW_ERROR_MESSAGE, - (messsage) => { - log.info('The error message shown to a user', messsage); - void vscode.window.showErrorMessage(messsage); + (message) => { + log.info('The error message shown to a user', message); + void vscode.window.showErrorMessage(message); } ); @@ -207,24 +201,6 @@ export default class LanguageServerController { return res; } - async getExportToLanguageMode( - params: PlaygroundTextAndSelection - ): Promise { - return this._client.sendRequest( - ServerCommands.GET_EXPORT_TO_LANGUAGE_MODE, - params - ); - } - - async getNamespaceForSelection( - params: PlaygroundTextAndSelection - ): Promise { - return this._client.sendRequest( - ServerCommands.GET_NAMESPACE_FOR_SELECTION, - params - ); - } - async activeConnectionChanged({ connectionId, connectionString, @@ -252,7 +228,7 @@ export default class LanguageServerController { } async resetCache(clear: ClearCompletionsCache): Promise { - log.info('Reseting MongoDBService cache...', clear); + log.info('Resetting MongoDBService cache...', clear); await this._client.sendRequest( ServerCommands.CLEAR_CACHED_COMPLETIONS, clear diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index 94315c2cb..f2563c56d 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -24,14 +24,11 @@ import translator from '@mongosh/i18n'; import { isAtlasStream } from 'mongodb-build-info'; import { Worker as WorkerThreads } from 'worker_threads'; -import { ExportToLanguageMode } from '../types/playgroundType'; import formatError from '../utils/formatError'; import { ServerCommands } from './serverCommands'; import type { ShellEvaluateResult, PlaygroundEvaluateParams, - ExportToLanguageNamespace, - PlaygroundTextAndSelection, MongoClientOptions, } from '../types/playgroundType'; import type { ClearCompletionsCache } from '../types/completionsCache'; @@ -306,9 +303,7 @@ export default class MongoDBService { // Listen for cancellation request from the language server client. token.onCancellationRequested(async () => { - this._connection.console.log('PLAYGROUND cancellation requested'); - void this._connection.sendNotification( - ServerCommands.SHOW_INFO_MESSAGE, + this._connection.console.log( 'The running playground operation was canceled.' ); @@ -520,49 +515,6 @@ export default class MongoDBService { return state; } - /** - * Parse code from a playground to identify - * a context in which export to language action is being called. - */ - getExportToLanguageMode( - params: PlaygroundTextAndSelection - ): ExportToLanguageMode { - const state = this._visitor.parseASTForExportToLanguage(params); - - if (state.isArraySelection) { - return ExportToLanguageMode.AGGREGATION; - } - - if (state.isObjectSelection) { - return ExportToLanguageMode.QUERY; - } - - return ExportToLanguageMode.OTHER; - } - - /** - * Parse code from a playground to identify - * a namespace for the export to language action. - */ - getNamespaceForSelection( - params: PlaygroundTextAndSelection - ): ExportToLanguageNamespace { - try { - const state = this.withDefaultDatabase( - this._visitor.parseASTForNamespace(params) - ); - return { - databaseName: state.databaseName, - collectionName: state.collectionName, - }; - } catch (error) { - this._connection.console.error( - `VISITOR namespace for selection error: ${util.inspect(error)}` - ); - return { databaseName: null, collectionName: null }; - } - } - _getAggregationDocumentation({ operator, description, diff --git a/src/language/server.ts b/src/language/server.ts index 587e6096c..0c5ec5878 100644 --- a/src/language/server.ts +++ b/src/language/server.ts @@ -17,10 +17,7 @@ import { TextDocument } from 'vscode-languageserver-textdocument'; import MongoDBService from './mongoDBService'; import { ServerCommands } from './serverCommands'; -import type { - PlaygroundEvaluateParams, - PlaygroundTextAndSelection, -} from '../types/playgroundType'; +import type { PlaygroundEvaluateParams } from '../types/playgroundType'; import type { ClearCompletionsCache } from '../types/completionsCache'; // Create a connection for the server. The connection uses Node's IPC as a transport. @@ -185,22 +182,6 @@ connection.onRequest( } ); -// Identify if the playground selection is an array or object. -connection.onRequest( - ServerCommands.GET_EXPORT_TO_LANGUAGE_MODE, - (params: PlaygroundTextAndSelection) => { - return mongoDBService.getExportToLanguageMode(params); - } -); - -// Find the current namespace for a playground selection. -connection.onRequest( - ServerCommands.GET_NAMESPACE_FOR_SELECTION, - (params: PlaygroundTextAndSelection) => { - return mongoDBService.getNamespaceForSelection(params); - } -); - // Clear cached completions by provided cache names. connection.onRequest( ServerCommands.CLEAR_CACHED_COMPLETIONS, diff --git a/src/language/serverCommands.ts b/src/language/serverCommands.ts index bc2aee9a9..3a7822422 100644 --- a/src/language/serverCommands.ts +++ b/src/language/serverCommands.ts @@ -4,7 +4,6 @@ export enum ServerCommands { EXECUTE_RANGE_FROM_PLAYGROUND = 'EXECUTE_RANGE_FROM_PLAYGROUND', SHOW_ERROR_MESSAGE = 'SHOW_ERROR_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', UPDATE_CURRENT_SESSION_FIELDS = 'UPDATE_CURRENT_SESSION_FIELDS', CLEAR_CACHED_COMPLETIONS = 'CLEAR_CACHED_COMPLETIONS', diff --git a/src/language/visitor.ts b/src/language/visitor.ts index b6adf7c7d..929af5811 100644 --- a/src/language/visitor.ts +++ b/src/language/visitor.ts @@ -38,18 +38,13 @@ export interface CompletionState { isFindCursor: boolean; } -export interface ExportToLanguageState { - isObjectSelection: boolean; - isArraySelection: boolean; -} - export interface NamespaceState { databaseName: string | null; collectionName: string | null; } export class Visitor { - _state: CompletionState | ExportToLanguageState | NamespaceState | {}; + _state: CompletionState | NamespaceState | {}; _selection: VisitorSelection; constructor() { @@ -65,7 +60,6 @@ export class Visitor { return; } - this._checkIsBSONSelection(path.node); this._checkIsUseCall(path.node); this._checkIsCollectionNameAsCallExpression(path.node); this._checkIsStreamProcessorNameAsCallExpression(path.node); @@ -105,24 +99,11 @@ export class Visitor { _visitArrayExpression(path: NodePath): void { if (path.node.type === 'ArrayExpression') { - this._checkIsBSONSelection(path.node); this._checkIsStage(path.node); this._checkIsStageOperator(path); } } - _visitVariableDeclarator(path: NodePath): void { - if (path.node.type === 'VariableDeclarator') { - this._checkIsBSONSelection(path.node); - } - } - - _visitObjectProperty(path: NodePath): void { - if (path.node.type === 'ObjectProperty') { - this._checkIsBSONSelection(path.node); - } - } - _handleTriggerCharacter( textFromEditor: string, position: { line: number; character: number } @@ -164,19 +145,7 @@ export class Visitor { return this._state as CompletionState; } - parseASTForExportToLanguage(params): ExportToLanguageState { - this._state = this._getDefaultsForExportToLanguage(); - this.parseAST(params); - return this._state as ExportToLanguageState; - } - - parseASTForNamespace(params): NamespaceState { - this._state = this._getDefaultsForNamespace(); - this.parseAST(params); - return this._state as NamespaceState; - } - - parseAST({ textFromEditor, selection }: VisitorTextAndSelection) { + parseAST({ textFromEditor, selection }: VisitorTextAndSelection): void { this._selection = selection; let ast; @@ -196,19 +165,15 @@ export class Visitor { this._visitExpressionStatement(path); this._visitObjectExpression(path); this._visitArrayExpression(path); - this._visitVariableDeclarator(path); - this._visitObjectProperty(path); }, }); } - _getDefaultsForCompletion() { + _getDefaultsForCompletion(): CompletionState { return { databaseName: null, collectionName: null, streamProcessorName: null, - isObjectSelection: false, - isArraySelection: false, isObjectKey: false, isIdentifierObjectValue: false, isTextObjectValue: false, @@ -227,20 +192,6 @@ export class Visitor { }; } - _getDefaultsForExportToLanguage() { - return { - isArraySelection: false, - isObjectSelection: false, - }; - } - - _getDefaultsForNamespace() { - return { - databaseName: null, - collectionName: null, - }; - } - _checkIsUseCallAsSimpleString(node: t.CallExpression): void { if ( node.callee.type === 'Identifier' && @@ -461,79 +412,6 @@ export class Visitor { return false; } - _checkIsArrayWithinSelection(node: t.Node): void { - if ( - node.type === 'ArrayExpression' && - this._isWithinSelection(node) && - 'isArraySelection' in this._state - ) { - this._state.isArraySelection = true; - } - } - - _checkIsObjectWithinSelection(node: t.Node): void { - if ( - node.type === 'ObjectExpression' && - this._isWithinSelection(node) && - 'isObjectSelection' in this._state - ) { - this._state.isObjectSelection = true; - } - } - - _checkIsBSONSelectionInArray(node: t.Node): void { - if ( - node.type === 'ArrayExpression' && - this._isParentAroundSelection(node) - ) { - node.elements.forEach((item) => { - if (item) { - this._checkIsObjectWithinSelection(item); - this._checkIsArrayWithinSelection(item); - } - }); - } - } - - _checkIsBSONSelectionInFunction(node: t.Node): void { - if (node.type === 'CallExpression' && this._isParentAroundSelection(node)) { - node.arguments.forEach((item) => { - if (item) { - this._checkIsObjectWithinSelection(item); - this._checkIsArrayWithinSelection(item); - } - }); - } - } - - _checkIsBSONSelectionInVariable(node: t.Node) { - if ( - node.type === 'VariableDeclarator' && - node.init && - this._isVariableIdentifierBeforeSelection(node) - ) { - this._checkIsObjectWithinSelection(node.init); - this._checkIsArrayWithinSelection(node.init); - } - } - - _checkIsBSONSelectionInObject(node: t.Node) { - if ( - node.type === 'ObjectProperty' && - this._isObjectPropBeforeSelection(node) - ) { - this._checkIsObjectWithinSelection(node.value); - this._checkIsArrayWithinSelection(node.value); - } - } - - _checkIsBSONSelection(node: t.Node): void { - this._checkIsBSONSelectionInFunction(node); - this._checkIsBSONSelectionInArray(node); - this._checkIsBSONSelectionInVariable(node); - this._checkIsBSONSelectionInObject(node); - } - _checkIsCollectionNameAsMemberExpression(node: t.MemberExpression): void { if ( node.object.type === 'Identifier' && @@ -649,7 +527,7 @@ export class Visitor { } } - _checkHasCollectionNameCallExpression(node: t.MemberExpression) { + _checkHasCollectionNameCallExpression(node: t.MemberExpression): void { if ( node.object.type === 'CallExpression' && node.object.callee.type === 'MemberExpression' && @@ -781,7 +659,7 @@ export class Visitor { } } - _checkHasStreamProcessorNameCallExpression(node: t.MemberExpression) { + _checkHasStreamProcessorNameCallExpression(node: t.MemberExpression): void { if ( node.object.type === 'CallExpression' && node.object.callee.type === 'MemberExpression' && diff --git a/src/language/worker.ts b/src/language/worker.ts index 190d9755f..54b3175f2 100644 --- a/src/language/worker.ts +++ b/src/language/worker.ts @@ -103,7 +103,7 @@ export const execute = async ({ const namespace = source && source.namespace ? `${source.namespace.db}.${source.namespace.collection}` - : null; + : undefined; // Prepare a playground result. const result = { diff --git a/src/mdbExtensionController.ts b/src/mdbExtensionController.ts index 0f8e6bdf6..8077e60d2 100644 --- a/src/mdbExtensionController.ts +++ b/src/mdbExtensionController.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import ActiveConnectionCodeLensProvider from './editors/activeConnectionCodeLensProvider'; -import PlaygroundSelectedCodeActionProvider from './editors/playgroundSelectedCodeActionProvider'; +import PlaygroundSelectionCodeActionProvider from './editors/playgroundSelectionCodeActionProvider'; import PlaygroundDiagnosticsCodeActionProvider from './editors/playgroundDiagnosticsCodeActionProvider'; import ConnectionController from './connectionController'; import type ConnectionTreeItem from './explorer/connectionTreeItem'; @@ -24,7 +24,10 @@ import { HelpExplorer, } from './explorer'; import ExportToLanguageCodeLensProvider from './editors/exportToLanguageCodeLensProvider'; -import { ExportToLanguages } from './types/playgroundType'; +import { + ExportToLanguage, + type ExportToLanguageResult, +} from './types/playgroundType'; import EXTENSION_COMMANDS from './commands'; import type FieldTreeItem from './explorer/fieldTreeItem'; import type IndexListTreeItem from './explorer/indexListTreeItem'; @@ -52,7 +55,7 @@ import type { OpenSchemaCommandArgs } from './participant/prompts/schema'; // This class is the top-level controller for our extension. // Commands which the extensions handles are defined in the function `activate`. export default class MDBExtensionController implements vscode.Disposable { - _playgroundSelectedCodeActionProvider: PlaygroundSelectedCodeActionProvider; + _playgroundSelectionCodeActionProvider: PlaygroundSelectionCodeActionProvider; _playgroundDiagnosticsCodeActionProvider: PlaygroundDiagnosticsCodeActionProvider; _connectionController: ConnectionController; _connectionStorage: ConnectionStorage; @@ -67,7 +70,7 @@ export default class MDBExtensionController implements vscode.Disposable { _telemetryService: TelemetryService; _languageServerController: LanguageServerController; _webviewController: WebviewController; - _playgroundResultViewProvider: PlaygroundResultProvider; + _playgroundResultProvider: PlaygroundResultProvider; _activeConnectionCodeLensProvider: ActiveConnectionCodeLensProvider; _editDocumentCodeLensProvider: EditDocumentCodeLensProvider; _exportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; @@ -102,16 +105,16 @@ export default class MDBExtensionController implements vscode.Disposable { this._editDocumentCodeLensProvider = new EditDocumentCodeLensProvider( this._connectionController ); - this._playgroundResultViewProvider = new PlaygroundResultProvider( + this._playgroundResultProvider = new PlaygroundResultProvider( this._connectionController, this._editDocumentCodeLensProvider ); this._activeConnectionCodeLensProvider = new ActiveConnectionCodeLensProvider(this._connectionController); this._exportToLanguageCodeLensProvider = - new ExportToLanguageCodeLensProvider(); - this._playgroundSelectedCodeActionProvider = - new PlaygroundSelectedCodeActionProvider(); + new ExportToLanguageCodeLensProvider(this._playgroundResultProvider); + this._playgroundSelectionCodeActionProvider = + new PlaygroundSelectionCodeActionProvider(); this._playgroundDiagnosticsCodeActionProvider = new PlaygroundDiagnosticsCodeActionProvider(); this._playgroundController = new PlaygroundController({ @@ -119,15 +122,16 @@ export default class MDBExtensionController implements vscode.Disposable { languageServerController: this._languageServerController, telemetryService: this._telemetryService, statusView: this._statusView, - playgroundResultViewProvider: this._playgroundResultViewProvider, + playgroundResultProvider: this._playgroundResultProvider, + playgroundSelectionCodeActionProvider: + this._playgroundSelectionCodeActionProvider, exportToLanguageCodeLensProvider: this._exportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider: - this._playgroundSelectedCodeActionProvider, }); this._participantController = new ParticipantController({ connectionController: this._connectionController, storageController: this._storageController, telemetryService: this._telemetryService, + playgroundResultProvider: this._playgroundResultProvider, }); this._editorsController = new EditorsController({ context, @@ -135,11 +139,11 @@ export default class MDBExtensionController implements vscode.Disposable { playgroundController: this._playgroundController, statusView: this._statusView, telemetryService: this._telemetryService, - playgroundResultViewProvider: this._playgroundResultViewProvider, + playgroundResultProvider: this._playgroundResultProvider, activeConnectionCodeLensProvider: this._activeConnectionCodeLensProvider, exportToLanguageCodeLensProvider: this._exportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider: - this._playgroundSelectedCodeActionProvider, + playgroundSelectionCodeActionProvider: + this._playgroundSelectionCodeActionProvider, playgroundDiagnosticsCodeActionProvider: this._playgroundDiagnosticsCodeActionProvider, editDocumentCodeLensProvider: this._editDocumentCodeLensProvider, @@ -249,36 +253,50 @@ export default class MDBExtensionController implements vscode.Disposable { // ------ EXPORT TO LANGUAGE ------ // this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_PYTHON, () => - this._playgroundController.exportToLanguage(ExportToLanguages.PYTHON) + this._participantController.exportPlaygroundToLanguage( + ExportToLanguage.PYTHON + ) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_JAVA, () => - this._playgroundController.exportToLanguage(ExportToLanguages.JAVA) + this._participantController.exportPlaygroundToLanguage( + ExportToLanguage.JAVA + ) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_CSHARP, () => - this._playgroundController.exportToLanguage(ExportToLanguages.CSHARP) + this._participantController.exportPlaygroundToLanguage( + ExportToLanguage.CSHARP + ) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_NODE, () => - this._playgroundController.exportToLanguage(ExportToLanguages.JAVASCRIPT) + this._participantController.exportPlaygroundToLanguage( + ExportToLanguage.JAVASCRIPT + ) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_RUBY, () => - this._playgroundController.exportToLanguage(ExportToLanguages.RUBY) + this._participantController.exportPlaygroundToLanguage( + ExportToLanguage.RUBY + ) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_GO, () => - this._playgroundController.exportToLanguage(ExportToLanguages.GO) + this._participantController.exportPlaygroundToLanguage( + ExportToLanguage.GO + ) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_RUST, () => - this._playgroundController.exportToLanguage(ExportToLanguages.RUST) + this._participantController.exportPlaygroundToLanguage( + ExportToLanguage.RUST + ) ); this.registerCommand(EXTENSION_COMMANDS.MDB_EXPORT_TO_PHP, () => - this._playgroundController.exportToLanguage(ExportToLanguages.PHP) + this._participantController.exportPlaygroundToLanguage( + ExportToLanguage.PHP + ) ); this.registerCommand( - EXTENSION_COMMANDS.MDB_CHANGE_EXPORT_TO_LANGUAGE_ADDONS, - (exportToLanguageAddons) => - this._playgroundController.changeExportToLanguageAddons( - exportToLanguageAddons - ) + EXTENSION_COMMANDS.MDB_CHANGE_DRIVER_SYNTAX, + (includeDriverSyntax: boolean) => + this._participantController.changeDriverSyntax(includeDriverSyntax) ); // ------ DOCUMENTS ------ // @@ -314,6 +332,12 @@ export default class MDBExtensionController implements vscode.Disposable { ); } ); + this.registerParticipantCommand( + EXTENSION_COMMANDS.SHOW_EXPORT_TO_LANGUAGE_RESULT, + (data: ExportToLanguageResult) => { + return this._playgroundController.showExportToLanguageResult(data); + } + ); this.registerCommand( EXTENSION_COMMANDS.CONNECT_WITH_PARTICIPANT, (data: { id?: string; command?: string }) => { diff --git a/src/participant/model.ts b/src/participant/model.ts index f5c2568d0..adb6f3cea 100644 --- a/src/participant/model.ts +++ b/src/participant/model.ts @@ -2,22 +2,21 @@ import * as vscode from 'vscode'; import { CHAT_PARTICIPANT_MODEL } from './constants'; -let model: vscode.LanguageModelChat; +let selectedModel: vscode.LanguageModelChat | undefined; export async function getCopilotModel(): Promise< vscode.LanguageModelChat | undefined > { - if (!model) { + if (!selectedModel) { try { const [model] = await vscode.lm.selectChatModels({ vendor: 'copilot', family: CHAT_PARTICIPANT_MODEL, }); - return model; + selectedModel = model; } catch (err) { // Model is not ready yet. It is being initialised with the first user prompt. } } - - return; + return selectedModel; } diff --git a/src/participant/participant.ts b/src/participant/participant.ts index c8ee509c3..303b19554 100644 --- a/src/participant/participant.ts +++ b/src/participant/participant.ts @@ -43,8 +43,14 @@ import formatError from '../utils/formatError'; import { getContent, type ModelInput } from './prompts/promptBase'; import { processStreamWithIdentifiers } from './streamParsing'; import type { PromptIntent } from './prompts/intent'; +import { isPlayground, getSelectedText, getAllText } from '../utils/playground'; import type { DataService } from 'mongodb-data-service'; import { ParticipantErrorTypes } from './participantErrorTypes'; +import type PlaygroundResultProvider from '../editors/playgroundResultProvider'; +import { + type ExportToLanguageResult, + isExportToLanguageResult, +} from '../types/playgroundType'; import { PromptHistory } from './prompts/promptHistory'; const log = createLogger('participant'); @@ -73,21 +79,25 @@ export default class ParticipantController { _chatMetadataStore: ChatMetadataStore; _docsChatbotAIService: DocsChatbotAIService; _telemetryService: TelemetryService; + _playgroundResultProvider: PlaygroundResultProvider; constructor({ connectionController, storageController, telemetryService, + playgroundResultProvider, }: { connectionController: ConnectionController; storageController: StorageController; telemetryService: TelemetryService; + playgroundResultProvider: PlaygroundResultProvider; }) { this._connectionController = connectionController; this._storageController = storageController; this._chatMetadataStore = new ChatMetadataStore(); this._telemetryService = telemetryService; this._docsChatbotAIService = new DocsChatbotAIService(); + this._playgroundResultProvider = playgroundResultProvider; } createParticipant(context: vscode.ExtensionContext): vscode.ChatParticipant { @@ -215,18 +225,25 @@ export default class ParticipantController { }); } - async streamChatResponseContentToPlayground({ + async streamChatResponseWithExportToLanguage({ modelInput, token, + language, }: { modelInput: ModelInput; token: vscode.CancellationToken; + language?: string; }): Promise { const chatResponse = await this._getChatResponse({ modelInput, token, }); + const languageCodeBlockIdentifier = { + start: `\`\`\`${language ? language : 'javascript'}`, + end: '```', + }; + const runnableContent: string[] = []; await processStreamWithIdentifiers({ processStreamFragment: () => {}, @@ -234,7 +251,7 @@ export default class ParticipantController { runnableContent.push(content.trim()); }, inputIterable: chatResponse.text, - identifier: codeBlockIdentifier, + identifier: languageCodeBlockIdentifier, }); return runnableContent.length ? runnableContent.join('') : null; } @@ -1623,22 +1640,11 @@ export default class ParticipantController { } async exportCodeToPlayground(): Promise { - const activeTextEditor = vscode.window.activeTextEditor; - if (!activeTextEditor) { - await vscode.window.showErrorMessage('Active editor not found.'); - return false; - } + const selectedText = getSelectedText(); + const codeToExport = selectedText || getAllText(); - const sortedSelections = Array.from(activeTextEditor.selections).sort( - (a, b) => a.start.compareTo(b.start) - ); - const selectedText = sortedSelections - .map((selection) => activeTextEditor.document.getText(selection)) - .join('\n'); - const code = - selectedText || activeTextEditor.document.getText().trim() || ''; try { - const progressResult = await vscode.window.withProgress( + const content = await vscode.window.withProgress( { location: vscode.ProgressLocation.Notification, title: 'Exporting code to a playground...', @@ -1646,70 +1652,103 @@ export default class ParticipantController { }, async (progress, token): Promise => { const modelInput = await Prompts.exportToPlayground.buildMessages({ - request: { prompt: code }, + request: { prompt: codeToExport }, }); const result = await Promise.race([ - this.getChatResponseContent({ + this.streamChatResponseWithExportToLanguage({ modelInput, token, }), - new Promise((resolve) => + new Promise((resolve) => token.onCancellationRequested(() => { - resolve(undefined); + log.info('The export to a playground operation was canceled.'); + resolve(null); }) ), ]); if (result?.includes("Sorry, I can't assist with that.")) { void vscode.window.showErrorMessage( - "Sorry, I can't assist with that." + 'Sorry, we were unable to generate the playground, please try again. If the error persists, try changing your selected code.' ); return null; } - return this.streamChatResponseContentToPlayground({ - modelInput, - token, - }); + return result; } ); - if (progressResult) { - await vscode.commands.executeCommand( - EXTENSION_COMMANDS.OPEN_PARTICIPANT_CODE_IN_PLAYGROUND, - { - runnableContent: progressResult, - } - ); - } else { - await vscode.window.showErrorMessage('Exporting to playground failed.'); + if (!content) { + return true; } + await vscode.commands.executeCommand( + EXTENSION_COMMANDS.OPEN_PARTICIPANT_CODE_IN_PLAYGROUND, + { + runnableContent: content, + } + ); + return true; } catch (error) { const message = formatError(error).message; - if ( - error instanceof vscode.LanguageModelError && - message.includes('Canceled') - ) { - await vscode.window.showInformationMessage( - 'The running export to a playground operation was canceled.' - ); - return false; - } - this._telemetryService.trackCopilotParticipantError( error, 'exportToPlayground' ); - await vscode.window.showErrorMessage( + void vscode.window.showErrorMessage( `An error occurred exporting to a playground: ${message}` ); return false; } } + async _exportPlaygroundToLanguageWithCancelModal({ + codeToTranspile, + language, + includeDriverSyntax, + }: Omit): Promise { + const progressResult = await vscode.window.withProgress( + { + location: vscode.ProgressLocation.Notification, + title: `Exporting playground to ${language}...`, + cancellable: true, + }, + async (progress, token): Promise => { + const modelInput = await Prompts.exportToLanguage.buildMessages({ + request: { prompt: codeToTranspile }, + language, + includeDriverSyntax, + }); + const result = await Promise.race([ + this.streamChatResponseWithExportToLanguage({ + modelInput, + token, + language, + }), + new Promise((resolve) => + token.onCancellationRequested(() => { + log.info(`The export to ${language} operation was canceled.`); + resolve(null); + }) + ), + ]); + + if (result?.includes("Sorry, I can't assist with that.")) { + void vscode.window.showErrorMessage( + `Sorry, we were unable to export code to the "${language}" language, please try again. If the error persists, try changing your selected code.` + ); + return null; + } + + return result || null; + } + ); + + return progressResult; + } + async chatHandler( ...args: [ vscode.ChatRequest, @@ -1820,4 +1859,103 @@ Please see our [FAQ](https://www.mongodb.com/docs/generative-ai-faq/) for more i .getSavedConnections() .map((connection) => connection.name); } + + async changeDriverSyntax(includeDriverSyntax: boolean): Promise { + if ( + !this._playgroundResultProvider._playgroundResult || + !isExportToLanguageResult( + this._playgroundResultProvider._playgroundResult + ) + ) { + void vscode.window.showErrorMessage( + 'Unable to change the driver syntax, no playground content found.' + ); + return false; + } + + return this._transpile({ + ...this._playgroundResultProvider._playgroundResult, + includeDriverSyntax, + }); + } + + async exportPlaygroundToLanguage(language: string): Promise { + const editor = vscode.window.activeTextEditor; + + if (!isPlayground(editor?.document.uri)) { + void vscode.window.showErrorMessage( + 'Please select one or more lines in the playground.' + ); + return false; + } + + const selectedText = getSelectedText(); + const codeToTranspile = selectedText || getAllText(); + let includeDriverSyntax = false; + + if ( + this._playgroundResultProvider._playgroundResult && + isExportToLanguageResult(this._playgroundResultProvider._playgroundResult) + ) { + includeDriverSyntax = + this._playgroundResultProvider._playgroundResult.includeDriverSyntax; + } + + return this._transpile({ + codeToTranspile, + language, + includeDriverSyntax, + }); + } + + async _transpile({ + codeToTranspile, + language, + includeDriverSyntax, + }: Omit): Promise { + log.info(`Exporting to the '${language}' language...`); + + try { + const transpiledContent = + await this._exportPlaygroundToLanguageWithCancelModal({ + codeToTranspile, + language, + includeDriverSyntax, + }); + + if (!transpiledContent) { + return true; + } + + log.info(`The playground was exported to ${language}`, { + codeToTranspile, + language, + includeDriverSyntax, + }); + + await vscode.commands.executeCommand( + EXTENSION_COMMANDS.SHOW_EXPORT_TO_LANGUAGE_RESULT, + { + content: transpiledContent, + codeToTranspile, + language, + includeDriverSyntax, + } + ); + + this._telemetryService.trackPlaygroundExportedToLanguageExported({ + language, + exported_code_length: transpiledContent?.length || 0, + with_driver_syntax: includeDriverSyntax, + }); + } catch (error) { + log.error(`Export to ${language} failed`, error); + const printableError = formatError(error); + void vscode.window.showErrorMessage( + `Unable to export to ${language}: ${printableError.message}` + ); + } + + return true; + } } diff --git a/src/participant/prompts/exportToLanguage.ts b/src/participant/prompts/exportToLanguage.ts new file mode 100644 index 000000000..04bf930bf --- /dev/null +++ b/src/participant/prompts/exportToLanguage.ts @@ -0,0 +1,34 @@ +import { PromptBase, type PromptArgsBase } from './promptBase'; +import type { UserPromptResponse } from './promptBase'; + +export interface ExportToLanguagePromptArgs extends PromptArgsBase { + language: string; + includeDriverSyntax: boolean; +} + +export class ExportToLanguagePrompt extends PromptBase { + protected getAssistantPrompt({ + language, + }: ExportToLanguagePromptArgs): string { + return `You are a MongoDB expert. +Your task is to convert a MongoDB playground script to the ${language} language. +Take a user prompt as an input string and translate it to the target language. +If the user specified to include driver syntax, add required MongoDB helpers and import statements to the transpiled code. +If the user specified to not include driver syntax, transpile only provided by the user prompt without adding any MongoDB helpers or import statements. +Keep your response concise. +Respond with markdown, suggest code in a Markdown code block that begins with \`\`\`${language} and ends with \`\`\`.`; + } + + getUserPrompt({ + request, + includeDriverSyntax, + }: ExportToLanguagePromptArgs): Promise { + const prompt = request.prompt; + return Promise.resolve({ + prompt: `${ + prompt ? `The user provided additional information: "${prompt}"\n` : '' + }Include driver syntax: ${includeDriverSyntax}`, + hasSampleDocs: false, + }); + } +} diff --git a/src/participant/prompts/exportToPlayground.ts b/src/participant/prompts/exportToPlayground.ts index 5b0410c6e..cd652d89f 100644 --- a/src/participant/prompts/exportToPlayground.ts +++ b/src/participant/prompts/exportToPlayground.ts @@ -1,13 +1,5 @@ import { PromptBase, type PromptArgsBase } from './promptBase'; -export interface ExportToPlaygroundPromptArgs extends PromptArgsBase { - databaseName: string; - collectionName: string; - schema: string; - amountOfDocumentsSampled: number; - connectionNames: string[]; -} - export class ExportToPlaygroundPrompt extends PromptBase { protected getAssistantPrompt(): string { return `You are a MongoDB expert. diff --git a/src/participant/prompts/index.ts b/src/participant/prompts/index.ts index 4ed793dc7..896606fb1 100644 --- a/src/participant/prompts/index.ts +++ b/src/participant/prompts/index.ts @@ -6,6 +6,7 @@ import { NamespacePrompt } from './namespace'; import { QueryPrompt } from './query'; import { SchemaPrompt } from './schema'; import { ExportToPlaygroundPrompt } from './exportToPlayground'; +import { ExportToLanguagePrompt } from './exportToLanguage'; import { isContentEmpty } from './promptBase'; export { getContentLength } from './promptBase'; @@ -17,6 +18,7 @@ export class Prompts { public static query = new QueryPrompt(); public static schema = new SchemaPrompt(); public static exportToPlayground = new ExportToPlaygroundPrompt(); + public static exportToLanguage = new ExportToLanguagePrompt(); public static isPromptEmpty(request: vscode.ChatRequest): boolean { return !request.prompt || request.prompt.trim().length === 0; diff --git a/src/telemetry/telemetryService.ts b/src/telemetry/telemetryService.ts index 8a58258dc..d4514581b 100644 --- a/src/telemetry/telemetryService.ts +++ b/src/telemetry/telemetryService.ts @@ -49,19 +49,10 @@ type DocumentEditedTelemetryEventProperties = { source: DocumentSource; }; -type AggregationExportedTelemetryEventProperties = { - language: string; - num_stages: number | null; - with_import_statements: boolean; - with_builders: boolean; - with_driver_syntax: boolean; -}; - -type QueryExportedTelemetryEventProperties = { - language: string; - with_import_statements: boolean; - with_builders: boolean; - with_driver_syntax: boolean; +type PlaygroundExportedToLanguageTelemetryEventProperties = { + language?: string; + exported_code_length: number; + with_driver_syntax?: boolean; }; type PlaygroundCreatedTelemetryEventProperties = { @@ -156,8 +147,7 @@ type TelemetryEventProperties = | DocumentUpdatedTelemetryEventProperties | ConnectionEditedTelemetryEventProperties | DocumentEditedTelemetryEventProperties - | QueryExportedTelemetryEventProperties - | AggregationExportedTelemetryEventProperties + | PlaygroundExportedToLanguageTelemetryEventProperties | PlaygroundCreatedTelemetryEventProperties | PlaygroundSavedTelemetryEventProperties | PlaygroundLoadedTelemetryEventProperties @@ -180,8 +170,7 @@ export enum TelemetryEventTypes { PLAYGROUND_LOADED = 'Playground Loaded', DOCUMENT_UPDATED = 'Document Updated', DOCUMENT_EDITED = 'Document Edited', - QUERY_EXPORTED = 'Query Exported', - AGGREGATION_EXPORTED = 'Aggregation Exported', + PLAYGROUND_EXPORTED_TO_LANGUAGE = 'Playground Exported To Language', PLAYGROUND_CREATED = 'Playground Created', KEYTAR_SECRETS_MIGRATION_FAILED = 'Keytar Secrets Migration Failed', SAVED_CONNECTIONS_LOADED = 'Saved Connections Loaded', @@ -407,16 +396,13 @@ export default class TelemetryService { this.track(TelemetryEventTypes.DOCUMENT_EDITED, { source }); } - trackQueryExported( - queryExportedProps: QueryExportedTelemetryEventProperties + trackPlaygroundExportedToLanguageExported( + playgroundExportedProps: PlaygroundExportedToLanguageTelemetryEventProperties ): void { - this.track(TelemetryEventTypes.QUERY_EXPORTED, queryExportedProps); - } - - trackAggregationExported( - aggExportedProps: AggregationExportedTelemetryEventProperties - ): void { - this.track(TelemetryEventTypes.AGGREGATION_EXPORTED, aggExportedProps); + this.track( + TelemetryEventTypes.PLAYGROUND_EXPORTED_TO_LANGUAGE, + playgroundExportedProps + ); } trackPlaygroundCreated(playgroundType: string): void { diff --git a/src/test/fixture/testSaving.mongodb b/src/test/fixture/testPlayground.mongodb similarity index 100% rename from src/test/fixture/testSaving.mongodb rename to src/test/fixture/testPlayground.mongodb diff --git a/src/test/fixture/testSaving.mongodb.js b/src/test/fixture/testPlayground.mongodb.js similarity index 100% rename from src/test/fixture/testSaving.mongodb.js rename to src/test/fixture/testPlayground.mongodb.js diff --git a/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts b/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts index a4d1024fa..89192c591 100644 --- a/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts +++ b/src/test/suite/editors/exportToLanguageCodeLensProvider.test.ts @@ -2,138 +2,129 @@ import { beforeEach } from 'mocha'; import chai from 'chai'; import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; -import { ExportToLanguageMode } from '../../../types/playgroundType'; +import PlaygroundResultProvider from '../../../editors/playgroundResultProvider'; +import StorageController from '../../../storage/storageController'; +import { ExtensionContextStub } from '../stubs'; +import TelemetryService from '../../../telemetry/telemetryService'; +import StatusView from '../../../views/statusView'; +import ConnectionController from '../../../connectionController'; +import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; const expect = chai.expect; +const DEFAULT_EXPORT_TO_LANGUAGE_RESULT = { + content: '123', + codeToTranspile: '123', + includeDriverSyntax: false, + language: 'shell', +}; + suite('Export To Language Code Lens Provider Test Suite', function () { - const defaults = { - importStatements: false, - driverSyntax: false, - builders: false, - language: 'shell', - }; let testExportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; + let testPlaygroundResultProvider: PlaygroundResultProvider; + let testStorageController: StorageController; + let testTelemetryService: TelemetryService; + let testStatusView: StatusView; + let testConnectionController: ConnectionController; + let testEditDocumentCodeLensProvider: EditDocumentCodeLensProvider; - beforeEach(() => { - testExportToLanguageCodeLensProvider = - new ExportToLanguageCodeLensProvider(); - }); + const extensionContextStub = new ExtensionContextStub(); - test('has the include import statements code lens when importStatements is false', () => { - testExportToLanguageCodeLensProvider.refresh(defaults); + // The test extension runner. + extensionContextStub.extensionPath = '../../'; - const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(2); - expect(codeLenses[0].command?.title).to.be.equal( - 'Include Import Statements' + beforeEach(() => { + testStorageController = new StorageController(extensionContextStub); + testTelemetryService = new TelemetryService( + testStorageController, + extensionContextStub ); - }); - - test('has the exclude import statements code lens when importStatements is true', () => { - testExportToLanguageCodeLensProvider.refresh({ - ...defaults, - importStatements: true, + testStatusView = new StatusView(extensionContextStub); + testConnectionController = new ConnectionController({ + statusView: testStatusView, + storageController: testStorageController, + telemetryService: testTelemetryService, }); - - const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(2); - expect(codeLenses[0].command?.title).to.be.equal( - 'Exclude Import Statements' + testEditDocumentCodeLensProvider = new EditDocumentCodeLensProvider( + testConnectionController + ); + testPlaygroundResultProvider = new PlaygroundResultProvider( + testConnectionController, + testEditDocumentCodeLensProvider + ); + testExportToLanguageCodeLensProvider = new ExportToLanguageCodeLensProvider( + testPlaygroundResultProvider ); }); - test('has the include import statements code lens when driverSyntax is false', () => { - testExportToLanguageCodeLensProvider.refresh(defaults); + test('renders the include driver syntax code lens by default for shell', () => { + testPlaygroundResultProvider.setPlaygroundResult( + DEFAULT_EXPORT_TO_LANGUAGE_RESULT + ); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(2); - expect(codeLenses[1].command?.title).to.be.equal('Include Driver Syntax'); + expect(codeLenses).to.exist; + if (codeLenses) { + expect(codeLenses.length).to.be.equal(1); + expect(codeLenses[0].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, + test('renders the include driver syntax code lens when includeDriverSyntax is false for shell', () => { + testPlaygroundResultProvider.setPlaygroundResult({ + ...DEFAULT_EXPORT_TO_LANGUAGE_RESULT, + includeDriverSyntax: false, }); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(2); - expect(codeLenses[1].command?.title).to.be.equal('Exclude Driver Syntax'); + expect(codeLenses).to.exist; + if (codeLenses) { + expect(codeLenses.length).to.be.equal(1); + expect(codeLenses[0].command?.title).to.be.equal('Include Driver Syntax'); + } }); - test('has the use builders code lens when builders is false, language is java, and mode is query', () => { - testExportToLanguageCodeLensProvider.refresh({ - ...defaults, - mode: ExportToLanguageMode.QUERY, - language: 'java', + test('renders the exclude driver syntax code lens when includeDriverSyntax is true for shell', () => { + testPlaygroundResultProvider.setPlaygroundResult({ + ...DEFAULT_EXPORT_TO_LANGUAGE_RESULT, + includeDriverSyntax: true, }); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(3); - expect(codeLenses[2].command?.title).to.be.equal('Use Builders'); + expect(codeLenses).to.exist; + if (codeLenses) { + expect(codeLenses.length).to.be.equal(1); + expect(codeLenses[0].command?.title).to.be.equal('Exclude Driver Syntax'); + } }); - test('does not have the include driver syntax code lens when language is csharp', () => { - testExportToLanguageCodeLensProvider.refresh({ - ...defaults, - mode: ExportToLanguageMode.QUERY, + test('does not render code lenses for csharp', () => { + testPlaygroundResultProvider.setPlaygroundResult({ + ...DEFAULT_EXPORT_TO_LANGUAGE_RESULT, language: 'csharp', }); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(1); // Csharp does not support driver syntax. - expect(codeLenses[0].command?.title).to.be.equal( - 'Include Import Statements' - ); - }); - - 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: ExportToLanguageMode.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: ExportToLanguageMode.OTHER, - language: 'java', - }); - - const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(2); + expect(codeLenses?.length).to.be.equal(0); // Csharp does not support driver syntax. }); test('does not render code lenses for json text', () => { - testExportToLanguageCodeLensProvider.refresh({ - ...defaults, - builders: true, - mode: ExportToLanguageMode.OTHER, + testPlaygroundResultProvider.setPlaygroundResult({ + ...DEFAULT_EXPORT_TO_LANGUAGE_RESULT, language: 'json', }); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(0); + expect(codeLenses).to.not.exist; }); test('does not render code lenses for plain text text', () => { - testExportToLanguageCodeLensProvider.refresh({ - ...defaults, - builders: true, - mode: ExportToLanguageMode.OTHER, + testPlaygroundResultProvider.setPlaygroundResult({ + ...DEFAULT_EXPORT_TO_LANGUAGE_RESULT, language: 'plaintext', }); const codeLenses = testExportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(0); + expect(codeLenses).to.not.exist; }); }); diff --git a/src/test/suite/editors/playgroundController.test.ts b/src/test/suite/editors/playgroundController.test.ts index 57c8ca63c..0bb8cf545 100644 --- a/src/test/suite/editors/playgroundController.test.ts +++ b/src/test/suite/editors/playgroundController.test.ts @@ -4,15 +4,11 @@ import chai from 'chai'; import type { DataService } from 'mongodb-data-service'; import sinon from 'sinon'; import type { SinonSpy, SinonStub } from 'sinon'; -import { v4 as uuidv4 } from 'uuid'; -import path from 'path'; import chaiAsPromised from 'chai-as-promised'; -import PlaygroundSelectedCodeActionProvider from '../../../editors/playgroundSelectedCodeActionProvider'; +import PlaygroundSelectionCodeActionProvider from '../../../editors/playgroundSelectionCodeActionProvider'; import ConnectionController from '../../../connectionController'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; -import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; -import { ExportToLanguageMode } from '../../../types/playgroundType'; import type { LanguageServerController } from '../../../language'; import { PlaygroundController } from '../../../editors'; import PlaygroundResultProvider from '../../../editors/playgroundResultProvider'; @@ -21,20 +17,13 @@ import { StorageController } from '../../../storage'; import TelemetryService from '../../../telemetry/telemetryService'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import { ExtensionContextStub, LanguageServerControllerStub } from '../stubs'; +import { mockTextEditor } from '../stubs'; +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; const expect = chai.expect; chai.use(chaiAsPromised); -const mockFileName = path.join( - 'nonexistent', - `playground-${uuidv4()}.mongodb.js` -); -const mockDocumentUri = vscode.Uri.from({ - path: mockFileName, - scheme: 'untitled', -}); - suite('Playground Controller Test Suite', function () { this.timeout(5000); @@ -49,8 +38,7 @@ suite('Playground Controller Test Suite', function () { let testConnectionController: ConnectionController; let testEditDocumentCodeLensProvider: EditDocumentCodeLensProvider; let testPlaygroundResultProvider: PlaygroundResultProvider; - let testExportToLanguageCodeLensProvider: ExportToLanguageCodeLensProvider; - let testCodeActionProvider: PlaygroundSelectedCodeActionProvider; + let testCodeActionProvider: PlaygroundSelectionCodeActionProvider; let languageServerControllerStub: LanguageServerController; let testPlaygroundController: PlaygroundController; let showErrorMessageStub: SinonStub; @@ -76,21 +64,22 @@ suite('Playground Controller Test Suite', function () { testConnectionController, testEditDocumentCodeLensProvider ); - testExportToLanguageCodeLensProvider = - new ExportToLanguageCodeLensProvider(); - testCodeActionProvider = new PlaygroundSelectedCodeActionProvider(); + testCodeActionProvider = new PlaygroundSelectionCodeActionProvider(); languageServerControllerStub = new LanguageServerControllerStub( extensionContextStub, testStorageController ); + const testExportToLanguageCodeLensProvider = + new ExportToLanguageCodeLensProvider(testPlaygroundResultProvider); + testPlaygroundController = new PlaygroundController({ connectionController: testConnectionController, languageServerController: languageServerControllerStub, telemetryService: testTelemetryService, statusView: testStatusView, - playgroundResultViewProvider: testPlaygroundResultProvider, + playgroundResultProvider: testPlaygroundResultProvider, + playgroundSelectionCodeActionProvider: testCodeActionProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider: testCodeActionProvider, }); showErrorMessageStub = sandbox.stub(vscode.window, 'showErrorMessage'); sandbox.stub(testTelemetryService, 'trackNewConnection'); @@ -168,12 +157,13 @@ suite('Playground Controller Test Suite', function () { let showInformationMessageStub: SinonStub; beforeEach(() => { - testPlaygroundController._activeTextEditor = undefined; - showInformationMessageStub = sandbox.stub( vscode.window, 'showInformationMessage' ); + sandbox.stub(vscode.window, 'activeTextEditor').get(function getterFn() { + return undefined; + }); }); test('run all playground tells to open a playground file', async () => { @@ -205,32 +195,19 @@ suite('Playground Controller Test Suite', function () { }); suite('playground is open', () => { - let mockActiveTestEditor; let showInformationMessageStub: SinonStub; beforeEach(() => { - mockActiveTestEditor = { - document: { - languageId: 'javascript', - uri: mockDocumentUri, - getText: (): string => "use('dbName');", - lineAt: (): { text: string } => ({ text: "use('dbName');" }), - }, - selections: [ - new vscode.Selection( - new vscode.Position(0, 0), - new vscode.Position(0, 0) - ), - ], - }; - - testPlaygroundController._activeTextEditor = - mockActiveTestEditor as vscode.TextEditor; - testPlaygroundController._selectedText = undefined; showInformationMessageStub = sandbox.stub( vscode.window, 'showInformationMessage' ); + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => '123'; + sandbox.stub(vscode.window, 'activeTextEditor').get(function getterFn() { + return activeTextEditor; + }); }); suite('user is not connected', () => { @@ -252,7 +229,6 @@ suite('Playground Controller Test Suite', function () { }); test('run selected playground blocks shows please connect to a database error', async () => { - testPlaygroundController._selectedText = '{}'; const expectedMessage = 'Please connect to a database before running a playground.'; await testPlaygroundController.runSelectedPlaygroundBlocks(); @@ -262,7 +238,6 @@ suite('Playground Controller Test Suite', function () { }); test('run all or selected playground blocks shows please connect to a database error', async () => { - testPlaygroundController._selectedText = '{}'; const expectedMessage = 'Please connect to a database before running a playground.'; await testPlaygroundController.runAllOrSelectedPlaygroundBlocks(); @@ -371,65 +346,6 @@ suite('Playground Controller Test Suite', function () { expect(showTextDocumentOptions.viewColumn).to.be.equal(-2); }); - test('playground controller loads the active editor on start', () => { - sandbox.replaceGetter( - vscode.window, - 'activeTextEditor', - () => mockActiveTestEditor as vscode.TextEditor - ); - - const playgroundController = new PlaygroundController({ - connectionController: testConnectionController, - languageServerController: languageServerControllerStub, - telemetryService: testTelemetryService, - statusView: testStatusView, - playgroundResultViewProvider: testPlaygroundResultProvider, - exportToLanguageCodeLensProvider: - testExportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider: testCodeActionProvider, - }); - - expect(playgroundController._activeTextEditor).to.deep.equal( - mockActiveTestEditor - ); - }); - - test('exportToLanguage thrown an error for invalid syntax', async () => { - const playgroundController = new PlaygroundController({ - connectionController: testConnectionController, - languageServerController: languageServerControllerStub, - telemetryService: testTelemetryService, - statusView: testStatusView, - playgroundResultViewProvider: testPlaygroundResultProvider, - exportToLanguageCodeLensProvider: - testExportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider: testCodeActionProvider, - }); - const textFromEditor = 'var x = { name: qwerty }'; - const selection = { - start: { line: 0, character: 8 }, - end: { line: 0, character: 24 }, - } as vscode.Selection; - const mode = ExportToLanguageMode.OTHER; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - playgroundController._selectedText = '{ name: qwerty }'; - playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - playgroundController._playgroundSelectedCodeActionProvider.mode = mode; - playgroundController._activeTextEditor = activeTextEditor; - - await playgroundController.exportToLanguage('csharp'); - - const expectedMessage = - "Unable to export to csharp language: Symbol 'qwerty' is undefined"; - expect(showErrorMessageStub.firstCall.args[0]).to.equal( - expectedMessage - ); - }); - suite('confirmation modal', () => { beforeEach(function () { sandbox.replace( diff --git a/src/test/suite/editors/playgroundResultProvider.test.ts b/src/test/suite/editors/playgroundResultProvider.test.ts index 533e5d725..b7d972d0f 100644 --- a/src/test/suite/editors/playgroundResultProvider.test.ts +++ b/src/test/suite/editors/playgroundResultProvider.test.ts @@ -41,23 +41,8 @@ suite('Playground Result Provider Test Suite', () => { sandbox.restore(); }); - test('constructor sets default playground result', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( - testConnectionController, - testEditDocumentCodeLensProvider - ); - expect(testPlaygroundResultViewProvider._playgroundResult).to.be.deep.equal( - { - namespace: null, - type: null, - content: undefined, - language: null, - } - ); - }); - test('setPlaygroundResult refreshes private playground result property', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); @@ -70,133 +55,126 @@ suite('Playground Result Provider Test Suite', () => { }, language: 'json', }; - testPlaygroundResultViewProvider.setPlaygroundResult(playgroundResult); - expect(testPlaygroundResultViewProvider._playgroundResult).to.be.deep.equal( + testPlaygroundResultProvider.setPlaygroundResult(playgroundResult); + expect(testPlaygroundResultProvider._playgroundResult).to.be.deep.equal( playgroundResult ); }); test('provideTextDocumentContent returns undefined formatted to string if content is undefined', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); - testPlaygroundResultViewProvider._playgroundResult = { + testPlaygroundResultProvider._playgroundResult = { namespace: 'db.berlin', type: 'undefined', content: null, language: 'plaintext', }; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal('undefined'); }); test('provideTextDocumentContent returns null formatted to string if content is null', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); - testPlaygroundResultViewProvider._playgroundResult = { + testPlaygroundResultProvider._playgroundResult = { namespace: 'db.berlin', type: 'object', content: null, language: 'plaintext', }; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal('null'); }); test('provideTextDocumentContent returns number formatted to string if content is number', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); - testPlaygroundResultViewProvider._playgroundResult = { + testPlaygroundResultProvider._playgroundResult = { namespace: 'db.berlin', type: 'number', content: 4, language: 'plaintext', }; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal('4'); }); test('provideTextDocumentContent returns array formatted to string if content is array', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); - testPlaygroundResultViewProvider._playgroundResult = { + testPlaygroundResultProvider._playgroundResult = { namespace: 'db.berlin', type: 'object', content: [], language: 'json', }; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal('[]'); }); test('provideTextDocumentContent returns object formatted to string if content is object', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); - testPlaygroundResultViewProvider._playgroundResult = { + testPlaygroundResultProvider._playgroundResult = { namespace: 'db.berlin', type: 'object', content: {}, language: 'json', }; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal('{}'); }); test('provideTextDocumentContent returns boolean formatted to string if content is boolean', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); - testPlaygroundResultViewProvider._playgroundResult = { + testPlaygroundResultProvider._playgroundResult = { namespace: 'db.berlin', type: 'boolean', content: true, language: 'plaintext', }; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal('true'); }); test('provideTextDocumentContent returns string if content is string', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); - testPlaygroundResultViewProvider._playgroundResult = { + testPlaygroundResultProvider._playgroundResult = { namespace: 'db.berlin', type: 'string', content: 'Berlin', language: 'plaintext', }; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal('Berlin'); }); test('provideTextDocumentContent returns Cursor formatted to string if content is string', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); @@ -219,15 +197,14 @@ suite('Playground Result Provider Test Suite', () => { const fakeUpdateCodeLensesForPlayground = sandbox.fake(); sandbox.replace( - testPlaygroundResultViewProvider._editDocumentCodeLensProvider, + testPlaygroundResultProvider._editDocumentCodeLensProvider, 'updateCodeLensesForPlayground', fakeUpdateCodeLensesForPlayground ); - testPlaygroundResultViewProvider._playgroundResult = playgroundResult; + testPlaygroundResultProvider._playgroundResult = playgroundResult; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal(JSON.stringify(content, null, 2)); expect(fakeUpdateCodeLensesForPlayground.calledOnce).to.equal(true); expect( @@ -236,7 +213,7 @@ suite('Playground Result Provider Test Suite', () => { }); test('provideTextDocumentContent returns Document formatted to string if content is string', () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); @@ -253,15 +230,14 @@ suite('Playground Result Provider Test Suite', () => { const fakeUpdateCodeLensesForPlayground = sandbox.fake(); sandbox.replace( - testPlaygroundResultViewProvider._editDocumentCodeLensProvider, + testPlaygroundResultProvider._editDocumentCodeLensProvider, 'updateCodeLensesForPlayground', fakeUpdateCodeLensesForPlayground ); - testPlaygroundResultViewProvider._playgroundResult = playgroundResult; + testPlaygroundResultProvider._playgroundResult = playgroundResult; - const result = - testPlaygroundResultViewProvider.provideTextDocumentContent(); + const result = testPlaygroundResultProvider.provideTextDocumentContent(); expect(result).to.be.equal(JSON.stringify(content, null, 2)); expect(fakeUpdateCodeLensesForPlayground.calledOnce).to.equal(true); expect( @@ -270,7 +246,7 @@ suite('Playground Result Provider Test Suite', () => { }); test('provideTextDocumentContent sets different code lenses for the playground and the collection', async () => { - const testPlaygroundResultViewProvider = new PlaygroundResultProvider( + const testPlaygroundResultProvider = new PlaygroundResultProvider( testConnectionController, testEditDocumentCodeLensProvider ); @@ -318,11 +294,11 @@ suite('Playground Result Provider Test Suite', () => { } as unknown as typeof vscode.window.activeTextEditor) ); - testPlaygroundResultViewProvider.setPlaygroundResult(playgroundResult); - testPlaygroundResultViewProvider.provideTextDocumentContent(); + testPlaygroundResultProvider.setPlaygroundResult(playgroundResult); + testPlaygroundResultProvider.provideTextDocumentContent(); let codeLenses = - testPlaygroundResultViewProvider._editDocumentCodeLensProvider.provideCodeLenses(); + testPlaygroundResultProvider._editDocumentCodeLensProvider.provideCodeLenses(); expect(codeLenses.length).to.be.equal(2); let firstCodeLensRange = codeLenses[0].range; @@ -333,7 +309,7 @@ suite('Playground Result Provider Test Suite', () => { expect(secondCodeLensRange.start.line).to.be.equal(9); let codeLensesInfo = - testPlaygroundResultViewProvider._editDocumentCodeLensProvider + testPlaygroundResultProvider._editDocumentCodeLensProvider ._codeLensesInfo; expect(Object.keys(codeLensesInfo).length).to.be.equal(1); @@ -394,9 +370,9 @@ suite('Playground Result Provider Test Suite', () => { activeTextEditorDocument.uri = collectionUri; // Switch active editor. await testCollectionViewProvider.provideTextDocumentContent(collectionUri); - testPlaygroundResultViewProvider._editDocumentCodeLensProvider.provideCodeLenses(); + testPlaygroundResultProvider._editDocumentCodeLensProvider.provideCodeLenses(); codeLenses = - testPlaygroundResultViewProvider._editDocumentCodeLensProvider.provideCodeLenses(); + testPlaygroundResultProvider._editDocumentCodeLensProvider.provideCodeLenses(); expect(codeLenses.length).to.be.equal(1); @@ -406,7 +382,7 @@ suite('Playground Result Provider Test Suite', () => { expect(codeLenses[0].command?.title).to.be.equal('Edit Document'); codeLensesInfo = - testPlaygroundResultViewProvider._editDocumentCodeLensProvider + testPlaygroundResultProvider._editDocumentCodeLensProvider ._codeLensesInfo; expect(Object.keys(codeLensesInfo).length).to.be.equal(2); diff --git a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts b/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts deleted file mode 100644 index 774f912ca..000000000 --- a/src/test/suite/editors/playgroundSelectedCodeActionProvider.test.ts +++ /dev/null @@ -1,1124 +0,0 @@ -import * as vscode from 'vscode'; -import { beforeEach, afterEach } from 'mocha'; -import chai from 'chai'; -import sinon from 'sinon'; - -import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; -import PlaygroundSelectedCodeActionProvider from '../../../editors/playgroundSelectedCodeActionProvider'; -import { LanguageServerController } from '../../../language'; -import { mdbTestExtension } from '../stubbableMdbExtension'; -import { PlaygroundController } from '../../../editors'; -import type { PlaygroundResult } from '../../../types/playgroundType'; -import { ExportToLanguageMode } from '../../../types/playgroundType'; -import { TEST_DATABASE_URI } from '../dbTestHelper'; -import { ExtensionContextStub } from '../stubs'; - -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { version } = require('../../../../package.json'); - -const expect = chai.expect; - -suite('Playground Selected CodeAction Provider Test Suite', function () { - this.timeout(5000); - - const extensionContextStub = new ExtensionContextStub(); - - const EXPORT_LANGUAGES_CODEACTIONS_COUNT = 8; - const TOTAL_CODEACTIONS_COUNT = EXPORT_LANGUAGES_CODEACTIONS_COUNT + 1; - - // The test extension runner. - extensionContextStub.extensionPath = '../../'; - - suite('the MongoDB playground in JS', () => { - const testCodeActionProvider = new PlaygroundSelectedCodeActionProvider(); - const sandbox = sinon.createSandbox(); - - beforeEach(async () => { - sandbox.replace( - mdbTestExtension.testExtensionController, - '_languageServerController', - new LanguageServerController(extensionContextStub) - ); - sandbox.stub(vscode.window, 'showInformationMessage'); - sandbox.stub( - mdbTestExtension.testExtensionController._telemetryService, - 'trackNewConnection' - ); - - await mdbTestExtension.testExtensionController._connectionController.addNewConnectionStringAndConnect( - TEST_DATABASE_URI - ); - - const testExportToLanguageCodeLensProvider = - new ExportToLanguageCodeLensProvider(); - - mdbTestExtension.testExtensionController._playgroundController = - new PlaygroundController({ - connectionController: - mdbTestExtension.testExtensionController._connectionController, - languageServerController: - mdbTestExtension.testExtensionController._languageServerController, - telemetryService: - mdbTestExtension.testExtensionController._telemetryService, - statusView: mdbTestExtension.testExtensionController._statusView, - playgroundResultViewProvider: - mdbTestExtension.testExtensionController - ._playgroundResultViewProvider, - exportToLanguageCodeLensProvider: - testExportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider: testCodeActionProvider, - }); - - const fakeOpenPlaygroundResult = sandbox.fake(); - sandbox.replace( - mdbTestExtension.testExtensionController._playgroundController, - '_openInResultPane', - fakeOpenPlaygroundResult - ); - - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', false); - - await mdbTestExtension.testExtensionController._languageServerController.startLanguageServer(); - await mdbTestExtension.testExtensionController._playgroundController._activeConnectionChanged(); - - const fakeIsPlayground = sandbox.fake.returns(true); - sandbox.replace(testCodeActionProvider, 'isPlayground', fakeIsPlayground); - }); - - afterEach(async () => { - await vscode.commands.executeCommand( - 'workbench.action.closeActiveEditor' - ); - await vscode.workspace - .getConfiguration('mdb') - .update('confirmRunAll', true); - await mdbTestExtension.testExtensionController._connectionController.disconnect(); - mdbTestExtension.testExtensionController._connectionController.clearAllConnections(); - sandbox.restore(); - }); - - test('returns undefined when text is not selected', () => { - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.be.undefined; - }); - - test('returns a run selected playground blocks action', async () => { - mdbTestExtension.testExtensionController._playgroundController._selectedText = - '123'; - - const selection = { - start: { line: 0, character: 0 }, - end: { line: 0, character: 4 }, - } as vscode.Selection; - - testCodeActionProvider.refresh({ - selection, - mode: ExportToLanguageMode.OTHER, - }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.exist; - - if (codeActions) { - expect(codeActions.length).to.be.equal(1); - const actionCommand = codeActions[0].command; - - if (actionCommand) { - expect(actionCommand.command).to.be.equal( - 'mdb.runSelectedPlaygroundBlocks' - ); - expect(actionCommand.title).to.be.equal( - 'Run selected playground blocks' - ); - - await vscode.commands.executeCommand(actionCommand.command); - - 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); - } - } - }); - - test('returns an export to java action with whitespaces around objects', () => { - const textFromEditor = ' { name: "Alena Khineika" } '; - const selection = { - start: { line: 0, character: 2 }, - end: { line: 0, character: 27 }, - } as vscode.Selection; - const mode = ExportToLanguageMode.QUERY; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - mdbTestExtension.testExtensionController._playgroundController._selectedText = - textFromEditor; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.mode = - mode; - mdbTestExtension.testExtensionController._playgroundController._activeTextEditor = - activeTextEditor; - - testCodeActionProvider.refresh({ selection, mode }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.exist; - - if (codeActions) { - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[2].command; - - if (actionCommand) { - expect(actionCommand.command).to.be.equal('mdb.exportToJava'); - expect(actionCommand.title).to.be.equal('Export To Java'); - } - } - }); - - suite('exports to java', () => { - const expectedResult = { - namespace: 'DATABASE_NAME.COLLECTION_NAME', - type: null, - content: 'new Document("name", "22")', - language: 'java', - }; - - beforeEach(async () => { - const textFromEditor = "{ name: '22' }"; - const selection = { - start: { line: 0, character: 0 }, - end: { line: 0, character: 14 }, - } as vscode.Selection; - const mode = ExportToLanguageMode.QUERY; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - mdbTestExtension.testExtensionController._playgroundController._selectedText = - textFromEditor; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.mode = - mode; - mdbTestExtension.testExtensionController._playgroundController._activeTextEditor = - activeTextEditor; - - testCodeActionProvider.refresh({ selection, mode }); - - // this is to ensure we're starting each test in the same state - await vscode.commands.executeCommand( - 'mdb.changeExportToLanguageAddons', - { - ...mdbTestExtension.testExtensionController._playgroundController - ._exportToLanguageCodeLensProvider._exportToLanguageAddons, - builders: false, - importStatements: false, - driverSyntax: false, - } - ); - }); - - test('include builders (only)', async () => { - const codeActions = testCodeActionProvider.provideCodeActions(); - - if (!codeActions) { - expect.fail('No code actions'); - return false; - } - - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[2].command; - - if (!actionCommand) { - expect.fail('Action command not found'); - return false; - } - - expect(actionCommand.command).to.be.equal('mdb.exportToJava'); - expect(actionCommand.title).to.be.equal('Export To Java'); - - await vscode.commands.executeCommand(actionCommand.command); - - let codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(3); - let lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Include Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Include Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - - // Only java queries supports builders. - await vscode.commands.executeCommand( - 'mdb.changeExportToLanguageAddons', - { - ...mdbTestExtension.testExtensionController._playgroundController - ._exportToLanguageCodeLensProvider._exportToLanguageAddons, - builders: true, - importStatements: false, - driverSyntax: false, - } - ); - - codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Include Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Include Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Raw Query' - ); - - expectedResult.content = 'eq("name", "22")'; - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult - ).to.be.deep.equal(expectedResult); - }); - - test('include driver syntax (only)', async () => { - const codeActions = testCodeActionProvider.provideCodeActions(); - - if (!codeActions) { - expect.fail('No code actions'); - return false; - } - - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[2].command; - - if (!actionCommand) { - expect.fail('Action command not found'); - return false; - } - - expect(actionCommand.command).to.be.equal('mdb.exportToJava'); - expect(actionCommand.title).to.be.equal('Export To Java'); - - await vscode.commands.executeCommand(actionCommand.command); - - let codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(3); - let lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Include Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Include Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - - await vscode.commands.executeCommand( - 'mdb.changeExportToLanguageAddons', - { - ...mdbTestExtension.testExtensionController._playgroundController - ._exportToLanguageCodeLensProvider._exportToLanguageAddons, - builders: false, - importStatements: false, - driverSyntax: true, - } - ); - - codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Include Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Exclude Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - - const driverSyntaxRawQuery = - 'Bson filter = new Document("name", "22");'; - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult?.content - ).to.include(driverSyntaxRawQuery); - }); - - test('include import statements (only)', async () => { - const codeActions = testCodeActionProvider.provideCodeActions(); - - if (!codeActions) { - expect.fail('No code actions'); - return false; - } - - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[2].command; - - if (!actionCommand) { - expect.fail('Action command not found'); - return false; - } - - expect(actionCommand.command).to.be.equal('mdb.exportToJava'); - expect(actionCommand.title).to.be.equal('Export To Java'); - - await vscode.commands.executeCommand(actionCommand.command); - - let codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(3); - let lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Include Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Include Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - - await vscode.commands.executeCommand( - 'mdb.changeExportToLanguageAddons', - { - ...mdbTestExtension.testExtensionController._playgroundController - ._exportToLanguageCodeLensProvider._exportToLanguageAddons, - builders: false, - importStatements: true, - driverSyntax: false, - } - ); - - codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Exclude Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Include Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - - // imports without driver syntax are limited - const rawQueryWithImport = - 'import org.bson.Document;\n\nnew Document("name", "22")'; - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult?.content - ).to.deep.equal(rawQueryWithImport); - }); - - test('include driver syntax and import statements (in a single export)', async () => { - const codeActions = testCodeActionProvider.provideCodeActions(); - - if (!codeActions) { - expect.fail('No code actions'); - return false; - } - - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[2].command; - - if (!actionCommand) { - expect.fail('Action command not found'); - return false; - } - - expect(actionCommand.command).to.be.equal('mdb.exportToJava'); - expect(actionCommand.title).to.be.equal('Export To Java'); - - await vscode.commands.executeCommand(actionCommand.command); - - let codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(3); - let lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Include Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Include Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - await vscode.commands.executeCommand( - 'mdb.changeExportToLanguageAddons', - { - ...mdbTestExtension.testExtensionController._playgroundController - ._exportToLanguageCodeLensProvider._exportToLanguageAddons, - builders: false, - importStatements: true, - driverSyntax: true, - } - ); - - codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Exclude Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Exclude Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - - // With driver syntax, java includes generic import statements - const mongoClientImport = 'import com.mongodb.MongoClient;'; - // as well as import statements which depend on the exportToLanguageMode. the following is for QUERY - const queryImport = 'import com.mongodb.client.FindIterable;'; - const content = - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult?.content; - expect(content).to.include(mongoClientImport); - expect(content).to.include(queryImport); - }); - - test('include driver syntax and then import statements in a subsequent export', async () => { - const codeActions = testCodeActionProvider.provideCodeActions(); - - if (!codeActions) { - expect.fail('No code actions'); - return false; - } - - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[2].command; - - if (!actionCommand) { - expect.fail('Action command not found'); - return false; - } - - expect(actionCommand.command).to.be.equal('mdb.exportToJava'); - expect(actionCommand.title).to.be.equal('Export To Java'); - - /* 1st export - we'll select drivers only */ - await vscode.commands.executeCommand(actionCommand.command); - - let codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(3); - let lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Include Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Include Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - - await vscode.commands.executeCommand( - 'mdb.changeExportToLanguageAddons', - { - ...mdbTestExtension.testExtensionController._playgroundController - ._exportToLanguageCodeLensProvider._exportToLanguageAddons, - builders: false, - importStatements: false, - driverSyntax: true, - } - ); - - codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - lensesObj = { lenses: codeLenses }; - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Include Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Exclude Driver Syntax' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[2].command.title', - 'Use Builders' - ); - - /* 2nd export - this time we add import statements on top of drivers */ - await vscode.commands.executeCommand(actionCommand.command); - - codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(3); - lensesObj = { lenses: codeLenses }; - // the state is persisted from the 1st export - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Exclude Driver Syntax' - ); - - // We add import on top of the drivers - await vscode.commands.executeCommand( - 'mdb.changeExportToLanguageAddons', - { - ...mdbTestExtension.testExtensionController._playgroundController - ._exportToLanguageCodeLensProvider._exportToLanguageAddons, - importStatements: true, - } - ); - - codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(3); - lensesObj = { lenses: codeLenses }; - // the state is persisted from the 1st export - expect(lensesObj).to.have.nested.property( - 'lenses[0].command.title', - 'Exclude Import Statements' - ); - expect(lensesObj).to.have.nested.property( - 'lenses[1].command.title', - 'Exclude Driver Syntax' - ); - - // The imports and driver syntax are both applied - const mongoClientImport = 'import com.mongodb.MongoClient;'; - const queryImport = 'import com.mongodb.client.FindIterable;'; - const content = - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult?.content; - expect(content).to.include(mongoClientImport); - expect(content).to.include(queryImport); - }); - }); - - 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 = ExportToLanguageMode.QUERY; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - mdbTestExtension.testExtensionController._playgroundController._selectedText = - textFromEditor; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.mode = - mode; - mdbTestExtension.testExtensionController._playgroundController._activeTextEditor = - activeTextEditor; - - testCodeActionProvider.refresh({ selection, mode }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.exist; - - if (codeActions) { - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - 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: 'DATABASE_NAME.COLLECTION_NAME', - type: null, - content: 'new BsonDocument("name", "22")', - language: 'csharp', - }; - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult - ).to.be.deep.equal(expectedResult); - - const codeLenses = - mdbTestExtension.testExtensionController._playgroundController._exportToLanguageCodeLensProvider.provideCodeLenses(); - expect(codeLenses.length).to.be.equal(1); // 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 = ExportToLanguageMode.QUERY; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - mdbTestExtension.testExtensionController._playgroundController._selectedText = - "{ name: '22' }"; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.mode = - mode; - mdbTestExtension.testExtensionController._playgroundController._activeTextEditor = - activeTextEditor; - - testCodeActionProvider.refresh({ selection, mode }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.exist; - - if (codeActions) { - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - 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: 'DATABASE_NAME.COLLECTION_NAME', - type: null, - content: "{\n 'name': '22'\n}", - language: 'python', - }; - 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: null, - content: `# Requires the PyMongo package.\n# https://api.mongodb.com/python/current\n\nclient = MongoClient('mongodb://localhost:27088/?appname=mongodb-vscode+${version}')\nfilter={\n 'name': '22'\n}\n\nresult = client['db']['coll'].find(\n filter=filter\n)`, - language: 'python', - }; - - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult - ).to.be.deep.equal(expectedResult); - } - } - }); - - test('exports to ruby 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 = ExportToLanguageMode.QUERY; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - mdbTestExtension.testExtensionController._playgroundController._selectedText = - "{ name: '22' }"; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.mode = - mode; - mdbTestExtension.testExtensionController._playgroundController._activeTextEditor = - activeTextEditor; - - testCodeActionProvider.refresh({ selection, mode }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.exist; - - if (codeActions) { - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[5].command; - - if (actionCommand) { - expect(actionCommand.command).to.be.equal('mdb.exportToRuby'); - expect(actionCommand.title).to.be.equal('Export To Ruby'); - - await vscode.commands.executeCommand(actionCommand.command); - - let expectedResult: PlaygroundResult = { - namespace: 'DATABASE_NAME.COLLECTION_NAME', - type: null, - content: "{\n 'name' => '22'\n}", - language: 'ruby', - }; - 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: null, - content: `# Requires the MongoDB Ruby Driver\n# https://docs.mongodb.com/ruby-driver/master/\n\nclient = Mongo::Client.new('mongodb://localhost:27088/?appname=mongodb-vscode+${version}', :database => 'db')\n\nresult = client.database['coll'].find({\n 'name' => '22'\n})`, - language: 'ruby', - }; - - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult - ).to.be.deep.equal(expectedResult); - } - } - }); - - test('exports to go 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 = ExportToLanguageMode.QUERY; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - mdbTestExtension.testExtensionController._playgroundController._selectedText = - "{ name: '22' }"; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.mode = - mode; - mdbTestExtension.testExtensionController._playgroundController._activeTextEditor = - activeTextEditor; - - testCodeActionProvider.refresh({ selection, mode }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.exist; - - if (codeActions) { - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[6].command; - - if (actionCommand) { - expect(actionCommand.command).to.be.equal('mdb.exportToGo'); - expect(actionCommand.title).to.be.equal('Export To Go'); - - await vscode.commands.executeCommand(actionCommand.command); - - let expectedResult: PlaygroundResult = { - namespace: 'DATABASE_NAME.COLLECTION_NAME', - type: null, - content: 'bson.D{{"name", "22"}}', - language: 'go', - }; - 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: null, - content: `// Requires the MongoDB Go Driver\n// https://go.mongodb.org/mongo-driver\nctx := context.TODO()\n\n// Set client options\nclientOptions := options.Client().ApplyURI(\"mongodb://localhost:27088/?appname=mongodb-vscode+${version}\")\n\n// Connect to MongoDB\nclient, err := mongo.Connect(ctx, clientOptions)\nif err != nil {\n log.Fatal(err)\n}\ndefer func() {\n if err := client.Disconnect(ctx); err != nil {\n log.Fatal(err)\n }\n}()\n\ncoll := client.Database(\"db\").Collection(\"coll\")\n_, err = coll.Find(ctx, bson.D{{\"name\", \"22\"}})\nif err != nil {\n log.Fatal(err)\n}`, - language: 'go', - }; - - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult - ).to.be.deep.equal(expectedResult); - } - } - }); - - test('exports to rust 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 = ExportToLanguageMode.QUERY; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - mdbTestExtension.testExtensionController._playgroundController._selectedText = - "{ name: '22' }"; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.mode = - mode; - mdbTestExtension.testExtensionController._playgroundController._activeTextEditor = - activeTextEditor; - - testCodeActionProvider.refresh({ selection, mode }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.exist; - - if (codeActions) { - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[7].command; - - if (actionCommand) { - expect(actionCommand.command).to.be.equal('mdb.exportToRust'); - expect(actionCommand.title).to.be.equal('Export To Rust'); - - await vscode.commands.executeCommand(actionCommand.command); - - let expectedResult: PlaygroundResult = { - namespace: 'DATABASE_NAME.COLLECTION_NAME', - type: null, - content: 'doc! {\n "name": "22"\n}', - language: 'rust', - }; - 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: null, - content: `// Requires the MongoDB crate.\n// https://crates.io/crates/mongodb\n\nlet client = Client::with_uri_str(\"mongodb://localhost:27088/?appname=mongodb-vscode+${version}\").await?;\nlet result = client.database(\"db\").collection::(\"coll\").find(doc! {\n \"name\": \"22\"\n}, None).await?;`, - language: 'rust', - }; - - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult - ).to.be.deep.equal(expectedResult); - } - } - }); - - test('exports to php 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 = ExportToLanguageMode.QUERY; - const activeTextEditor = { - document: { getText: () => textFromEditor }, - } as vscode.TextEditor; - - mdbTestExtension.testExtensionController._playgroundController._selectedText = - "{ name: '22' }"; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.selection = - selection; - mdbTestExtension.testExtensionController._playgroundController._playgroundSelectedCodeActionProvider.mode = - mode; - mdbTestExtension.testExtensionController._playgroundController._activeTextEditor = - activeTextEditor; - - testCodeActionProvider.refresh({ selection, mode }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.exist; - - if (codeActions) { - expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); - const actionCommand = codeActions[8].command; - - if (actionCommand) { - expect(actionCommand.command).to.be.equal('mdb.exportToPHP'); - expect(actionCommand.title).to.be.equal('Export To PHP'); - - await vscode.commands.executeCommand(actionCommand.command); - - let expectedResult: PlaygroundResult = { - namespace: 'DATABASE_NAME.COLLECTION_NAME', - type: null, - content: "['name' => '22']", - language: 'php', - }; - 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: null, - content: `// Requires the MongoDB PHP Driver\n// https://www.mongodb.com/docs/drivers/php/\n\n$client = new Client('mongodb://localhost:27088/?appname=mongodb-vscode+${version}');\n$collection = $client->selectCollection('db', 'coll');\n$cursor = $collection->find(['name' => '22']);`, - language: 'php', - }; - - expect( - mdbTestExtension.testExtensionController._playgroundController - ._playgroundResult - ).to.be.deep.equal(expectedResult); - } - } - }); - }); - - suite('the regular JS file', () => { - const testCodeActionProvider = new PlaygroundSelectedCodeActionProvider(); - const sandbox = sinon.createSandbox(); - - beforeEach(() => { - const fakeIsPlayground = sandbox.fake.returns(false); - sandbox.replace(testCodeActionProvider, 'isPlayground', fakeIsPlayground); - sandbox.stub( - mdbTestExtension.testExtensionController._telemetryService, - 'trackNewConnection' - ); - }); - - afterEach(() => { - sandbox.restore(); - }); - - test('returns undefined when text is not selected', () => { - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.be.undefined; - }); - - test('returns undefined when text is selected', () => { - mdbTestExtension.testExtensionController._playgroundController._selectedText = - '123'; - - const selection = { - start: { line: 0, character: 0 }, - end: { line: 0, character: 4 }, - } as vscode.Selection; - - testCodeActionProvider.refresh({ - selection, - mode: ExportToLanguageMode.OTHER, - }); - - const codeActions = testCodeActionProvider.provideCodeActions(); - expect(codeActions).to.be.undefined; - }); - }); -}); diff --git a/src/test/suite/editors/playgroundSelectionCodeActionProvider.test.ts b/src/test/suite/editors/playgroundSelectionCodeActionProvider.test.ts new file mode 100644 index 000000000..0df3f9efc --- /dev/null +++ b/src/test/suite/editors/playgroundSelectionCodeActionProvider.test.ts @@ -0,0 +1,596 @@ +import * as vscode from 'vscode'; +import { beforeEach, afterEach } from 'mocha'; +import chai from 'chai'; +import sinon from 'sinon'; +import PlaygroundSelectionCodeActionProvider from '../../../editors/playgroundSelectionCodeActionProvider'; +import { LanguageServerController } from '../../../language'; +import { mdbTestExtension } from '../stubbableMdbExtension'; +import { PlaygroundController } from '../../../editors'; +import { TEST_DATABASE_URI } from '../dbTestHelper'; +import { ExtensionContextStub } from '../stubs'; +import { mockTextEditor } from '../stubs'; +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; + +const expect = chai.expect; + +suite('Playground Selection Code Action Provider Test Suite', function () { + this.timeout(5000); + + const extensionContextStub = new ExtensionContextStub(); + + const EXPORT_LANGUAGES_CODEACTIONS_COUNT = 8; + const TOTAL_CODEACTIONS_COUNT = EXPORT_LANGUAGES_CODEACTIONS_COUNT + 1; + + // The test extension runner. + extensionContextStub.extensionPath = '../../'; + + suite('the MongoDB playground in JS', () => { + const testCodeActionProvider = new PlaygroundSelectionCodeActionProvider(); + const sandbox = sinon.createSandbox(); + let testActiveTextEditor; + + beforeEach(async () => { + sandbox.replace( + mdbTestExtension.testExtensionController, + '_languageServerController', + new LanguageServerController(extensionContextStub) + ); + sandbox.stub(vscode.window, 'showInformationMessage'); + sandbox.stub( + mdbTestExtension.testExtensionController._telemetryService, + 'trackNewConnection' + ); + + await mdbTestExtension.testExtensionController._connectionController.addNewConnectionStringAndConnect( + TEST_DATABASE_URI + ); + + const testExportToLanguageCodeLensProvider = + new ExportToLanguageCodeLensProvider( + mdbTestExtension.testExtensionController._playgroundResultProvider + ); + + mdbTestExtension.testExtensionController._playgroundController = + new PlaygroundController({ + connectionController: + mdbTestExtension.testExtensionController._connectionController, + languageServerController: + mdbTestExtension.testExtensionController._languageServerController, + telemetryService: + mdbTestExtension.testExtensionController._telemetryService, + statusView: mdbTestExtension.testExtensionController._statusView, + playgroundResultProvider: + mdbTestExtension.testExtensionController._playgroundResultProvider, + playgroundSelectionCodeActionProvider: testCodeActionProvider, + exportToLanguageCodeLensProvider: + testExportToLanguageCodeLensProvider, + }); + + const fakeOpenPlaygroundResult = sandbox.fake(); + sandbox.replace( + mdbTestExtension.testExtensionController._playgroundController, + '_openInResultPane', + fakeOpenPlaygroundResult + ); + + await vscode.workspace + .getConfiguration('mdb') + .update('confirmRunAll', false); + + await mdbTestExtension.testExtensionController._languageServerController.startLanguageServer(); + await mdbTestExtension.testExtensionController._playgroundController._activeConnectionChanged(); + + testActiveTextEditor = sandbox.stub(vscode.window, 'activeTextEditor'); + }); + + afterEach(async () => { + await vscode.commands.executeCommand( + 'workbench.action.closeActiveEditor' + ); + await vscode.workspace + .getConfiguration('mdb') + .update('confirmRunAll', true); + await mdbTestExtension.testExtensionController._connectionController.disconnect(); + mdbTestExtension.testExtensionController._connectionController.clearAllConnections(); + sandbox.restore(); + }); + + suite('copilot is disabled', () => { + beforeEach(() => { + sandbox.replace( + vscode.extensions, + 'getExtension', + sandbox.fake.returns(undefined) + ); + }); + + test('renders only the run selected playground blocks code action', () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => '123'; + activeTextEditor.selections = [ + { + start: { line: 0, character: 0 }, + end: { line: 0, character: 4 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(1); + const actionCommand = codeActions[0].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal( + 'mdb.runSelectedPlaygroundBlocks' + ); + expect(actionCommand.title).to.be.equal( + 'Run selected playground blocks' + ); + } + } + }); + }); + + suite('copilot is active', () => { + beforeEach(() => { + sandbox.replace( + vscode.extensions, + 'getExtension', + sandbox.fake.returns({ isActive: true }) + ); + }); + + test('does not render code actions when text is not selected', () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => '123'; + activeTextEditor.selections = []; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.not.exist; + }); + + test('renders the run selected playground blocks code action', () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => '123'; + activeTextEditor.selections = [ + { + start: { line: 0, character: 0 }, + end: { line: 0, character: 4 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); + const actionCommand = codeActions[0].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal( + 'mdb.runSelectedPlaygroundBlocks' + ); + expect(actionCommand.title).to.be.equal( + 'Run selected playground blocks' + ); + } + } + }); + + suite('renders export to java code actions', () => { + beforeEach(() => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => 'Berlin'; + activeTextEditor.selections = [ + { + start: { line: 0, character: 0 }, + end: { line: 0, character: 6 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + }); + + test('renders the include driver syntax code action and changes it to exclude', async () => { + const codeActions = testCodeActionProvider.provideCodeActions(); + + if (!codeActions) { + expect.fail('No code actions'); + return false; + } + + expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); + const actionCommand = codeActions[2].command; + + if (!actionCommand) { + expect.fail('Action command not found'); + return false; + } + + expect(actionCommand.command).to.be.equal('mdb.exportToJava'); + expect(actionCommand.title).to.be.equal('Export To Java'); + + await vscode.commands.executeCommand(actionCommand.command); + + mdbTestExtension.testExtensionController._playgroundResultProvider.setPlaygroundResult( + { + content: 'Berlin', + codeToTranspile: 'Berlin', + language: 'java', + includeDriverSyntax: false, + } + ); + + let codeLenses = + mdbTestExtension.testExtensionController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses?.length).to.be.equal(1); + let lensesObj = { lenses: codeLenses }; + expect(lensesObj).to.have.nested.property( + 'lenses[0].command.title', + 'Include Driver Syntax' + ); + + mdbTestExtension.testExtensionController._playgroundResultProvider.setPlaygroundResult( + { + content: 'Berlin', + codeToTranspile: 'Berlin', + language: 'java', + includeDriverSyntax: true, + } + ); + + codeLenses = + mdbTestExtension.testExtensionController._exportToLanguageCodeLensProvider.provideCodeLenses(); + lensesObj = { lenses: codeLenses }; + expect(lensesObj).to.have.nested.property( + 'lenses[0].command.title', + 'Exclude Driver Syntax' + ); + }); + }); + + test('renders export to csharp code actions', async () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => "{ name: '22' }"; + activeTextEditor.selections = [ + { + start: { line: 0, character: 0 }, + end: { line: 0, character: 14 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); + 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 codeLenses = + mdbTestExtension.testExtensionController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses).to.not.exist; // Csharp does not support driver syntax. + } + } + }); + + test('renders export to python code actions', async () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => + "use('db'); db.coll.find({ name: '22' })"; + activeTextEditor.selections = [ + { + start: { line: 0, character: 24 }, + end: { line: 0, character: 38 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); + 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'); + + mdbTestExtension.testExtensionController._playgroundResultProvider.setPlaygroundResult( + { + content: 'Does not matter', + codeToTranspile: "use('db'); db.coll.find({ name: '22' })", + language: 'python', + includeDriverSyntax: false, + } + ); + + await vscode.commands.executeCommand(actionCommand.command); + const codeLenses = + mdbTestExtension.testExtensionController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses?.length).to.be.equal(1); + const lensesObj = { lenses: codeLenses }; + expect(lensesObj).to.have.nested.property( + 'lenses[0].command.title', + 'Include Driver Syntax' + ); + } + } + }); + + test('renders export to ruby code actions', async () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => + "use('db'); db.coll.find({ name: '22' })"; + activeTextEditor.selections = [ + { + start: { line: 0, character: 24 }, + end: { line: 0, character: 38 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); + const actionCommand = codeActions[5].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal('mdb.exportToRuby'); + expect(actionCommand.title).to.be.equal('Export To Ruby'); + + mdbTestExtension.testExtensionController._playgroundResultProvider.setPlaygroundResult( + { + content: 'Does not matter', + codeToTranspile: "use('db'); db.coll.find({ name: '22' })", + language: 'ruby', + includeDriverSyntax: false, + } + ); + + await vscode.commands.executeCommand(actionCommand.command); + + const codeLenses = + mdbTestExtension.testExtensionController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses?.length).to.be.equal(1); + const lensesObj = { lenses: codeLenses }; + expect(lensesObj).to.have.nested.property( + 'lenses[0].command.title', + 'Include Driver Syntax' + ); + } + } + }); + + test('renders export to go code actions', async () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => + "use('db'); db.coll.find({ name: '22' })"; + activeTextEditor.selections = [ + { + start: { line: 0, character: 24 }, + end: { line: 0, character: 38 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); + const actionCommand = codeActions[6].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal('mdb.exportToGo'); + expect(actionCommand.title).to.be.equal('Export To Go'); + + mdbTestExtension.testExtensionController._playgroundResultProvider.setPlaygroundResult( + { + content: 'Does not matter', + codeToTranspile: "use('db'); db.coll.find({ name: '22' })", + language: 'go', + includeDriverSyntax: false, + } + ); + + await vscode.commands.executeCommand(actionCommand.command); + + const codeLenses = + mdbTestExtension.testExtensionController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses?.length).to.be.equal(1); + const lensesObj = { lenses: codeLenses }; + expect(lensesObj).to.have.nested.property( + 'lenses[0].command.title', + 'Include Driver Syntax' + ); + } + } + }); + + test('renders export to rust code actions', async () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => + "use('db'); db.coll.find({ name: '22' })"; + activeTextEditor.selections = [ + { + start: { line: 0, character: 24 }, + end: { line: 0, character: 38 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); + const actionCommand = codeActions[7].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal('mdb.exportToRust'); + expect(actionCommand.title).to.be.equal('Export To Rust'); + + mdbTestExtension.testExtensionController._playgroundResultProvider.setPlaygroundResult( + { + content: 'Does not matter', + codeToTranspile: "use('db'); db.coll.find({ name: '22' })", + language: 'rust', + includeDriverSyntax: false, + } + ); + + await vscode.commands.executeCommand(actionCommand.command); + + const codeLenses = + mdbTestExtension.testExtensionController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses?.length).to.be.equal(1); + const lensesObj = { lenses: codeLenses }; + expect(lensesObj).to.have.nested.property( + 'lenses[0].command.title', + 'Include Driver Syntax' + ); + } + } + }); + + test('renders export to php code actions', async () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.mongodb.js'); + activeTextEditor.document.getText = (): string => + "use('db'); db.coll.find({ name: '22' })"; + activeTextEditor.selections = [ + { + start: { line: 0, character: 24 }, + end: { line: 0, character: 38 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.exist; + + if (codeActions) { + expect(codeActions.length).to.be.equal(TOTAL_CODEACTIONS_COUNT); + const actionCommand = codeActions[8].command; + + if (actionCommand) { + expect(actionCommand.command).to.be.equal('mdb.exportToPHP'); + expect(actionCommand.title).to.be.equal('Export To PHP'); + + mdbTestExtension.testExtensionController._playgroundResultProvider.setPlaygroundResult( + { + content: 'Does not matter', + codeToTranspile: "use('db'); db.coll.find({ name: '22' })", + language: 'php', + includeDriverSyntax: false, + } + ); + + await vscode.commands.executeCommand(actionCommand.command); + + const codeLenses = + mdbTestExtension.testExtensionController._exportToLanguageCodeLensProvider.provideCodeLenses(); + expect(codeLenses?.length).to.be.equal(1); + const lensesObj = { lenses: codeLenses }; + expect(lensesObj).to.have.nested.property( + 'lenses[0].command.title', + 'Include Driver Syntax' + ); + } + } + }); + }); + }); + + suite('the regular JS file', () => { + const testCodeActionProvider = new PlaygroundSelectionCodeActionProvider(); + const sandbox = sinon.createSandbox(); + let testActiveTextEditor; + + beforeEach(() => { + sandbox.stub( + mdbTestExtension.testExtensionController._telemetryService, + 'trackNewConnection' + ); + testActiveTextEditor = sandbox.stub(vscode.window, 'activeTextEditor'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + test('does not render code actions when text is not selected', () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.js'); + activeTextEditor.document.getText = (): string => '123'; + activeTextEditor.selections = []; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.be.undefined; + }); + + test('does not render code actions when text is selected', () => { + const activeTextEditor = mockTextEditor; + activeTextEditor.document.uri = vscode.Uri.parse('test.js'); + activeTextEditor.document.getText = (): string => '123'; + activeTextEditor.selections = [ + { + start: { line: 0, character: 0 }, + end: { line: 0, character: 4 }, + } as vscode.Selection, + ]; + testActiveTextEditor.get(function getterFn() { + return activeTextEditor; + }); + + const codeActions = testCodeActionProvider.provideCodeActions(); + expect(codeActions).to.be.undefined; + }); + }); +}); diff --git a/src/test/suite/language/languageServerController.test.ts b/src/test/suite/language/languageServerController.test.ts index 45919d9b0..686785729 100644 --- a/src/test/suite/language/languageServerController.test.ts +++ b/src/test/suite/language/languageServerController.test.ts @@ -8,10 +8,9 @@ import type { SinonStub } from 'sinon'; import type { DataService } from 'mongodb-data-service'; import chaiAsPromised from 'chai-as-promised'; -import PlaygroundSelectedCodeActionProvider from '../../../editors/playgroundSelectedCodeActionProvider'; +import PlaygroundSelectionCodeActionProvider from '../../../editors/playgroundSelectionCodeActionProvider'; import ConnectionController from '../../../connectionController'; import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; -import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; import { LanguageServerController } from '../../../language'; import { mdbTestExtension } from '../stubbableMdbExtension'; import { PlaygroundController } from '../../../editors'; @@ -21,6 +20,7 @@ import { StorageController } from '../../../storage'; import { TEST_DATABASE_URI } from '../dbTestHelper'; import TelemetryService from '../../../telemetry/telemetryService'; import { ExtensionContextStub } from '../stubs'; +import ExportToLanguageCodeLensProvider from '../../../editors/exportToLanguageCodeLensProvider'; const expect = chai.expect; @@ -50,9 +50,7 @@ suite('Language Server Controller Test Suite', () => { testConnectionController, testEditDocumentCodeLensProvider ); - const testExportToLanguageCodeLensProvider = - new ExportToLanguageCodeLensProvider(); - const testCodeActionProvider = new PlaygroundSelectedCodeActionProvider(); + const testCodeActionProvider = new PlaygroundSelectionCodeActionProvider(); let languageServerControllerStub: LanguageServerController; let testPlaygroundController: PlaygroundController; @@ -63,14 +61,17 @@ suite('Language Server Controller Test Suite', () => { languageServerControllerStub = new LanguageServerController( extensionContextStub ); + const testExportToLanguageCodeLensProvider = + new ExportToLanguageCodeLensProvider(testPlaygroundResultProvider); + testPlaygroundController = new PlaygroundController({ connectionController: testConnectionController, languageServerController: languageServerControllerStub, telemetryService: testTelemetryService, statusView: testStatusView, - playgroundResultViewProvider: testPlaygroundResultProvider, + playgroundResultProvider: testPlaygroundResultProvider, + playgroundSelectionCodeActionProvider: testCodeActionProvider, exportToLanguageCodeLensProvider: testExportToLanguageCodeLensProvider, - playgroundSelectedCodeActionProvider: testCodeActionProvider, }); await languageServerControllerStub.startLanguageServer(); await testPlaygroundController._activeConnectionChanged(); diff --git a/src/test/suite/language/mongoDBService.test.ts b/src/test/suite/language/mongoDBService.test.ts index c3fc37f60..6e7fee02f 100644 --- a/src/test/suite/language/mongoDBService.test.ts +++ b/src/test/suite/language/mongoDBService.test.ts @@ -1,4 +1,3 @@ -import type * as vscode from 'vscode'; import { before, after, beforeEach, afterEach } from 'mocha'; import { CancellationTokenSource, @@ -1345,7 +1344,7 @@ suite('MongoDBService Test Suite', () => { 'when connected to a default db and not explicitly using a db', dbInUse: 'defaultDB', defaultContent: '', - beforeAssertions: () => { + beforeAssertions: (): void => { Sinon.stub(testMongoDBService, 'connectionString').get( () => `${params.connectionString}/defaultDB` ); @@ -1356,7 +1355,7 @@ suite('MongoDBService Test Suite', () => { 'when connected to a default db and also explicitly using another db', dbInUse: 'anotherTestDB', defaultContent: "use('anotherTestDB');", - beforeAssertions: () => { + beforeAssertions: (): void => { Sinon.stub(testMongoDBService, 'connectionString').get( () => `${params.connectionString}/defaultDB` ); @@ -1367,8 +1366,8 @@ suite('MongoDBService Test Suite', () => { 'when not connected to a default db and explicitly using another db', dbInUse: 'anotherTestDB', defaultContent: "use('anotherTestDB');", - beforeAssertions: () => { - () => params.connectionString; + beforeAssertions: (): void => { + (): string => params.connectionString; }, }, ].forEach( @@ -2377,7 +2376,7 @@ suite('MongoDBService Test Suite', () => { ); testMongoDBService._cacheStreamProcessorCompletionItems([ - { name: 'testProccessor' }, + { name: 'testProcessor' }, ]); const result = await testMongoDBService.provideCompletionItems({ @@ -2385,7 +2384,7 @@ suite('MongoDBService Test Suite', () => { position, }); const completion = result.find( - (item: CompletionItem) => item.label === 'testProccessor' + (item: CompletionItem) => item.label === 'testProcessor' ); expect(completion).to.have.property('kind', CompletionItemKind.Folder); }); @@ -2596,7 +2595,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'number', content: 2, language: 'plaintext', @@ -2743,7 +2742,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'number', content: 3, language: 'plaintext', @@ -2764,7 +2763,7 @@ suite('MongoDBService Test Suite', () => { ); const firstRes = { result: { - namespace: null, + namespace: undefined, type: 'number', content: 2, language: 'plaintext', @@ -2782,7 +2781,7 @@ suite('MongoDBService Test Suite', () => { ); const secondRes = { result: { - namespace: null, + namespace: undefined, type: 'number', content: 3, language: 'plaintext', @@ -2805,7 +2804,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'object', content: { _id: { @@ -2831,7 +2830,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'object', content: { name: 'a short string', @@ -2855,7 +2854,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'object', content: [ { @@ -2880,7 +2879,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'undefined', content: undefined, language: 'plaintext', @@ -2901,7 +2900,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'object', content: null, language: 'plaintext', @@ -2923,7 +2922,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'string', content: 'A single line string', language: 'plaintext', @@ -2947,7 +2946,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'string', content: `vscode is @@ -3000,7 +2999,7 @@ suite('MongoDBService Test Suite', () => { const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'number', content: 42, language: 'plaintext', @@ -3037,7 +3036,7 @@ suite('MongoDBService Test Suite', () => { ); const expectedResult = { result: { - namespace: null, + namespace: undefined, type: 'number', content: 3, language: 'plaintext', @@ -3049,166 +3048,6 @@ suite('MongoDBService Test Suite', () => { }); }); - suite('Export to language mode', function () { - this.timeout(INCREASED_TEST_TIMEOUT); - - const up = new StreamStub(); - const down = new StreamStub(); - const connection = createConnection(up, down); - - connection.listen(); - - const testMongoDBService = new MongoDBService(connection); - - test('returns other for call expression', () => { - const textFromEditor = `db.sales.insertMany([ - { '_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') }, - { '_id': 3, 'item': 'xyz', 'price': 5, 'quantity': 10, 'date': new Date('2014-03-15T09:00:00Z') }, - { '_id': 4, 'item': 'xyz', 'price': 5, 'quantity': 20, 'date': new Date('2014-04-04T11:21:39.736Z') }, - { '_id': 5, 'item': 'abc', 'price': 10, 'quantity': 10, 'date': new Date('2014-04-04T21:23:13.331Z') }, - { '_id': 6, 'item': 'def', 'price': 7.5, 'quantity': 5, 'date': new Date('2015-06-04T05:08:13Z') }, - { '_id': 7, 'item': 'def', 'price': 7.5, 'quantity': 10, 'date': new Date('2015-09-10T08:43:00Z') }, - { '_id': 8, 'item': 'abc', 'price': 10, 'quantity': 5, 'date': new Date('2016-02-06T20:20:13Z') }, - ]);`; - const selection = { - start: { line: 0, character: 0 }, - end: { line: 9, character: 3 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('OTHER'); - }); - - test('returns query for an object', () => { - const textFromEditor = - "db.sales.insertMany([{ '_id': 1, 'item': 'abc', 'price': 10, 'quantity': 2, 'date': new Date('2014-03-01T08:00:00Z') }]);"; - const selection = { - start: { line: 0, character: 21 }, - end: { line: 0, character: 118 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('QUERY'); - }); - - test('returns aggregation for an array as function argument', () => { - const textFromEditor = - "db.sales.insertMany([{ '_id': 1, 'item': 'abc', 'price': 10, 'quantity': 2, 'date': new Date('2014-03-01T08:00:00Z') }]);"; - const selection = { - start: { line: 0, character: 20 }, - end: { line: 0, character: 119 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('AGGREGATION'); - }); - - test('returns query for an object as function argument', () => { - const textFromEditor = - "db.sales.insertMany({ '_id': 1, 'item': 'abc' });"; - const selection = { - start: { line: 0, character: 20 }, - end: { line: 0, character: 47 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('QUERY'); - }); - - test('returns aggregation for an array assigned to a variable', () => { - const textFromEditor = "const arr = [{ '_id': 1, 'item': 'abc' }];"; - const selection = { - start: { line: 0, character: 12 }, - end: { line: 0, character: 41 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('AGGREGATION'); - }); - - test('returns query for an object assigned to a variable', () => { - const textFromEditor = "const obj = { '_id': 1 };"; - const selection = { - start: { line: 0, character: 12 }, - end: { line: 0, character: 24 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('QUERY'); - }); - - test('returns other for a variable declaration', () => { - const textFromEditor = "const arr = [{ '_id': 1, 'item': 'abc' }];"; - const selection = { - start: { line: 0, character: 0 }, - end: { line: 0, character: 42 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('OTHER'); - }); - - test('returns query for an object used as another object property', () => { - const textFromEditor = - "const obj = { prop: { '_id': 1, 'item': 'abc' } };"; - const selection = { - start: { line: 0, character: 20 }, - end: { line: 0, character: 47 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('QUERY'); - }); - - test('returns aggregation for an array inside another array', () => { - const textFromEditor = 'const arr = [[]];'; - const selection = { - start: { line: 0, character: 13 }, - end: { line: 0, character: 15 }, - } as vscode.Selection; - - const mode = testMongoDBService.getExportToLanguageMode({ - textFromEditor, - selection, - }); - - expect(mode).to.be.equal('AGGREGATION'); - }); - }); - suite('Diagnostic', function () { const up = new StreamStub(); const down = new StreamStub(); diff --git a/src/test/suite/mdbExtensionController.test.ts b/src/test/suite/mdbExtensionController.test.ts index 5e11b1cc3..d45cfd291 100644 --- a/src/test/suite/mdbExtensionController.test.ts +++ b/src/test/suite/mdbExtensionController.test.ts @@ -35,7 +35,7 @@ function getTestConnectionTreeItem( options?: Partial[0]> ): ConnectionTreeItem { return new ConnectionTreeItem({ - connectionId: 'tasty_sandwhich', + connectionId: 'tasty_sandwich', collapsibleState: vscode.TreeItemCollapsibleState.None, isExpanded: false, connectionController: @@ -184,7 +184,7 @@ suite('MDBExtensionController Test Suite', function () { ); openExternalStub = sandbox.stub(vscode.env, 'openExternal'); openTextDocumentStub = sandbox.stub(vscode.workspace, 'openTextDocument'); - fakeActiveConnectionId = sandbox.fake.returns('tasty_sandwhich'); + fakeActiveConnectionId = sandbox.fake.returns('tasty_sandwich'); sandbox.replace( mdbTestExtension.testExtensionController._connectionController, 'getActiveConnectionId', @@ -913,7 +913,7 @@ suite('MDBExtensionController Test Suite', function () { mdbTestExtension.testExtensionController._connectionController.clearAllConnections(); }); - test('mdb.openMongoDBDocumentFromTree openes a document from the sidebar and saves it to MongoDB', async () => { + test('mdb.openMongoDBDocumentFromTree opens a document from the sidebar and saves it to MongoDB', async () => { const mockDocument = { _id: 'pancakes', name: '', @@ -935,12 +935,13 @@ suite('MDBExtensionController Test Suite', function () { 'VIEW_DOCUMENT_SCHEME:/', 'waffle.house:pancakes.json?', 'namespace=waffle.house&', - 'connectionId=tasty_sandwhich&', + 'connectionId=tasty_sandwich&', 'documentId=93333a0d-83f6-4e6f-a575-af7ea6187a4a&', 'source=treeview', ].join('') ); - activeTextEditor.document.getText = () => JSON.stringify(mockDocument); + activeTextEditor.document.getText = (): string => + JSON.stringify(mockDocument); sandbox.replaceGetter( vscode.window, 'activeTextEditor', @@ -1002,7 +1003,7 @@ suite('MDBExtensionController Test Suite', function () { ); }); - test('mdb.openMongoDBDocumentFromTree openes a document from a tree with a treeview source', async () => { + test('mdb.openMongoDBDocumentFromTree opens a document from a tree with a treeview source', async () => { const mockDocument = { _id: 'pancakes', name: '', @@ -1030,7 +1031,7 @@ suite('MDBExtensionController Test Suite', function () { ); }); - test('mdb.openMongoDBDocumentFromCodeLens openes a document from a playground results with a playground source', async () => { + test('mdb.openMongoDBDocumentFromCodeLens opens a document from a playground results with a playground source', async () => { const documentItem = { source: 'playground', line: 1, @@ -1080,7 +1081,7 @@ suite('MDBExtensionController Test Suite', function () { scheme: 'VIEW_DOCUMENT_SCHEME', query: [ 'namespace=waffle.house', - 'connectionId=tasty_sandwhich', + 'connectionId=tasty_sandwich', 'documentId=93333a0d-83f6-4e6f-a575-af7ea6187a4a', 'source=treeview', ].join('&'), @@ -1130,7 +1131,7 @@ suite('MDBExtensionController Test Suite', function () { scheme: 'VIEW_DOCUMENT_SCHEME', query: [ 'namespace=waffle.house', - 'connectionId=tasty_sandwhich', + 'connectionId=tasty_sandwich', 'documentId=93333a0d-83f6-4e6f-a575-af7ea6187a4a', 'source=playground', ].join('&'), diff --git a/src/test/suite/participant/participant.test.ts b/src/test/suite/participant/participant.test.ts index 67bcd5f7a..5be22ddf5 100644 --- a/src/test/suite/participant/participant.test.ts +++ b/src/test/suite/participant/participant.test.ts @@ -35,10 +35,13 @@ import { createMarkdownLink } from '../../../participant/markdown'; import EXTENSION_COMMANDS from '../../../commands'; import { getContentLength } from '../../../participant/prompts/promptBase'; import { ParticipantErrorTypes } from '../../../participant/participantErrorTypes'; +import * as model from '../../../participant/model'; import { createChatRequestTurn, createChatResponseTurn, } from './participantHelpers'; +import EditDocumentCodeLensProvider from '../../../editors/editDocumentCodeLensProvider'; +import PlaygroundResultProvider from '../../../editors/playgroundResultProvider'; // The Copilot's model in not available in tests, // therefore we need to mock its methods and returning values. @@ -91,6 +94,8 @@ suite('Participant Controller Test Suite', function () { let testStatusView: StatusView; let testTelemetryService: TelemetryService; let testParticipantController: ParticipantController; + let testEditDocumentCodeLensProvider: EditDocumentCodeLensProvider; + let testPlaygroundResultProvider: PlaygroundResultProvider; let chatContextStub: vscode.ChatContext; let chatStreamStub: { push: sinon.SinonSpy; @@ -192,10 +197,18 @@ suite('Participant Controller Test Suite', function () { telemetryService: testTelemetryService, }); sinon.replace(ChatMetadataStore, 'createNewChatId', () => testChatId); + testEditDocumentCodeLensProvider = new EditDocumentCodeLensProvider( + testConnectionController + ); + testPlaygroundResultProvider = new PlaygroundResultProvider( + testConnectionController, + testEditDocumentCodeLensProvider + ); testParticipantController = new ParticipantController({ connectionController: testConnectionController, storageController: testStorageController, telemetryService: testTelemetryService, + playgroundResultProvider: testPlaygroundResultProvider, }); chatContextStub = { history: [ @@ -218,21 +231,17 @@ suite('Participant Controller Test Suite', function () { countTokensStub = sinon.stub(); // The model returned by vscode.lm.selectChatModels is always undefined in tests. sendRequestStub = sinon.stub(); - sinon.replace( - vscode.lm, - 'selectChatModels', - sinon.fake.returns([ - { - id: 'modelId', - vendor: 'copilot', - family: 'gpt-4o', - version: 'gpt-4o-date', - name: 'GPT 4o (date)', - maxInputTokens: MAX_TOTAL_PROMPT_LENGTH_MOCK, - countTokens: countTokensStub, - sendRequest: sendRequestStub, - }, - ]) + sinon.replace(model, 'getCopilotModel', () => + Promise.resolve({ + id: 'modelId', + vendor: 'copilot', + family: 'gpt-4o', + version: 'gpt-4o-date', + name: 'GPT 4o (date)', + maxInputTokens: MAX_TOTAL_PROMPT_LENGTH_MOCK, + countTokens: countTokensStub, + sendRequest: sendRequestStub, + }) ); sinon.replace(testTelemetryService, 'track', telemetryTrackStub); diff --git a/src/test/suite/stubs.ts b/src/test/suite/stubs.ts index 11ac366d7..baefbeb7a 100644 --- a/src/test/suite/stubs.ts +++ b/src/test/suite/stubs.ts @@ -10,12 +10,7 @@ import path from 'path'; import type { Document, Filter, FindOptions } from 'mongodb'; import type { StorageController } from '../../storage'; - -import type { - ShellEvaluateResult, - ExportToLanguageNamespace, -} from '../../types/playgroundType'; -import { ExportToLanguageMode } from '../../types/playgroundType'; +import type { ShellEvaluateResult } from '../../types/playgroundType'; // Bare mock of the extension context for vscode. class ExtensionContextStub implements vscode.ExtensionContext { @@ -46,19 +41,19 @@ class ExtensionContextStub implements vscode.ExtensionContext { constructor() { this.languageModelAccessInformation = { onDidChange: (): any => {}, - canSendRequest: () => undefined, + canSendRequest: (): undefined => undefined, }; this.globalStoragePath = ''; this.logPath = ''; this.subscriptions = []; this.secrets = { - get: (key: string) => { + get: (key: string): string => { return this._secrets[key]; }, - store: (key: string, value: string) => { + store: (key: string, value: string): void => { this._secrets[key] = value; }, - delete: (key: string) => { + delete: (key: string): void => { delete this._secrets[key]; }, }; @@ -188,11 +183,15 @@ class DataServiceStub { return Promise.resolve(mockDatabases[databaseName].collections); } - find(namespace: string, filter: Filter, options: FindOptions) { + find( + namespace: string, + filter: Filter, + options: FindOptions + ): Promise { return Promise.resolve(mockDocuments.slice(0, options.limit)); } - estimatedCount() { + estimatedCount(): Promise { return Promise.resolve(mockDocuments.length); } } @@ -229,8 +228,8 @@ const mockVSCodeTextDocument = { positionAt: (/* offset: number */): vscode.Position => mockPosition, getText: (/* range?: vscode.Range */): string => '', - getWordRangeAtPosition: (/* position: vscode.Position, regex?: RegExp */) => - undefined, + getWordRangeAtPosition: + (/* position: vscode.Position, regex?: RegExp */): undefined => undefined, validateRange: (/* range: vscode.Range */): vscode.Range => mockRange, validatePosition: (/* position: vscode.Position */): vscode.Position => mockPosition, @@ -253,12 +252,12 @@ const mockTextEditor = { lineNumbers: vscode.TextEditorLineNumbersStyle.Off, }, viewColumn: vscode.ViewColumn.Beside, - edit: () => Promise.resolve(true), - insertSnippet: () => Promise.resolve(true), - setDecorations: () => {}, - revealRange: () => {}, - show: () => {}, - hide: () => {}, + edit: (): Promise => Promise.resolve(true), + insertSnippet: (): Promise => Promise.resolve(true), + setDecorations: (): void => {}, + revealRange: (): void => {}, + show: (): void => {}, + hide: (): void => {}, }; class LanguageServerControllerStub { @@ -343,22 +342,14 @@ class LanguageServerControllerStub { evaluate(/* codeToEvaluate: string */): Promise { return Promise.resolve({ result: { - namespace: null, - type: null, + namespace: undefined, + type: undefined, content: 'Result', language: 'plaintext', }, }); } - getExportToLanguageMode(/* params: PlaygroundTextAndSelection */): Promise { - return Promise.resolve(ExportToLanguageMode.OTHER); - } - - getNamespaceForSelection(/* params: PlaygroundTextAndSelection */): Promise { - return Promise.resolve({ databaseName: null, collectionName: null }); - } - activeConnectionChanged(/* params: { connectionString?: string; connectionOptions?: MongoClientOptions; @@ -381,12 +372,12 @@ class LanguageServerControllerStub { } class StreamStub extends Duplex { - _write(chunk: string, _encoding: string, done: () => void) { + _write(chunk: string, _encoding: string, done: () => void): void { this.emit('data', chunk); done(); } - _read() {} + _read(): void {} } export { diff --git a/src/test/suite/telemetry/telemetryService.test.ts b/src/test/suite/telemetry/telemetryService.test.ts index 3e63a59eb..cfdcf8be1 100644 --- a/src/test/suite/telemetry/telemetryService.test.ts +++ b/src/test/suite/telemetry/telemetryService.test.ts @@ -72,15 +72,6 @@ suite('Telemetry Controller Test Suite', () => { 'getActiveConnectionId', sandbox.fake.returns('testconnectionId') ); - sandbox.replace( - mdbTestExtension.testExtensionController._playgroundController - ._languageServerController, - 'getNamespaceForSelection', - sandbox.fake.resolves({ - collectionName: 'coll', - databaseName: 'db', - }) - ); sandbox.replace( mdbTestExtension.testExtensionController._playgroundController ._connectionController, @@ -88,14 +79,6 @@ suite('Telemetry Controller Test Suite', () => { sandbox.fake.returns('mongodb://localhost') ); sandbox.stub(vscode.window, 'showErrorMessage'); - sandbox.replace( - mdbTestExtension.testExtensionController._playgroundController, - 'getTranspiledContent', - sandbox.fake.resolves({ - namespace: 'db.coll', - expressio: '{}', - }) - ); sandbox.stub(vscode.window, 'showInformationMessage'); sandbox.replace( testTelemetryService, @@ -261,7 +244,7 @@ suite('Telemetry Controller Test Suite', () => { test.skip('track mongodb playground loaded event', async () => { const docPath = path.resolve( __dirname, - '../../../../src/test/fixture/testSaving.mongodb' + '../../../../src/test/fixture/testPlayground.mongodb' ); await vscode.workspace.openTextDocument(vscode.Uri.file(docPath)); sandbox.assert.calledWith( @@ -280,7 +263,7 @@ suite('Telemetry Controller Test Suite', () => { test.skip('track mongodbjs playground loaded event', async () => { const docPath = path.resolve( __dirname, - '../../../../src/test/fixture/testSaving.mongodb.js' + '../../../../src/test/fixture/testPlayground.mongodb.js' ); await vscode.workspace.openTextDocument(vscode.Uri.file(docPath)); sandbox.assert.calledWith( @@ -327,36 +310,10 @@ suite('Telemetry Controller Test Suite', () => { ); }); - test('track query exported to language', function () { - testTelemetryService.trackQueryExported({ - language: 'python', - with_import_statements: false, - with_builders: false, - with_driver_syntax: false, - }); - - sandbox.assert.calledWith( - fakeSegmentAnalyticsTrack, - sinon.match({ - anonymousId, - event: 'Query Exported', - properties: { - language: 'python', - with_import_statements: false, - with_builders: false, - with_driver_syntax: false, - extension_version: version, - }, - }) - ); - }); - - test('track aggregation exported to language', () => { - testTelemetryService.trackAggregationExported({ + test('track playground exported to language', () => { + testTelemetryService.trackPlaygroundExportedToLanguageExported({ language: 'java', - num_stages: 1, - with_import_statements: false, - with_builders: false, + exported_code_length: 3, with_driver_syntax: false, }); @@ -364,12 +321,9 @@ suite('Telemetry Controller Test Suite', () => { fakeSegmentAnalyticsTrack, sinon.match({ anonymousId, - event: 'Aggregation Exported', + event: 'Playground Exported To Language', properties: { language: 'java', - num_stages: 1, - with_import_statements: false, - with_builders: false, with_driver_syntax: false, extension_version: version, }, @@ -381,7 +335,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert AggregationCursor shellApiType to aggregation telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'AggregationCursor', content: '', language: 'plaintext', @@ -394,7 +348,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert BulkWriteResult shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'BulkWriteResult', content: '', language: 'plaintext', @@ -407,7 +361,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert Collection shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'Collection', content: '', language: 'plaintext', @@ -420,7 +374,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert Cursor shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'Cursor', content: '', language: 'plaintext', @@ -433,7 +387,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert Database shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'Database', content: '', language: 'plaintext', @@ -446,7 +400,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert DeleteResult shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'DeleteResult', content: '', language: 'plaintext', @@ -459,7 +413,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert InsertManyResult shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'InsertManyResult', content: '', language: 'plaintext', @@ -472,7 +426,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert InsertOneResult shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'InsertOneResult', content: '', language: 'plaintext', @@ -485,7 +439,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert ReplicaSet shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'ReplicaSet', content: '', language: 'plaintext', @@ -498,7 +452,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert Shard shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'Shard', content: '', language: 'plaintext', @@ -511,7 +465,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert ShellApi shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'ShellApi', content: '', language: 'plaintext', @@ -524,7 +478,7 @@ suite('Telemetry Controller Test Suite', () => { test('convert UpdateResult shellApiType to other telemetry type', () => { const res = { result: { - namespace: null, + namespace: undefined, type: 'UpdateResult', content: '', language: 'plaintext', @@ -537,8 +491,8 @@ suite('Telemetry Controller Test Suite', () => { test('return other telemetry type if evaluation returns a string', () => { const res = { result: { - namespace: null, - type: null, + namespace: undefined, + type: undefined, content: '2', language: 'plaintext', }, diff --git a/src/types/playgroundType.ts b/src/types/playgroundType.ts index 61ec2f613..07067b8af 100644 --- a/src/types/playgroundType.ts +++ b/src/types/playgroundType.ts @@ -1,19 +1,28 @@ import type * as vscode from 'vscode'; import type { NodeDriverServiceProvider } from '@mongosh/service-provider-node-driver'; -export type OutputItem = { - namespace: string | null; - type: string | null; +export type PlaygroundRunResult = { + namespace?: string; + type?: string; content: any; - language: string | null; + language?: string; }; -export type PlaygroundDebug = OutputItem[] | undefined; +export type ExportToLanguageResult = { + content: string; + codeToTranspile: string; + language: string; + includeDriverSyntax: boolean; +}; -export type PlaygroundResult = OutputItem | undefined; +export function isExportToLanguageResult( + result: PlaygroundRunResult | ExportToLanguageResult +): result is ExportToLanguageResult { + return (result as ExportToLanguageResult).codeToTranspile !== undefined; +} export type ShellEvaluateResult = { - result: PlaygroundResult; + result: PlaygroundRunResult | undefined; } | null; export type PlaygroundEvaluateParams = { @@ -22,23 +31,12 @@ export type PlaygroundEvaluateParams = { filePath?: string; }; -export interface ExportToLanguageAddons { - textFromEditor?: string; - selectedText?: string; - selection?: vscode.Selection; - importStatements: boolean; - driverSyntax: boolean; - builders: boolean; - language: string; - mode?: ExportToLanguageMode; -} - export interface PlaygroundTextAndSelection { textFromEditor: string; selection: vscode.Selection; } -export enum ExportToLanguages { +export enum ExportToLanguage { PYTHON = 'python', JAVA = 'java', CSHARP = 'csharp', @@ -49,17 +47,6 @@ export enum ExportToLanguages { PHP = 'php', } -export enum ExportToLanguageMode { - QUERY = 'QUERY', - AGGREGATION = 'AGGREGATION', - OTHER = 'OTHER', -} - -export interface ExportToLanguageNamespace { - databaseName: string | null; - collectionName: string | null; -} - // MongoClientOptions is the second argument of NodeDriverServiceProvider.connect(connectionStr, options). export type MongoClientOptions = NonNullable< Parameters<(typeof NodeDriverServiceProvider)['connect']>[1] diff --git a/src/utils/playground.ts b/src/utils/playground.ts index 673509f70..392fbf47f 100644 --- a/src/utils/playground.ts +++ b/src/utils/playground.ts @@ -61,7 +61,7 @@ const stat = (filePath: string): Promise => { return fs.promises.lstat(filePath); }; -export const isPlayground = (fileUri?: vscode.Uri) => { +export const isPlayground = (fileUri?: vscode.Uri): boolean => { if (!fileUri) { return false; } @@ -82,8 +82,31 @@ export const isPlayground = (fileUri?: vscode.Uri) => { ); }; -export const getPlaygroundExtensionForTelemetry = (fileUri?: vscode.Uri) => { - let fileType; +export const getSelectedText = (): string | undefined => { + const editor = vscode.window.activeTextEditor; + + if (!editor) { + return; + } + + // Sort lines selected as the may be mis-ordered from alt+click. + const sortedSelections = (editor.selections as Array).sort( + (a, b) => (a.start.line > b.start.line ? 1 : -1) + ); + + return sortedSelections + .map((item) => editor.document.getText(item) || '') + .join('\n'); +}; + +export const getAllText = (): string => { + return vscode.window.activeTextEditor?.document.getText().trim() || ''; +}; + +export const getPlaygroundExtensionForTelemetry = ( + fileUri?: vscode.Uri +): string => { + let fileType = 'other'; if (fileUri?.fsPath.match(/\.(mongodb\.js)$/gi)) { fileType = 'mongodbjs';