From 5476d7315cfe457d384e88332f409433400b80a5 Mon Sep 17 00:00:00 2001 From: Basit Date: Thu, 11 Apr 2024 19:10:01 +0200 Subject: [PATCH 1/3] support local require when running playground --- src/editors/playgroundController.ts | 1 + src/language/languageServerController.ts | 1 + src/language/mongoDBService.ts | 1 + src/language/worker.ts | 38 +++++++++++++++++------- src/types/playgroundType.ts | 1 + 5 files changed, 32 insertions(+), 10 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..32fec0351 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,17 @@ const execute = async ( }, }); + // In order to support local require direclty 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 direclty concatenating the require with the code either due to "use strict" + if (filePath) { + await runtime.evaluate(`(function () { + require = require('module').createRequire('${filePath}'); + } ())`); + } + // Evaluate a playground content. const { source, type, printable } = await runtime.evaluate(codeToEvaluate); const namespace = @@ -94,11 +116,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/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 { From e654e13c0e14a486cd2a0dd71a8148ed3894616c Mon Sep 17 00:00:00 2001 From: Basit Date: Fri, 12 Apr 2024 16:31:46 +0200 Subject: [PATCH 2/3] pr feedback --- src/language/worker.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/language/worker.ts b/src/language/worker.ts index 32fec0351..316e9f3e5 100644 --- a/src/language/worker.ts +++ b/src/language/worker.ts @@ -78,14 +78,16 @@ const execute = async ({ }, }); - // In order to support local require direclty from the file where code lives, we can not wrap the + // 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 direclty concatenating the require with the code either due to "use strict" + // We are also not directly concatenating the require with the code either due to "use strict" if (filePath) { await runtime.evaluate(`(function () { - require = require('module').createRequire('${filePath}'); + globalThis.require = require('module').createRequire(${JSON.stringify( + filePath + )}); } ())`); } From 660684c8c8cbd9bb183f77a7ff041251cf9b1eeb Mon Sep 17 00:00:00 2001 From: Basit Date: Wed, 17 Apr 2024 01:13:09 +0200 Subject: [PATCH 3/3] add test for local require --- .../suite/language/mongoDBService.test.ts | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) 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 () {