Repository: compiler-explorer/compiler-explorer
Pull request #7008 (2024-10-24 06:47:45)

Tsify most of clientstate
Add typing, some refactoring to switch-case.  Mark unused functions - but not deleting yet, out of suspicion it has something to do with #6980.



Patch:
diff --git a/compiler-args-app.ts b/compiler-args-app.ts
index 5f2517955..f7279b219 100644
--- a/compiler-args-app.ts
+++ b/compiler-args-app.ts
@@ -32,6 +32,7 @@ import {CompilerArguments} from './lib/compiler-arguments.js';
 import * as Parsers from './lib/compilers/argument-parsers.js';
 import {executeDirect} from './lib/exec.js';
 import {logger} from './lib/logger.js';
+import {padRight} from './lib/utils.js';
 
 const opts = nopt({
     parser: [String],
@@ -133,8 +134,7 @@ class CompilerArgsApp {
     async print() {
         const args = _.keys(this.compiler.possibleArguments.possibleArguments);
         for (const arg of args) {
-            const description = this.compiler.possibleArguments.possibleArguments[arg].description;
-            console.log(`${arg.padEnd(this.pad, ' ')} ${description}`);
+            console.log(padRight(arg, this.pad) + this.compiler.possibleArguments.possibleArguments[arg].description);
         }
 
         console.log('Stdvers:');
diff --git a/infra b/infra
new file mode 160000
index 000000000..3d4a55bdc
--- /dev/null
+++ b/infra
@@ -0,0 +1,1 @@
+Subproject commit 3d4a55bdc2dab8089db08839a1b5adabf3e228f7
diff --git a/lib/cfg/cfg-parsers/clang.ts b/lib/cfg/cfg-parsers/clang.ts
index 036755d40..b9da99ad3 100644
--- a/lib/cfg/cfg-parsers/clang.ts
+++ b/lib/cfg/cfg-parsers/clang.ts
@@ -25,6 +25,7 @@
 import _ from 'underscore';
 
 import type {ResultLine} from '../../../types/resultline/resultline.interfaces.js';
+import * as utils from '../../utils.js';
 
 import {BaseCFGParser} from './base.js';
 
@@ -41,8 +42,8 @@ export class ClangCFGParser extends BaseCFGParser {
         const removeComments = (x: ResultLine) => {
             const pos_x86 = x.text.indexOf('# ');
             const pos_arm = x.text.indexOf('// ');
-            if (pos_x86 !== -1) x.text = x.text.substring(0, pos_x86).trimEnd();
-            if (pos_arm !== -1) x.text = x.text.substring(0, pos_arm).trimEnd();
+            if (pos_x86 !== -1) x.text = utils.trimRight(x.text.substring(0, pos_x86));
+            if (pos_arm !== -1) x.text = utils.trimRight(x.text.substring(0, pos_arm));
             return x;
         };
 
diff --git a/lib/clientstate-normalizer.ts b/lib/clientstate-normalizer.ts
index 79e08aa26..c9034f76c 100644
--- a/lib/clientstate-normalizer.ts
+++ b/lib/clientstate-normalizer.ts
@@ -23,7 +23,14 @@
 // POSSIBILITY OF SUCH DAMAGE.
 
 import {assert} from './assert.js';
-import {ClientState, ClientStateCompiler, ClientStateConformanceView, ClientStateExecutor} from './clientstate.js';
+import {
+    ClientState,
+    ClientStateCompiler,
+    ClientStateConformanceView,
+    ClientStateExecutor,
+    ClientStateSession,
+    ClientStateTree,
+} from './clientstate.js';
 
 type BasicGoldenLayoutStruct = {
     type: string;
@@ -52,9 +59,9 @@ type GoldenLayoutRootStruct = {
 
 export class ClientStateNormalizer {
     normalized = new ClientState();
-    rootContent = null;
+    rootContent: Array<BasicGoldenLayoutStruct> | undefined;
 
-    fromGoldenLayoutContent(content) {
+    fromGoldenLayoutContent(content: BasicGoldenLayoutStruct[]) {
         if (!this.rootContent) this.rootContent = content;
 
         for (const component of content) {
@@ -62,7 +69,7 @@ export class ClientStateNormalizer {
         }
     }
 
-    setFilterSettingsFromComponentState(compiler, componentState) {
+    setFilterSettingsFromComponentState(compiler: ClientStateCompiler, componentState) {
         compiler.filters.binary = componentState.filters.binary;
         compiler.filters.binaryObject = componentState.filters.binaryObject;
         compiler.filters.execute = componentState.filters.execute;
@@ -76,19 +83,22 @@ export class ClientStateNormalizer {
         compiler.filters.debugCalls = componentState.filters.debugCalls;
     }
 
-    setFilterSettingsFromComponent(compiler, component) {
+    setFilterSettingsFromComponent(compiler: ClientStateCompiler, component) {
         this.setFilterSettingsFromComponentState(compiler, component.componentState);
     }
 
-    findCompilerInGoldenLayout(content, id) {
-        let result;
+    findCompilerInGoldenLayout(
+        content: Array<BasicGoldenLayoutStruct | GoldenLayoutComponentStruct>,
+        id: number,
+    ): GoldenLayoutComponentStruct | null {
+        let result: GoldenLayoutComponentStruct | null = null;
 
         for (const component of content) {
-            if (component.componentName === 'compiler') {
+            if ('componentName' in component && component.componentName === 'compiler') {
                 if (component.componentState.id === id) {
                     return component;
                 }
-            } else if (component.content && component.content.length > 0) {
+            } else if ('content' in component && component.content.length > 0) {
                 result = this.findCompilerInGoldenLayout(component.content, id);
                 if (result) break;
             }
@@ -97,23 +107,24 @@ export class ClientStateNormalizer {
         return result;
     }
 
-    findOrCreateSessionFromEditorOrCompiler(editorId, compilerId) {
-        let session;
-        if (editorId) {
-            session = this.normalized.findOrCreateSession(editorId);
-        } else {
-            const glCompiler = this.findCompilerInGoldenLayout(this.rootContent, compilerId);
-            if (glCompiler) {
-                if (glCompiler.componentState.source) {
-                    session = this.normalized.findOrCreateSession(glCompiler.componentState.source);
-                }
-            }
-        }
-        return session;
-    }
-
-    addSpecialOutputToCompiler(compilerId, name, editorId) {
-        const glCompiler = this.findCompilerInGoldenLayout(this.rootContent, compilerId);
+    // UNUSED. not deleting yet
+    // findOrCreateSessionFromEditorOrCompiler(editorId, compilerId) {
+    //     let session;
+    //     if (editorId) {
+    //         session = this.normalized.findOrCreateSession(editorId);
+    //     } else {
+    //         const glCompiler = this.findCompilerInGoldenLayout(this.rootContent, compilerId);
+    //         if (glCompiler) {
+    //             if (glCompiler.componentState.source) {
+    //                 session = this.normalized.findOrCreateSession(glCompiler.componentState.source);
+    //             }
+    //         }
+    //     }
+    //     return session;
+    // }
+
+    addSpecialOutputToCompiler(compilerId: number, name: string, editorId: number) {
+        const glCompiler = this.findCompilerInGoldenLayout(this.rootContent!, compilerId);
         if (glCompiler) {
             let compiler;
             if (glCompiler.componentState.source) {
@@ -132,8 +143,8 @@ export class ClientStateNormalizer {
         }
     }
 
-    addToolToCompiler(compilerId, editorId, toolId, args, stdin) {
-        const glCompiler = this.findCompilerInGoldenLayout(this.rootContent, compilerId);
+    addToolToCompiler(compilerId, toolId, args, stdin) {
+        const glCompiler = this.findCompilerInGoldenLayout(this.rootContent!, compilerId);
         if (glCompiler) {
             let compiler;
             if (glCompiler.componentState.source) {
@@ -182,21 +193,22 @@ export class ClientStateNormalizer {
         }
     }
 
-    addCompilerFromComponentState(componentState) {
+    addCompilerFromComponentState(componentState: ClientStateCompiler) {
         let compiler;
         if (componentState.id) {
             if (componentState.source) {
                 const session = this.normalized.findOrCreateSession(componentState.source);
-                compiler = session.findOrCreateCompiler(componentState.id);
+                compiler = session.findOrCreateCompiler(componentState.id as number);
             } else if (componentState.tree) {
                 const tree = this.normalized.findOrCreateTree(componentState.tree);
-                compiler = tree.findOrCreateCompiler(componentState.id);
+                compiler = tree.findOrCreateCompiler(componentState.id as number);
             } else {
                 return;
             }
         } else {
-            compiler = new ClientStateCompiler();
+            // Ofek: do we ever get here?
 
+            compiler = new ClientStateCompiler();
             if (componentState.source) {
                 const session = this.normalized.findOrCreateSession(componentState.source);
                 session.compilers.push(compiler);
@@ -210,6 +222,7 @@ export class ClientStateNormalizer {
             }
         }
 
+        // Ofek: id and compiler get mixed up in Compiler.getCurrentState?
         compiler.id = componentState.compiler;
         compiler.options = componentState.options;
         compiler.libs = componentState.libs;
@@ -219,56 +232,96 @@ export class ClientStateNormalizer {
         this.setFilterSettingsFromComponentState(compiler, componentState);
     }
 
-    fromGoldenLayoutComponent(component) {
-        if (component.componentName === 'tree') {
-            const tree = this.normalized.findOrCreateTree(component.componentState.id);
-            tree.fromJsonData(component.componentState);
-        } else if (component.componentName === 'codeEditor') {
-            const session = this.normalized.findOrCreateSession(component.componentState.id);
-            session.language = component.componentState.lang;
-            session.source = component.componentState.source;
-            if (component.componentState.filename) session.filename = component.componentState.filename;
-        } else if (component.componentName === 'compiler') {
-            this.addCompilerFromComponentState(component.componentState);
-        } else if (component.componentName === 'executor') {
-            this.addExecutorFromComponentState(component.componentState);
-        } else if (component.componentName === 'ast') {
-            this.addSpecialOutputToCompiler(component.componentState.id, 'ast', component.componentState.editorid);
-        } else if (component.componentName === 'opt') {
-            this.addSpecialOutputToCompiler(component.componentState.id, 'opt', component.componentState.editorid);
-        } else if (component.componentName === 'stackusage') {
-            this.addSpecialOutputToCompiler(
-                component.componentState.id,
-                'stackusage',
-                component.componentState.editorid,
-            );
-        } else if (component.componentName === 'cfg') {
-            this.addSpecialOutputToCompiler(component.componentState.id, 'cfg', component.componentState.editorid);
-        } else if (component.componentName === 'gccdump') {
-            this.addSpecialOutputToCompiler(
-                component.componentState._compilerid,
-                'gccdump',
-                component.componentState._editorid,
-            );
-        } else if (component.componentName === 'output') {
-            this.addSpecialOutputToCompiler(
-                component.componentState.compiler,
-                'compilerOutput',
-                component.componentState.editor,
-            );
-        } else if (component.componentName === 'conformance') {
-            const session = this.normalized.findOrCreateSession(component.componentState.editorid);
-            session.conformanceview = new ClientStateConformanceView(component.componentState);
-        } else if (component.componentName === 'tool') {
-            this.addToolToCompiler(
-                component.componentState.id,
-                component.componentState.editor,
-                component.componentState.toolId,
-                component.componentState.args,
-                component.componentState.stdin,
-            );
-        } else if (component.content) {
-            this.fromGoldenLayoutContent(component.content);
+    fromGoldenLayoutComponent(component: BasicGoldenLayoutStruct | GoldenLayoutComponentStruct) {
+        if ('componentName' in component) {
+            // handle GoldenLayoutComponentStruct
+            switch (component.componentName) {
+                case 'tree': {
+                    const tree = this.normalized.findOrCreateTree(component.componentState.id);
+                    tree.fromJsonData(component.componentState);
+                    break;
+                }
+                case 'codeEditor': {
+                    const session = this.normalized.findOrCreateSession(component.componentState.id);
+                    session.language = component.componentState.lang;
+                    session.source = component.componentState.source;
+                    if (component.componentState.filename) session.filename = component.componentState.filename;
+                    break;
+                }
+                case 'compiler': {
+                    this.addCompilerFromComponentState(component.componentState);
+                    break;
+                }
+                case 'executor': {
+                    this.addExecutorFromComponentState(component.componentState);
+                    break;
+                }
+                case 'ast': {
+                    this.addSpecialOutputToCompiler(
+                        component.componentState.id,
+                        'ast',
+                        component.componentState.editorid,
+                    );
+                    break;
+                }
+                case 'opt': {
+                    this.addSpecialOutputToCompiler(
+                        component.componentState.id,
+                        'opt',
+                        component.componentState.editorid,
+                    );
+                    break;
+                }
+                case 'stackusage': {
+                    this.addSpecialOutputToCompiler(
+                        component.componentState.id,
+                        'stackusage',
+                        component.componentState.editorid,
+                    );
+                    break;
+                }
+                case 'cfg': {
+                    this.addSpecialOutputToCompiler(
+                        component.componentState.id,
+                        'cfg',
+                        component.componentState.editorid,
+                    );
+                    break;
+                }
+                case 'gccdump': {
+                    this.addSpecialOutputToCompiler(
+                        component.componentState._compilerid,
+                        'gccdump',
+                        component.componentState._editorid,
+                    );
+                    break;
+                }
+                case 'output': {
+                    this.addSpecialOutputToCompiler(
+                        component.componentState.compiler,
+                        'compilerOutput',
+                        component.componentState.editor,
+                    );
+                    break;
+                }
+                case 'conformance': {
+                    const session = this.normalized.findOrCreateSession(component.componentState.editorid);
+                    session.conformanceview = new ClientStateConformanceView(component.componentState);
+                    break;
+                }
+                case 'tool': {
+                    this.addToolToCompiler(
+                        component.componentState.id,
+                        component.componentState.toolId,
+                        component.componentState.args,
+                        component.componentState.stdin,
+                    );
+                    break;
+                }
+            }
+        } else if ('content' in component) {
+            // handle BasicGoldenLayoutStruct
+            this.fromGoldenLayoutContent(component.content as unknown as BasicGoldenLayoutStruct[]);
         }
 
         this.fixFilesInTrees();
@@ -290,7 +343,7 @@ export class ClientStateNormalizer {
         }
     }
 
-    fromGoldenLayout(globj) {
+    fromGoldenLayout(globj: GoldenLayoutRootStruct) {
         this.rootContent = globj.content;
 
         if (globj.content) {
@@ -300,7 +353,7 @@ export class ClientStateNormalizer {
 }
 
 class GoldenLayoutComponents {
-    createSourceComponent(session, customSessionId?): GoldenLayoutComponentStruct {
+    createSourceComponent(session: ClientStateSession, customSessionId?: number): GoldenLayoutComponentStruct {
         const editor = {
             type: 'component',
             componentName: 'codeEditor',
@@ -327,7 +380,7 @@ class GoldenLayoutComponents {
         }
     }
 
-    createTreeComponent(tree, customTreeId?): GoldenLayoutComponentStruct {
+    createTreeComponent(tree: ClientStateTree, customTreeId?: number): GoldenLayoutComponentStruct {
         const treeComponent = {
             type: 'component',
             componentName: 'tree',
@@ -351,7 +404,11 @@ class GoldenLayoutComponents {
         return treeComponent;
     }
 
-    createAstComponent(session, compilerIndex, customSessionId?): GoldenLayoutComponentStruct {
+    createAstComponent(
+        session: ClientStateSession | null,
+        compilerIndex: number,
+        customSessionId?: number,
+    ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'ast',
@@ -364,7 +421,11 @@ class GoldenLayoutComponents {
         };
     }
 
-    createOptComponent(session, compilerIndex, customSessionId?): GoldenLayoutComponentStruct {
+    createOptComponent(
+        session: ClientStateSession | null,
+        compilerIndex: number,
+        customSessionId?: number,
+    ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'opt',
@@ -376,7 +437,11 @@ class GoldenLayoutComponents {
             reorderEnabled: true,
         };
     }
-    createStackUsageComponent(session, compilerIndex, customSessionId?): GoldenLayoutComponentStruct {
+    createStackUsageComponent(
+        session: ClientStateSession,
+        compilerIndex: number,
+        customSessionId?: number,
+    ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'stackusage',
@@ -389,7 +454,11 @@ class GoldenLayoutComponents {
         };
     }
 
-    createCfgComponent(session, compilerIndex, customSessionId?): GoldenLayoutComponentStruct {
+    createCfgComponent(
+        session: ClientStateSession | null,
+        compilerIndex: number,
+        customSessionId?: number,
+    ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'opt',
@@ -406,7 +475,11 @@ class GoldenLayoutComponents {
         };
     }
 
-    createGccDumpComponent(session, compilerIndex, customSessionId?): GoldenLayoutComponentStruct {
+    createGccDumpComponent(
+        session: ClientStateSession | null,
+        compilerIndex: number,
+        customSessionId?: number,
+    ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'gccdump',
@@ -419,7 +492,11 @@ class GoldenLayoutComponents {
         };
     }
 
-    createCompilerOutComponent(session, compilerIndex, customSessionId?): GoldenLayoutComponentStruct {
+    createCompilerOutComponent(
+        session: ClientStateSession | null,
+        compilerIndex: number,
+        customSessionId?: number,
+    ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'output',
@@ -434,7 +511,14 @@ class GoldenLayoutComponents {
         };
     }
 
-    createToolComponent(session, compilerIndex, toolId, args, stdin, customSessionId?): GoldenLayoutComponentStruct {
+    createToolComponent(
+        session: ClientStateSession | null,
+        compilerIndex: number,
+        toolId: number,
+        args,
+        stdin,
+        customSessionId?,
+    ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'tool',
@@ -450,12 +534,15 @@ class GoldenLayoutComponents {
         };
     }
 
-    createConformanceViewComponent(session, conformanceview, customSessionId?): GoldenLayoutComponentStruct {
+    createConformanceViewComponent(
+        session: ClientStateSession,
+        conformanceview: ClientStateConformanceView,
+    ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'conformance',
             componentState: {
-                editorid: customSessionId || session.id,
+                editorid: session.id,
                 langId: session.language,
                 compilers: [],
                 libs: conformanceview.libs,
@@ -469,7 +556,12 @@ class GoldenLayoutComponents {
         return {...filters};
     }
 
-    createSourceCompilerComponent(session, compiler, customSessionId?, idxCompiler?) {
+    createSourceCompilerComponent(
+        session: ClientStateSession,
+        compiler,
+        customSessionId?: number,
+        idxCompiler?: number,
+    ) {
         return {
             type: 'component',
             componentName: 'compiler',
@@ -488,7 +580,12 @@ class GoldenLayoutComponents {
         };
     }
 
-    createCompilerComponentForTree(tree, compiler, customTreeId?, idxCompiler?) {
+    createCompilerComponentForTree(
+        tree: ClientStateTree,
+        compiler: ClientStateCompiler,
+        customTreeId: number | null,
+        idxCompiler?: number,
+    ) {
         return {
             type: 'component',
             componentName: 'compiler',
@@ -508,7 +605,7 @@ class GoldenLayoutComponents {
         };
     }
 
-    createExecutorComponent(session, executor, customSessionId?) {
+    createExecutorComponent(session: ClientStateSession, executor, customSessionId?: number) {
         return {
             type: 'component',
             componentName: 'executor',
@@ -533,7 +630,7 @@ class GoldenLayoutComponents {
         };
     }
 
-    createExecutorComponentForTree(tree, executor, customTreeId?) {
+    createExecutorComponentForTree(tree: ClientStateTree, executor, customTreeId?: number) {
         return {
             type: 'component',
             componentName: 'executor',
@@ -559,69 +656,90 @@ class GoldenLayoutComponents {
         };
     }
 
-    createDiffComponent(left, right) {
-        return {
-            type: 'component',
-            componentName: 'diff',
-            componentState: {
-                lhs: left,
-                rhs: right,
-                lhsdifftype: 0,
-                rhsdifftype: 0,
-                fontScale: 14,
-            },
-        };
-    }
-
-    createSpecialOutputComponent(viewtype, session, idxCompiler, customSessionId?): GoldenLayoutComponentStruct {
-        if (viewtype === 'ast') {
-            return this.createAstComponent(session, idxCompiler + 1, customSessionId);
-        } else if (viewtype === 'opt') {
-            return this.createOptComponent(session, idxCompiler + 1, customSessionId);
-        } else if (viewtype === 'stackusage') {
-            return this.createStackUsageComponent(session, idxCompiler + 1, customSessionId);
-        } else if (viewtype === 'cfg') {
-            return this.createCfgComponent(session, idxCompiler + 1, customSessionId);
-        } else if (viewtype === 'gccdump') {
-            return this.createGccDumpComponent(session, idxCompiler + 1, customSessionId);
-        } else if (viewtype === 'compilerOutput') {
-            return this.createCompilerOutComponent(session, idxCompiler + 1, customSessionId);
-        } else {
-            throw new Error(`Unknown viewtype for compiler(${idxCompiler + 1}) ${viewtype}`);
+    // UNUSED. not deleting yet
+    // createDiffComponent(left, right) {
+    //     return {
+    //         type: 'component',
+    //         componentName: 'diff',
+    //         componentState: {
+    //             lhs: left,
+    //             rhs: right,
+    //             lhsdifftype: 0,
+    //             rhsdifftype: 0,
+    //             fontScale: 14,
+    //         },
+    //     };
+    // }
+
+    createSpecialOutputComponent(
+        viewtype: string,
+        session: ClientStateSession,
+        idxCompiler: number,
+        customSessionId?: number,
+    ): GoldenLayoutComponentStruct {
+        switch (viewtype) {
+            case 'ast': {
+                return this.createAstComponent(session, idxCompiler + 1, customSessionId);
+            }
+            case 'opt': {
+                return this.createOptComponent(session, idxCompiler + 1, customSessionId);
+            }
+            case 'stackusage': {
+                return this.createStackUsageComponent(session, idxCompiler + 1, customSessionId);
+            }
+            case 'cfg': {
+                return this.createCfgComponent(session, idxCompiler + 1, customSessionId);
+            }
+            case 'gccdump': {
+                return this.createGccDumpComponent(session, idxCompiler + 1, customSessionId);
+            }
+            case 'compilerOutput': {
+                return this.createCompilerOutComponent(session, idxCompiler + 1, customSessionId);
+            }
+            default: {
+                throw new Error(`Unknown viewtype for compiler(${idxCompiler + 1}) ${viewtype}`);
+            }
         }
     }
 
-    createSpecialOutputComponentForTreeCompiler(viewtype, idxCompiler): GoldenLayoutComponentStruct {
-        if (viewtype === 'ast') {
-            return this.createAstComponent(null, idxCompiler + 1, false);
-        } else if (viewtype === 'opt') {
-            return this.createOptComponent(null, idxCompiler + 1, false);
-        } else if (viewtype === 'stackusage') {
-            return this.createOptComponent(null, idxCompiler + 1, false);
-        } else if (viewtype === 'cfg') {
-            return this.createCfgComponent(null, idxCompiler + 1, false);
-        } else if (viewtype === 'gccdump') {
-            return this.createGccDumpComponent(null, idxCompiler + 1, false);
-        } else if (viewtype === 'compilerOutput') {
-            return this.createCompilerOutComponent(null, idxCompiler + 1, false);
-        } else {
-            throw new Error(`Unknown viewtype (for tree compiler ${idxCompiler + 1}) ${viewtype}`);
+    createSpecialOutputComponentForTreeCompiler(viewtype: string, idxCompiler: number): GoldenLayoutComponentStruct {
+        switch (viewtype) {
+            case 'ast': {
+                return this.createAstComponent(null, idxCompiler + 1);
+            }
+            case 'opt': {
+                return this.createOptComponent(null, idxCompiler + 1);
+            }
+            case 'stackusage': {
+                return this.createOptComponent(null, idxCompiler + 1);
+            }
+            case 'cfg': {
+                return this.createCfgComponent(null, idxCompiler + 1);
+            }
+            case 'gccdump': {
+                return this.createGccDumpComponent(null, idxCompiler + 1);
+            }
+            case 'compilerOutput': {
+                return this.createCompilerOutComponent(null, idxCompiler + 1);
+            }
+            default: {
+                throw new Error(`Unknown viewtype (for tree compiler ${idxCompiler + 1}) ${viewtype}`);
+            }
         }
     }
 
     createToolComponentForTreeCompiler(
-        tree,
-        compilerIndex,
-        toolId,
-        args,
-        stdin,
-        customTreeId?,
+        tree: ClientStateTree,
+        compilerIndex: number,
+        toolId: number,
+        args: string,
+        stdin: string,
     ): GoldenLayoutComponentStruct {
         return {
             type: 'component',
             componentName: 'tool',
             componentState: {
-                tree: customTreeId || (tree ? tree.id : undefined),
+                tree: tree ? tree.id : undefined,
                 compiler: compilerIndex,
                 toolId: toolId,
                 args: args,
@@ -667,42 +785,67 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
         };
     }
 
-    newTreeFromTree(tree, width: number): BasicGoldenLayoutStruct {
+    newTreeFromTree(tree: ClientStateTree, width: number): BasicGoldenLayoutStruct {
         return this.newStackWithOneComponent(width, this.createTreeComponent(tree));
     }
 
-    newSourceStackFromSession(session, width: number): BasicGoldenLayoutStruct {
+    newSourceStackFromSession(session: ClientStateSession, width: number): BasicGoldenLayoutStruct {
         return this.newStackWithOneComponent(width, this.createSourceComponent(session));
     }
 
-    newAstStackFromCompiler(session, compilerIndex: number, width: number): BasicGoldenLayoutStruct {
+    newAstStackFromCompiler(
+        session: ClientStateSession,
+        compilerIndex: number,
+        width: number,
+    ): BasicGoldenLayoutStruct {
         return this.newStackWithOneComponent(width, this.createAstComponent(session, compilerIndex));
     }
 
-    newOptStackFromCompiler(session, compilerIndex: number, width: number): BasicGoldenLayoutStruct {
+    newOptStackFromCompiler(
+        session: ClientStateSession,
+        compilerIndex: number,
+        width: number,
+    ): BasicGoldenLayoutStruct {
         return this.newStackWithOneComponent(width, this.createOptComponent(session, compilerIndex));
     }
 
-    newCfgStackFromCompiler(session, compilerIndex: number, width: number): BasicGoldenLayoutStruct {
+    newCfgStackFromCompiler(
+        session: ClientStateSession,
+        compilerIndex: number,
+        width: number,
+    ): BasicGoldenLayoutStruct {
         return this.newStackWithOneComponent(width, this.createCfgComponent(session, compilerIndex));
     }
 
-    newGccDumpStackFromCompiler(session, compilerIndex, width: number): BasicGoldenLayoutStruct {
-        return this.newStackWithOneComponent(width, this.createGccDumpComponent(session, compilerIndex));
-    }
+    // UNUSED. not deleting yet
+    // newGccDumpStackFromCompiler(session, compilerIndex, width: number): BasicGoldenLayoutStruct {
+    //     return this.newStackWithOneComponent(width, this.createGccDumpComponent(session, compilerIndex));
+    // }
 
-    newCompilerOutStackFromCompiler(session, compilerIndex, width: number): BasicGoldenLayoutStruct {
-        return this.newStackWithOneComponent(width, this.createCompilerOutComponent(session, compilerIndex));
-    }
+    // UNUSED. not deleting yet
+    // newCompilerOutStackFromCompiler(session, compilerIndex, width: number): BasicGoldenLayoutStruct {
+    //     return this.newStackWithOneComponent(width, this.createCompilerOutComponent(session, compilerIndex));
+    // }
 
-    newToolStackFromCompiler(session, compilerIndex, toolId, args, stdin, width: number): BasicGoldenLayoutStruct {
+    newToolStackFromCompiler(
+        session: ClientStateSession,
+        compilerIndex: number,
+        toolId,
+        args,
+        stdin,
+        width: number,
+    ): BasicGoldenLayoutStruct {
         return this.newStackWithOneComponent(
             width,
             this.createToolComponent(session, compilerIndex, toolId, args, stdin),
         );
     }
 
-    newConformanceViewStack(session, width: number, conformanceview): BasicGoldenLayoutStruct {
+    newConformanceViewStack(
+        session: ClientStateSession,
+        width: number,
+        conformanceview: ClientStateConformanceView,
+    ): BasicGoldenLayoutStruct {
         const component = this.createConformanceViewComponent(session, conformanceview);
 
         for (const compiler of conformanceview.compilers) {
@@ -717,24 +860,24 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
         return this.newStackWithOneComponent(width, component);
     }
 
-    newCompilerStackFromSession(session, compiler, width: number): BasicGoldenLayoutStruct {
+    newCompilerStackFromSession(session: ClientStateSession, compiler, width: number): BasicGoldenLayoutStruct {
         return this.newStackWithOneComponent(
             width,
-            this.createSourceCompilerComponent(session, compiler, false, compiler._internalId),
+            this.createSourceCompilerComponent(session, compiler, undefined, compiler._internalId),
         );
     }
 
-    newExecutorStackFromSession(session, executor, width: number): BasicGoldenLayoutStruct {
+    newExecutorStackFromSession(session: ClientStateSession, executor, width: number): BasicGoldenLayoutStruct {
         return this.newStackWithOneComponent(width, this.createExecutorComponent(session, executor));
     }
 
-    createSourceContentArray(state, left, right): BasicGoldenLayoutStruct[] {
-        if (left.session === right.session) {
-            return [this.createPresentationModeComponents(state.sessions[left.session], 1, 100)];
+    createSourceContentArray(state: ClientState, leftSession: number, rightSession: number): BasicGoldenLayoutStruct[] {
+        if (leftSession === rightSession) {
+            return [this.createPresentationModeComponents(state.sessions[leftSession], 1, 100)];
         } else {
             return [
-                this.createPresentationModeComponents(state.sessions[left.session], 1),
-                this.createPresentationModeComponents(state.sessions[right.session], 2),
+                this.createPresentationModeComponents(state.sessions[leftSession], 1),
+                this.createPresentationModeComponents(state.sessions[rightSession], 2),
             ];
         }
     }
@@ -783,29 +926,29 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
         };
     }
 
-    getPresentationModeLayoutForSource(state, left) {
+    getPresentationModeLayoutForSource(state: ClientState, sessionIdx: number) {
         const gl = this.getPresentationModeEmptyLayout();
         gl.content[0].content = [
             {
                 type: 'column',
                 width: 100,
-                content: this.createSourceContentArray(state, left, left),
+                content: this.createSourceContentArray(state, sessionIdx, sessionIdx),
             },
         ];
 
         return gl;
     }
 
-    closeAllEditors(tree) {
+    closeAllEditors(tree: ClientStateTree) {
         for (const file of tree.files) {
             file.editorId = -1;
             file.isOpen = false;
         }
     }
 
-    getPresentationModeLayoutForTree(state, left) {
+    getPresentationModeLayoutForTree(state: ClientState, leftTreeIdx: number) {
         const gl = this.getPresentationModeEmptyLayout();
-        const tree = state.trees[left.tree];
+        const tree = state.trees[leftTreeIdx];
         const customTreeId = 1;
 
         this.closeAllEditors(tree);
@@ -859,31 +1002,32 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
         return gl;
     }
 
-    getPresentationModeLayoutForComparisonSlide(state, left, right) {
-        const gl = this.getPresentationModeEmptyLayout();
-        gl.content[0].content = [
-            {
-                type: 'row',
-                height: 50,
-                content: this.createSourceContentArray(state, left, right),
-            },
-            {
-                type: 'row',
-                height: 50,
-                content: [
-                    {
-                        type: 'stack',
-                        width: 100,
-                        content: [this.createDiffComponent(left.compiler + 1, right.compiler + 1)],
-                    },
-                ],
-            },
-        ];
-
-        return gl;
-    }
-
-    createPresentationModeComponents(session, customSessionId?, customWidth?) {
+    // UNUSED. not deleting yet
+    // getPresentationModeLayoutForComparisonSlide(state, left, right) {
+    //     const gl = this.getPresentationModeEmptyLayout();
+    //     gl.content[0].content = [
+    //         {
+    //             type: 'row',
+    //             height: 50,
+    //             content: this.createSourceContentArray(state, left, right),
+    //         },
+    //         {
+    //             type: 'row',
+    //             height: 50,
+    //             content: [
+    //                 {
+    //                     type: 'stack',
+    //                     width: 100,
+    //                     content: [this.createDiffComponent(left.compiler + 1, right.compiler + 1)],
+    //                 },
+    //             ],
+    //         },
+    //     ];
+
+    //     return gl;
+    // }
+
+    createPresentationModeComponents(session: ClientStateSession, customSessionId?: number, customWidth?: number) {
         const stack = {
             type: 'stack',
             width: customWidth || 50,
@@ -901,7 +1045,7 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
 
             for (const tool of compiler.tools) {
                 stack.content.push(
-                    this.createToolComponent(false, idxCompiler + 1, tool.id, tool.args, tool.stdin, customSessionId),
+                    this.createToolComponent(null, idxCompiler + 1, tool.id, tool.args, tool.stdin, customSessionId),
                 );
             }
         }
@@ -913,14 +1057,12 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
         return stack;
     }
 
-    generatePresentationModeMobileViewerSlides(state) {
+    generatePresentationModeMobileViewerSlides(state: ClientState) {
         const slides: any[] = [];
 
         if (state.trees.length > 0) {
             for (let idxTree = 0; idxTree < state.trees.length; idxTree++) {
-                const gl = this.getPresentationModeLayoutForTree(state, {
-                    tree: idxTree,
-                });
+                const gl = this.getPresentationModeLayoutForTree(state, idxTree);
                 slides.push(gl);
             }
 
@@ -928,17 +1070,14 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
         }
 
         for (let idxSession = 0; idxSession < state.sessions.length; idxSession++) {
-            const gl = this.getPresentationModeLayoutForSource(state, {
-                session: idxSession,
-                compiler: 0,
-            });
+            const gl = this.getPresentationModeLayoutForSource(state, idxSession);
             slides.push(gl);
         }
 
         return slides;
     }
 
-    treeLayoutFromClientstate(state, leaveSomeSpace: boolean): BasicGoldenLayoutStruct | undefined {
+    treeLayoutFromClientstate(state: ClientState, leaveSomeSpace: boolean): BasicGoldenLayoutStruct | undefined {
         const firstTree = state.trees[0];
         const leftSide = this.newTreeFromTree(firstTree, 25);
         const middle = this.newEmptyStack(40);
@@ -962,7 +1101,7 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
 
         let idxCompiler = 0;
         for (const compiler of firstTree.compilers) {
-            contentRow.content.push(this.createCompilerComponentForTree(firstTree, compiler, false, idxCompiler + 1));
+            contentRow.content.push(this.createCompilerComponentForTree(firstTree, compiler, null, idxCompiler + 1));
 
             for (const specialOutput of compiler.specialoutputs) {
                 contentRow.content.push(
@@ -981,7 +1120,7 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
         return extraRow;
     }
 
-    hasEditorCompilersOrExecutors(state) {
+    hasEditorCompilersOrExecutors(state: ClientState) {
         for (const session of state.sessions) {
             if (session.compilers.length > 0 || session.executors.length > 0) {
                 return true;
@@ -991,7 +1130,7 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
         return false;
     }
 
-    fromClientState(state) {
+    fromClientState(state: ClientState) {
         this.golden = {
             settings: {
                 hasHeaders: true,
@@ -1058,7 +1197,7 @@ export class ClientStateGoldenifier extends GoldenLayoutComponents {
                         const compilerComponent = this.createSourceCompilerComponent(
                             session,
                             compiler,
-                            false,
+                            undefined,
                             treeCompilerCount + idxCompiler + 1,
                         );
                         rightStack.content.push(compilerComponent);
diff --git a/lib/clientstate.ts b/lib/clientstate.ts
index ee1fde945..a7bdd0d21 100644
--- a/lib/clientstate.ts
+++ b/lib/clientstate.ts
@@ -37,11 +37,11 @@ export class ClientStateCompilerOptions {
     trim = false;
     debugCalls = false;
 
-    constructor(jsondata?) {
+    constructor(jsondata?: any) {
         if (jsondata) this.fromJsonData(jsondata);
     }
 
-    fromJsonData(jsondata) {
+    fromJsonData(jsondata: any) {
         if (jsondata.binary !== undefined) this.binary = jsondata.binary;
         if (jsondata.binaryObject !== undefined) this.binaryObject = jsondata.binaryObject;
         if (jsondata.commentOnly !== undefined) this.commentOnly = jsondata.commentOnly;
@@ -57,16 +57,19 @@ export class ClientStateCompilerOptions {
 }
 
 export class ClientStateCompiler {
-    _internalid: any = undefined;
-    id = '';
+    _internalid: number | undefined = undefined;
+    id: string | number | undefined = undefined;
+    compiler?: string;
     options = '';
     filters: ClientStateCompilerOptions;
     libs: any[] = [];
-    specialoutputs: any[] = [];
+    specialoutputs: string[] = [];
     tools: any[] = [];
     overrides: any[] = [];
+    source?: number;
+    tree?: number;
 
-    constructor(jsondata?) {
+    constructor(jsondata?: any) {
         if (jsondata) {
             this.filters = undefined as any as ClientStateCompilerOptions;
             this.fromJsonData(jsondata);
@@ -75,7 +78,7 @@ export class ClientStateCompiler {
         }
     }
 
-    fromJsonData(jsondata) {
+    fromJsonData(jsondata: any) {
         if (jsondata._internalid !== undefined) {
             this._internalid = jsondata._internalid;
         }
@@ -126,7 +129,7 @@ export class ClientStateExecutor {
     wrap?: boolean;
     runtimeTools: any[] = [];
 
-    constructor(jsondata?) {
+    constructor(jsondata?: any) {
         if (jsondata) {
             // hack so TS doesn't think this.compiler is accessed before assignment below
             this.compiler = undefined as any as ClientStateCompiler;
@@ -139,7 +142,7 @@ export class ClientStateExecutor {
         delete this.compiler._internalid;
     }
 
-    fromJsonData(jsondata) {
+    fromJsonData(jsondata: any) {
         if (jsondata.compilerVisible !== undefined) this.compilerVisible = jsondata.compilerVisible;
         if (jsondata.compilerOutputVisible !== undefined) this.compilerOutputVisible = jsondata.compilerOutputVisible;
         if (jsondata.arguments !== undefined) this.arguments = jsondata.arguments;
@@ -162,11 +165,11 @@ export class ClientStateConformanceView {
     libs: any[] = [];
     compilers: ClientStateCompiler[] = [];
 
-    constructor(jsondata?) {
+    constructor(jsondata?: any) {
         if (jsondata) this.fromJsonData(jsondata);
     }
 
-    fromJsonData(jsondata) {
+    fromJsonData(jsondata: any) {
         this.libs = jsondata.libs;
         if (jsondata.compilers) {
             for (const compilerdata of jsondata.compilers) {
@@ -189,11 +192,11 @@ export class MultifileFile {
     editorId = -1;
     langId = 'c++';
 
-    constructor(jsondata?) {
+    constructor(jsondata?: any) {
         if (jsondata) this.fromJsonData(jsondata);
     }
 
-    fromJsonData(jsondata) {
+    fromJsonData(jsondata: any) {
         this.fileId = jsondata.fileId;
         this.isIncluded = jsondata.isIncluded;
         this.isOpen = jsondata.isOpen;
@@ -216,11 +219,11 @@ export class ClientStateTree {
     compilers: ClientStateCompiler[] = [];
     executors: ClientStateExecutor[] = [];
 
-    constructor(jsondata?) {
+    constructor(jsondata?: any) {
         if (jsondata) this.fromJsonData(jsondata);
     }
 
-    fromJsonData(jsondata) {
+    fromJsonData(jsondata: any) {
         this.id = jsondata.id;
         this.cmakeArgs = jsondata.cmakeArgs;
         this.customOutputFilename = jsondata.customOutputFilename;
@@ -282,19 +285,20 @@ export class ClientStateTree {
 }
 
 export class ClientStateSession {
-    id: number | false = false;
+    id: number | null = null;
     language = '';
     source = '';
+    // Should default to 'null' but still afraid to break existing links
     conformanceview: ClientStateConformanceView | false = false;
-    compilers: any[] = [];
-    executors: any[] = [];
+    compilers: ClientStateCompiler[] = [];
+    executors: ClientStateExecutor[] = [];
     filename = undefined;
 
-    constructor(jsondata?) {
+    constructor(jsondata?: any) {
         if (jsondata) this.fromJsonData(jsondata);
     }
 
-    fromJsonData(jsondata) {
+    fromJsonData(jsondata: any) {
         if (jsondata.id !== undefined) this.id = jsondata.id;
         this.language = jsondata.language;
         this.source = jsondata.source;
@@ -325,7 +329,7 @@ export class ClientStateSession {
     }
 
     findOrCreateCompiler(id: number) {
-        let foundCompiler;
+        let foundCompiler: ClientStateCompiler | undefined;
         for (const compiler of this.compilers) {
             if (compiler._internalid === id) {
                 foundCompiler = compiler;
@@ -359,11 +363,11 @@ export class ClientState {
     sessions: ClientStateSession[] = [];
     trees: ClientStateTree[] = [];
 
-    constructor(jsondata?) {
+    constructor(jsondata?: any) {
         if (jsondata) this.fromJsonData(jsondata);
     }
 
-    fromJsonData(jsondata) {
+    fromJsonData(jsondata: any) {
         for (const sessiondata of jsondata.sessions) {
             const session = new ClientStateSession(sessiondata);
             this.numberCompilersIfNeeded(session);
@@ -389,15 +393,13 @@ export class ClientState {
         return nextId;
     }
 
-    numberCompilersIfNeeded(session: ClientStateSession, startAt?) {
+    numberCompilersIfNeeded(session: ClientStateSession, startAt?: number) {
         let id = startAt;
         let someIdsNeedNumbering = false;
 
         for (const compiler of session.compilers) {
-            if (compiler._internalid) {
-                if (compiler._internalid >= id) {
-                    id = compiler._internalid + 1;
-                }
+            if (compiler._internalid && id && compiler._internalid >= id) {
+                id = compiler._internalid + 1;
             } else {
                 someIdsNeedNumbering = true;
             }
@@ -407,7 +409,7 @@ export class ClientState {
             for (const compiler of session.compilers) {
                 if (!compiler._internalid) {
                     compiler._internalid = id;
-                    id++;
+                    if (id) id++;
                 }
             }
         }
diff --git a/lib/handlers/api.ts b/lib/handlers/api.ts
index ef027e9b2..1e610b485 100644
--- a/lib/handlers/api.ts
+++ b/lib/handlers/api.ts
@@ -32,14 +32,15 @@ import {Language, LanguageKey} from '../../types/languages.interfaces.js';
 import {assert, unwrap} from '../assert.js';
 import {ClientStateNormalizer} from '../clientstate-normalizer.js';
 import {CompilationEnvironment} from '../compilation-env.js';
+import {LocalExecutionEnvironment} from '../execution/base-execution-env.js';
 import {IExecutionEnvironment} from '../execution/execution-env.interfaces.js';
-import {LocalExecutionEnvironment} from '../execution/index.js';
 import {logger} from '../logger.js';
 import {ClientOptionsHandler} from '../options-handler.js';
 import {PropertyGetter} from '../properties.interfaces.js';
 import {SentryCapture} from '../sentry.js';
 import {BaseShortener, getShortenerTypeByKey} from '../shortener/index.js';
 import {StorageBase} from '../storage/index.js';
+import * as utils from '../utils.js';
 
 import {withAssemblyDocumentationProviders} from './assembly-documentation.js';
 import {CompileHandler} from './compile.js';
@@ -237,10 +238,12 @@ export class ApiHandler {
                 .concat([title])
                 .map(item => item.length),
         );
-        const header = title.padEnd(maxLength, ' ') + ' | Name\n';
-        const body = list.map(lang => lang.id.padEnd(maxLength, ' ') + ' | ' + lang.name).join('\n');
         res.set('Content-Type', 'text/plain');
-        res.send(header + body);
+        res.send(
+            utils.padRight(title, maxLength) +
+                ' | Name\n' +
+                list.map(lang => utils.padRight(lang.id, maxLength) + ' | ' + lang.name).join('\n'),
+        );
     }
 
     getLibrariesAsArray(languageId: LanguageKey) {
diff --git a/lib/handlers/route-api.ts b/lib/handlers/route-api.ts
index 5a5d73717..c97076f5f 100644
--- a/lib/handlers/route-api.ts
+++ b/lib/handlers/route-api.ts
@@ -189,8 +189,8 @@ export class RouteAPI {
         assert(isString(req.query.code));
         session.source = req.query.code;
         const compiler = session.findOrCreateCompiler(1);
-        compiler.id = req.query.compiler;
-        compiler.options = req.query.compiler_flags || '';
+        compiler.id = req.query.compiler as string;
+        compiler.options = (req.query.compiler_flags as string) || '';
 
         this.renderClientState(state, null, req, res);
     }
diff --git a/lib/utils.ts b/lib/utils.ts
index 858403b0e..acf956e65 100644
--- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -261,6 +261,17 @@ export function parseRustOutput(lines: string, inputFilename?: string, pathPrefi
     return result;
 }
 
+export function padRight(name: string, len: number): string {
+    while (name.length < len) name = name + ' ';
+    return name;
+}
+
+export function trimRight(name: string): string {
+    let l = name.length;
+    while (l > 0 && name[l - 1] === ' ') l -= 1;
+    return name.substring(0, l);
+}
+
 /***
  * Anonymizes given IP.
  * For IPv4, it removes the last octet
@@ -384,6 +395,27 @@ export function toProperty(prop: string): boolean | number | string {
     return prop;
 }
 
+/***
+ * This function replaces all the "oldValues" in line with "newValue". It handles overlapping string replacement cases,
+ * and is careful to return the exact same line object if there's no matches. This turns out to be super important for
+ * performance.
+ * @param {string} line
+ * @param {string} oldValue
+ * @param {string} newValue
+ * @returns {string}
+ */
+export function replaceAll(line: string, oldValue: string, newValue: string): string {
+    if (oldValue.length === 0) return line;
+    let startPoint = 0;
+    for (;;) {
+        const index = line.indexOf(oldValue, startPoint);
+        if (index === -1) break;
+        line = line.substring(0, index) + newValue + line.substring(index + oldValue.length);
+        startPoint = index + newValue.length;
+    }
+    return line;
+}
+
 // Initially based on http://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
 const BASE32_ALPHABET = '13456789EGKMPTWYabcdefhjnoqrsvxz';
 
diff --git a/package-lock.json b/package-lock.json
index 931b710f9..9c9a68e3b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -106,6 +106,7 @@
         "@typescript-eslint/parser": "^7.18.0",
         "@vitest/coverage-v8": "^2.1.3",
         "aws-sdk-client-mock": "^4.1.0",
+        "c8": "^10.1.2",
         "cheerio": "^1.0.0",
         "css-loader": "^7.1.2",
         "css-minimizer-webpack-plugin": "^7.0.0",
@@ -5793,6 +5794,39 @@
         "node": ">= 0.8"
       }
     },
+    "node_modules/c8": {
+      "version": "10.1.2",
+      "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.2.tgz",
+      "integrity": "sha512-Qr6rj76eSshu5CgRYvktW0uM0CFY0yi4Fd5D0duDXO6sYinyopmftUiJVuzBQxQcwQLor7JWDVRP+dUfCmzgJw==",
+      "dev": true,
+      "dependencies": {
+        "@bcoe/v8-coverage": "^0.2.3",
+        "@istanbuljs/schema": "^0.1.3",
+        "find-up": "^5.0.0",
+        "foreground-child": "^3.1.1",
+        "istanbul-lib-coverage": "^3.2.0",
+        "istanbul-lib-report": "^3.0.1",
+        "istanbul-reports": "^3.1.6",
+        "test-exclude": "^7.0.1",
+        "v8-to-istanbul": "^9.0.0",
+        "yargs": "^17.7.2",
+        "yargs-parser": "^21.1.1"
+      },
+      "bin": {
+        "c8": "bin/c8.js"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "peerDependencies": {
+        "monocart-coverage-reports": "^2"
+      },
+      "peerDependenciesMeta": {
+        "monocart-coverage-reports": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/cac": {
       "version": "6.7.14",
       "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
@@ -6120,6 +6154,20 @@
         "tiny-emitter": "^2.0.0"
       }
     },
+    "node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "dev": true,
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/clone-deep": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz",
@@ -6355,6 +6403,12 @@
         "node": ">= 0.6"
       }
     },
+    "node_modules/convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+      "dev": true
+    },
     "node_modules/cookie": {
       "version": "0.7.1",
       "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
@@ -8782,6 +8836,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "dev": true,
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
     "node_modules/get-east-asian-width": {
       "version": "1.3.0",
       "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz",
@@ -13118,6 +13181,15 @@
         "uuid": "bin/uuid"
       }
     },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/require-from-string": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -15162,6 +15234,20 @@
       "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
       "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg=="
     },
+    "node_modules/v8-to-istanbul": {
+      "version": "9.3.0",
+      "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz",
+      "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==",
+      "dev": true,
+      "dependencies": {
+        "@jridgewell/trace-mapping": "^0.3.12",
+        "@types/istanbul-lib-coverage": "^2.0.1",
+        "convert-source-map": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10.12.0"
+      }
+    },
     "node_modules/validate-npm-package-license": {
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
@@ -15893,6 +15979,15 @@
         "node": ">=0.4"
       }
     },
+    "node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "dev": true,
+      "engines": {
+        "node": ">=10"
+      }
+    },
     "node_modules/yaml": {
       "version": "2.6.0",
       "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
@@ -15904,6 +15999,33 @@
         "node": ">= 14"
       }
     },
+    "node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "dev": true,
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/yauzl": {
       "version": "2.10.0",
       "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
diff --git a/package.json b/package.json
index 977ffe833..f77664e9c 100644
--- a/package.json
+++ b/package.json
@@ -115,6 +115,7 @@
     "@typescript-eslint/parser": "^7.18.0",
     "@vitest/coverage-v8": "^2.1.3",
     "aws-sdk-client-mock": "^4.1.0",
+    "c8": "^10.1.2",
     "cheerio": "^1.0.0",
     "css-loader": "^7.1.2",
     "css-minimizer-webpack-plugin": "^7.0.0",
@@ -152,6 +153,12 @@
     "webpack-dev-middleware": "^7.4.2",
     "webpack-manifest-plugin": "^5.0.0"
   },
+  "c8": {
+    "reporter": [
+      "text",
+      "cobertura"
+    ]
+  },
   "scripts": {
     "ci-lint": "eslint --format github .",
     "ci-test": "vitest run --coverage",


Test patch:
diff --git a/test/utils-tests.ts b/test/utils-tests.ts
index 89ce583ec..b24379409 100644
--- a/test/utils-tests.ts
+++ b/test/utils-tests.ts
@@ -337,6 +337,26 @@ describe('Tool output', () => {
     });
 });
 
+describe('Pads right', () => {
+    it('works', () => {
+        expect(utils.padRight('abcd', 8)).toEqual('abcd    ');
+        expect(utils.padRight('a', 8)).toEqual('a       ');
+        expect(utils.padRight('', 8)).toEqual('        ');
+        expect(utils.padRight('abcd', 4)).toEqual('abcd');
+        expect(utils.padRight('abcd', 2)).toEqual('abcd');
+    });
+});
+
+describe('Trim right', () => {
+    it('works', () => {
+        expect(utils.trimRight('  ')).toEqual('');
+        expect(utils.trimRight('')).toEqual('');
+        expect(utils.trimRight(' ab ')).toEqual(' ab');
+        expect(utils.trimRight(' a  b ')).toEqual(' a  b');
+        expect(utils.trimRight('a    ')).toEqual('a');
+    });
+});
+
 describe('Anonymizes all kind of IPs', () => {
     it('Ignores localhost', () => {
         expect(utils.anonymizeIp('localhost')).toEqual('localhost');
@@ -459,6 +479,35 @@ describe('squashes horizontal whitespace', () => {
     });
 });
 
+describe('replaces all substrings', () => {
+    it('works with no substitutions', () => {
+        const string = 'This is a line with no replacements';
+        expect(utils.replaceAll(string, 'not present', "won't be substituted")).toEqual(string);
+    });
+    it('handles odd cases', () => {
+        expect(utils.replaceAll('', '', '')).toEqual('');
+        expect(utils.replaceAll('Hello', '', '')).toEqual('Hello');
+    });
+    it('works with single replacement', () => {
+        expect(utils.replaceAll('This is a line with a mistook in it', 'mistook', 'mistake')).toEqual(
+            'This is a line with a mistake in it',
+        );
+        expect(utils.replaceAll('This is a line with a mistook', 'mistook', 'mistake')).toEqual(
+            'This is a line with a mistake',
+        );
+        expect(utils.replaceAll('Mistooks were made', 'Mistooks', 'Mistakes')).toEqual('Mistakes were made');
+    });
+
+    it('works with multiple replacements', () => {
+        expect(utils.replaceAll('A mistook is a mistook', 'mistook', 'mistake')).toEqual('A mistake is a mistake');
+        expect(utils.replaceAll('aaaaaaaaaaaaaaaaaaaaaaaaaaa', 'a', 'b')).toEqual('bbbbbbbbbbbbbbbbbbbbbbbbbbb');
+    });
+
+    it('works with overlapping replacements', () => {
+        expect(utils.replaceAll('aaaaaaaa', 'a', 'ba')).toEqual('babababababababa');
+    });
+});
+
 describe('encodes in our version of base32', () => {
     function doTest(original, expected) {
         expect(utils.base32Encode(Buffer.from(original))).toEqual(expected);
diff --git a/vitest.config.ts b/vitest.config.ts
index 5f9997f2a..5b932ebe4 100644
--- a/vitest.config.ts
+++ b/vitest.config.ts
@@ -4,10 +4,6 @@ import {defineConfig} from 'vitest/config';
 // eslint-disable-next-line import/no-default-export
 export default defineConfig({
     test: {
-        coverage: {
-            provider: 'v8',
-            reporter: ['text', 'json', 'html'],
-        },
         include: ['test/**/*.ts'],
         exclude: ['test/_*.ts', 'test/utils.ts'],
         setupFiles: ['/test/_setup-fake-aws.ts', '/test/_setup-log.ts'],