Skip to content
17 changes: 17 additions & 0 deletions packages/bruno-app/src/utils/codemirror/javascript-lint.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ if (!SERVER_RENDERED) {
CodeMirror = require('codemirror');
const { filter } = require('lodash');

const TOP_LEVEL_AWAIT_DYNAMIC_IMPORT_PATTERN = /\bawait(?:\s+|\/\*[^*]*\*+(?:[^/*][^*]*\*+)*\/\s*)+import\s*\(/;

function validator(text, options) {
if (!window.JSHINT) {
if (window.console) {
Expand Down Expand Up @@ -61,11 +63,26 @@ if (!SERVER_RENDERED) {
* codemirror error: "Missing semicolon."
* - W024: 'await' used as identifier (JSHint doesn't recognize top-level await syntax)
* codemirror error: "Expected an identifier and instead saw 'await' (a reserved word)."
* - E033: 'import' in dynamic import after top-level await
* codemirror error: "Expected an operator and instead saw 'import'."
*
* Once JSHINT top level await support is added, this file can be removed
* and we can use the default javascript-lint addon from codemirror
*/
errors = filter(errors, (error) => {
if (
error.code === 'E033'
) {
if (
error.a === 'import'
&& error.evidence
&& TOP_LEVEL_AWAIT_DYNAMIC_IMPORT_PATTERN.test(error.evidence)
&& error.scope === '(main)'
Comment thread
coderabbitai[bot] marked this conversation as resolved.
) {
return false;
}
}

if (error.code === 'E058' || error.code === 'W024') {
if (
error.evidence
Expand Down
52 changes: 52 additions & 0 deletions packages/bruno-app/src/utils/codemirror/javascript-lint.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { describe, it, expect, beforeEach, jest } = require('@jest/globals');
const { JSHINT } = require('jshint');

jest.mock('codemirror', () => {
const codemirror = require('test-utils/mocks/codemirror');
return codemirror;
});

describe('javascript lint', () => {
let CodeMirror;
let lintJavascript;

beforeEach(() => {
jest.resetModules();
window.JSHINT = JSHINT;

require('./javascript-lint');
CodeMirror = require('codemirror');
CodeMirror.Pos = (line, ch) => ({ line, ch });
lintJavascript = CodeMirror.lint.javascript;
});

it('does not report top-level await dynamic import errors', () => {
const result = lintJavascript('const helper = await import("./helper.mjs");');

expect(result).toEqual([]);
});

it('does not report top-level await dynamic import errors split across lines', () => {
const result = lintJavascript('const helper = await\n import("./helper.mjs");');

expect(result).toEqual([]);
});

it('does not report top-level await dynamic import errors with a comment between await and import', () => {
const result = lintJavascript('const helper = await /* load esm */ import("./helper.mjs");');

expect(result).toEqual([]);
});

it('continues to report unrelated syntax errors', () => {
const result = lintJavascript('const value = ;');

expect(result).toEqual(
expect.arrayContaining([
expect.objectContaining({
severity: 'error'
})
])
);
});
});
56 changes: 1 addition & 55 deletions packages/bruno-js/src/sandbox/node-vm/cjs-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,7 @@ const fs = require('node:fs');
const path = require('node:path');
const nodeModule = require('node:module');

const { isBuiltinModule, isPathWithinAllowedRoots } = require('./utils');

/**
* Resolve a local module path, handling files and directories
* Follows Node.js resolution algorithm:
* 1. Exact path (with extension)
* 2. Path + .js extension
* 3. Directory with package.json (main field)
* 4. Directory with index.js
* @param {string} fromDir - Directory to resolve from
* @param {string} moduleName - Module name/path
* @returns {string} Resolved absolute path
*/
function resolveLocalModulePath(fromDir, moduleName) {
const basePath = path.resolve(fromDir, moduleName);

// 1. If has extension, use as-is
if (path.extname(moduleName)) {
return path.normalize(basePath);
}

// 2. Try with .js extension
const withJs = basePath + '.js';
if (fs.existsSync(withJs)) {
return path.normalize(withJs);
}

// 3. Check if it's a directory
if (fs.existsSync(basePath) && fs.statSync(basePath).isDirectory()) {
// 3a. Check for package.json with main field
const pkgPath = path.join(basePath, 'package.json');
if (fs.existsSync(pkgPath)) {
try {
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
if (pkg.main) {
const mainPath = path.resolve(basePath, pkg.main);
if (fs.existsSync(mainPath)) {
return path.normalize(mainPath);
}
}
} catch {
// Ignore JSON parse errors, fall through to index.js
}
}

// 3b. Check for index.js
const indexPath = path.join(basePath, 'index.js');
if (fs.existsSync(indexPath)) {
return path.normalize(indexPath);
}
}

// 4. Fall back to original path (will likely fail with file not found)
return path.normalize(basePath);
}
const { isBuiltinModule, isPathWithinAllowedRoots, resolveLocalModulePath } = require('./utils');

/**
* Creates a custom require function with enhanced security and local module support
Expand Down
Loading