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,32 +132,51 @@ 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,
(unmodified, prefix, filename, suffix) => {
const isAllowedImport = scripts.some((s) => areImportsEquals(s.filename, filename));
if (!isAllowedImport) return unmodified;
// Find the corresponding script. // Parse the code into an ast tree
const [importedScript] = scripts.filter((s) => areImportsEquals(s.filename, filename)); const ast: any = parse(script.code, { sourceType: "module", ecmaVersion: "latest", ranges: true });
// Check to see if the urls for this script are stored in the cache by the hash value. const importNodes: Array<any> = [];
let urls = ImportCache.get(importedScript.hash()); // Walk the nodes of this tree and find any import declaration statements.
// If we don't have it in the cache, then we need to generate the urls for it. walk.simple(ast, {
if (!urls) { ImportDeclaration(node: any) {
// Try to get a URL for the requested script and its dependencies. // Push this import onto the stack to replace
urls = _getScriptUrls(importedScript, scripts, seen); 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;
// The top url in the stack is the replacement import file for this script. // Find the corresponding script.
urlStack.push(...urls); const matchingScripts = scripts.filter((s) => areImportsEquals(s.filename, filename));
const blob = urls[urls.length - 1].url; if (matchingScripts.length === 0) continue;
ImportCache.store(importedScript.hash(), urls);
// Replace the blob inside the import statement. const [importedScript] = matchingScripts;
return [prefix, blob, suffix].join(""); // Check to see if the urls for this script are stored in the cache by the hash value.
}, 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 (!urls) {
// Try to get a URL for the requested script and its dependencies.
urls = _getScriptUrls(importedScript, scripts, seen);
}
// The top url in the stack is the replacement import file for this script.
urlStack.push(...urls);
const blob = urls[urls.length - 1].url;
ImportCache.store(importedScript.hash(), urls);
// Replace the blob inside the import statement.
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