mirror of
https://github.com/bitburner-official/bitburner-src.git
synced 2026-04-29 12:27:07 +02:00
MISC: Support JSX, TS, TSX script files (#1216)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user