Replace regex import with acorn AST parser

This commit is contained in:
theit8514 2022-01-03 10:20:46 -05:00
parent 772317a4f1
commit a5d69248dd

@ -1,3 +1,10 @@
/**
* Uses the acorn.js library to parse a script's code into an AST and
* recursively walk through that AST to replace import urls with blobs
*/
import * as walk from "acorn-walk";
import { parse } from "acorn";
import { makeRuntimeRejectMsg } from "./NetscriptEvaluator"; import { makeRuntimeRejectMsg } from "./NetscriptEvaluator";
import { ScriptUrl } from "./Script/ScriptUrl"; import { ScriptUrl } from "./Script/ScriptUrl";
import { WorkerScript } from "./Netscript/WorkerScript"; import { WorkerScript } from "./Netscript/WorkerScript";
@ -125,15 +132,35 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
// import {foo} from "blob://<uuid>" // import {foo} from "blob://<uuid>"
// //
// Where the blob URL contains the script content. // Where the blob URL contains the script content.
let transformedCode = script.code.replace(
/((?:from|import)\s+(?:'|"))(?:\.\/)?([^'"]+)('|")/g, // Parse the code into an ast tree
(unmodified, prefix, filename, suffix) => { const ast: any = parse(script.code, { sourceType: "module", ecmaVersion: "latest", ranges: true });
const isAllowedImport = scripts.some((s) => areImportsEquals(s.filename, filename));
if (!isAllowedImport) return unmodified; const importNodes: Array<any> = [];
// Walk the nodes of this tree and find any import declaration statements.
walk.simple(ast, {
ImportDeclaration(node: any) {
// Push this import onto the stack to replace
importNodes.push({
filename: node.source.value,
start: node.source.range[0] + 1,
end: node.source.range[1] - 1
});
}
});
// Sort the nodes from last start index to first. This replaces the last import with a blob first,
// preventing the ranges for other imports from being shifted.
importNodes.sort((a, b) => b.start - a.start);
let transformedCode = script.code;
// Loop through each node and replace the script name with a blob url.
for (const node of importNodes) {
const filename = node.filename.startsWith("./") ? node.filename.substring(2) : node.filename;
// Find the corresponding script. // Find the corresponding script.
const [importedScript] = scripts.filter((s) => areImportsEquals(s.filename, filename)); const matchingScripts = scripts.filter((s) => areImportsEquals(s.filename, filename));
if (matchingScripts.length === 0) continue;
const [importedScript] = matchingScripts;
// Check to see if the urls for this script are stored in the cache by the hash value. // Check to see if the urls for this script are stored in the cache by the hash value.
let urls = ImportCache.get(importedScript.hash()); let urls = ImportCache.get(importedScript.hash());
// If we don't have it in the cache, then we need to generate the urls for it. // If we don't have it in the cache, then we need to generate the urls for it.
@ -148,9 +175,8 @@ function _getScriptUrls(script: Script, scripts: Script[], seen: Script[]): Scri
ImportCache.store(importedScript.hash(), urls); ImportCache.store(importedScript.hash(), urls);
// Replace the blob inside the import statement. // Replace the blob inside the import statement.
return [prefix, blob, suffix].join(""); transformedCode = transformedCode.substring(0, node.start) + blob + transformedCode.substring(node.end);
}, }
);
// We automatically define a print function() in the NetscriptJS module so that // We automatically define a print function() in the NetscriptJS module so that
// accidental calls to window.print() do not bring up the "print screen" dialog // accidental calls to window.print() do not bring up the "print screen" dialog