diff --git a/src/Documentation/doc/basic/scripts.md b/src/Documentation/doc/basic/scripts.md
index 2b2a70575..577333afc 100644
--- a/src/Documentation/doc/basic/scripts.md
+++ b/src/Documentation/doc/basic/scripts.md
@@ -62,6 +62,21 @@ For example, if a script run with 1 thread is able to hack \$10,000, then runnin
When "multithreading" a script, the total [RAM](ram.md) cost can be calculated by simply multiplying the [RAM](ram.md) cost of a single instance of your script by the number of threads you will use. [See [`ns.getScriptRam()`](https://github.com/bitburner-official/bitburner-src/blob/bec737a25307be29c7efef147fc31effca65eedc/markdown/bitburner.ns.getscriptram.md) or the `mem` terminal command detailed below]
+## Never-ending scripts
+
+Sometimes it might be necessary for a script to never end and keep doing a particular task.
+In that case you would want to write your script in a never-ending loop, like `while (true)`.
+
+However, if you are not careful, this can crash your game.
+If the code inside the loop doesn't `await` for some time, it will never give other scripts and the game itself time to process.
+
+
+
+To help you find this potential bug, any `while (true)` loop without any `await` statement inside it will be marked.
+A red decoration will appear on the left side of the script editor, telling you about the issue.
+
+If you are really sure that this is not an oversight, you can suppress the warning using the comment `// @ignore-infinite` directly above the loop.
+
## Working with Scripts in Terminal
Here are some [terminal](terminal.md) commands you will find useful when working with scripts:
diff --git a/src/Script/RamCalculations.ts b/src/Script/RamCalculations.ts
index 394858581..1a5222f49 100644
--- a/src/Script/RamCalculations.ts
+++ b/src/Script/RamCalculations.ts
@@ -199,16 +199,16 @@ function parseOnlyRamCalculate(otherScripts: Map, code:
return { cost: ram, entries: detailedCosts.filter((e) => e.cost > 0) };
}
-export function checkInfiniteLoop(code: string): number {
+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 -1;
+ return [];
}
function nodeHasTrueTest(node: acorn.Node): boolean {
- return node.type === "Literal" && "raw" in node && node.raw === "true";
+ return node.type === "Literal" && "raw" in node && (node.raw === "true" || node.raw === "1");
}
function hasAwait(ast: acorn.Node): boolean {
@@ -225,14 +225,19 @@ export function checkInfiniteLoop(code: string): number {
return hasAwait;
}
- let missingAwaitLine = -1;
+ const possibleLines: number[] = [];
walk.recursive(
ast,
{},
{
WhileStatement: (node: Node, st: unknown, walkDeeper: walk.WalkerCallback) => {
+ const previousLines = code.slice(0, node.start).trimEnd().split("\n");
+ const lineNumber = previousLines.length + 1;
+ if (previousLines[previousLines.length - 1].match(/^\s*\/\/\s*@ignore-infinite/)) {
+ return;
+ }
if (nodeHasTrueTest(node.test) && !hasAwait(node)) {
- missingAwaitLine = (code.slice(0, node.start).match(/\n/g) || []).length + 1;
+ possibleLines.push(lineNumber);
} else {
node.body && walkDeeper(node.body, st);
}
@@ -240,7 +245,7 @@ export function checkInfiniteLoop(code: string): number {
},
);
- return missingAwaitLine;
+ return possibleLines;
}
interface ParseDepsResult {
diff --git a/src/ScriptEditor/ui/ScriptEditorRoot.tsx b/src/ScriptEditor/ui/ScriptEditorRoot.tsx
index 052d37ad0..6562dfe78 100644
--- a/src/ScriptEditor/ui/ScriptEditorRoot.tsx
+++ b/src/ScriptEditor/ui/ScriptEditorRoot.tsx
@@ -116,10 +116,10 @@ function Root(props: IProps): React.ReactElement {
if (editorRef.current === null || currentScript === null) return;
if (!decorations) decorations = editorRef.current.createDecorationsCollection();
if (!currentScript.path.endsWith(".js")) return;
- const awaitWarning = checkInfiniteLoop(newCode);
- if (awaitWarning !== -1) {
- decorations.set([
- {
+ const possibleLines = checkInfiniteLoop(newCode);
+ if (possibleLines.length !== 0) {
+ decorations.set(
+ possibleLines.map((awaitWarning) => ({
range: {
startLineNumber: awaitWarning,
startColumn: 1,
@@ -130,11 +130,12 @@ function Root(props: IProps): React.ReactElement {
isWholeLine: true,
glyphMarginClassName: "myGlyphMarginClass",
glyphMarginHoverMessage: {
- value: "Possible infinite loop, await something.",
+ value:
+ "Possible infinite loop, await something. If this is a false-positive, use `// @ignore-infinite` to suppress.",
},
},
- },
- ]);
+ })),
+ );
} else decorations.clear();
}