From 0390097521a3c459f72dcda4a9a26cf56591b005 Mon Sep 17 00:00:00 2001 From: Basit <1305718+mabaasit@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:51:09 +0200 Subject: [PATCH] feat(playground): add support local require VSCODE-468 (#718) * support local require when running playground * pr feedback * add test for local require --- src/editors/playgroundController.ts | 1 + src/language/languageServerController.ts | 1 + src/language/mongoDBService.ts | 1 + src/language/worker.ts | 40 ++++++++++++----- .../suite/language/mongoDBService.test.ts | 43 ++++++++++++++++++- src/types/playgroundType.ts | 1 + 6 files changed, 75 insertions(+), 12 deletions(-) diff --git a/src/editors/playgroundController.ts b/src/editors/playgroundController.ts index e1403fa23..64b2594a3 100644 --- a/src/editors/playgroundController.ts +++ b/src/editors/playgroundController.ts @@ -449,6 +449,7 @@ export default class PlaygroundController { result = await this._languageServerController.evaluate({ codeToEvaluate, connectionId, + filePath: vscode.window.activeTextEditor?.document.uri.fsPath, }); } catch (error) { const msg = diff --git a/src/language/languageServerController.ts b/src/language/languageServerController.ts index 4525b0014..13c579e09 100644 --- a/src/language/languageServerController.ts +++ b/src/language/languageServerController.ts @@ -186,6 +186,7 @@ export default class LanguageServerController { ): Promise { log.info('Running a playground...', { connectionId: playgroundExecuteParameters.connectionId, + filePath: playgroundExecuteParameters.filePath, inputLength: playgroundExecuteParameters.codeToEvaluate.length, }); this._isExecutingInProgress = true; diff --git a/src/language/mongoDBService.ts b/src/language/mongoDBService.ts index 3e5d967d0..09d4c942d 100644 --- a/src/language/mongoDBService.ts +++ b/src/language/mongoDBService.ts @@ -296,6 +296,7 @@ export default class MongoDBService { name: ServerCommands.EXECUTE_CODE_FROM_PLAYGROUND, data: { codeToEvaluate: params.codeToEvaluate, + filePath: params.filePath, connectionString: this.connectionString, connectionOptions: this.connectionOptions, }, diff --git a/src/language/worker.ts b/src/language/worker.ts index f0f7ba819..316e9f3e5 100644 --- a/src/language/worker.ts +++ b/src/language/worker.ts @@ -36,14 +36,25 @@ const getLanguage = (evaluationResult: EvaluationResult) => { return 'plaintext'; }; +type ExecuteCodeOptions = { + codeToEvaluate: string; + connectionString: string; + connectionOptions: MongoClientOptions; + filePath?: string; +}; + /** * Execute code from a playground. */ -const execute = async ( - codeToEvaluate: string, - connectionString: string, - connectionOptions: MongoClientOptions -): Promise<{ data?: ShellEvaluateResult; error?: any }> => { +const execute = async ({ + codeToEvaluate, + connectionString, + connectionOptions, + filePath, +}: ExecuteCodeOptions): Promise<{ + data?: ShellEvaluateResult; + error?: any; +}> => { const serviceProvider = await CliServiceProvider.connect( connectionString, connectionOptions @@ -67,6 +78,19 @@ const execute = async ( }, }); + // In order to support local require directly from the file where code lives, we can not wrap the + // whole code in a function for two reasons: + // 1. We need to return the response and can not simply add return. And + // 2. We can not use eval to evaluate the *codeToEvaluate* as mongosh async-rewriter can’t see into the eval. + // We are also not directly concatenating the require with the code either due to "use strict" + if (filePath) { + await runtime.evaluate(`(function () { + globalThis.require = require('module').createRequire(${JSON.stringify( + filePath + )}); + } ())`); + } + // Evaluate a playground content. const { source, type, printable } = await runtime.evaluate(codeToEvaluate); const namespace = @@ -94,11 +118,7 @@ const handleMessageFromParentPort = async ({ name, data }): Promise => { if (name === ServerCommands.EXECUTE_CODE_FROM_PLAYGROUND) { parentPort?.postMessage({ name: ServerCommands.CODE_EXECUTION_RESULT, - payload: await execute( - data.codeToEvaluate, - data.connectionString, - data.connectionOptions - ), + payload: await execute(data), }); } }; diff --git a/src/test/suite/language/mongoDBService.test.ts b/src/test/suite/language/mongoDBService.test.ts index 1b2f06ec4..6f7e1fe5d 100644 --- a/src/test/suite/language/mongoDBService.test.ts +++ b/src/test/suite/language/mongoDBService.test.ts @@ -10,7 +10,8 @@ import { import type { CompletionItem } from 'vscode-languageclient/node'; import chai from 'chai'; import { createConnection } from 'vscode-languageserver/node'; -import fs from 'fs'; +import fs from 'fs/promises'; +import os from 'os'; import path from 'path'; import { TextDocument } from 'vscode-languageserver-textdocument'; import type { Db } from 'mongodb'; @@ -45,7 +46,7 @@ suite('MongoDBService Test Suite', () => { 'dist', languageServerWorkerFileName ); - await fs.promises.stat(languageServerModuleBundlePath); + await fs.stat(languageServerModuleBundlePath); }); suite('Extension path', () => { @@ -3008,6 +3009,44 @@ suite('MongoDBService Test Suite', () => { expect(result).to.deep.equal(expectedResult); }); }); + + suite('evaluate allows to import local files', function () { + let tmpDir: string; + beforeEach(async () => { + tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'local-import')); + await fs.writeFile( + path.join(tmpDir, 'utils.js'), + `module.exports.add = function (a, b) { + return a + b; + }; + ` + ); + }); + afterEach(async () => { + await fs.rm(tmpDir, { recursive: true }); + }); + test('evaluate allows to import file', async () => { + const source = new CancellationTokenSource(); + const result = await testMongoDBService.evaluate( + { + connectionId: 'pineapple', + codeToEvaluate: 'const { add } = require("./utils.js"); add(1, 2);', + filePath: path.join(tmpDir, 'utils.js'), + }, + source.token + ); + const expectedResult = { + result: { + namespace: null, + type: 'number', + content: 3, + language: 'plaintext', + }, + }; + + expect(result).to.deep.equal(expectedResult); + }); + }); }); suite('Export to language mode', function () { diff --git a/src/types/playgroundType.ts b/src/types/playgroundType.ts index 350a7d890..0e43a9f21 100644 --- a/src/types/playgroundType.ts +++ b/src/types/playgroundType.ts @@ -21,6 +21,7 @@ export type ShellEvaluateResult = export type PlaygroundEvaluateParams = { codeToEvaluate: string; connectionId: string; + filePath?: string; }; export interface ExportToLanguageAddons {