diff --git a/.eslintrc.js b/.eslintrc.js index 018335f8c..2e1d646e3 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -34,6 +34,7 @@ module.exports = { "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-explicit-any": "off", "react/no-unescaped-entities": "off", + "@typescript-eslint/restrict-template-expressions": "off", }, settings: { react: { diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index 3df0bd410..9289bcc2c 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -31,4 +31,21 @@ declare global { }; }; } + + /** + * "loader" is not exposed in the public API. + */ + module "monaco-editor" { + namespace languages { + interface ILanguageExtensionPoint { + loader: () => Promise<{ + language: { + tokenizer: { + root: any[]; + }; + }; + }>; + } + } + } } diff --git a/src/CodingContractGenerator.ts b/src/CodingContractGenerator.ts index b1a4805b8..c50cf7283 100644 --- a/src/CodingContractGenerator.ts +++ b/src/CodingContractGenerator.ts @@ -159,7 +159,7 @@ function sanitizeRewardType(rewardType: CodingContractRewardType): CodingContrac try { return Factions[fac].getInfo().offerHackingWork; } catch (e) { - console.error(`Error when trying to filter Hacking Factions for Coding Contract Generation: ${e}`); + console.error("Error when trying to filter Hacking Factions for Coding Contract Generation", e); return false; } }); diff --git a/src/Corporation/Actions.ts b/src/Corporation/Actions.ts index bcb6e395d..505634bda 100644 --- a/src/Corporation/Actions.ts +++ b/src/Corporation/Actions.ts @@ -207,8 +207,8 @@ export function sellMaterial(material: Material, amount: string, price: string): try { if (temp.includes("MP")) throw "Only one reference to MP is allowed in sell price."; temp = eval?.(temp); - } catch (e) { - throw new Error("Invalid value or expression for sell price field: " + e); + } catch (error) { + throw new Error("Invalid value or expression for sell price field", { cause: error }); } if (temp == null || isNaN(parseFloat(temp))) { @@ -231,8 +231,8 @@ export function sellMaterial(material: Material, amount: string, price: string): tempQty = tempQty.replace(/INV/g, material.productionAmount.toString()); try { tempQty = eval?.(tempQty); - } catch (e) { - throw new Error("Invalid value or expression for sell quantity field: " + e); + } catch (error) { + throw new Error("Invalid value or expression for sell quantity field", { cause: error }); } if (tempQty == null || isNaN(parseFloat(tempQty))) { @@ -263,8 +263,8 @@ export function sellProduct(product: Product, city: CityName, amt: string, price try { if (temp.includes("MP")) throw "Only one reference to MP is allowed in sell price."; temp = eval?.(temp); - } catch (e) { - throw new Error("Invalid value or expression for sell price field: " + e); + } catch (error) { + throw new Error("Invalid value or expression for sell price field.", { cause: error }); } if (temp == null || isNaN(parseFloat(temp))) { throw new Error("Invalid value or expression for sell price field."); @@ -291,8 +291,8 @@ export function sellProduct(product: Product, city: CityName, amt: string, price temp = temp.replace(/INV/g, product.cityData[city].stored.toString()); try { temp = eval?.(temp); - } catch (e) { - throw new Error("Invalid value or expression for sell quantity field: " + e); + } catch (error) { + throw new Error("Invalid value or expression for sell quantity field", { cause: error }); } if (temp == null || isNaN(parseFloat(temp))) { @@ -585,13 +585,16 @@ Attempted export amount: ${amount}`); } if (!error && isNaN(evaluated)) error = "evaluated value is NaN"; if (error) { - throw new Error(`Error while trying to set the exported amount of ${material.name}. + throw new Error( + `Error while trying to set the exported amount of ${material.name}. Error occurred while testing keyword replacement with ${testReplacement}. Your input: ${amount} Sanitized input: ${sanitizedAmt} Input after replacement: ${replaced} -Evaluated value: ${evaluated} -Error encountered: ${error}`); +Evaluated value: ${evaluated}` + + // eslint-disable-next-line @typescript-eslint/no-base-to-string + `Error encountered: ${error}`, + ); } } diff --git a/src/Corporation/ui/ExpandNewCity.tsx b/src/Corporation/ui/ExpandNewCity.tsx index a12474650..148475307 100644 --- a/src/Corporation/ui/ExpandNewCity.tsx +++ b/src/Corporation/ui/ExpandNewCity.tsx @@ -29,8 +29,8 @@ export function ExpandNewCity(props: IProps): React.ReactElement { function expand(): void { try { purchaseOffice(corp, division, city); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); return; } diff --git a/src/Corporation/ui/NewDivisionTab.tsx b/src/Corporation/ui/NewDivisionTab.tsx index 39ffed4e0..6aa0aa2b0 100644 --- a/src/Corporation/ui/NewDivisionTab.tsx +++ b/src/Corporation/ui/NewDivisionTab.tsx @@ -39,8 +39,8 @@ export function NewDivisionTab(props: IProps): React.ReactElement { if (disabledText) return; try { createDivision(corp, industry, name); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); return; } diff --git a/src/Corporation/ui/modals/BuybackSharesModal.tsx b/src/Corporation/ui/modals/BuybackSharesModal.tsx index f967d649b..4e7471739 100644 --- a/src/Corporation/ui/modals/BuybackSharesModal.tsx +++ b/src/Corporation/ui/modals/BuybackSharesModal.tsx @@ -43,8 +43,8 @@ export function BuybackSharesModal(props: IProps): React.ReactElement { props.onClose(); props.rerender(); setShares(NaN); - } catch (err) { - dialogBoxCreate(`${err}`); + } catch (error) { + dialogBoxCreate(String(error)); } } diff --git a/src/Corporation/ui/modals/ExportModal.tsx b/src/Corporation/ui/modals/ExportModal.tsx index dd059417a..36976deb0 100644 --- a/src/Corporation/ui/modals/ExportModal.tsx +++ b/src/Corporation/ui/modals/ExportModal.tsx @@ -59,8 +59,8 @@ export function ExportModal(props: ExportModalProps): React.ReactElement { try { if (!targetDivision || !targetCity) return; actions.exportMaterial(targetDivision, targetCity, props.mat, exportAmount); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); } props.onClose(); } diff --git a/src/Corporation/ui/modals/FindInvestorsModal.tsx b/src/Corporation/ui/modals/FindInvestorsModal.tsx index 1ecb65ac7..0c06ad694 100644 --- a/src/Corporation/ui/modals/FindInvestorsModal.tsx +++ b/src/Corporation/ui/modals/FindInvestorsModal.tsx @@ -37,8 +37,8 @@ export function FindInvestorsModal(props: IProps): React.ReactElement { ); props.onClose(); props.rerender(); - } catch (err) { - dialogBoxCreate(`${err}`); + } catch (error) { + dialogBoxCreate(String(error)); } } diff --git a/src/Corporation/ui/modals/GoPublicModal.tsx b/src/Corporation/ui/modals/GoPublicModal.tsx index 29eba01b4..1e772216b 100644 --- a/src/Corporation/ui/modals/GoPublicModal.tsx +++ b/src/Corporation/ui/modals/GoPublicModal.tsx @@ -45,8 +45,8 @@ export function GoPublicModal(props: IProps): React.ReactElement { props.onClose(); props.rerender(); setShares(NaN); - } catch (err) { - dialogBoxCreate(`${err}`); + } catch (error) { + dialogBoxCreate(String(error)); } } diff --git a/src/Corporation/ui/modals/IssueDividendsModal.tsx b/src/Corporation/ui/modals/IssueDividendsModal.tsx index a512aa281..8fdbc9977 100644 --- a/src/Corporation/ui/modals/IssueDividendsModal.tsx +++ b/src/Corporation/ui/modals/IssueDividendsModal.tsx @@ -27,8 +27,8 @@ export function IssueDividendsModal(props: IProps): React.ReactElement { if (percent === null) return; try { actions.issueDividends(corp, percent / 100); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); } props.onClose(); diff --git a/src/Corporation/ui/modals/IssueNewSharesModal.tsx b/src/Corporation/ui/modals/IssueNewSharesModal.tsx index 0031b02b0..b67f4b316 100644 --- a/src/Corporation/ui/modals/IssueNewSharesModal.tsx +++ b/src/Corporation/ui/modals/IssueNewSharesModal.tsx @@ -55,8 +55,8 @@ export function IssueNewSharesModal(props: IProps): React.ReactElement { ); props.onClose(); props.rerender(); - } catch (err) { - dialogBoxCreate(`${err}`); + } catch (error) { + dialogBoxCreate(String(error)); } } diff --git a/src/Corporation/ui/modals/MakeProductModal.tsx b/src/Corporation/ui/modals/MakeProductModal.tsx index 9874863dc..24f9a5e0f 100644 --- a/src/Corporation/ui/modals/MakeProductModal.tsx +++ b/src/Corporation/ui/modals/MakeProductModal.tsx @@ -47,8 +47,8 @@ export function MakeProductModal(props: IProps): React.ReactElement { if (isNaN(design) || isNaN(marketing)) return; try { actions.makeProduct(corp, division, city, name, design, marketing); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); } props.onClose(); } diff --git a/src/Corporation/ui/modals/PurchaseMaterialModal.tsx b/src/Corporation/ui/modals/PurchaseMaterialModal.tsx index d4ed5df51..b62fa3ab2 100644 --- a/src/Corporation/ui/modals/PurchaseMaterialModal.tsx +++ b/src/Corporation/ui/modals/PurchaseMaterialModal.tsx @@ -67,8 +67,8 @@ function BulkPurchaseSection(props: IBPProps): React.ReactElement { function bulkPurchase(): void { try { actions.bulkPurchase(corp, division, props.warehouse, props.mat, parseFloat(buyAmt)); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); } props.onClose(); } @@ -119,8 +119,8 @@ export function PurchaseMaterialModal(props: IProps): React.ReactElement { if (buyAmt === null) return; try { actions.buyMaterial(division, props.mat, buyAmt); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); } props.onClose(); diff --git a/src/Corporation/ui/modals/ResearchModal.tsx b/src/Corporation/ui/modals/ResearchModal.tsx index e2021fb8f..3d238fc56 100644 --- a/src/Corporation/ui/modals/ResearchModal.tsx +++ b/src/Corporation/ui/modals/ResearchModal.tsx @@ -39,8 +39,8 @@ function Upgrade({ n, division }: INodeProps): React.ReactElement { if (n === null || disabled) return; try { actions.research(division, n.researchName); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); return; } diff --git a/src/Corporation/ui/modals/SellMaterialModal.tsx b/src/Corporation/ui/modals/SellMaterialModal.tsx index 424a56ca7..0c12b682e 100644 --- a/src/Corporation/ui/modals/SellMaterialModal.tsx +++ b/src/Corporation/ui/modals/SellMaterialModal.tsx @@ -24,8 +24,8 @@ export function SellMaterialModal(props: IProps): React.ReactElement { function sellMaterial(): void { try { actions.sellMaterial(props.mat, amt, price); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); } props.onClose(); } diff --git a/src/Corporation/ui/modals/SellProductModal.tsx b/src/Corporation/ui/modals/SellProductModal.tsx index 88d1354e5..76553ee6b 100644 --- a/src/Corporation/ui/modals/SellProductModal.tsx +++ b/src/Corporation/ui/modals/SellProductModal.tsx @@ -31,8 +31,8 @@ export function SellProductModal(props: IProps): React.ReactElement { function sellProduct(): void { try { actions.sellProduct(props.product, props.city, iQty, px, checked); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); } props.onClose(); diff --git a/src/Corporation/ui/modals/SmartSupplyModal.tsx b/src/Corporation/ui/modals/SmartSupplyModal.tsx index b24c93213..0a614cb1d 100644 --- a/src/Corporation/ui/modals/SmartSupplyModal.tsx +++ b/src/Corporation/ui/modals/SmartSupplyModal.tsx @@ -27,8 +27,9 @@ function SSoption(props: ISSoptionProps): React.ReactElement { const matName = props.matName; const material = props.warehouse.materials[matName]; setSmartSupplyOption(props.warehouse, material, newValue); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); + return; } setChecked(newValue); } @@ -40,8 +41,9 @@ function SSoption(props: ISSoptionProps): React.ReactElement { const matName = props.matName; const material = props.warehouse.materials[matName]; setSmartSupplyOption(props.warehouse, material, newValue); - } catch (err) { - dialogBoxCreate(err + ""); + } catch (error) { + dialogBoxCreate(String(error)); + return; } setChecked(newValue); } diff --git a/src/Electron.tsx b/src/Electron.tsx index 5d2b43113..88659f06b 100644 --- a/src/Electron.tsx +++ b/src/Electron.tsx @@ -12,7 +12,7 @@ import { CONSTANTS } from "./Constants"; import { commitHash } from "./utils/helpers/commitHash"; import { resolveFilePath } from "./Paths/FilePath"; import { hasScriptExtension } from "./Paths/ScriptFilePath"; -import { handleGetSaveDataError } from "./Netscript/ErrorMessages"; +import { handleGetSaveDataInfoError } from "./Netscript/ErrorMessages"; interface IReturnWebStatus extends IReturnStatus { data?: Record; @@ -103,14 +103,14 @@ function initAppNotifier(): void { const funcs = { terminal: (message: string, type?: string) => { const typesFn: Record void> = { - info: Terminal.info, - warn: Terminal.warn, - error: Terminal.error, - success: Terminal.success, + info: (s) => Terminal.info(s), + warn: (s) => Terminal.warn(s), + error: (s) => Terminal.error(s), + success: (s) => Terminal.success(s), }; let fn; if (type) fn = typesFn[type]; - if (!fn) fn = Terminal.print; + if (!fn) fn = (s: string) => Terminal.print(s); fn.bind(Terminal)(message); }, toast: (message: string, type: ToastVariant, duration = 2000) => SnackbarEvents.emit(message, type, duration), @@ -124,12 +124,10 @@ function initSaveFunctions(): void { const funcs = { triggerSave: (): Promise => saveObject.saveGame(true), triggerGameExport: (): void => { - try { - saveObject.exportGame(); - } catch (error) { + saveObject.exportGame().catch((error) => { console.error(error); SnackbarEvents.emit("Could not export game.", ToastVariant.ERROR, 2000); - } + }); }, triggerScriptsExport: (): void => exportScripts("*", Player.getHomeComputer()), getSaveData: async (): Promise<{ save: SaveData; fileName: string }> => { @@ -159,22 +157,28 @@ function initElectronBridge(): void { const bridge = window.electronBridge; if (!bridge) return; - bridge.receive("get-save-data-request", async () => { - let saveData; - try { - saveData = await window.appSaveFns.getSaveData(); - } catch (error) { - handleGetSaveDataError(error); - return; - } - bridge.send("get-save-data-response", saveData); + bridge.receive("get-save-data-request", () => { + window.appSaveFns + .getSaveData() + .then((saveData) => { + bridge.send("get-save-data-response", saveData); + }) + .catch((error) => { + handleGetSaveDataInfoError(error); + }); }); - bridge.receive("get-save-info-request", async (saveData: unknown) => { + bridge.receive("get-save-info-request", (saveData: unknown) => { if (typeof saveData !== "string" && !(saveData instanceof Uint8Array)) { throw new Error("Error while trying to get save info"); } - const saveInfo = await window.appSaveFns.getSaveInfo(saveData); - bridge.send("get-save-info-response", saveInfo); + window.appSaveFns + .getSaveInfo(saveData) + .then((saveInfo) => { + bridge.send("get-save-info-response", saveInfo); + }) + .catch((error) => { + handleGetSaveDataInfoError(error, true); + }); }); bridge.receive("push-save-request", (params: unknown) => { if (typeof params !== "object") throw new Error("Error trying to push save request"); @@ -182,7 +186,7 @@ function initElectronBridge(): void { window.appSaveFns.pushSaveData(save, automatic); }); bridge.receive("trigger-save", () => { - return window.appSaveFns + window.appSaveFns .triggerSave() .then(() => { bridge.send("save-completed"); diff --git a/src/Faction/ui/FactionRoot.tsx b/src/Faction/ui/FactionRoot.tsx index d65a9f0ba..be324619e 100644 --- a/src/Faction/ui/FactionRoot.tsx +++ b/src/Faction/ui/FactionRoot.tsx @@ -97,7 +97,7 @@ function MainPage({ faction, rerender, onAugmentations }: IMainProps): React.Rea startWork(); } - // We have a special flag for whether the player this faction is the player's + // We have a special flag for whether this faction is the player's // gang faction because if the player has a gang, they cannot do any other action const isPlayersGang = Player.gang && Player.getGangName() === faction.name; diff --git a/src/GameOptions/ui/GameOptionsSidebar.tsx b/src/GameOptions/ui/GameOptionsSidebar.tsx index df0517d16..a6c3891d5 100644 --- a/src/GameOptions/ui/GameOptionsSidebar.tsx +++ b/src/GameOptions/ui/GameOptionsSidebar.tsx @@ -90,6 +90,7 @@ export const GameOptionsSidebar = (props: IProps): React.ReactElement => { try { await saveObject.importGame(importData.saveData); } catch (e: unknown) { + console.error(e); SnackbarEvents.emit(String(e), ToastVariant.ERROR, 5000); } diff --git a/src/Gang/Gang.ts b/src/Gang/Gang.ts index fed1e6a58..813a392ed 100644 --- a/src/Gang/Gang.ts +++ b/src/Gang/Gang.ts @@ -112,7 +112,7 @@ export class Gang { this.processTerritoryAndPowerGains(cycles); this.storedCycles -= cycles; } catch (e: unknown) { - console.error(`Exception caught when processing Gang: ${e}`); + console.error("Exception caught when processing Gang", e); } // Handle "nextUpdate" resolver after this update diff --git a/src/Go/boardAnalysis/example.js b/src/Go/boardAnalysis/example.js deleted file mode 100644 index 16d4a7136..000000000 --- a/src/Go/boardAnalysis/example.js +++ /dev/null @@ -1,96 +0,0 @@ -/** @param {NS} ns */ -export async function main(ns) { - let result; - - do { - const board = ns.go.getBoardState(); - const validMoves = ns.go.analysis.getValidMoves(); - - const [growX, growY] = getGrowMove(board, validMoves); - const [randX, randY] = getRandomMove(board, validMoves); - // Try to pick a grow move, otherwise choose a random move - const x = growX ?? randX; - const y = growY ?? randY; - - if (x === undefined) { - // Pass turn if no moves are found - result = await ns.go.passTurn(); - } else { - // Play the selected move - result = await ns.go.makeMove(x, y); - } - - await ns.sleep(100); - } while (result?.type !== "gameOver" && result?.type !== "pass"); - - // After the opponent passes, end the game by passing as well - await ns.go.passTurn(); -} - -/** - * Choose one of the empty points on the board at random to play - */ -const getRandomMove = (board, validMoves) => { - const moveOptions = []; - const size = board[0].length; - - // Look through all the points on the board - for (let x = 0; x < size; x++) { - for (let y = 0; y < size; y++) { - // Make sure the point is a valid move - const isValidMove = validMoves[x][y]; - // Leave some spaces to make it harder to capture our pieces - const isNotReservedSpace = x % 2 || y % 2; - - if (isValidMove && isNotReservedSpace) { - moveOptions.push([x, y]); - } - } - } - - // Choose one of the found moves at random - const randomIndex = Math.floor(Math.random() * moveOptions.length); - return moveOptions[randomIndex] ?? []; -}; - -/** - * Choose a point connected to a friendly stone to play - */ -const getGrowMove = (board, validMoves) => { - const moveOptions = []; - const size = board[0].length; - - // Look through all the points on the board - for (let x = 0; x < size; x++) { - for (let y = 0; y < size; y++) { - // make sure the move is valid - const isValidMove = validMoves[x][y]; - // Leave some open spaces to make it harder to capture our pieces - const isNotReservedSpace = x % 2 || y % 2; - - // Make sure we are connected to a friendly piece - const neighbors = getNeighbors(board, x, y); - const hasFriendlyNeighbor = neighbors.includes("X"); - - if (isValidMove && isNotReservedSpace && hasFriendlyNeighbor) { - moveOptions.push([x, y]); - } - } - } - - // Choose one of the found moves at random - const randomIndex = Math.floor(Math.random() * moveOptions.length); - return moveOptions[randomIndex] ?? []; -}; - -/** - * Find all adjacent points in the four connected directions - */ -const getNeighbors = (board, x, y) => { - const north = board[x + 1]?.[y]; - const east = board[x][y + 1]; - const south = board[x - 1]?.[y]; - const west = board[x]?.[y - 1]; - - return [north, east, south, west]; -}; diff --git a/src/Go/ui/GoGameboardWrapper.tsx b/src/Go/ui/GoGameboardWrapper.tsx index ecbdfe480..1cce3e597 100644 --- a/src/Go/ui/GoGameboardWrapper.tsx +++ b/src/Go/ui/GoGameboardWrapper.tsx @@ -20,6 +20,7 @@ import { GoSubnetSearch } from "./GoSubnetSearch"; import { CorruptableText } from "../../ui/React/CorruptableText"; import { makeAIMove, resetAI, resolveCurrentTurn } from "../boardAnalysis/goAI"; import { GoScoreExplanation } from "./GoScoreExplanation"; +import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; interface GoGameboardWrapperProps { showInstructions: () => void; @@ -63,7 +64,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps // Do not implement useCallback for this function without ensuring GoGameboard still rerenders for every move // Currently this function changing is what triggers a GoGameboard rerender, which is needed - async function clickHandler(x: number, y: number) { + function clickHandler(x: number, y: number) { if (showPriorMove) { SnackbarEvents.emit( `Currently showing a past board state. Please disable "Show previous move" to continue.`, @@ -94,7 +95,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps const didUpdateBoard = makeMove(boardState, x, y, currentPlayer); if (didUpdateBoard) { rerender(); - takeAiTurn(boardState); + takeAiTurn(boardState).catch((error) => exceptionAlert(error)); } } @@ -113,7 +114,7 @@ export function GoGameboardWrapper({ showInstructions }: GoGameboardWrapperProps } setTimeout(() => { - takeAiTurn(boardState); + takeAiTurn(boardState).catch((error) => exceptionAlert(error)); }, 100); } diff --git a/src/Netscript/ErrorMessages.ts b/src/Netscript/ErrorMessages.ts index 366356db9..624e4c80e 100644 --- a/src/Netscript/ErrorMessages.ts +++ b/src/Netscript/ErrorMessages.ts @@ -99,13 +99,13 @@ export function handleUnknownError(e: unknown, ws: WorkerScript | null = null, i Error has been logged to the console.\n\nType of error: ${typeof e}\nValue of error: ${e}`; e = ws ? basicErrorMessage(ws, msg, "UNKNOWN") : msg; } - dialogBoxCreate(initialText + e); + dialogBoxCreate(initialText + String(e)); } -/** Use this handler to handle the error when we call getSaveData function */ -export function handleGetSaveDataError(error: unknown) { +/** Use this handler to handle the error when we call getSaveData function or getSaveInfo function */ +export function handleGetSaveDataInfoError(error: unknown, fromGetSaveInfo = false) { console.error(error); - let errorMessage = `Cannot get save data. Error: ${error}.`; + let errorMessage = `Cannot get save ${fromGetSaveInfo ? "info" : "data"}. Error: ${error}.`; if (error instanceof RangeError) { errorMessage += " This may be because the save data is too large."; } diff --git a/src/Netscript/NetscriptHelpers.tsx b/src/Netscript/NetscriptHelpers.tsx index 5cfc925dc..9c9eb0441 100644 --- a/src/Netscript/NetscriptHelpers.tsx +++ b/src/Netscript/NetscriptHelpers.tsx @@ -232,7 +232,7 @@ function spawnOptions(ctx: NetscriptContext, threadOrOption: unknown): CompleteS function mapToString(map: Map): string { const formattedMap = [...map] .map((m) => { - return `${m[0]} => ${m[1]}`; + return `${String(m[0])} => ${String(m[1])}`; }) .join("; "); return `< Map: ${formattedMap} >`; @@ -245,7 +245,7 @@ function setToString(set: Set): string { /** Convert multiple arguments for tprint or print into a single string. */ function argsToString(args: unknown[]): string { // Reduce array of args into a single output string - return args.reduce((out, arg) => { + return args.reduce((out: string, arg) => { if (arg === null) { return (out += "null"); } @@ -264,12 +264,13 @@ function argsToString(args: unknown[]): string { return (out += setToString(nativeArg)); } if (typeof nativeArg === "object") { - return (out += JSON.stringify(nativeArg, (_, value) => { + return (out += JSON.stringify(nativeArg, (_, value: unknown) => { /** * If the property is a promise, we will return a string that clearly states that it's a promise object, not a * normal object. If we don't do that, all promises will be serialized into "{}". */ if (value instanceof Promise) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string -- "[object Promise]" is exactly the string that we want. return value.toString(); } if (value instanceof Map) { @@ -282,8 +283,8 @@ function argsToString(args: unknown[]): string { })); } - return (out += `${nativeArg}`); - }, "") as string; + return (out += String(nativeArg)); + }, ""); } function validateHGWOptions(ctx: NetscriptContext, opts: unknown): CompleteHGWOptions { @@ -296,6 +297,7 @@ function validateHGWOptions(ctx: NetscriptContext, opts: unknown): CompleteHGWOp return result; } if (typeof opts !== "object") { + // eslint-disable-next-line @typescript-eslint/no-base-to-string throw errorMessage(ctx, `BasicHGWOptions must be an object if specified, was ${opts}`); } // Safe assertion since threadOrOption type has been narrowed to a non-null object @@ -723,7 +725,7 @@ function createPublicRunningScript(runningScript: RunningScript, workerScript?: args: runningScript.args.slice(), dynamicRamUsage: workerScript && roundToTwo(workerScript.dynamicRamUsage), filename: runningScript.filename, - logs: runningScript.logs.map((x) => "" + x), + logs: runningScript.logs.map((x) => String(x)), offlineExpGained: runningScript.offlineExpGained, offlineMoneyMade: runningScript.offlineMoneyMade, offlineRunningTime: runningScript.offlineRunningTime, @@ -782,13 +784,21 @@ function validateBitNodeOptions(ctx: NetscriptContext, bitNodeOptions: unknown): return result; } if (typeof bitNodeOptions !== "object") { + // eslint-disable-next-line @typescript-eslint/no-base-to-string throw errorMessage(ctx, `bitNodeOptions must be an object if it's specified. It was ${bitNodeOptions}.`); } const options = bitNodeOptions as Unknownify; if (!(options.sourceFileOverrides instanceof Map)) { throw errorMessage(ctx, `sourceFileOverrides must be a Map.`); } - const validationResultForSourceFileOverrides = validateSourceFileOverrides(options.sourceFileOverrides, true); + const validationResultForSourceFileOverrides = validateSourceFileOverrides( + /** + * Cast the type from Map to Map to satisfy the lint rule. The validation logic in + * validateSourceFileOverrides will check the data. + */ + options.sourceFileOverrides as Map, + true, + ); if (!validationResultForSourceFileOverrides.valid) { throw errorMessage( ctx, diff --git a/src/NetscriptFunctions.ts b/src/NetscriptFunctions.ts index 318acc0ae..d2e224726 100644 --- a/src/NetscriptFunctions.ts +++ b/src/NetscriptFunctions.ts @@ -542,7 +542,7 @@ export const ns: InternalAPI = { return [] as string[]; } - return runningScriptObj.logs.map((x) => "" + x); + return runningScriptObj.logs.map((x) => String(x)); }, tail: (ctx) => @@ -1870,7 +1870,7 @@ function getFunctionNames(obj: object, prefix: string): string[] { } else if (typeof value === "function") { functionNames.push(prefix + key); } else if (typeof value === "object") { - functionNames.push(...getFunctionNames(value, `${prefix}${key}.`)); + functionNames.push(...getFunctionNames(value as object, `${prefix}${key}.`)); } } return functionNames; diff --git a/src/NetscriptFunctions/Singularity.ts b/src/NetscriptFunctions/Singularity.ts index d3ef7480e..c66f5291d 100644 --- a/src/NetscriptFunctions/Singularity.ts +++ b/src/NetscriptFunctions/Singularity.ts @@ -1,4 +1,4 @@ -import type { Singularity as ISingularity, Task as ITask } from "@nsdefs"; +import type { Singularity as ISingularity } from "@nsdefs"; import { Player } from "@player"; import { AugmentationName, CityName, FactionWorkType, GymType, LocationName, UniversityClassType } from "@enums"; @@ -1142,7 +1142,7 @@ export function NetscriptSingularity(): InternalAPI { ? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name) : false; if (cbScript === null) { - throw helpers.errorMessage(ctx, `Could not resolve file path: ${_cbScript}`); + throw helpers.errorMessage(ctx, `Could not resolve file path. callbackScript is null.`); } enterBitNode(true, Player.bitNodeN, nextBN, helpers.validateBitNodeOptions(ctx, _bitNodeOptions)); if (cbScript) { @@ -1159,7 +1159,7 @@ export function NetscriptSingularity(): InternalAPI { ? resolveScriptFilePath(helpers.string(ctx, "cbScript", _cbScript), ctx.workerScript.name) : false; if (cbScript === null) { - throw helpers.errorMessage(ctx, `Could not resolve file path: ${_cbScript}`); + throw helpers.errorMessage(ctx, `Could not resolve file path. callbackScript is null.`); } const wd = GetServer(SpecialServers.WorldDaemon); @@ -1194,7 +1194,7 @@ export function NetscriptSingularity(): InternalAPI { getCurrentWork: (ctx) => () => { helpers.checkSingularityAccess(ctx); if (!Player.currentWork) return null; - return Player.currentWork.APICopy() as ITask; + return Player.currentWork.APICopy(); }, exportGame: (ctx) => () => { helpers.checkSingularityAccess(ctx); diff --git a/src/NetscriptJSEvaluator.ts b/src/NetscriptJSEvaluator.ts index 16dcbc7b4..f478d77a7 100644 --- a/src/NetscriptJSEvaluator.ts +++ b/src/NetscriptJSEvaluator.ts @@ -30,7 +30,7 @@ function makeScriptBlob(code: string): Blob { // config object to provide a hook point. export const config = { doImport(url: ScriptURL): Promise { - return import(/*webpackIgnore:true*/ url); + return import(/*webpackIgnore:true*/ url) as Promise; }, }; diff --git a/src/PersonObjects/Player/PlayerObjectGangMethods.ts b/src/PersonObjects/Player/PlayerObjectGangMethods.ts index 88d7745ba..fbfc4e70f 100644 --- a/src/PersonObjects/Player/PlayerObjectGangMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectGangMethods.ts @@ -36,9 +36,9 @@ export function getGangFaction(this: PlayerObject): Faction { return fac; } -export function getGangName(this: PlayerObject): string { +export function getGangName(this: PlayerObject): FactionName | null { const gang = this.gang; - return gang ? gang.facName : ""; + return gang ? gang.facName : null; } export function hasGangWith(this: PlayerObject, facName: FactionName): boolean { diff --git a/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts b/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts index 341491946..50fd5ee71 100644 --- a/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts +++ b/src/PersonObjects/Player/PlayerObjectGeneralMethods.ts @@ -302,7 +302,7 @@ export function applyForJob( } if (!company.hasPosition(pos)) { - console.error(`Company ${company.name} does not have position ${pos}. Player.applyToCompany() failed.`); + console.error(`Company ${company.name} does not have position ${pos.name}. Player.applyToCompany() failed.`); return null; } diff --git a/src/Player.ts b/src/Player.ts index 5d465375d..9c82bbdef 100644 --- a/src/Player.ts +++ b/src/Player.ts @@ -11,7 +11,7 @@ export function setPlayer(playerObj: PlayerObject): void { } export function loadPlayer(saveString: string): PlayerObject { - const player = JSON.parse(saveString, Reviver); + const player = JSON.parse(saveString, Reviver) as PlayerObject; player.money = parseFloat(player.money + ""); player.exploits = sanitizeExploits(player.exploits); return player; diff --git a/src/RemoteFileAPI/MessageHandlers.ts b/src/RemoteFileAPI/MessageHandlers.ts index f5d230bad..f5af08889 100644 --- a/src/RemoteFileAPI/MessageHandlers.ts +++ b/src/RemoteFileAPI/MessageHandlers.ts @@ -18,7 +18,7 @@ function error(errorMsg: string, { id }: RFAMessage): RFAMessage { return new RFAMessage({ error: errorMsg, id: id }); } -export const RFARequestHandler: Record void | RFAMessage> = { +export const RFARequestHandler: Record RFAMessage> = { pushFile: function (msg: RFAMessage): RFAMessage { if (!isFileData(msg.params)) return error("Misses parameters", msg); diff --git a/src/RemoteFileAPI/Remote.ts b/src/RemoteFileAPI/Remote.ts index 826213ca9..f9b9b8d28 100644 --- a/src/RemoteFileAPI/Remote.ts +++ b/src/RemoteFileAPI/Remote.ts @@ -40,7 +40,7 @@ export class Remote { } function handleMessageEvent(this: WebSocket, e: MessageEvent): void { - const msg: RFAMessage = JSON.parse(e.data); + const msg = JSON.parse(e.data as string) as RFAMessage; if (!msg.method || !RFARequestHandler[msg.method]) { const response = new RFAMessage({ error: "Unknown message received", id: msg.id }); diff --git a/src/SaveObject.ts b/src/SaveObject.ts index 171097197..98cb42b94 100644 --- a/src/SaveObject.ts +++ b/src/SaveObject.ts @@ -44,7 +44,7 @@ import { isBinaryFormat } from "../electron/saveDataBinaryFormat"; import { downloadContentAsFile } from "./utils/FileUtils"; import { showAPIBreaks } from "./utils/APIBreaks/APIBreak"; import { breakInfos261 } from "./utils/APIBreaks/2.6.1"; -import { handleGetSaveDataError } from "./Netscript/ErrorMessages"; +import { handleGetSaveDataInfoError } from "./Netscript/ErrorMessages"; /* SaveObject.js * Defines the object used to save/load games @@ -130,7 +130,7 @@ class BitburnerSaveObject { try { saveData = await this.getSaveData(); } catch (error) { - handleGetSaveDataError(error); + handleGetSaveDataInfoError(error); return; } try { @@ -170,7 +170,7 @@ class BitburnerSaveObject { try { saveData = await this.getSaveData(); } catch (error) { - handleGetSaveDataError(error); + handleGetSaveDataInfoError(error); return; } const filename = this.getSaveFileName(); @@ -698,9 +698,12 @@ function evaluateVersionCompatibility(ver: string | number): void { } catch (e) { anyExportsFailed = true; // We just need the text error, not a full stack trace - console.error(`Failed to load export of material ${material.name} (${division.name} ${warehouse.city}) + console.error( + `Failed to load export of material ${material.name} (${division.name} ${warehouse.city}) Original export details: ${JSON.stringify(originalExport)} -Error: ${e}`); +Error: ${e}`, + e, + ); } } } @@ -805,16 +808,16 @@ async function loadGame(saveData: SaveData): Promise { if (Object.hasOwn(saveObj, "LastExportBonus")) { try { ExportBonus.setLastExportBonus(JSON.parse(saveObj.LastExportBonus)); - } catch (err) { + } catch (error) { ExportBonus.setLastExportBonus(new Date().getTime()); - console.error("ERROR: Failed to parse last export bonus Settings " + err); + console.error(`ERROR: Failed to parse last export bonus setting. Error: ${error}.`, error); } } if (Player.gang && Object.hasOwn(saveObj, "AllGangsSave")) { try { loadAllGangs(saveObj.AllGangsSave); - } catch (e) { - console.error("ERROR: Failed to parse AllGangsSave: " + e); + } catch (error) { + console.error(`ERROR: Failed to parse AllGangsSave. Error: ${error}.`, error); } } if (Object.hasOwn(saveObj, "VersionSave")) { diff --git a/src/Script/RunningScript.ts b/src/Script/RunningScript.ts index 0670387a2..68278109f 100644 --- a/src/Script/RunningScript.ts +++ b/src/Script/RunningScript.ts @@ -106,7 +106,7 @@ export class RunningScript { let logEntry = txt; if (Settings.TimestampsFormat && typeof txt === "string") { - logEntry = "[" + formatTime(Settings.TimestampsFormat) + "] " + logEntry; + logEntry = `[${formatTime(Settings.TimestampsFormat)}] ${txt}`; } this.logs.push(logEntry); diff --git a/src/ScriptEditor/ScriptEditor.ts b/src/ScriptEditor/ScriptEditor.ts index ba031f74a..7a27ffd89 100644 --- a/src/ScriptEditor/ScriptEditor.ts +++ b/src/ScriptEditor/ScriptEditor.ts @@ -14,6 +14,7 @@ import { NetscriptExtra } from "../NetscriptFunctions/Extra"; import * as enums from "../Enums"; import { ns } from "../NetscriptFunctions"; import { isLegacyScript } from "../Paths/ScriptFilePath"; +import { exceptionAlert } from "../utils/helpers/exceptionAlert"; /** Event emitter used for tracking when changes have been made to a content file. */ export const fileEditEvents = new EventEmitter<[hostname: string, filename: ContentFilePath]>(); @@ -36,7 +37,9 @@ export class ScriptEditor { for (const [apiKey, apiValue] of Object.entries(apiLayer)) { if (apiLayer === api && apiKey in hiddenAPI) continue; apiKeys.push(apiKey); - if (typeof apiValue === "object") populate(apiValue); + if (typeof apiValue === "object") { + populate(apiValue as object); + } } } populate(); @@ -44,23 +47,25 @@ export class ScriptEditor { (async function () { // We have to improve the default js language otherwise theme sucks const jsLanguage = monaco.languages.getLanguages().find((l) => l.id === "javascript"); - // Unsupported function is not exposed in monaco public API. - const l = await (jsLanguage as any).loader(); + if (!jsLanguage) { + return; + } + const loader = await jsLanguage.loader(); // replaced the bare tokens with regexes surrounded by \b, e.g. \b{token}\b which matches a word-break on either side // this prevents the highlighter from highlighting pieces of variables that start with a reserved token name - l.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]); + loader.language.tokenizer.root.unshift([new RegExp("\\bns\\b"), { token: "ns" }]); for (const symbol of apiKeys) - l.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]); + loader.language.tokenizer.root.unshift([new RegExp(`\\b${symbol}\\b`), { token: "netscriptfunction" }]); const otherKeywords = ["let", "const", "var", "function", "arguments"]; const otherKeyvars = ["true", "false", "null", "undefined"]; otherKeywords.forEach((k) => - l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }]), + loader.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeywords" }]), ); otherKeyvars.forEach((k) => - l.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }]), + loader.language.tokenizer.root.unshift([new RegExp(`\\b${k}\\b`), { token: "otherkeyvars" }]), ); - l.language.tokenizer.root.unshift([new RegExp("\\bthis\\b"), { token: "this" }]); - })(); + loader.language.tokenizer.root.unshift([new RegExp("\\bthis\\b"), { token: "this" }]); + })().catch((e) => exceptionAlert(e)); // Add ts definitions for API const source = netscriptDefinitions.replace(/export /g, ""); @@ -112,16 +117,20 @@ export class ScriptEditor { // returns a reject promise if the language worker is not loaded yet, so we wait to // call it until the language gets loaded. const languageWorker = new Promise<(...uris: monaco.Uri[]) => unknown>((resolve) => - monaco.languages.onLanguage(language, () => getLanguageWorker().then(resolve)), + monaco.languages.onLanguage(language, () => { + getLanguageWorker() + .then(resolve) + .catch((error) => exceptionAlert(error)); + }), ); - // Whenever a model is created, arange for it synced to the language server. + // Whenever a model is created, arrange for it to be synced to the language server. monaco.editor.onDidCreateModel((model) => { if (language === "typescript" && isLegacyScript(model.uri.path)) { // Don't sync legacy scripts to typescript worker. return; } if (["javascript", "typescript"].includes(model.getLanguageId())) { - languageWorker.then((cb) => cb(model.uri)); + languageWorker.then((resolve) => resolve(model.uri)).catch((error) => exceptionAlert(error)); } }); } diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx index beb311d72..97d391d41 100644 --- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx +++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx @@ -33,6 +33,7 @@ import { useCallback } from "react"; import { type AST, getFileType, parseAST } from "../../utils/ScriptTransformer"; import { RamCalculationErrorCode } from "../../Script/RamCalculationErrorCodes"; import { hasScriptExtension, isLegacyScript } from "../../Paths/ScriptFilePath"; +import { exceptionAlert } from "../../utils/helpers/exceptionAlert"; interface IProps { // Map of filename -> code @@ -105,7 +106,9 @@ function Root(props: IProps): React.ReactElement { const server = GetServer(currentScript.hostname); if (server === null) throw new Error("Server should not be null but it is."); server.writeToContentFile(currentScript.path, currentScript.code); - if (Settings.SaveGameOnFileSave) saveObject.saveGame(); + if (Settings.SaveGameOnFileSave) { + saveObject.saveGame().catch((error) => exceptionAlert(error)); + } rerender(); }, [rerender]); @@ -285,7 +288,9 @@ function Root(props: IProps): React.ReactElement { if (!server) throw new Error("Server should not be null but it is."); // This server helper already handles overwriting, etc. server.writeToContentFile(scriptToSave.path, scriptToSave.code); - if (Settings.SaveGameOnFileSave) saveObject.saveGame(); + if (Settings.SaveGameOnFileSave) { + saveObject.saveGame().catch((error) => exceptionAlert(error)); + } } function currentTabIndex(): number | undefined { diff --git a/src/ScriptEditor/ui/Toolbar.tsx b/src/ScriptEditor/ui/Toolbar.tsx index 61e971e00..b1ea3e78f 100644 --- a/src/ScriptEditor/ui/Toolbar.tsx +++ b/src/ScriptEditor/ui/Toolbar.tsx @@ -35,7 +35,10 @@ export function Toolbar({ editor, onSave }: IProps) { const [optionsOpen, { on: openOptions, off: closeOptions }] = useBoolean(false); function beautify(): void { - editor?.getAction("editor.action.formatDocument")?.run(); + editor + ?.getAction("editor.action.formatDocument") + ?.run() + .catch((error) => console.error(error)); } const { ram, ramEntries, isUpdatingRAM, options, saveOptions } = useScriptEditorContext(); diff --git a/src/ScriptEditor/ui/themes.ts b/src/ScriptEditor/ui/themes.ts index 40cf38f64..23152c2c8 100644 --- a/src/ScriptEditor/ui/themes.ts +++ b/src/ScriptEditor/ui/themes.ts @@ -218,7 +218,7 @@ export function makeTheme(theme: IScriptEditorTheme): editor.IStandaloneThemeDat return { base: theme.base, inherit: theme.inherit, rules: themeRules, colors: themeColors }; } -export async function loadThemes(defineTheme: DefineThemeFn): Promise { +export function loadThemes(defineTheme: DefineThemeFn): void { defineTheme("monokai", { base: "vs-dark", inherit: true, diff --git a/src/Settings/Settings.ts b/src/Settings/Settings.ts index 1693a5ed8..c90077dab 100644 --- a/src/Settings/Settings.ts +++ b/src/Settings/Settings.ts @@ -157,7 +157,12 @@ export const Settings = { disableSuffixes: false, load(saveString: string) { - const save = JSON.parse(saveString); + const save = JSON.parse(saveString) as { + theme?: typeof Settings.theme; + styles?: typeof Settings.styles; + overview?: typeof Settings.overview; + EditorTheme?: typeof Settings.EditorTheme; + }; save.theme && Object.assign(Settings.theme, save.theme); save.styles && Object.assign(Settings.styles, save.styles); save.overview && Object.assign(Settings.overview, save.overview); diff --git a/src/StockMarket/BuyingAndSelling.tsx b/src/StockMarket/BuyingAndSelling.tsx index a8b4e2a8e..60ff1969b 100644 --- a/src/StockMarket/BuyingAndSelling.tsx +++ b/src/StockMarket/BuyingAndSelling.tsx @@ -50,7 +50,7 @@ export function buyStock( } if (stock == null || isNaN(shares)) { if (ctx) { - helpers.log(ctx, () => `Invalid arguments: stock='${stock}' shares='${shares}'`); + helpers.log(ctx, () => `Invalid arguments: stock='${stock?.name}' shares='${shares}'`); } else if (opts.suppressDialog !== true) { dialogBoxCreate("Failed to buy stock. This may be a bug, contact developer"); } @@ -145,7 +145,7 @@ export function sellStock( // Sanitize/Validate arguments if (stock == null || shares < 0 || isNaN(shares)) { if (ctx) { - helpers.log(ctx, () => `Invalid arguments: stock='${stock}' shares='${shares}'`); + helpers.log(ctx, () => `Invalid arguments: stock='${stock?.name}' shares='${shares}'`); } else if (opts.suppressDialog !== true) { dialogBoxCreate( "Failed to sell stock. This is probably due to an invalid quantity. Otherwise, this may be a bug, contact developer", @@ -225,7 +225,7 @@ export function shortStock( } if (stock == null || isNaN(shares)) { if (ctx) { - helpers.log(ctx, () => `Invalid arguments: stock='${stock}' shares='${shares}'`); + helpers.log(ctx, () => `Invalid arguments: stock='${stock?.name}' shares='${shares}'`); } else if (opts.suppressDialog !== true) { dialogBoxCreate( "Failed to initiate a short position in a stock. This is probably " + @@ -321,7 +321,7 @@ export function sellShort( ): boolean { if (stock == null || isNaN(shares) || shares < 0) { if (ctx) { - helpers.log(ctx, () => `Invalid arguments: stock='${stock}' shares='${shares}'`); + helpers.log(ctx, () => `Invalid arguments: stock='${stock?.name}' shares='${shares}'`); } else if (!opts.suppressDialog) { dialogBoxCreate( "Failed to sell a short position in a stock. This is probably " + diff --git a/src/Terminal/commands/download.ts b/src/Terminal/commands/download.ts index 6d4f8de7d..946936c70 100644 --- a/src/Terminal/commands/download.ts +++ b/src/Terminal/commands/download.ts @@ -20,7 +20,13 @@ export function exportScripts(pattern: string, server: BaseServer, currDir = roo const filename = `bitburner${ hasScriptExtension(pattern) ? "Scripts" : pattern.endsWith(".txt") ? "Texts" : "Files" }.zip`; - zip.generateAsync({ type: "blob" }).then((content: Blob) => downloadContentAsFile(content, filename)); + zip + .generateAsync({ type: "blob" }) + .then((content: Blob) => downloadContentAsFile(content, filename)) + .catch((error) => { + console.error(error); + Terminal.error(`Cannot compress scripts with pattern ${pattern} on ${server.hostname}. Error: ${error}`); + }); } export function download(args: (string | number | boolean)[], server: BaseServer): void { @@ -33,9 +39,10 @@ export function download(args: (string | number | boolean)[], server: BaseServer try { exportScripts(pattern, server, Terminal.currDir); return; - } catch (e: any) { - const msg = String(e?.message ?? e); - return Terminal.error(msg); + } catch (error) { + console.error(error); + Terminal.error(`Cannot export scripts with pattern ${pattern} on ${server.hostname}. Error: ${error}`); + return; } } const path = Terminal.getFilepath(pattern); diff --git a/src/Terminal/commands/grep.ts b/src/Terminal/commands/grep.ts index 9f8844cbd..6a1afc5fd 100644 --- a/src/Terminal/commands/grep.ts +++ b/src/Terminal/commands/grep.ts @@ -383,13 +383,19 @@ function writeToTerminal( if (options.isVerbose) Terminal.print(verboseInfo); } -function checkOutFile(outFileStr: string, options: Options, server: BaseServer): ContentFilePath | void { - if (!outFileStr) return; +function checkOutFile(outFileStr: string, options: Options, server: BaseServer): ContentFilePath | null { + if (!outFileStr) { + return null; + } const outFilePath = Terminal.getFilepath(outFileStr); if (!outFilePath || !hasTextExtension(outFilePath)) { - return Terminal.error(ERR.badOutFile(outFileStr)); + Terminal.error(ERR.badOutFile(outFileStr)); + return null; + } + if (!options.isOverWrite && server.textFiles.has(outFilePath)) { + Terminal.error(ERR.outFileExists(outFileStr)); + return null; } - if (!options.isOverWrite && server.textFiles.has(outFilePath)) return Terminal.error(ERR.outFileExists(outFileStr)); return outFilePath; } @@ -433,7 +439,8 @@ export function grep(args: (string | number | boolean)[], server: BaseServer): v if (options.isPipeIn) files.length = 0; if (!options.isQuiet) writeToTerminal(prettyResult, options, results, files, pattern); if (params.outfile && outFilePath) server.writeToContentFile(outFilePath, rawResult.join("\n")); - } catch (e) { - Terminal.error("grep processing error: " + e); + } catch (error) { + console.error(error); + Terminal.error(`grep processing error: ${error}`); } } diff --git a/src/Terminal/commands/kill.ts b/src/Terminal/commands/kill.ts index ca20f5574..6639f14f9 100644 --- a/src/Terminal/commands/kill.ts +++ b/src/Terminal/commands/kill.ts @@ -44,7 +44,8 @@ export function kill(args: (string | number | boolean)[], server: BaseServer): v if (killed >= 5) { Terminal.print(`... killed ${killed} instances total`); } - } catch (e) { - Terminal.error(e + ""); + } catch (error) { + console.error(error); + Terminal.error(String(error)); } } diff --git a/src/Terminal/commands/mem.ts b/src/Terminal/commands/mem.ts index 5004fbb0d..df56e6768 100644 --- a/src/Terminal/commands/mem.ts +++ b/src/Terminal/commands/mem.ts @@ -43,7 +43,8 @@ export function mem(args: (string | number | boolean)[], server: BaseServer): vo // Let's warn the user that he might need to save his script again to generate the detailed entries Terminal.warn("You might have to open & save this script to see the detailed RAM usage information."); } - } catch (e) { - Terminal.error(e + ""); + } catch (error) { + console.error(error); + Terminal.error(String(error)); } } diff --git a/src/Terminal/commands/run.ts b/src/Terminal/commands/run.ts index 4fd486d28..3d711c68e 100644 --- a/src/Terminal/commands/run.ts +++ b/src/Terminal/commands/run.ts @@ -19,7 +19,10 @@ export function run(args: (string | number | boolean)[], server: BaseServer): vo if (hasScriptExtension(path)) { return runScript(path, args, server); } else if (hasContractExtension(path)) { - Terminal.runContract(path); + Terminal.runContract(path).catch((error) => { + console.error(error); + Terminal.error(`Cannot run contract ${path} on ${server.hostname}. Error: ${error}.`); + }); return; } else if (hasProgramExtension(path)) { return runProgram(path, args, server); diff --git a/src/Terminal/commands/runScript.ts b/src/Terminal/commands/runScript.ts index 2d7310daa..5a24357b8 100644 --- a/src/Terminal/commands/runScript.ts +++ b/src/Terminal/commands/runScript.ts @@ -25,7 +25,7 @@ export function runScript(path: ScriptFilePath, commandArgs: (string | number | argv: commandArgs, }); } catch (error) { - Terminal.error(`Invalid arguments. ${String(error)}.`); + Terminal.error(`Invalid arguments. ${error}.`); return; } const tailFlag = flags["--tail"] === true; diff --git a/src/Terminal/commands/tail.ts b/src/Terminal/commands/tail.ts index dfde2c85c..b9e05c293 100644 --- a/src/Terminal/commands/tail.ts +++ b/src/Terminal/commands/tail.ts @@ -32,7 +32,8 @@ export function tail(commandArray: (string | number | boolean)[], server: BaseSe } LogBoxEvents.emit(runningScript); } - } catch (e) { - Terminal.error(e + ""); + } catch (error) { + console.error(error); + Terminal.error(String(error)); } } diff --git a/src/db.ts b/src/db.ts index a47aa4c59..2c3e9a793 100644 --- a/src/db.ts +++ b/src/db.ts @@ -37,7 +37,7 @@ function getDB(): Promise { export function load(): Promise { return getDB().then((db) => { return new Promise((resolve, reject) => { - const request: IDBRequest = db.get("save"); + const request = db.get("save") as IDBRequest; request.onerror = function (this: IDBRequest) { reject(new Error("Error in Database request to get save data", { cause: this.error })); }; diff --git a/src/engine.tsx b/src/engine.tsx index 6366f05da..93eb35a40 100644 --- a/src/engine.tsx +++ b/src/engine.tsx @@ -237,7 +237,7 @@ const Engine: { Engine.Counters.autoSaveCounter = 60 * 5; // Let's check back in a bit } else { Engine.Counters.autoSaveCounter = Settings.AutosaveInterval * 5; - saveObject.saveGame(!Settings.SuppressSavedGameToast); + saveObject.saveGame(!Settings.SuppressSavedGameToast).catch((error) => console.error(error)); } } }, diff --git a/src/ui/GameRoot.tsx b/src/ui/GameRoot.tsx index 962331e9a..c2bbd241e 100644 --- a/src/ui/GameRoot.tsx +++ b/src/ui/GameRoot.tsx @@ -75,6 +75,7 @@ import { HistoryProvider } from "./React/Documentation"; import { GoRoot } from "../Go/ui/GoRoot"; import { Settings } from "../Settings/Settings"; import { isBitNodeFinished } from "../BitNode/BitNodeUtils"; +import { exceptionAlert } from "../utils/helpers/exceptionAlert"; const htmlLocation = location; @@ -154,8 +155,14 @@ export function GameRoot(): React.ReactElement { for (const server of GetAllServers()) { server.runningScriptMap.clear(); } - saveObject.saveGame(); - setTimeout(() => htmlLocation.reload(), 2000); + saveObject + .saveGame() + .then(() => { + setTimeout(() => htmlLocation.reload(), 2000); + }) + .catch((error) => { + exceptionAlert(error); + }); } function attemptedForbiddenRouting(name: string) { @@ -333,11 +340,13 @@ export function GameRoot(): React.ReactElement { case Page.Options: { mainPage = ( saveObject.saveGame()} + save={() => { + saveObject.saveGame().catch((error) => exceptionAlert(error)); + }} export={() => { // Apply the export bonus before saving the game onExport(); - saveObject.exportGame(); + saveObject.exportGame().catch((error) => exceptionAlert(error)); }} forceKill={killAllScripts} softReset={softReset} @@ -356,7 +365,7 @@ export function GameRoot(): React.ReactElement { exportGameFn={() => { // Apply the export bonus before saving the game onExport(); - saveObject.exportGame(); + saveObject.exportGame().catch((error) => exceptionAlert(error)); }} installAugmentationsFn={() => { installAugmentations(); @@ -395,7 +404,9 @@ export function GameRoot(): React.ReactElement { !ITutorial.isRunning ? ( saveObject.saveGame()} + save={() => { + saveObject.saveGame().catch((error) => exceptionAlert(error)); + }} killScripts={killAllScripts} /> ) : ( diff --git a/src/ui/React/ImportSave/ImportSave.tsx b/src/ui/React/ImportSave/ImportSave.tsx index ab6900eaa..c55204a91 100644 --- a/src/ui/React/ImportSave/ImportSave.tsx +++ b/src/ui/React/ImportSave/ImportSave.tsx @@ -38,7 +38,7 @@ import { useBoolean } from "../hooks"; import { ComparisonIcon } from "./ComparisonIcon"; import { SaveData } from "../../../types"; -import { handleGetSaveDataError } from "../../../Netscript/ErrorMessages"; +import { handleGetSaveDataInfoError } from "../../../Netscript/ErrorMessages"; const useStyles = makeStyles()((theme: Theme) => ({ root: { @@ -140,7 +140,7 @@ export const ImportSave = (props: { saveData: SaveData; automatic: boolean }): J // We cannot show dialog box in this screen (due to "withPopups = false"), so we will try showing it with a // delay. 1 second is usually enough to go back to other normal screens that allow showing popups. setTimeout(() => { - handleGetSaveDataError(error); + handleGetSaveDataInfoError(error); }, 1000); }); } diff --git a/src/utils/EventEmitter.ts b/src/utils/EventEmitter.ts index feef01a5e..9772dc588 100644 --- a/src/utils/EventEmitter.ts +++ b/src/utils/EventEmitter.ts @@ -1,8 +1,6 @@ /** Generic Event Emitter class following a subscribe/publish paradigm. */ export class EventEmitter { - private subscribers: Set<(...args: [...T]) => void | undefined> = new Set(); - - constructor() {} + private subscribers: Set<(...args: [...T]) => void> = new Set(); subscribe(s: (...args: [...T]) => void): () => void { this.subscribers.add(s); diff --git a/src/utils/v2APIBreak.ts b/src/utils/v2APIBreak.ts index 68e331022..1b2541477 100644 --- a/src/utils/v2APIBreak.ts +++ b/src/utils/v2APIBreak.ts @@ -239,7 +239,7 @@ export const v2APIBreak = () => { for (const server of GetAllServers()) { server.runningScriptMap = new Map(); } - saveObject.exportGame(); + saveObject.exportGame().catch((e) => console.error(e)); }; const formatOffenders = (offenders: IFileLine[]): string => {