diff --git a/ui/.gitignore b/ui/.gitignore
index f4116f2d78..d2f6414102 100644
--- a/ui/.gitignore
+++ b/ui/.gitignore
@@ -24,4 +24,6 @@ dist-ssr
*.sw?
data.json
-gsa.wasm
\ No newline at end of file
+gsa.wasm
+
+coverage/
\ No newline at end of file
diff --git a/ui/common.ts b/ui/common.ts
index 6d057c55e8..d617bbc8dd 100644
--- a/ui/common.ts
+++ b/ui/common.ts
@@ -3,6 +3,7 @@ import {codecovVitePlugin} from "@codecov/vite-plugin";
import * as path from "node:path";
import react from "@vitejs/plugin-react-swc";
import {execSync} from "node:child_process";
+import type { InlineConfig } from 'vitest';
export function getSha(): string | undefined {
const envs = process.env;
@@ -72,3 +73,22 @@ export function build(dir: string): BuildOptions {
},
}
}
+
+export function testConfig(): InlineConfig {
+ return {
+ coverage: {
+ provider: "v8",
+ enabled: true,
+ exclude: [
+ "node_modules",
+ "dist",
+ "coverage",
+ "vite.config.ts",
+ "vite.config-explorer.ts",
+ "common.ts",
+ "src/tool/wasm_exec.js"
+ ]
+ }
+ }
+}
+
diff --git a/ui/package.json b/ui/package.json
index d4f7395a53..5a0884cdb3 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -10,7 +10,8 @@
"dev:explorer": "vite -c vite.config-explorer.ts",
"generate": "typia generate --input src/schema --output src/generated --project tsconfig.json",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
- "test": "vitest"
+ "test:ui": "vitest -c vite.config.ts",
+ "test:explorer": "vitest -c vite.config-explorer.ts"
},
"dependencies": {
"@emotion/react": "^11.11.4",
@@ -39,6 +40,7 @@
"@typescript-eslint/eslint-plugin": "^7.10.0",
"@typescript-eslint/parser": "^7.10.0",
"@vitejs/plugin-react-swc": "^3.7.0",
+ "@vitest/coverage-v8": "^1.6.0",
"eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.1",
"eslint-plugin-react-hooks": "^4.6.2",
diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml
index d2aa60beb1..9d75fa33b5 100644
--- a/ui/pnpm-lock.yaml
+++ b/ui/pnpm-lock.yaml
@@ -81,6 +81,9 @@ importers:
'@vitejs/plugin-react-swc':
specifier: ^3.7.0
version: 3.7.0(vite@5.2.11(@types/node@20.12.12)(lightningcss@1.25.1)(sass@1.77.2)(terser@5.31.0))
+ '@vitest/coverage-v8':
+ specifier: ^1.6.0
+ version: 1.6.0(vitest@1.6.0(@types/node@20.12.12)(jsdom@24.1.0)(lightningcss@1.25.1)(sass@1.77.2)(terser@5.31.0))
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -123,6 +126,10 @@ importers:
packages:
+ '@ampproject/remapping@2.3.0':
+ resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
+ engines: {node: '>=6.0.0'}
+
'@babel/code-frame@7.24.6':
resolution: {integrity: sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA==}
engines: {node: '>=6.9.0'}
@@ -143,6 +150,11 @@ packages:
resolution: {integrity: sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ==}
engines: {node: '>=6.9.0'}
+ '@babel/parser@7.24.6':
+ resolution: {integrity: sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
'@babel/runtime@7.24.6':
resolution: {integrity: sha512-Ja18XcETdEl5mzzACGd+DKgaGJzPTCow7EglgwTmHdwokzDFYh/MHua6lU6DV/hjF2IaOJ4oX2nqnjG7RElKOw==}
engines: {node: '>=6.9.0'}
@@ -151,6 +163,9 @@ packages:
resolution: {integrity: sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ==}
engines: {node: '>=6.9.0'}
+ '@bcoe/v8-coverage@0.2.3':
+ resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
+
'@codecov/bundler-plugin-core@0.0.1-beta.8':
resolution: {integrity: sha512-3yxNa4N+pZxqU60XQxJ10sDKUWcDf/YaQSvODrgxLfKG/peZhSdbd92uRc7PYi73szu7lr1CVZIvVerod9C4Uw==}
engines: {node: '>=18.0.0'}
@@ -397,6 +412,10 @@ packages:
'@humanwhocodes/object-schema@2.0.3':
resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==}
+ '@istanbuljs/schema@0.1.3':
+ resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==}
+ engines: {node: '>=8'}
+
'@jest/schemas@29.6.3':
resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -808,6 +827,11 @@ packages:
peerDependencies:
vite: ^4 || ^5
+ '@vitest/coverage-v8@1.6.0':
+ resolution: {integrity: sha512-KvapcbMY/8GYIG0rlwwOKCVNRc0OL20rrhFkg/CHNzncV03TE2XWvO5w9uZYoxNiMEBacAJt3unSOiZ7svePew==}
+ peerDependencies:
+ vitest: 1.6.0
+
'@vitest/expect@1.6.0':
resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==}
@@ -1551,6 +1575,9 @@ packages:
resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
engines: {node: '>=18'}
+ html-escaper@2.0.2:
+ resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+
html-minifier-terser@6.1.0:
resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==}
engines: {node: '>=12'}
@@ -1751,6 +1778,22 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ istanbul-lib-coverage@3.2.2:
+ resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
+ engines: {node: '>=8'}
+
+ istanbul-lib-report@3.0.1:
+ resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
+ engines: {node: '>=10'}
+
+ istanbul-lib-source-maps@5.0.4:
+ resolution: {integrity: sha512-wHOoEsNJTVltaJp8eVkm8w+GVkVNHT2YDYo53YdzQEL2gWm1hBX5cGFR9hQJtuGLebidVX7et3+dmDZrmclduw==}
+ engines: {node: '>=10'}
+
+ istanbul-reports@3.1.7:
+ resolution: {integrity: sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==}
+ engines: {node: '>=8'}
+
iterator.prototype@1.1.2:
resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==}
@@ -1903,6 +1946,13 @@ packages:
magic-string@0.30.10:
resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==}
+ magicast@0.3.4:
+ resolution: {integrity: sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==}
+
+ make-dir@4.0.0:
+ resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
+ engines: {node: '>=10'}
+
mdn-data@2.0.14:
resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==}
@@ -2444,6 +2494,10 @@ packages:
engines: {node: '>=10'}
hasBin: true
+ test-exclude@6.0.0:
+ resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==}
+ engines: {node: '>=8'}
+
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
@@ -2748,6 +2802,11 @@ packages:
snapshots:
+ '@ampproject/remapping@2.3.0':
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.5
+ '@jridgewell/trace-mapping': 0.3.25
+
'@babel/code-frame@7.24.6':
dependencies:
'@babel/highlight': 7.24.6
@@ -2768,6 +2827,10 @@ snapshots:
js-tokens: 4.0.0
picocolors: 1.0.1
+ '@babel/parser@7.24.6':
+ dependencies:
+ '@babel/types': 7.24.6
+
'@babel/runtime@7.24.6':
dependencies:
regenerator-runtime: 0.14.1
@@ -2778,6 +2841,8 @@ snapshots:
'@babel/helper-validator-identifier': 7.24.6
to-fast-properties: 2.0.0
+ '@bcoe/v8-coverage@0.2.3': {}
+
'@codecov/bundler-plugin-core@0.0.1-beta.8':
dependencies:
chalk: 4.1.2
@@ -2989,6 +3054,8 @@ snapshots:
'@humanwhocodes/object-schema@2.0.3': {}
+ '@istanbuljs/schema@0.1.3': {}
+
'@jest/schemas@29.6.3':
dependencies:
'@sinclair/typebox': 0.27.8
@@ -3371,6 +3438,25 @@ snapshots:
transitivePeerDependencies:
- '@swc/helpers'
+ '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.12.12)(jsdom@24.1.0)(lightningcss@1.25.1)(sass@1.77.2)(terser@5.31.0))':
+ dependencies:
+ '@ampproject/remapping': 2.3.0
+ '@bcoe/v8-coverage': 0.2.3
+ debug: 4.3.4
+ istanbul-lib-coverage: 3.2.2
+ istanbul-lib-report: 3.0.1
+ istanbul-lib-source-maps: 5.0.4
+ istanbul-reports: 3.1.7
+ magic-string: 0.30.10
+ magicast: 0.3.4
+ picocolors: 1.0.1
+ std-env: 3.7.0
+ strip-literal: 2.1.0
+ test-exclude: 6.0.0
+ vitest: 1.6.0(@types/node@20.12.12)(jsdom@24.1.0)(lightningcss@1.25.1)(sass@1.77.2)(terser@5.31.0)
+ transitivePeerDependencies:
+ - supports-color
+
'@vitest/expect@1.6.0':
dependencies:
'@vitest/spy': 1.6.0
@@ -4302,6 +4388,8 @@ snapshots:
dependencies:
whatwg-encoding: 3.1.1
+ html-escaper@2.0.2: {}
+
html-minifier-terser@6.1.0:
dependencies:
camel-case: 4.1.2
@@ -4501,6 +4589,27 @@ snapshots:
isexe@2.0.0: {}
+ istanbul-lib-coverage@3.2.2: {}
+
+ istanbul-lib-report@3.0.1:
+ dependencies:
+ istanbul-lib-coverage: 3.2.2
+ make-dir: 4.0.0
+ supports-color: 7.2.0
+
+ istanbul-lib-source-maps@5.0.4:
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.25
+ debug: 4.3.4
+ istanbul-lib-coverage: 3.2.2
+ transitivePeerDependencies:
+ - supports-color
+
+ istanbul-reports@3.1.7:
+ dependencies:
+ html-escaper: 2.0.2
+ istanbul-lib-report: 3.0.1
+
iterator.prototype@1.1.2:
dependencies:
define-properties: 1.2.1
@@ -4664,6 +4773,16 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.4.15
+ magicast@0.3.4:
+ dependencies:
+ '@babel/parser': 7.24.6
+ '@babel/types': 7.24.6
+ source-map-js: 1.2.0
+
+ make-dir@4.0.0:
+ dependencies:
+ semver: 7.6.2
+
mdn-data@2.0.14: {}
merge-stream@2.0.0: {}
@@ -5260,6 +5379,12 @@ snapshots:
commander: 2.20.3
source-map-support: 0.5.21
+ test-exclude@6.0.0:
+ dependencies:
+ '@istanbuljs/schema': 0.1.3
+ glob: 7.2.3
+ minimatch: 3.1.2
+
text-table@0.2.0: {}
throttle-debounce@3.0.1: {}
diff --git a/ui/src/explorer/app.tsx b/ui/src/explorer/app.tsx
index 161880d715..ae32b85d9f 100644
--- a/ui/src/explorer/app.tsx
+++ b/ui/src/explorer/app.tsx
@@ -1,7 +1,7 @@
import React, {ReactNode, useEffect, useMemo} from "react";
import {useAsync} from "react-use";
import gsa from "../../gsa.wasm?init";
-import {Entry} from "../tool/entry.ts";
+import {createEntry} from "../tool/entry.ts";
import {Dialog, DialogContent, DialogContentText, DialogTitle} from "@mui/material";
import {FileSelector} from "./file_selector.tsx";
import TreeMap from "../TreeMap.tsx";
@@ -51,7 +51,7 @@ export const App: React.FC = () => {
return null
}
- return new Entry(result)
+ return createEntry(result)
}, [result])
useEffect(() => {
diff --git a/ui/src/main.tsx b/ui/src/main.tsx
index a3721923ce..0f114381e4 100644
--- a/ui/src/main.tsx
+++ b/ui/src/main.tsx
@@ -3,10 +3,10 @@ import ReactDOM from 'react-dom/client'
import TreeMap from './TreeMap.tsx'
import {loadDataFromEmbed} from "./tool/utils.ts";
-import {Entry} from "./tool/entry.ts";
+import {createEntry} from "./tool/entry.ts";
ReactDOM.createRoot(document.getElementById('root')!).render(
-
+
,
)
diff --git a/ui/src/tool/aligner.ts b/ui/src/tool/aligner.ts
new file mode 100644
index 0000000000..ccfdf8009b
--- /dev/null
+++ b/ui/src/tool/aligner.ts
@@ -0,0 +1,23 @@
+import {max} from "d3-array";
+
+export class aligner {
+ private pre: string[] = [];
+ private post: string[] = [];
+
+ public add(pre: string, post: string): aligner {
+ this.pre.push(pre);
+ this.post.push(post);
+ return this;
+ }
+
+ public toString(): string {
+ // determine the maximum length of the pre-strings
+ const maxPreLength = max(this.pre, (d) => d.length) ?? 0;
+ let ret = "";
+ for (let i = 0; i < this.pre.length; i++) {
+ ret += this.pre[i].padEnd(maxPreLength + 1) + this.post[i] + "\n";
+ }
+ ret = ret.trimEnd();
+ return ret;
+ }
+}
\ No newline at end of file
diff --git a/ui/src/tool/entry.ts b/ui/src/tool/entry.ts
index 9376be9db2..92b30d5eb7 100644
--- a/ui/src/tool/entry.ts
+++ b/ui/src/tool/entry.ts
@@ -3,286 +3,376 @@ import {
FileSymbol,
Package,
Result,
- Section,
- isFile,
- isPackage,
- isResult,
- isSection,
- isSymbol
+ Section
} from "../generated/schema.ts";
import {orderedID} from "./id.ts";
import {formatBytes, title} from "./utils.ts";
-import {max} from "d3-array";
-
-type Candidate = Section | File | Package | Result | FileSymbol;
-
-export type EntryType = "section" | "file" | "package" | "result" | "symbol" | "disasm" | "unknown" | "container";
-
-export class Entry {
- private readonly type: EntryType;
- private readonly data?: Candidate;
- private readonly size: number;
- private readonly name: string;
- private readonly children: Entry[] = [];
- private readonly uid = orderedID();
- explain: string = ""; // should only be used by the container type
-
- constructor(data: Candidate)
- constructor(name: string, size: number, type: EntryType, children?: Entry[])
- constructor(data_or_name: Candidate | string, size?: number, type?: EntryType, children: Entry[] = []) {
- if (typeof data_or_name === "string") {
- this.type = type!;
- this.size = size!;
- this.name = data_or_name;
- this.children = children;
- return
- }
+import {aligner} from "./aligner.ts";
+
+type EntryType = "section" | "file" | "package" | "result" | "symbol" | "disasm" | "unknown" | "container";
+
+type EntryChildren = {
+ "section": never[],
+ "file": never[],
+ "package": EntryLike<"package" | "symbol" | "disasm" | "file">[],
+ "result": EntryLike<"section" | "container" | "unknown">[],
+ "symbol": never[],
+ "disasm": never[],
+ "unknown": never[],
+ "container": EntryLike<"package" | "disasm" | "section">[]
+}
- this.type = Entry.checkType(data_or_name);
- this.data = data_or_name;
- this.size = Entry.loadSize(data_or_name);
- this.name = Entry.candidateName(data_or_name, this.type);
- this.children = Entry.childrenFromData(data_or_name, this.type);
- }
-
- static childrenFromData(data: Candidate, type: EntryType): Entry[] {
- switch (type) {
- case "section":
- case "file":
- case "symbol":
- return []; // no children for section or file
- case "package":
- return childrenFromPackage(data as Package);
- case "result":
- return childrenFromResult(data as Result);
- default:
- throw new Error(`Unknown type: ${type} in childrenFromData`);
- }
+export interface EntryLike {
+ toString(): string;
+
+ getSize(): number;
+
+ getType(): T;
+
+ getName(): string;
+
+ getChildren(): EntryChildren[T]
+
+ getID(): number;
+}
+
+class BaseImpl {
+ private readonly id = orderedID()
+
+ getID(): number {
+ return this.id;
}
+}
- static candidateName(candidate: Candidate, type: EntryType): string {
- switch (type) {
- case "section":
- case "result":
- case "package":
- case "symbol":
- return (candidate).name;
+export class SectionImpl extends BaseImpl implements EntryLike<"section"> {
+ constructor(private readonly data: Section) {
+ super();
+ }
- case "file":
- return (candidate).file_path.split("/").pop()!;
+ getChildren(): EntryChildren["section"] {
+ return [];
+ }
- default:
- throw new Error(`Unknown type: ${type} in candidateName`);
- }
+ getName(): string {
+ return this.data.name;
}
- static loadSize(data: Candidate): number {
- switch (true) {
- case isSection(data):
- return data.file_size - data.known_size;
- default:
- return data.size;
- }
+ getSize(): number {
+ return this.data.file_size - this.data.known_size;
}
- static checkType(candidate: Candidate): EntryType {
- switch (true) {
- case isSection(candidate):
- return "section";
- case isFile(candidate):
- return "file";
- case isPackage(candidate):
- return "package";
- case isResult(candidate):
- return "result";
- case isSymbol(candidate):
- return "symbol";
- default:
- throw new Error(`Unknown type in checkType`);
- }
+ getType(): "section" {
+ return "section";
}
- public toString(): string {
+ toString(): string {
const align = new aligner();
+ align.add("Section:", this.data.name)
+ .add("Size:", formatBytes(this.getSize()))
+ .add("File Size:", formatBytes(this.data.file_size))
+ .add("Known size:", formatBytes(this.data.known_size))
+ .add("Unknown size:", formatBytes(this.getSize()))
+ .add("Offset:", `0x${this.data.offset.toString(16)} - 0x${this.data.end.toString(16)}`)
+ .add("Address:", `0x${this.data.addr.toString(16)} - 0x${this.data.addr_end.toString(16)}`)
+ .add("Memory:", this.data.only_in_memory.toString())
+ .add("Debug:", this.data.debug.toString());
+ return align.toString();
+ }
+}
- function assertTyp(_c?: Candidate): asserts _c is T {
- }
+export class FileImpl extends BaseImpl implements EntryLike<"file"> {
+ constructor(private readonly data: File) {
+ super();
+ }
- switch (this.type) {
- case "section":
- assertTyp(this.data);
- align.add("Section:", this.name);
- align.add("Size:", formatBytes(this.size));
- align.add("File Size:", formatBytes(this.data.file_size));
- align.add("Known size:", formatBytes(this.data.known_size));
- align.add("Unknown size:", formatBytes(this.data.size - this.data.known_size));
- align.add("Offset:", `0x${this.data.offset.toString(16)} - 0x${this.data.end.toString(16)}`);
- align.add("Address:", `0x${this.data.addr.toString(16)} - 0x${this.data.addr_end.toString(16)}`);
- align.add("Memory:", this.data.only_in_memory.toString());
- align.add("Debug:", this.data.debug.toString());
- return align.toString();
-
- case "file":
- assertTyp(this.data);
- align.add("File:", this.data.file_path);
- align.add("Path:", this.data.file_path);
- align.add("Size:", formatBytes(this.data.size));
- align.add("Pcln Size:", formatBytes(this.data.pcln_size));
- return align.toString();
-
- case "package":
- assertTyp(this.data);
- align.add("Package:", this.data.name);
- align.add("Type:", this.data.type);
- align.add("Size:", formatBytes(this.data.size));
- return align.toString();
-
- case "result":
- assertTyp(this.data);
- align.add("Result:", this.data.name);
- align.add("Size:", formatBytes(this.data.size));
- return align.toString();
-
- case "disasm": {
- align.add("Disasm:", this.name);
- align.add("Size:", formatBytes(this.size));
- let ret = align.toString();
- ret += "\n\n" +
- "This size was not accurate." +
- "The real size determined by disassembling can be larger.";
- return ret;
- }
+ getChildren(): EntryChildren["file"] {
+ return [];
+ }
- case "symbol": {
- assertTyp(this.data);
- align.add("Symbol:", this.data.name);
- align.add("Size:", formatBytes(this.size));
- align.add("Address:", `0x${this.data.addr.toString(16)}`);
- align.add("Type:", this.data.type);
- return align.toString();
- }
+ getName(): string {
+ return this.data.file_path.split("/").pop()!;
+ }
- case "unknown": {
- align.add("Size:", formatBytes(this.size));
- let ret = align.toString();
- ret += "\n\n" +
- "The unknown part in the binary.\n" +
- "Can be ELF Header, Program Header, align offset...\n" +
- "We just don't know.";
- return ret;
- }
+ getSize(): number {
+ return this.data.size;
+ }
- case "container": {
- let ret = this.explain + "\n"
- align.add("Size:", formatBytes(this.size));
- ret += "\n" + align.toString();
- return ret;
- }
+ getType(): "file" {
+ return "file";
+ }
+
+ toString(): string {
+ const align = new aligner();
+ align.add("File:", this.data.file_path)
+ .add("Path:", this.data.file_path)
+ .add("Size:", formatBytes(this.data.size))
+ .add("Pcln Size:", formatBytes(this.data.pcln_size));
+ return align.toString();
+ }
+}
+
+export class PackageImpl extends BaseImpl implements EntryLike<"package"> {
+ private readonly children: EntryChildren["package"];
+
+ constructor(private readonly data: Package) {
+ super();
+
+ const children: EntryChildren["package"] = [];
+ for (const file of data.files) {
+ children.push(new FileImpl(file));
+ }
+ for (const subPackage of Object.values(data.subPackages)) {
+ children.push(new PackageImpl(subPackage));
}
+
+ for (const s of data.symbols) {
+ children.push(new SymbolImpl(s));
+ }
+
+ const leftSize = data.size - children.reduce((acc, child) => acc + child.getSize(), 0);
+ if (leftSize > 0) {
+ const name = `${data.name} Disasm`
+ children.push(new DisasmImpl(name, leftSize));
+ }
+
+ this.children = children;
}
- public getSize(): number {
- return this.size;
+ getChildren(): EntryChildren["package"] {
+ return this.children;
}
- public getType(): EntryType {
- return this.type;
+ getName(): string {
+ return this.data.name;
}
- public getName(): string {
+ getSize(): number {
+ return this.data.size;
+ }
+
+ getType(): "package" {
+ return "package";
+ }
+
+ toString(): string {
+ const align = new aligner();
+ align.add("Package:", this.data.name)
+ .add("Type:", this.data.type)
+ .add("Size:", formatBytes(this.data.size));
+ return align.toString();
+ }
+}
+
+export class DisasmImpl extends BaseImpl implements EntryLike<"disasm"> {
+ constructor(private readonly name: string, private readonly size: number) {
+ super();
+ }
+
+ getChildren(): EntryChildren["disasm"] {
+ return [];
+ }
+
+ getName(): string {
return this.name;
}
- public getChildren(): Entry[] {
- return this.children;
+ getSize(): number {
+ return this.size;
}
- public getID(): number {
- return this.uid;
+ getType(): "disasm" {
+ return "disasm";
+ }
+
+ toString(): string {
+ const align = new aligner();
+ align.add("Disasm:", this.name)
+ .add("Size:", formatBytes(this.size));
+ let ret = align.toString();
+ ret += "\n\n" +
+ "This size was not accurate." +
+ "The real size determined by disassembling can be larger.";
+ return ret;
}
}
-function childrenFromPackage(pkg: Package): Entry[] {
- const children: Entry[] = [];
- for (const file of pkg.files) {
- children.push(new Entry(file));
+export class SymbolImpl extends BaseImpl implements EntryLike<"symbol"> {
+ constructor(private readonly data: FileSymbol) {
+ super();
}
- for (const subPackage of Object.values(pkg.subPackages)) {
- children.push(new Entry(subPackage));
+
+ getChildren(): EntryChildren["symbol"] {
+ return [];
+ }
+
+ getName(): string {
+ return this.data.name;
}
- for (const s of pkg.symbols) {
- children.push(new Entry(s));
+ getSize(): number {
+ return this.data.size;
}
- const leftSize = pkg.size - children.reduce((acc, child) => acc + child.getSize(), 0);
- if (leftSize > 0) {
- const name = `${pkg.name} Disasm`
- children.push(new Entry(name, leftSize, "disasm"));
+ getType(): "symbol" {
+ return "symbol";
}
- return children;
+ toString(): string {
+ const align = new aligner();
+ align.add("Symbol:", this.data.name)
+ .add("Size:", formatBytes(this.data.size))
+ .add("Address:", `0x${this.data.addr.toString(16)}`)
+ .add("Type:", this.data.type);
+ return align.toString();
+ }
}
-function childrenFromResult(result: Result): Entry[] {
- const children: Entry[] = [];
+export class ContainerImpl extends BaseImpl implements EntryLike<"container"> {
+ constructor(private readonly name: string,
+ private readonly size: number,
+ private readonly children: EntryChildren["container"],
+ private readonly explain: string = "") {
+ super();
+ }
- const sectionContainerChildren: Entry[] = []
- for (const section of result.sections) {
- sectionContainerChildren.push(new Entry(section));
+ getChildren(): EntryChildren["container"] {
+ return this.children;
}
- const sectionContainerSize = sectionContainerChildren.reduce((acc, child) => acc + child.getSize(), 0);
- const sectionContainer = new Entry("Unknown Sections Size", sectionContainerSize, "container", sectionContainerChildren);
- sectionContainer.explain = "The unknown size of the sections in the binary."
- children.push(sectionContainer);
- const typedPackages: Record = {};
- for (const pkg of Object.values(result.packages)) {
- if (typedPackages[pkg.type] == null) {
- typedPackages[pkg.type] = [];
- }
- typedPackages[pkg.type].push(pkg);
+ getName(): string {
+ return this.name;
}
- const typedPackagesChildren: Entry[] = []
- for (const [type, packages] of Object.entries(typedPackages)) {
- const packageContainerChildren: Entry[] = [];
- for (const pkg of packages) {
- packageContainerChildren.push(new Entry(pkg));
- }
- const packageContainerSize = packageContainerChildren.reduce((acc, child) => acc + child.getSize(), 0);
- const packageContainer = new Entry(`${title(type)} Packages Size`, packageContainerSize, "container", packageContainerChildren);
- packageContainer.explain = `The size of the ${type} packages in the binary.`
- typedPackagesChildren.push(packageContainer);
+
+ getSize(): number {
+ return this.size;
}
- children.push(...typedPackagesChildren);
- const leftSize = result.size - children.reduce((acc, child) => acc + child.getSize(), 0);
- if (leftSize > 0) {
- const name = `Unknown`
- children.push(new Entry(name, leftSize, "unknown"));
+ getType(): "container" {
+ return "container";
}
- return children;
+ toString(): string {
+ let ret = this.explain + "\n"
+ const align = new aligner();
+ align.add("Size:", formatBytes(this.size));
+ ret += "\n" + align.toString();
+ return ret;
+ }
}
-class aligner {
- private pre: string[] = [];
- private post: string[] = [];
+export class UnknownImpl extends BaseImpl implements EntryLike<"unknown"> {
+ constructor(private readonly size: number) {
+ super();
+ }
- public add(pre: string, post: string): void {
- this.pre.push(pre);
- this.post.push(post);
+ getChildren(): EntryChildren["unknown"] {
+ return [];
}
- public toString(): string {
- // determine the maximum length of the pre-strings
- const maxPreLength = max(this.pre, (d) => d.length) ?? 0;
- let ret = "";
- for (let i = 0; i < this.pre.length; i++) {
- ret += this.pre[i].padEnd(maxPreLength + 1) + this.post[i] + "\n";
- }
- ret = ret.trimEnd();
+ getName(): string {
+ return "Unknown";
+ }
+
+ getSize(): number {
+ return this.size;
+ }
+
+ getType(): "unknown" {
+ return "unknown";
+ }
+
+ toString(): string {
+ const align = new aligner();
+ align.add("Size:", formatBytes(this.size));
+ let ret = align.toString();
+ ret += "\n\n" +
+ "The unknown part in the binary.\n" +
+ "Can be ELF Header, Program Header, align offset...\n" +
+ "We just don't know.";
return ret;
}
}
+
+export class ResultImpl extends BaseImpl implements EntryLike<"result"> {
+ private readonly children: EntryChildren["result"];
+
+ constructor(private readonly data: Result) {
+ super();
+
+ const children: EntryChildren["result"] = [];
+
+ const sectionContainerChildren: EntryLike<"section">[] = []
+ for (const section of data.sections) {
+ sectionContainerChildren.push(new SectionImpl(section));
+ }
+ const sectionContainerSize = sectionContainerChildren.reduce((acc, child) => acc + child.getSize(), 0);
+ const sectionContainer = new ContainerImpl(
+ "Unknown Sections Size",
+ sectionContainerSize,
+ sectionContainerChildren,
+ "The unknown size of the sections in the binary.");
+ children.push(sectionContainer);
+
+ const typedPackages: Record = {};
+ for (const pkg of Object.values(data.packages)) {
+ if (typedPackages[pkg.type] == null) {
+ typedPackages[pkg.type] = [];
+ }
+ typedPackages[pkg.type].push(pkg);
+ }
+ const typedPackagesChildren: EntryLike<"container">[] = [];
+ for (const [type, packages] of Object.entries(typedPackages)) {
+ const packageContainerChildren: EntryLike<"package" | "disasm">[] = [];
+ for (const pkg of packages) {
+ packageContainerChildren.push(new PackageImpl(pkg));
+ }
+ const packageContainerSize = packageContainerChildren.reduce((acc, child) => acc + child.getSize(), 0);
+ const packageContainer = new ContainerImpl(
+ `${title(type)} Packages Size`,
+ packageContainerSize,
+ packageContainerChildren,
+ `The size of the ${type} packages in the binary.`
+ )
+
+ typedPackagesChildren.push(packageContainer);
+ }
+ children.push(...typedPackagesChildren);
+
+ const leftSize = data.size - children.reduce((acc, child) => acc + child.getSize(), 0);
+ if (leftSize > 0) {
+ children.push(new UnknownImpl(leftSize));
+ }
+
+ this.children = children;
+ }
+
+ getChildren(): EntryChildren["result"] {
+ return this.children;
+ }
+
+ getName(): string {
+ return this.data.name;
+ }
+
+ getSize(): number {
+ return this.data.size;
+ }
+
+ getType(): "result" {
+ return "result";
+ }
+
+ toString(): string {
+ const align = new aligner();
+ align.add("Result:", this.data.name)
+ .add("Size:", formatBytes(this.data.size));
+ return align.toString();
+ }
+}
+
+export type Entry = EntryLike;
+
+export function createEntry(data: Result): Entry {
+ return new ResultImpl(data);
+}
\ No newline at end of file
diff --git a/ui/vite.config-explorer.ts b/ui/vite.config-explorer.ts
index ec871f35e9..b558e1b649 100644
--- a/ui/vite.config-explorer.ts
+++ b/ui/vite.config-explorer.ts
@@ -1,5 +1,5 @@
-import {defineConfig} from 'vite'
-import {build, codecov, commonPlugin, getVersionTag} from "./common";
+import {defineConfig} from 'vitest/config';
+import {build, codecov, commonPlugin, getVersionTag, testConfig} from "./common";
import {createHtmlPlugin} from "vite-plugin-html";
export default defineConfig({
@@ -22,5 +22,6 @@ export default defineConfig({
watch: {
usePolling: true,
},
- }
+ },
+ test: testConfig(),
})
diff --git a/ui/vite.config.ts b/ui/vite.config.ts
index 1434003e0e..1f61d7eb5e 100644
--- a/ui/vite.config.ts
+++ b/ui/vite.config.ts
@@ -1,7 +1,7 @@
-import {defineConfig} from 'vite'
+import {defineConfig} from 'vitest/config';
import {viteSingleFile} from "vite-plugin-singlefile"
import * as fs from "node:fs"
-import {build, codecov, commonPlugin, getVersionTag} from "./common";
+import {build, codecov, commonPlugin, getVersionTag, testConfig} from "./common";
import {createHtmlPlugin} from "vite-plugin-html";
@@ -52,5 +52,6 @@ export default defineConfig({
codecov("gsa-ui"),
],
clearScreen: false,
- build: build("webui")
+ build: build("webui"),
+ test: testConfig(),
})