MISC: Support JSX, TS, TSX script files (#1216)

This commit is contained in:
catloversg 2024-07-15 04:47:10 +07:00 committed by GitHub
parent 783120c886
commit 864613c616
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 895 additions and 201 deletions

@ -19,5 +19,6 @@ module.exports = {
"^monaco-editor$": "<rootDir>/test/__mocks__/NullMock.js",
"^monaco-vim$": "<rootDir>/test/__mocks__/NullMock.js",
"/utils/Protections$": "<rootDir>/test/__mocks__/NullMock.js",
"@swc/wasm-web": "@swc/core",
},
};

@ -28,7 +28,7 @@ Data in the specified text file.
RAM cost: 0 GB
This function is used to read data from a text file (.txt) or script (.js or .script).
This function is used to read data from a text file (.txt, .json) or script (.js, .jsx, .ts, .tsx, .script).
This function will return the data in the specified file. If the file does not exist, an empty string will be returned.

@ -30,7 +30,7 @@ True if the data was successfully retrieved from the URL, false otherwise.
RAM cost: 0 GB
Retrieves data from a URL and downloads it to a file on the specified server. The data can only be downloaded to a script (.js or .script) or a text file (.txt). If the file already exists, it will be overwritten by this command. Note that it will not be possible to download data from many websites because they do not allow cross-origin resource sharing (CORS).
Retrieves data from a URL and downloads it to a file on the specified server. The data can only be downloaded to a script (.js, .jsx, .ts, .tsx, .script) or a text file (.txt, .json). If the file already exists, it will be overwritten by this command. Note that it will not be possible to download data from many websites because they do not allow cross-origin resource sharing (CORS).
IMPORTANT: This is an asynchronous function that returns a Promise. The Promises resolved value will be a boolean indicating whether or not the data was successfully retrieved from the URL. Because the function is async and returns a Promise, it is recommended you use wget in NetscriptJS (Netscript 2.0).

@ -28,7 +28,7 @@ void
RAM cost: 0 GB
This function can be used to write data to a text file (.txt) or a script (.js or .script).
This function can be used to write data to a text file (.txt, .json) or a script (.js, .jsx, .ts, .tsx, .script).
This function will write data to that file. If the specified file does not exist, then it will be created. The third argument mode defines how the data will be written to the file. If mode is set to “w”, then the data is written in “write” mode which means that it will overwrite all existing data on the file. If mode is set to any other value then the data will be written in “append” mode which means that the data will be added at the end of the file.

261
package-lock.json generated

@ -10,6 +10,7 @@
"hasInstallScript": true,
"license": "SEE LICENSE IN license.txt",
"dependencies": {
"@babel/standalone": "^7.24.4",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@material-ui/core": "^4.12.4",
@ -17,10 +18,12 @@
"@mui/material": "^5.14.12",
"@mui/styles": "^5.14.12",
"@mui/system": "^5.14.12",
"@swc/wasm-web": "^1.4.14",
"@types/estree": "^1.0.2",
"@types/react-syntax-highlighter": "^15.5.8",
"acorn": "^8.10.0",
"acorn-walk": "^8.2.0",
"acorn": "^8.11.3",
"acorn-jsx-walk": "^2.0.0",
"acorn-walk": "^8.3.2",
"arg": "^5.0.2",
"bcryptjs": "^2.4.3",
"better-react-mathjax": "^2.0.3",
@ -54,6 +57,8 @@
"@microsoft/api-documenter": "^7.23.9",
"@microsoft/api-extractor": "^7.38.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@swc/core": "^1.4.14",
"@types/babel__standalone": "^7.1.7",
"@types/bcryptjs": "^2.4.4",
"@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.5",
@ -1866,6 +1871,14 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/standalone": {
"version": "7.24.4",
"resolved": "https://registry.npmjs.org/@babel/standalone/-/standalone-7.24.4.tgz",
"integrity": "sha512-V4uqWeedadiuiCx5P5OHYJZ1PehdMpcBccNCEptKFGPiZIY3FI5f2ClxUl4r5wZ5U+ohcQ+4KW6jX2K6xXzq4Q==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
@ -4118,6 +4131,224 @@
"@sinonjs/commons": "^3.0.0"
}
},
"node_modules/@swc/core": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.14.tgz",
"integrity": "sha512-tHXg6OxboUsqa/L7DpsCcFnxhLkqN/ht5pCwav1HnvfthbiNIJypr86rNx4cUnQDJepETviSqBTIjxa7pSpGDQ==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@swc/counter": "^0.1.2",
"@swc/types": "^0.1.5"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.4.14",
"@swc/core-darwin-x64": "1.4.14",
"@swc/core-linux-arm-gnueabihf": "1.4.14",
"@swc/core-linux-arm64-gnu": "1.4.14",
"@swc/core-linux-arm64-musl": "1.4.14",
"@swc/core-linux-x64-gnu": "1.4.14",
"@swc/core-linux-x64-musl": "1.4.14",
"@swc/core-win32-arm64-msvc": "1.4.14",
"@swc/core-win32-ia32-msvc": "1.4.14",
"@swc/core-win32-x64-msvc": "1.4.14"
},
"peerDependencies": {
"@swc/helpers": "^0.5.0"
},
"peerDependenciesMeta": {
"@swc/helpers": {
"optional": true
}
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.14.tgz",
"integrity": "sha512-8iPfLhYNspBl836YYsfv6ErXwDUqJ7IMieddV3Ey/t/97JAEAdNDUdtTKDtbyP0j/Ebyqyn+fKcqwSq7rAof0g==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.14.tgz",
"integrity": "sha512-9CqSj8uRZ92cnlgAlVaWMaJJBdxtNvCzJxaGj5KuIseeG6Q0l1g+qk8JcU7h9dAsH9saHTNwNFBVGKQo0W0ujg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.14.tgz",
"integrity": "sha512-mfd5JArPITTzMjcezH4DwMw+BdjBV1y25Khp8itEIpdih9ei+fvxOOrDYTN08b466NuE2dF2XuhKtRLA7fXArQ==",
"cpu": [
"arm"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.14.tgz",
"integrity": "sha512-3Lqlhlmy8MVRS9xTShMaPAp0oyUt0KFhDs4ixJsjdxKecE0NJSV/MInuDmrkij1C8/RQ2wySRlV9np5jK86oWw==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.14.tgz",
"integrity": "sha512-n0YoCa64TUcJrbcXIHIHDWQjdUPdaXeMHNEu7yyBtOpm01oMGTKP3frsUXIABLBmAVWtKvqit4/W1KVKn5gJzg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.14.tgz",
"integrity": "sha512-CGmlwLWbfG1dB4jZBJnp2IWlK5xBMNLjN7AR5kKA3sEpionoccEnChOEvfux1UdVJQjLRKuHNV9yGyqGBTpxfQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.14.tgz",
"integrity": "sha512-xq4npk8YKYmNwmr8fbvF2KP3kUVdZYfXZMQnW425gP3/sn+yFQO8Nd0bGH40vOVQn41kEesSe0Z5O/JDor2TgQ==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.14.tgz",
"integrity": "sha512-imq0X+gU9uUe6FqzOQot5gpKoaC00aCUiN58NOzwp0QXEupn8CDuZpdBN93HiZswfLruu5jA1tsc15x6v9p0Yg==",
"cpu": [
"arm64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.14.tgz",
"integrity": "sha512-cH6QpXMw5D3t+lpx6SkErHrxN0yFzmQ0lgNAJxoDRiaAdDbqA6Col8UqUJwUS++Ul6aCWgNhCdiEYehPaoyDPA==",
"cpu": [
"ia32"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.14.tgz",
"integrity": "sha512-FmZ4Tby4wW65K/36BKzmuu7mlq7cW5XOxzvufaSNVvQ5PN4OodAlqPjToe029oma4Av+ykJiif64scMttyNAzg==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=10"
}
},
"node_modules/@swc/counter": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
"dev": true
},
"node_modules/@swc/types": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.6.tgz",
"integrity": "sha512-/JLo/l2JsT/LRd80C3HfbmVpxOAJ11FO2RCEslFrgzLltoP9j8XIbsyDcfCt2WWyX+CM96rBoNM+IToAkFOugg==",
"dev": true,
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@swc/wasm-web": {
"version": "1.4.14",
"resolved": "https://registry.npmjs.org/@swc/wasm-web/-/wasm-web-1.4.14.tgz",
"integrity": "sha512-AE9TBrhFnV0bt38ZPfgkT8SmqhYY4RzxoZcf6eNKC7dRQXlobQLi4+qwdjHSp+BE3EQ02U3gUwctsK6Nr7Pqaw=="
},
"node_modules/@szmarczak/http-timer": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
@ -4167,6 +4398,15 @@
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__standalone": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/@types/babel__standalone/-/babel__standalone-7.1.7.tgz",
"integrity": "sha512-4RUJX9nWrP/emaZDzxo/+RYW8zzLJTXWJyp2k78HufG459HCz754hhmSymt3VFOU6/Wy+IZqfPvToHfLuGOr7w==",
"dev": true,
"dependencies": {
"@types/babel__core": "^7.1.0"
}
},
"node_modules/@types/babel__template": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.2.tgz",
@ -5215,9 +5455,9 @@
}
},
"node_modules/acorn": {
"version": "8.10.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"bin": {
"acorn": "bin/acorn"
},
@ -5253,10 +5493,15 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/acorn-jsx-walk": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/acorn-jsx-walk/-/acorn-jsx-walk-2.0.0.tgz",
"integrity": "sha512-uuo6iJj4D4ygkdzd6jPtcxs8vZgDX9YFIkqczGImoypX2fQ4dVImmu3UzA4ynixCIMTrEOWW+95M2HuBaCEOVA=="
},
"node_modules/acorn-walk": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz",
"integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==",
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz",
"integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==",
"engines": {
"node": ">=0.4.0"
}

@ -10,6 +10,7 @@
"url": "https://github.com/bitburner-official/bitburner-src/issues"
},
"dependencies": {
"@babel/standalone": "^7.24.4",
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@material-ui/core": "^4.12.4",
@ -17,10 +18,12 @@
"@mui/material": "^5.14.12",
"@mui/styles": "^5.14.12",
"@mui/system": "^5.14.12",
"@swc/wasm-web": "^1.4.14",
"@types/estree": "^1.0.2",
"@types/react-syntax-highlighter": "^15.5.8",
"acorn": "^8.10.0",
"acorn-walk": "^8.2.0",
"acorn": "^8.11.3",
"acorn-jsx-walk": "^2.0.0",
"acorn-walk": "^8.3.2",
"arg": "^5.0.2",
"bcryptjs": "^2.4.3",
"better-react-mathjax": "^2.0.3",
@ -55,6 +58,8 @@
"@microsoft/api-documenter": "^7.23.9",
"@microsoft/api-extractor": "^7.38.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.11",
"@swc/core": "^1.4.14",
"@types/babel__standalone": "^7.1.7",
"@types/bcryptjs": "^2.4.4",
"@types/escodegen": "^0.0.7",
"@types/file-saver": "^2.0.5",

@ -11,3 +11,23 @@ declare module "*.png" {
declare interface Document {
achievements: string[];
}
declare global {
/**
* We use Babel Parser. It's one of many internal packages of babel-standalone, and those packages are not exposed in
* the declaration file.
* Ref: https://babeljs.io/docs/babel-standalone#internal-packages
*/
declare module "@babel/standalone" {
export const packages: {
parser: {
parse: (
code: string,
option: any,
) => {
program: import("../utils/ScriptTransformer").BabelASTProgram;
};
};
};
}
}

@ -30,6 +30,7 @@ import { workerScripts } from "../Netscript/WorkerScripts";
import { getRecordValues } from "../Types/Record";
import { ServerConstants } from "../Server/data/Constants";
import { canAccessBitNodeFeature, isBitNodeFinished, knowAboutBitverse } from "../BitNode/BitNodeUtils";
import { isLegacyScript } from "../Paths/ScriptFilePath";
// Unable to correctly cast the JSON data into AchievementDataJson type otherwise...
const achievementData = (<AchievementDataJson>(<unknown>data)).achievements;
@ -190,7 +191,7 @@ export const achievements: Record<string, Achievement> = {
NS2: {
...achievementData.NS2,
Icon: "ns2",
Condition: () => [...Player.getHomeComputer().scripts.values()].some((s) => s.filename.endsWith(".js")),
Condition: () => [...Player.getHomeComputer().scripts.values()].some((s) => !isLegacyScript(s.filename)),
},
FROZE: {
...achievementData.FROZE,

@ -2,7 +2,7 @@
In Bitburner "Programs" refer specifically to the list of `.exe` files found in the Programs tab of the side menu.
Unlike `.js` [scripts](scripts.md) you write for yourself with JavaScript, Programs are supplied to you by Bitburner and are only "programs" in name; they do not require or allow you to access actual lines of code. Instead once you have a Program you will be able to use it directly as a function in the [Terminal](terminal.md) or scripts.
Unlike [scripts](scripts.md) you write for yourself with JavaScript, Programs are supplied to you by Bitburner and are only "programs" in name; they do not require or allow you to access actual lines of code. Instead once you have a Program you will be able to use it directly as a function in the [Terminal](terminal.md) or scripts.
[n00dles /]> run BruteSSH.exe
[n00dles /]> scan-analyze 10

@ -114,7 +114,7 @@ Check how much [RAM](ram.md) a script requires to run with "n" threads
**nano [script]**
Create/Edit a script.
The name of a script must end with `.js`, but you can also create `.txt` files.
The name of a script must end with a script extension (.js, .jsx, .ts, .tsx, .script). You can also create a text file with a text extension (.txt, .json).
**ps**

@ -5,9 +5,10 @@
import * as walk from "acorn-walk";
import { parse } from "acorn";
import { LoadedModule, ScriptURL, ScriptModule } from "./Script/LoadedModule";
import { Script } from "./Script/Script";
import { ScriptFilePath, resolveScriptFilePath } from "./Paths/ScriptFilePath";
import { LoadedModule, type ScriptURL, type ScriptModule } from "./Script/LoadedModule";
import type { Script } from "./Script/Script";
import type { ScriptFilePath } from "./Paths/ScriptFilePath";
import { FileType, getFileType, getModuleScript, transformScript } from "./utils/ScriptTransformer";
// Acorn type def is straight up incomplete so we have to fill with our own.
export type Node = any;
@ -82,8 +83,26 @@ function generateLoadedModule(script: Script, scripts: Map<ScriptFilePath, Scrip
return script.mod;
}
let scriptCode;
const fileType = getFileType(script.filename);
switch (fileType) {
case FileType.JS:
scriptCode = script.code;
break;
case FileType.JSX:
case FileType.TS:
case FileType.TSX:
scriptCode = transformScript(script.filename, script.code, fileType);
break;
default:
throw new Error(`Invalid file type: ${fileType}. Filename: ${script.filename}, server: ${script.server}.`);
}
if (!scriptCode) {
throw new Error(`Cannot transform script. Filename: ${script.filename}, server: ${script.server}.`);
}
// Inspired by: https://stackoverflow.com/a/43834063/91401
const ast = parse(script.code, { sourceType: "module", ecmaVersion: "latest", ranges: true });
const ast = parse(scriptCode, { sourceType: "module", ecmaVersion: "latest", ranges: true });
interface importNode {
filename: string;
start: number;
@ -121,15 +140,10 @@ function generateLoadedModule(script: Script, scripts: Map<ScriptFilePath, Scrip
// 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 newCode = script.code;
let newCode = scriptCode;
// Loop through each node and replace the script name with a blob url.
for (const node of importNodes) {
const filename = resolveScriptFilePath(node.filename, script.filename, ".js");
if (!filename) throw new Error(`Failed to parse import: ${node.filename}`);
// Find the corresponding script.
const importedScript = scripts.get(filename);
if (!importedScript) continue;
const importedScript = getModuleScript(node.filename, script.filename, scripts);
seenStack.push(script);
importedScript.mod = generateLoadedModule(importedScript, scripts, seenStack);

@ -34,7 +34,7 @@ import { Terminal } from "./Terminal";
import { ScriptArg } from "@nsdefs";
import { CompleteRunOptions, getRunningScriptsByArgs } from "./Netscript/NetscriptHelpers";
import { handleUnknownError } from "./Netscript/ErrorMessages";
import { resolveScriptFilePath, ScriptFilePath } from "./Paths/ScriptFilePath";
import { isLegacyScript, legacyScriptExtension, resolveScriptFilePath, ScriptFilePath } from "./Paths/ScriptFilePath";
import { root } from "./Paths/Directory";
export const NetscriptPorts = new Map<PortNumber, Port>();
@ -158,7 +158,7 @@ function processNetscript1Imports(code: string, workerScript: WorkerScript): { c
walksimple(ast, {
ImportDeclaration: (node: Node) => {
hasImports = true;
const scriptName = resolveScriptFilePath(node.source.value, root, ".script");
const scriptName = resolveScriptFilePath(node.source.value, root, legacyScriptExtension);
if (!scriptName) throw new Error("'Import' failed due to invalid path: " + scriptName);
const script = getScript(scriptName);
if (!script) throw new Error("'Import' failed due to script not found: " + scriptName);
@ -326,7 +326,7 @@ Otherwise, this can also occur if you have attempted to launch a script from a t
workerScripts.set(pid, workerScript);
// Start the script's execution using the correct function for file type
(workerScript.name.endsWith(".js") ? startNetscript2Script : startNetscript1Script)(workerScript)
(isLegacyScript(workerScript.name) ? startNetscript1Script : startNetscript2Script)(workerScript)
// Once the code finishes (either resolved or rejected, doesnt matter), set its
// running status to false
.then(function () {

@ -1,7 +1,7 @@
import { Directory, isAbsolutePath } from "./Directory";
import { FilePath, isFilePath, resolveFilePath } from "./FilePath";
/** Filepath with the additional constraint of having a .cct extension */
/** Filepath with the additional constraint of having a .exe extension */
type WithProgramExtension = string & { __fileType: "Program" };
export type ProgramFilePath = FilePath & WithProgramExtension;

@ -1,14 +1,16 @@
import { Directory } from "./Directory";
import { FilePath, resolveFilePath } from "./FilePath";
/** Type for just checking a .js extension with no other verification*/
/** Type for just checking a script extension with no other verification*/
type WithScriptExtension = string & { __fileType: "Script" };
/** Type for a valid absolute FilePath with a script extension */
export type ScriptFilePath = FilePath & WithScriptExtension;
export const legacyScriptExtension = ".script";
/** Valid extensions. Used for some error messaging. */
export type ScriptExtension = ".js" | ".script";
export const validScriptExtensions: ScriptExtension[] = [".js", ".script"];
export const validScriptExtensions = [".js", ".jsx", ".ts", ".tsx", legacyScriptExtension] as const;
export type ScriptExtension = (typeof validScriptExtensions)[number];
/** Sanitize a player input, resolve any relative paths, and for imports add the correct extension if missing
* @param path The player-provided path to a file. Can contain relative parts.
@ -28,3 +30,7 @@ export function resolveScriptFilePath(
export function hasScriptExtension(path: string): path is WithScriptExtension {
return validScriptExtensions.some((extension) => path.endsWith(extension));
}
export function isLegacyScript(path: string): boolean {
return path.endsWith(legacyScriptExtension);
}

@ -1,7 +1,7 @@
import { Directory } from "./Directory";
import { FilePath, resolveFilePath } from "./FilePath";
/** Filepath with the additional constraint of having a .js extension */
/** Filepath with the additional constraint of having a text extension */
type WithTextExtension = string & { __fileType: "Text" };
export type TextFilePath = FilePath & WithTextExtension;

@ -1,21 +1,30 @@
/**
* Implements RAM Calculation functionality.
*
* Uses the acorn.js library to parse a script's code into an AST and
* recursively walk through that AST, calculating RAM usage along
* the way
* Uses acorn-walk to recursively walk through the AST, calculating RAM usage along the way.
*/
import * as walk from "acorn-walk";
import acorn, { parse } from "acorn";
import type * as acorn from "acorn";
import { extendAcornWalkForTypeScriptNodes } from "../ThirdParty/acorn-typescript-walk";
import { extend as extendAcornWalkForJsxNodes } from "acorn-jsx-walk";
import { RamCalculationErrorCode } from "./RamCalculationErrorCodes";
import { RamCosts, RamCostConstants } from "../Netscript/RamCostGenerator";
import { Script } from "./Script";
import { Node } from "../NetscriptJSEvaluator";
import { ScriptFilePath, resolveScriptFilePath } from "../Paths/ScriptFilePath";
import { ServerName } from "../Types/strings";
import type { Script } from "./Script";
import type { Node } from "../NetscriptJSEvaluator";
import type { ScriptFilePath } from "../Paths/ScriptFilePath";
import type { ServerName } from "../Types/strings";
import { roundToTwo } from "../utils/helpers/roundToTwo";
import {
type AST,
type FileTypeFeature,
getFileType,
getFileTypeFeature,
getModuleScript,
parseAST,
ModuleResolutionError,
} from "../utils/ScriptTransformer";
export interface RamUsageEntry {
type: "ns" | "dom" | "fn" | "misc";
@ -39,6 +48,12 @@ export type RamCalculationFailure = {
export type RamCalculation = RamCalculationSuccess | RamCalculationFailure;
// Extend acorn-walk to support TypeScript nodes.
extendAcornWalkForTypeScriptNodes(walk.base);
// Extend acorn-walk to support JSX nodes.
extendAcornWalkForJsxNodes(walk.base);
// These special strings are used to reference the presence of a given logical
// construct within a user script.
const specialReferenceIF = "__SPECIAL_referenceIf";
@ -61,17 +76,18 @@ function getNumericCost(cost: number | (() => number)): number {
/**
* Parses code into an AST and walks through it recursively to calculate
* RAM usage. Also accounts for imported modules.
* @param otherScripts - All other scripts on the server. Used to account for imported scripts
* @param code - The code being parsed
* @param scriptname - The name of the script that ram needs to be added to
* @param ast - AST of the code being parsed
* @param scriptName - The name of the script that ram needs to be added to
* @param server - Servername of the scripts for Error Message
* @param fileTypeFeature
* @param otherScripts - All other scripts on the server. Used to account for imported scripts
* */
function parseOnlyRamCalculate(
otherScripts: Map<ScriptFilePath, Script>,
code: string,
scriptname: ScriptFilePath,
ast: AST,
scriptName: ScriptFilePath,
server: ServerName,
ns1?: boolean,
fileTypeFeature: FileTypeFeature,
otherScripts: Map<ScriptFilePath, Script>,
): RamCalculation {
/**
* Maps dependent identifiers to their dependencies.
@ -91,14 +107,14 @@ function parseOnlyRamCalculate(
// Scripts we've discovered that need to be parsed.
const parseQueue: ScriptFilePath[] = [];
// Parses a chunk of code with a given module name, and updates parseQueue and dependencyMap.
function parseCode(code: string, moduleName: ScriptFilePath): void {
const result = parseOnlyCalculateDeps(code, moduleName, ns1);
function parseCode(ast: AST, moduleName: ScriptFilePath, fileTypeFeatureOfModule: FileTypeFeature): void {
const result = parseOnlyCalculateDeps(ast, moduleName, fileTypeFeatureOfModule, otherScripts);
completedParses.add(moduleName);
// Add any additional modules to the parse queue;
for (let i = 0; i < result.additionalModules.length; ++i) {
if (!completedParses.has(result.additionalModules[i])) {
parseQueue.push(result.additionalModules[i]);
for (const additionalModule of result.additionalModules) {
if (!completedParses.has(additionalModule) && !parseQueue.includes(additionalModule)) {
parseQueue.push(additionalModule);
}
}
@ -107,24 +123,40 @@ function parseOnlyRamCalculate(
}
// Parse the initial module, which is the "main" script that is being run
const initialModule = scriptname;
parseCode(code, initialModule);
const initialModule = scriptName;
parseCode(ast, initialModule, fileTypeFeature);
// Process additional modules, which occurs if the "main" script has any imports
while (parseQueue.length > 0) {
const nextModule = parseQueue.shift();
if (nextModule === undefined) throw new Error("nextModule should not be undefined");
if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) continue;
if (nextModule === undefined) {
throw new Error("nextModule should not be undefined");
}
if (nextModule.startsWith("https://") || nextModule.startsWith("http://")) {
continue;
}
const script = otherScripts.get(nextModule);
if (!script) {
return {
errorCode: RamCalculationErrorCode.ImportError,
errorMessage: `File: "${nextModule}" not found on server: ${server}`,
errorMessage: `"${nextModule}" does not exist on server: ${server}`,
};
}
parseCode(script.code, nextModule);
const scriptFileType = getFileType(script.filename);
let moduleAST;
try {
moduleAST = parseAST(script.code, scriptFileType);
} catch (error) {
return {
errorCode: RamCalculationErrorCode.ImportError,
errorMessage: `Cannot parse module: ${nextModule}. Filename: ${script.filename}. Reason: ${
error instanceof Error ? error.message : String(error)
}.`,
};
}
parseCode(moduleAST, nextModule, getFileTypeFeature(scriptFileType));
}
// Finally, walk the reference map and generate a ram cost. The initial set of keys to scan
@ -136,7 +168,9 @@ function parseOnlyRamCalculate(
const loadedFns: Record<string, boolean> = {};
while (unresolvedRefs.length > 0) {
const ref = unresolvedRefs.shift();
if (ref === undefined) throw new Error("ref should not be undefined");
if (ref === undefined) {
throw new Error("ref should not be undefined");
}
if (ref.endsWith(specialReferenceRAM)) {
if (ref !== initialModule + specialReferenceRAM) {
@ -169,13 +203,17 @@ function parseOnlyRamCalculate(
const prefix = ref.slice(0, ref.length - 2);
for (const ident of Object.keys(dependencyMap).filter((k) => k.startsWith(prefix))) {
for (const dep of dependencyMap[ident] || []) {
if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep);
if (!resolvedRefs.has(dep)) {
unresolvedRefs.push(dep);
}
}
}
} else {
// An exact reference. Add all dependencies of this ref.
for (const dep of dependencyMap[ref] || []) {
if (!resolvedRefs.has(dep)) unresolvedRefs.push(dep);
if (!resolvedRefs.has(dep)) {
unresolvedRefs.push(dep);
}
}
}
@ -194,14 +232,18 @@ function parseOnlyRamCalculate(
obj: object,
ref: string,
): { func: () => number | number; refDetail: string } | undefined => {
if (!obj) return;
if (!obj) {
return;
}
const elem = Object.entries(obj).find(([key]) => key === ref);
if (elem !== undefined && (typeof elem[1] === "function" || typeof elem[1] === "number")) {
return { func: elem[1], refDetail: `${prefix}${ref}` };
}
for (const [key, value] of Object.entries(obj)) {
const found = findFunc(`${key}.`, value, ref);
if (found) return found;
if (found) {
return found;
}
}
return undefined;
};
@ -222,14 +264,7 @@ function parseOnlyRamCalculate(
return { cost: ram, entries: detailedCosts.filter((e) => e.cost > 0) };
}
export function checkInfiniteLoop(code: string): number[] {
let ast: acorn.Node;
try {
ast = parse(code, { sourceType: "module", ecmaVersion: "latest" });
} catch (e) {
// If code cannot be parsed, do not provide infinite loop detection warning
return [];
}
export function checkInfiniteLoop(ast: AST, code: string): number[] {
function nodeHasTrueTest(node: acorn.Node): boolean {
return node.type === "Literal" && "raw" in node && (node.raw === "true" || node.raw === "1");
}
@ -250,7 +285,7 @@ export function checkInfiniteLoop(code: string): number[] {
const possibleLines: number[] = [];
walk.recursive(
ast,
ast as acorn.Node, // Pretend that ast is an acorn node
{},
{
WhileStatement: (node: Node, st: unknown, walkDeeper: walk.WalkerCallback<any>) => {
@ -282,8 +317,12 @@ interface ParseDepsResult {
* for RAM usage calculations. It also returns an array of additional modules
* that need to be parsed (i.e. are 'import'ed scripts).
*/
function parseOnlyCalculateDeps(code: string, currentModule: ScriptFilePath, ns1?: boolean): ParseDepsResult {
const ast = parse(code, { sourceType: "module", ecmaVersion: "latest" });
function parseOnlyCalculateDeps(
ast: AST,
currentModule: ScriptFilePath,
fileTypeFeature: FileTypeFeature,
otherScripts: Map<ScriptFilePath, Script>,
): ParseDepsResult {
// Everything from the global scope goes in ".". Everything else goes in ".function", where only
// the outermost layer of functions counts.
const globalKey = currentModule + memCheckGlobalKey;
@ -402,17 +441,17 @@ function parseOnlyCalculateDeps(code: string, currentModule: ScriptFilePath, ns1
}
walk.recursive<State>(
ast,
ast as acorn.Node, // Pretend that ast is an acorn node
{ key: globalKey },
Object.assign(
{
ImportDeclaration: (node: Node, st: State) => {
const importModuleName = resolveScriptFilePath(node.source.value, currentModule, ns1 ? ".script" : ".js");
if (!importModuleName)
throw new Error(
`ScriptFilePath couldnt be resolved in ImportDeclaration. Value: ${node.source.value} ScriptFilePath: ${currentModule}`,
);
const rawImportModuleName = node.source.value;
// Skip these modules. They are popular path aliases of NetscriptDefinitions.d.ts.
if (fileTypeFeature.isTypeScript && (rawImportModuleName === "@nsdefs" || rawImportModuleName === "@ns")) {
return;
}
const importModuleName = getModuleScript(rawImportModuleName, currentModule, otherScripts).filename;
additionalModules.push(importModuleName);
// This module's global scope refers to that module's global scope, no matter how we
@ -472,27 +511,31 @@ function parseOnlyCalculateDeps(code: string, currentModule: ScriptFilePath, ns1
}
/**
* Calculate's a scripts RAM Usage
* @param {string} code - The script's code
* @param {ScriptFilePath} scriptname - The script's name. Used to resolve relative paths
* @param {Script[]} otherScripts - All other scripts on the server.
* Used to account for imported scripts
* @param {ServerName} server - Servername of the scripts for Error Message
* @param {boolean} ns1 - Deprecated: is the fileExtension .script or .js
* Calculate RAM usage of a script
*
* @param input - Code's AST or code of the script
* @param scriptName - The script's name. Used to resolve relative paths
* @param server - Servername of the scripts for Error Message
* @param otherScripts - Other scripts on the server
* @returns
*/
export function calculateRamUsage(
code: string,
scriptname: ScriptFilePath,
otherScripts: Map<ScriptFilePath, Script>,
input: AST | string,
scriptName: ScriptFilePath,
server: ServerName,
ns1?: boolean,
otherScripts: Map<ScriptFilePath, Script>,
): RamCalculation {
try {
return parseOnlyRamCalculate(otherScripts, code, scriptname, server, ns1);
} catch (e) {
const fileType = getFileType(scriptName);
const ast = typeof input === "string" ? parseAST(input, fileType) : input;
return parseOnlyRamCalculate(ast, scriptName, server, getFileTypeFeature(fileType), otherScripts);
} catch (error) {
return {
errorCode: RamCalculationErrorCode.SyntaxError,
errorMessage: e instanceof Error ? e.message : undefined,
errorCode:
error instanceof ModuleResolutionError
? RamCalculationErrorCode.ImportError
: RamCalculationErrorCode.SyntaxError,
errorMessage: error instanceof Error ? error.message : String(error),
};
}
}

@ -73,13 +73,7 @@ export class Script implements ContentFile {
* @param {Script[]} otherScripts - Other scripts on the server. Used to process imports
*/
updateRamUsage(otherScripts: Map<ScriptFilePath, Script>): void {
const ramCalc = calculateRamUsage(
this.code,
this.filename,
otherScripts,
this.server,
this.filename.endsWith(".script"),
);
const ramCalc = calculateRamUsage(this.code, this.filename, this.server, otherScripts);
if (ramCalc.cost && ramCalc.cost >= RamCostConstants.Base) {
this.ramUsage = roundToTwo(ramCalc.cost);
this.ramUsageEntries = ramCalc.entries as RamUsageEntry[];

@ -6919,7 +6919,7 @@ export interface NS {
* @remarks
* RAM cost: 0 GB
*
* This function can be used to write data to a text file (.txt) or a script (.js or .script).
* This function can be used to write data to a text file (.txt, .json) or a script (.js, .jsx, .ts, .tsx, .script).
*
* This function will write data to that file. If the specified file does not exist,
* then it will be created. The third argument mode defines how the data will be written to
@ -6965,7 +6965,7 @@ export interface NS {
* @remarks
* RAM cost: 0 GB
*
* This function is used to read data from a text file (.txt) or script (.js or .script).
* This function is used to read data from a text file (.txt, .json) or script (.js, .jsx, .ts, .tsx, .script).
*
* This function will return the data in the specified file.
* If the file does not exist, an empty string will be returned.
@ -7430,7 +7430,7 @@ export interface NS {
* RAM cost: 0 GB
*
* Retrieves data from a URL and downloads it to a file on the specified server.
* The data can only be downloaded to a script (.js or .script) or a text file (.txt).
* The data can only be downloaded to a script (.js, .jsx, .ts, .tsx, .script) or a text file (.txt, .json).
* If the file already exists, it will be overwritten by this command.
* Note that it will not be possible to download data from many websites because they
* do not allow cross-origin resource sharing (CORS).

@ -71,6 +71,18 @@ export class ScriptEditor {
languageDefaults.addExtraLib(reactTypes, "react.d.ts");
languageDefaults.addExtraLib(reactDomTypes, "react-dom.d.ts");
}
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(),
jsx: monaco.languages.typescript.JsxEmit.ReactJSX,
allowUmdGlobalAccess: true,
});
/**
* Ignore these errors in the editor:
* - Cannot find module ''. Did you mean to set the 'moduleResolution' option to 'nodenext', or to add aliases to the 'paths' option?(2792)
*/
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
diagnosticCodesToIgnore: [2792],
});
monaco.languages.json.jsonDefaults.setModeConfiguration({
...monaco.languages.json.jsonDefaults.modeConfiguration,
//completion should be disabled because the

@ -1,20 +1,21 @@
import React, { useContext, useState } from "react";
import { Settings } from "../../Settings/Settings";
import { calculateRamUsage } from "../../Script/RamCalculations";
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { formatRam } from "../../ui/formatNumber";
import { useBoolean } from "../../ui/React/hooks";
import { calculateRamUsage, type RamCalculationFailure } from "../../Script/RamCalculations";
import { BaseServer } from "../../Server/BaseServer";
import { Settings } from "../../Settings/Settings";
import { useBoolean } from "../../ui/React/hooks";
import { formatRam } from "../../ui/formatNumber";
import { Options } from "./Options";
import { FilePath } from "../../Paths/FilePath";
import { hasScriptExtension } from "../../Paths/ScriptFilePath";
import type { AST } from "../../utils/ScriptTransformer";
import type { Options } from "./Options";
import { type ScriptFilePath } from "../../Paths/ScriptFilePath";
export interface ScriptEditorContextShape {
ram: string;
ramEntries: string[][];
updateRAM: (newCode: string | null, filename: FilePath | null, server: BaseServer | null) => void;
showRAMError: (error?: RamCalculationFailure) => void;
updateRAM: (ast: AST, path: ScriptFilePath, server: BaseServer) => void;
isUpdatingRAM: boolean;
startUpdatingRAM: () => void;
@ -30,13 +31,30 @@ export function ScriptEditorContextProvider({ children }: { children: React.Reac
const [ram, setRAM] = useState("RAM: ???");
const [ramEntries, setRamEntries] = useState<string[][]>([["???", ""]]);
const updateRAM: ScriptEditorContextShape["updateRAM"] = (newCode, filename, server) => {
if (newCode == null || filename == null || server == null || !hasScriptExtension(filename)) {
const showRAMError: ScriptEditorContextShape["showRAMError"] = (error) => {
if (!error) {
setRAM("N/A");
setRamEntries([["N/A", ""]]);
return;
}
const ramUsage = calculateRamUsage(newCode, filename, server.scripts, server.hostname);
let errorType;
switch (error.errorCode) {
case RamCalculationErrorCode.SyntaxError:
errorType = "Syntax Error";
break;
case RamCalculationErrorCode.ImportError:
errorType = "Import Error";
break;
default:
errorType = "Unknown Error";
break;
}
setRAM(`RAM: ${errorType}`);
setRamEntries([[errorType, error.errorMessage ?? ""]]);
};
const updateRAM: ScriptEditorContextShape["updateRAM"] = (ast, path, server) => {
const ramUsage = calculateRamUsage(ast, path, server.hostname, server.scripts);
if (ramUsage.cost && ramUsage.cost > 0) {
const entries = ramUsage.entries?.sort((a, b) => b.cost - a.cost) ?? [];
const entriesDisp = [];
@ -50,18 +68,10 @@ export function ScriptEditorContextProvider({ children }: { children: React.Reac
}
if (ramUsage.errorCode !== undefined) {
setRamEntries([["Syntax Error", ramUsage.errorMessage ?? ""]]);
switch (ramUsage.errorCode) {
case RamCalculationErrorCode.ImportError:
setRAM("RAM: Import Error");
break;
case RamCalculationErrorCode.SyntaxError:
setRAM("RAM: Syntax Error");
break;
}
showRAMError(ramUsage);
} else {
setRAM("RAM: Syntax Error");
setRamEntries([["Syntax Error", ""]]);
setRAM("RAM: Unknown Error");
setRamEntries([["Unknown Error", ""]]);
}
};
@ -96,7 +106,17 @@ export function ScriptEditorContextProvider({ children }: { children: React.Reac
return (
<ScriptEditorContext.Provider
value={{ ram, ramEntries, updateRAM, isUpdatingRAM, startUpdatingRAM, finishUpdatingRAM, options, saveOptions }}
value={{
ram,
ramEntries,
showRAMError,
updateRAM,
isUpdatingRAM,
startUpdatingRAM,
finishUpdatingRAM,
options,
saveOptions,
}}
>
{children}
</ScriptEditorContext.Provider>

@ -30,6 +30,9 @@ import { NoOpenScripts } from "./NoOpenScripts";
import { ScriptEditorContextProvider, useScriptEditorContext } from "./ScriptEditorContext";
import { useVimEditor } from "./useVimEditor";
import { useCallback } from "react";
import { type AST, getFileType, parseAST } from "../../utils/ScriptTransformer";
import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes";
import { hasScriptExtension, isLegacyScript } from "../../Paths/ScriptFilePath";
interface IProps {
// Map of filename -> code
@ -44,7 +47,7 @@ function Root(props: IProps): React.ReactElement {
const rerender = useRerender();
const editorRef = useRef<IStandaloneCodeEditor | null>(null);
const { updateRAM, startUpdatingRAM, finishUpdatingRAM } = useScriptEditorContext();
const { showRAMError, updateRAM, startUpdatingRAM, finishUpdatingRAM } = useScriptEditorContext();
let decorations: monaco.editor.IEditorDecorationsCollection | undefined;
@ -112,11 +115,14 @@ function Root(props: IProps): React.ReactElement {
return () => document.removeEventListener("keydown", keydown);
}, [save]);
function infLoop(newCode: string): void {
if (editorRef.current === null || currentScript === null) return;
if (!decorations) decorations = editorRef.current.createDecorationsCollection();
if (!currentScript.path.endsWith(".js")) return;
const possibleLines = checkInfiniteLoop(newCode);
function infLoop(ast: AST, code: string): void {
if (editorRef.current === null || currentScript === null || isLegacyScript(currentScript.path)) {
return;
}
if (!decorations) {
decorations = editorRef.current.createDecorationsCollection();
}
const possibleLines = checkInfiniteLoop(ast, code);
if (possibleLines.length !== 0) {
decorations.set(
possibleLines.map((awaitWarning) => ({
@ -131,21 +137,34 @@ function Root(props: IProps): React.ReactElement {
glyphMarginClassName: "myGlyphMarginClass",
glyphMarginHoverMessage: {
value:
"Possible infinite loop, await something. If this is a false-positive, use `// @ignore-infinite` to suppress.",
"Possible infinite loop, await something. If this is a false positive, use `// @ignore-infinite` to suppress.",
},
},
})),
);
} else decorations.clear();
} else {
decorations.clear();
}
}
const debouncedCodeParsing = debounce((newCode: string) => {
infLoop(newCode);
updateRAM(
!currentScript || currentScript.isTxt ? null : newCode,
currentScript && currentScript.path,
currentScript && GetServer(currentScript.hostname),
);
let server;
if (!currentScript || !hasScriptExtension(currentScript.path) || !(server = GetServer(currentScript.hostname))) {
showRAMError();
return;
}
let ast;
try {
ast = parseAST(newCode, getFileType(currentScript.path));
} catch (error) {
showRAMError({
errorCode: RamCalculationErrorCode.SyntaxError,
errorMessage: error instanceof Error ? error.message : String(error),
});
return;
}
infLoop(ast, newCode);
updateRAM(ast, currentScript.path, server);
finishUpdatingRAM();
}, 300);

@ -1,6 +1,7 @@
import { GetServer } from "../../Server/AllServers";
import { editor, Uri } from "monaco-editor";
import { OpenScript } from "./OpenScript";
import { getFileType, FileType } from "../../utils/ScriptTransformer";
function getServerCode(scripts: OpenScript[], index: number): string | null {
const openScript = scripts[index];
@ -26,7 +27,29 @@ function makeModel(hostname: string, filename: string, code: string) {
scheme: "file",
path: `${hostname}/${filename}`,
});
const language = filename.endsWith(".txt") ? "plaintext" : filename.endsWith(".json") ? "json" : "javascript";
let language;
const fileType = getFileType(filename);
switch (fileType) {
case FileType.PLAINTEXT:
language = "plaintext";
break;
case FileType.JSON:
language = "json";
break;
case FileType.JS:
case FileType.JSX:
language = "javascript";
break;
case FileType.TS:
case FileType.TSX:
language = "typescript";
break;
case FileType.NS1:
language = "javascript";
break;
default:
throw new Error(`Invalid file type: ${fileType}. Filename: ${filename}.`);
}
//if somehow a model already exist return it
return editor.getModel(uri) ?? editor.createModel(code, language, uri);
}

@ -52,10 +52,11 @@ const TemplatedHelpTexts: Record<string, (command: string) => string[]> = {
return [
`Usage: ${command} [file names...] | [glob]`,
` `,
`Opens up the specified file(s) in the Script Editor. Only scripts (.js, or .script) or text files (.txt) `,
`can be edited using the Script Editor. If a file does not exist a new one will be created`,
`Opens up the specified file(s) in the Script Editor. Only scripts (.js, .jsx, .ts, .tsx, .script) `,
`or text files (.txt, .json) can be edited using the Script Editor. If a file does not exist, a new `,
`one will be created.`,
` `,
`If provided a glob as the only argument, ${command} can spider directories and open all matching `,
`If a glob is provided as the only argument, ${command} can crawl directories and open all matching `,
`files at once. ${command} cannot create files using globs, so your scripts must already exist.`,
` `,
`Examples:`,
@ -456,8 +457,8 @@ export const HelpTexts: Record<string, string[]> = {
"Usage: scp [file names...] [target server]",
" ",
"Copies the specified file(s) from the current server to the target server. ",
"This command only works for script files (.script or .js extension), literature files (.lit extension), ",
"and text files (.txt extension). ",
"This command only works for script files (.js, .jsx, .ts, .tsx, .script), text files (.txt, .json), ",
"and literature files (.lit).",
"The second argument passed in must be the hostname or IP of the target server. Examples:",
" ",
" scp foo.script n00dles",
@ -518,8 +519,8 @@ export const HelpTexts: Record<string, string[]> = {
"Usage: wget [url] [target file]",
" ",
"Retrieves data from a URL and downloads it to a file on the current server. The data can only ",
"be downloaded to a script (.script or .js) or a text file (.txt). If the file already exists, ",
"it will be overwritten by this command.",
"be downloaded to a script (.js, .jsx, .ts, .tsx, .script) or a text file (.txt, .json).",
"If the file already exists, it will be overwritten by this command.",
" ",
"Note that it will not be possible to download data from many websites because they do not allow ",
"cross-origin resource sharing (CORS). Example:",

@ -20,7 +20,9 @@ export function cat(args: (string | number | boolean)[], server: BaseServer): vo
return dialogBoxCreate(`${file.filename}\n\n${file.content}`);
}
if (!path.endsWith(".msg") && !path.endsWith(".lit")) {
return Terminal.error("Invalid file extension. Filename must end with .msg, .txt, .lit, .script or .js");
return Terminal.error(
"Invalid file extension. Filename must end with .msg, .lit, a script extension (.js, .jsx, .ts, .tsx, .script) or a text extension (.txt, .json)",
);
}
// Message

@ -2,10 +2,11 @@ import { Terminal } from "../../../Terminal";
import { ScriptEditorRouteOptions, Page } from "../../../ui/Router";
import { Router } from "../../../ui/GameRoot";
import { BaseServer } from "../../../Server/BaseServer";
import { ScriptFilePath, hasScriptExtension } from "../../../Paths/ScriptFilePath";
import { type ScriptFilePath, hasScriptExtension, isLegacyScript } from "../../../Paths/ScriptFilePath";
import { TextFilePath, hasTextExtension } from "../../../Paths/TextFilePath";
import { getGlobbedFileMap } from "../../../Paths/GlobbedFiles";
import { sendDeprecationNotice } from "./deprecation";
import { getFileType, getFileTypeFeature } from "../../../utils/ScriptTransformer";
// 2.3: Globbing implementation was removed from this file. Globbing will be reintroduced as broader functionality and integrated here.
@ -14,14 +15,22 @@ interface EditorParameters {
server: BaseServer;
}
function isNs2(filename: string): boolean {
return filename.endsWith(".js");
}
function getScriptTemplate(path: string): string {
if (isLegacyScript(path)) {
return "";
}
const fileTypeFeature = getFileTypeFeature(getFileType(path));
if (fileTypeFeature.isTypeScript) {
return `export async function main(ns: NS) {
const newNs2Template = `/** @param {NS} ns */
}`;
} else {
return `/** @param {NS} ns */
export async function main(ns) {
}`;
}
}
export function commonEditor(
command: string,
@ -30,14 +39,16 @@ export function commonEditor(
): void {
if (args.length < 1) return Terminal.error(`Incorrect usage of ${command} command. Usage: ${command} [scriptname]`);
const files = new Map<ScriptFilePath | TextFilePath, string>();
let hasNs1 = false;
let hasLegacyScript = false;
for (const arg of args) {
const pattern = String(arg);
// Glob of existing files
if (pattern.includes("*") || pattern.includes("?")) {
for (const [path, file] of getGlobbedFileMap(pattern, server, Terminal.currDir)) {
if (path.endsWith(".script")) hasNs1 = true;
if (isLegacyScript(path)) {
hasLegacyScript = true;
}
files.set(path, file.content);
}
continue;
@ -49,12 +60,13 @@ export function commonEditor(
if (!hasScriptExtension(path) && !hasTextExtension(path)) {
return Terminal.error(`${command}: Only scripts or text files can be edited. Invalid file type: ${arg}`);
}
if (path.endsWith(".script")) hasNs1 = true;
if (isLegacyScript(path)) {
hasLegacyScript = true;
}
const file = server.getContentFile(path);
const content = file ? file.content : isNs2(path) ? newNs2Template : "";
files.set(path, content);
files.set(path, file ? file.content : getScriptTemplate(path));
}
if (hasNs1) {
if (hasLegacyScript) {
sendDeprecationNotice();
}
Router.toPage(Page.ScriptEditor, { files, options });

@ -24,5 +24,5 @@ export function run(args: (string | number | boolean)[], server: BaseServer): vo
} else if (hasProgramExtension(path)) {
return runProgram(path, args, server);
}
Terminal.error(`Invalid file extension. Only .js, .script, .cct, and .exe files can be ran.`);
Terminal.error(`Invalid file extension. Only .js, .jsx, .ts, .tsx, .script, .cct, and .exe files can be run.`);
}

@ -7,7 +7,7 @@ import libarg from "arg";
import { formatRam } from "../../ui/formatNumber";
import { ScriptArg } from "@nsdefs";
import { isPositiveInteger } from "../../types";
import { ScriptFilePath } from "../../Paths/ScriptFilePath";
import { ScriptFilePath, isLegacyScript } from "../../Paths/ScriptFilePath";
import { sendDeprecationNotice } from "./common/deprecation";
import { roundToTwo } from "../../utils/helpers/roundToTwo";
import { RamCostConstants } from "../../Netscript/RamCostGenerator";
@ -61,7 +61,7 @@ export function runScript(path: ScriptFilePath, commandArgs: (string | number |
const success = startWorkerScript(runningScript, server);
if (!success) return Terminal.error(`Failed to start script`);
if (path.endsWith(".script")) {
if (isLegacyScript(path)) {
sendDeprecationNotice();
}
Terminal.print(

@ -36,7 +36,7 @@ export function scp(args: (string | number | boolean)[], server: BaseServer): vo
// Error for invalid filetype
if (!hasScriptExtension(path) && !hasTextExtension(path)) {
return Terminal.error(
`scp failed: ${path} has invalid extension. scp only works for scripts (.js or .script), text files (.txt), and literature files (.lit)`,
`scp failed: ${path} has invalid extension. scp only works for scripts (.js, .jsx, .ts, .tsx, .script), text files (.txt, .json), and literature files (.lit)`,
);
}
const sourceContentFile = server.getContentFile(path);

@ -9,7 +9,7 @@ import { Flags } from "../NetscriptFunctions/Flags";
import { AutocompleteData } from "@nsdefs";
import libarg from "arg";
import { getAllDirectories, resolveDirectory, root } from "../Paths/Directory";
import { resolveScriptFilePath } from "../Paths/ScriptFilePath";
import { isLegacyScript, resolveScriptFilePath } from "../Paths/ScriptFilePath";
import { enums } from "../NetscriptFunctions";
// TODO: this shouldn't be hardcoded in two places with no typechecks to verify equivalence
@ -299,7 +299,7 @@ export async function getTabCompletionPossibilities(terminalText: string, baseDi
}
const filepath = resolveScriptFilePath(filename, baseDir);
if (!filepath) return; // Not a script path.
if (filepath.endsWith(".script")) return; // Doesn't work with ns1.
if (isLegacyScript(filepath)) return; // Doesn't work with ns1.
const script = currServ.scripts.get(filepath);
if (!script) return; // Doesn't exist.

1
src/ThirdParty/acorn-jsx-walk.d.ts vendored Normal file

@ -0,0 +1 @@
declare module "acorn-jsx-walk";

@ -0,0 +1,88 @@
/**
* From isTypeScript()
*
* https://github.com/babel/babel/blob/main/packages/babel-types/src/validators/generated/index.ts
*/
const typescriptNodeTypes = [
"TSParameterProperty",
"TSDeclareFunction",
"TSDeclareMethod",
"TSQualifiedName",
"TSCallSignatureDeclaration",
"TSConstructSignatureDeclaration",
"TSPropertySignature",
"TSMethodSignature",
"TSIndexSignature",
"TSAnyKeyword",
"TSBooleanKeyword",
"TSBigIntKeyword",
"TSIntrinsicKeyword",
"TSNeverKeyword",
"TSNullKeyword",
"TSNumberKeyword",
"TSObjectKeyword",
"TSStringKeyword",
"TSSymbolKeyword",
"TSUndefinedKeyword",
"TSUnknownKeyword",
"TSVoidKeyword",
"TSThisType",
"TSFunctionType",
"TSConstructorType",
"TSTypeReference",
"TSTypePredicate",
"TSTypeQuery",
"TSTypeLiteral",
"TSArrayType",
"TSTupleType",
"TSOptionalType",
"TSRestType",
"TSNamedTupleMember",
"TSUnionType",
"TSIntersectionType",
"TSConditionalType",
"TSInferType",
"TSParenthesizedType",
"TSTypeOperator",
"TSIndexedAccessType",
"TSMappedType",
"TSLiteralType",
"TSExpressionWithTypeArguments",
"TSInterfaceDeclaration",
"TSInterfaceBody",
"TSTypeAliasDeclaration",
"TSInstantiationExpression",
"TSAsExpression",
"TSSatisfiesExpression",
"TSTypeAssertion",
"TSEnumDeclaration",
"TSEnumMember",
"TSModuleDeclaration",
"TSModuleBlock",
"TSImportType",
"TSImportEqualsDeclaration",
"TSExternalModuleReference",
"TSNonNullExpression",
"TSExportAssignment",
"TSNamespaceExportDeclaration",
"TSTypeAnnotation",
"TSTypeParameterInstantiation",
"TSTypeParameterDeclaration",
"TSTypeParameter",
];
export function extendAcornWalkForTypeScriptNodes(base: any) {
// By default, we ignore all TypeScript nodes.
for (const nodeType of typescriptNodeTypes) {
if (base[nodeType]) {
continue;
}
base[nodeType] = base.EmptyStatement;
}
// Only walk relevant TypeScript nodes.
base.TSModuleBlock = base.BlockStatement;
base.TSAsExpression = base.TSNonNullExpression = base.ExpressionStatement;
base.TSModuleDeclaration = (node: any, state: any, callback: any) => {
callback(node.body, state);
};
}

@ -318,7 +318,10 @@ export function InteractiveTutorialRoot(): React.ReactElement {
</Typography>
<Typography classes={{ root: classes.textfield }}>{"[home /]> nano"}</Typography>
<Typography>Scripts must end with the .js extension. Let's make a script now by entering </Typography>
<Typography>
Scripts must end with a script extension (.js, .jsx, .ts, .tsx, .script). Let's make a script now by
entering
</Typography>
<Typography classes={{ root: classes.textfield }}>{`[home /]> nano ${tutorialScriptName}`}</Typography>
</>
),

@ -11,6 +11,7 @@ import { CONSTANTS } from "../Constants";
import { ActivateRecoveryMode } from "./React/RecoveryRoot";
import { hash } from "../hash/hash";
import { pushGameReady } from "../Electron";
import initSwc from "@swc/wasm-web";
export function LoadingScreen(): React.ReactElement {
const [show, setShow] = useState(false);
@ -33,6 +34,7 @@ export function LoadingScreen(): React.ReactElement {
useEffect(() => {
load().then(async (saveData) => {
try {
await initSwc();
await Engine.load(saveData);
} catch (error) {
console.error(error);

@ -0,0 +1,176 @@
import * as babel from "@babel/standalone";
import { transformSync, type ParserConfig } from "@swc/wasm-web";
import * as acorn from "acorn";
import { resolveScriptFilePath, validScriptExtensions, type ScriptFilePath } from "../Paths/ScriptFilePath";
import type { Script } from "../Script/Script";
// This is only for testing. It will be removed after we decide between Babel and SWC.
declare global {
// eslint-disable-next-line no-var
var forceBabelTransform: boolean;
}
export type AcornASTProgram = acorn.Program;
export type BabelASTProgram = object;
export type AST = AcornASTProgram | BabelASTProgram;
export enum FileType {
PLAINTEXT,
JSON,
JS,
JSX,
TS,
TSX,
NS1,
}
export interface FileTypeFeature {
isReact: boolean;
isTypeScript: boolean;
}
export class ModuleResolutionError extends Error {}
const supportedFileTypes = [FileType.JSX, FileType.TS, FileType.TSX] as const;
export function getFileType(filename: string): FileType {
const extension = filename.substring(filename.lastIndexOf(".") + 1);
switch (extension) {
case "txt":
return FileType.PLAINTEXT;
case "json":
return FileType.JSON;
case "js":
return FileType.JS;
case "jsx":
return FileType.JSX;
case "ts":
return FileType.TS;
case "tsx":
return FileType.TSX;
case "script":
return FileType.NS1;
default:
throw new Error(`Invalid extension: ${extension}. Filename: ${filename}.`);
}
}
export function getFileTypeFeature(fileType: FileType): FileTypeFeature {
const result: FileTypeFeature = {
isReact: false,
isTypeScript: false,
};
if (fileType === FileType.JSX || fileType === FileType.TSX) {
result.isReact = true;
}
if (fileType === FileType.TS || fileType === FileType.TSX) {
result.isTypeScript = true;
}
return result;
}
export function parseAST(code: string, fileType: FileType): AST {
const fileTypeFeature = getFileTypeFeature(fileType);
let ast: AST;
/**
* acorn is much faster than babel-parser, especially when parsing many big JS files, so we use it to parse the AST of
* JS code. babel-parser is only useful when we have to parse JSX and TypeScript.
*/
if (fileType === FileType.JS) {
ast = acorn.parse(code, { sourceType: "module", ecmaVersion: "latest" });
} else {
const plugins = [];
if (fileTypeFeature.isReact) {
plugins.push("jsx");
}
if (fileTypeFeature.isTypeScript) {
plugins.push("typescript");
}
ast = babel.packages.parser.parse(code, {
sourceType: "module",
ecmaVersion: "latest",
/**
* The usage of the "estree" plugin is mandatory. We use acorn-walk to walk the AST. acorn-walk only supports the
* ESTree AST format, but babel-parser uses the Babel AST format by default.
*/
plugins: [["estree", { classFeatures: true }], ...plugins],
}).program;
}
return ast;
}
/**
* Simple module resolution algorithm:
* - Try each extension in validScriptExtensions
* - Return the first script found
*/
export function getModuleScript(
moduleName: string,
baseModule: ScriptFilePath,
scripts: Map<ScriptFilePath, Script>,
): Script {
let script;
for (const extension of validScriptExtensions) {
const filename = resolveScriptFilePath(moduleName, baseModule, extension);
if (!filename) {
throw new ModuleResolutionError(`Invalid module: "${moduleName}". Base module: "${baseModule}".`);
}
script = scripts.get(filename);
if (script) {
break;
}
}
if (!script) {
throw new ModuleResolutionError(`Invalid module: "${moduleName}". Base module: "${baseModule}".`);
}
return script;
}
/**
* This function must be synchronous to avoid race conditions. Check https://github.com/bitburner-official/bitburner-src/pull/1173#issuecomment-2026940461
* for more information.
*
* @param filename
* @param code
* @param fileType
* @returns
*/
export function transformScript(filename: string, code: string, fileType: FileType): string | null | undefined {
if (supportedFileTypes.every((v) => v !== fileType)) {
throw new Error(`Invalid file type: ${fileType}`);
}
const fileTypeFeature = getFileTypeFeature(fileType);
// This is only for testing. It will be removed after we decide between Babel and SWC.
if (globalThis.forceBabelTransform) {
const presets = [];
if (fileTypeFeature.isReact) {
presets.push("react");
}
if (fileTypeFeature.isTypeScript) {
presets.push("typescript");
}
return babel.transform(code, { filename: filename, presets: presets }).code;
}
let parserConfig: ParserConfig;
if (fileTypeFeature.isTypeScript) {
parserConfig = {
syntax: "typescript",
};
if (fileTypeFeature.isReact) {
parserConfig.tsx = true;
}
} else {
parserConfig = {
syntax: "ecmascript",
};
if (fileTypeFeature.isReact) {
parserConfig.jsx = true;
}
}
return transformSync(code, {
jsc: {
parser: parserConfig,
target: "es2020",
},
}).code;
}

@ -1,3 +1,4 @@
import { isLegacyScript } from "../Paths/ScriptFilePath";
import { TextFilePath } from "../Paths/TextFilePath";
import { saveObject } from "../SaveObject";
import { Script } from "../Script/Script";
@ -277,7 +278,7 @@ const processScript = (rules: IRule[], script: Script) => {
for (let i = 0; i < lines.length; i++) {
for (const rule of rules) {
const line = lines[i];
const match = script.filename.endsWith(".script") ? rule.matchScript ?? rule.matchJS : rule.matchJS;
const match = isLegacyScript(script.filename) ? rule.matchScript ?? rule.matchJS : rule.matchJS;
if (line.match(match)) {
rule.offenders.push({
file: script.filename,

@ -72,7 +72,6 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
extraLayerCost = 0,
) {
const code = `${fnPath.join(".")}();\n`.repeat(3);
const filename = "testfile.js" as ScriptFilePath;
const fnName = fnPath[fnPath.length - 1];
const server = "testserver";
@ -80,7 +79,7 @@ describe("Netscript RAM Calculation/Generation Tests", function () {
expect(getRamCost(fnPath, true)).toEqual(expectedRamCost);
// Static ram check
const staticCost = calculateRamUsage(code, filename, new Map(), server).cost;
const staticCost = calculateRamUsage(code, `${fnName}.js` as ScriptFilePath, server, new Map()).cost;
expect(staticCost).toBeCloseTo(Math.min(baseCost + expectedRamCost + extraLayerCost, maxCost));
// reset workerScript for dynamic check

@ -28,7 +28,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const code = `
export async function main(ns) { }
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, 0);
});
@ -38,7 +38,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.print("Slum snakes r00l!");
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, 0);
});
@ -48,7 +48,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns");
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -58,7 +58,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await X.hack("joesguns");
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -69,7 +69,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns");
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -80,7 +80,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.grow("joesguns");
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, HackCost + GrowCost);
});
@ -93,7 +93,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
await ns.hack("joesguns");
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -108,7 +108,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
async doHacking() { await this.ns.hack("joesguns"); }
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, HackCost);
});
@ -123,7 +123,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
async doHacking() { await this.#ns.hack("joesguns"); }
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, HackCost);
});
});
@ -136,7 +136,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
}
function get() { return 0; }
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, 0);
});
@ -147,7 +147,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
}
function purchaseNode() { return 0; }
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
// Works at present, because the parser checks the namespace only, not the function name
expectCost(calculated, 0);
});
@ -160,7 +160,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
}
function getTask() { return 0; }
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, 0);
});
});
@ -172,7 +172,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.hacknet.purchaseNode(0);
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, HacknetCost);
});
@ -182,7 +182,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
ns.sleeve.getTask(3);
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, SleeveGetTaskCost);
});
});
@ -203,8 +203,8 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
filename,
new Map([["libTest.js" as ScriptFilePath, lib]]),
server,
new Map([["libTest.js" as ScriptFilePath, lib]]),
).cost;
expectCost(calculated, 0);
});
@ -224,8 +224,8 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
filename,
new Map([["libTest.js" as ScriptFilePath, lib]]),
server,
new Map([["libTest.js" as ScriptFilePath, lib]]),
).cost;
expectCost(calculated, HackCost);
});
@ -246,8 +246,8 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
filename,
new Map([["libTest.js" as ScriptFilePath, lib]]),
server,
new Map([["libTest.js" as ScriptFilePath, lib]]),
).cost;
expectCost(calculated, HackCost);
});
@ -268,8 +268,8 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
filename,
new Map([["libTest.js" as ScriptFilePath, lib]]),
server,
new Map([["libTest.js" as ScriptFilePath, lib]]),
).cost;
expectCost(calculated, HackCost + GrowCost);
});
@ -291,7 +291,7 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
${lines.join("\n")};
}
`;
const calculated = calculateRamUsage(code, filename, new Map(), server).cost;
const calculated = calculateRamUsage(code, filename, server, new Map()).cost;
expectCost(calculated, MaxCost);
});
@ -316,8 +316,8 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
filename,
new Map([["libTest.js" as ScriptFilePath, lib]]),
server,
new Map([["libTest.js" as ScriptFilePath, lib]]),
).cost;
expectCost(calculated, HackCost);
});
@ -347,8 +347,8 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
filename,
new Map([["libTest.js" as ScriptFilePath, lib]]),
server,
new Map([["libTest.js" as ScriptFilePath, lib]]),
).cost;
expectCost(calculated, GrowCost);
});
@ -370,8 +370,8 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
folderFilename,
new Map([["test/libTest.js" as ScriptFilePath, lib]]),
server,
new Map([["test/libTest.js" as ScriptFilePath, lib]]),
).cost;
expectCost(calculated, HackCost);
});
@ -404,11 +404,11 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
folderFilename,
server,
new Map([
[libNameOne, libScriptOne],
[libNameTwo, libScriptTwo],
]),
server,
).cost;
expectCost(calculated, HackCost);
});
@ -449,12 +449,12 @@ describe("Parsing NetScript code to work out static RAM costs", function () {
const calculated = calculateRamUsage(
code,
folderFilename,
server,
new Map([
[libNameOne, libScriptOne],
[libNameTwo, libScriptTwo],
[incorrect_libNameTwo, incorrect_libScriptTwo],
]),
server,
).cost;
expectCost(calculated, HackCost);
});

@ -192,5 +192,11 @@ module.exports = (env, argv) => {
fallback: { crypto: false },
},
stats: statsConfig,
ignoreWarnings: [
{
module: /@babel\/standalone/,
message: /Critical dependency: the request of a dependency is an expression/,
},
],
};
};