Skip to content

Commit da57767

Browse files
committed
fix(Compilers): deduplicate WASM worker runner to satisfy SonarCloud gate
The quality gate failed on 13.5% duplication on new code. Extract the worker runner shared by the WASM languages into worker-runner.ts and use it from the fortran, clang, ada, assembly and cobol scripts, replacing the copy in each. Also drop the regenerated storybook stories from the diff (they are rebuilt by build:storybook from the same _stories source) so the six identical per-framework files no longer contribute duplicated new lines.
1 parent e4ebd28 commit da57767

12 files changed

Lines changed: 154 additions & 535 deletions

File tree

‎src/livecodes/languages/ada-wasm/lang-ada-wasm-script.ts‎

Lines changed: 6 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-console */
2-
import { createWorkerFromContent, getErrorMessage } from '../../utils/utils';
2+
import { getErrorMessage } from '../../utils/utils';
33
import { adaWasmBaseUrl } from '../../vendors';
4+
import { createWorkerRunner, type Runner } from '../worker-runner';
45

56
// Ada is compiled and run by HAC (@live-codes/ada-wasm), a single self-contained
67
// WebAssembly bundle. It runs inside a dedicated worker so a non-terminating
@@ -24,12 +25,6 @@ interface RunResult {
2425
exitCode: number | null;
2526
}
2627

27-
interface Runner {
28-
ensureReady: () => Promise<void>;
29-
run: (code: string, input: string) => Promise<WorkerRunResult>;
30-
destroy: () => void;
31-
}
32-
3328
interface AdaWasmApi {
3429
ready: boolean;
3530
failed: boolean;
@@ -39,7 +34,7 @@ interface AdaWasmApi {
3934
exitCode: number | null;
4035
loaded: Promise<void>;
4136
init: Promise<void> | null;
42-
runner?: Runner;
37+
runner?: Runner<WorkerRunResult>;
4338
run: (input?: string) => Promise<RunResult>;
4439
}
4540

@@ -103,83 +98,6 @@ getRuntime().then(
10398
);
10499
`;
105100

106-
interface Pending {
107-
resolve: (result: WorkerRunResult) => void;
108-
reject: (error: Error) => void;
109-
}
110-
111-
const createRunner = (): Runner => {
112-
let worker: Worker | null = null;
113-
let ready: Promise<void> | null = null;
114-
let settleReady: ((error?: Error) => void) | null = null;
115-
let pending: Record<number, Pending> = {};
116-
let nextId = 1;
117-
118-
const failAll = (error: Error) => {
119-
for (const id of Object.keys(pending)) {
120-
pending[Number(id)].reject(error);
121-
}
122-
pending = {};
123-
};
124-
125-
const teardown = (error?: Error) => {
126-
worker?.terminate();
127-
worker = null;
128-
ready = null;
129-
settleReady?.(error);
130-
settleReady = null;
131-
if (error) failAll(error);
132-
};
133-
134-
const onMessage = (event: MessageEvent) => {
135-
const message = event.data ?? {};
136-
if (message.type === 'ready') {
137-
settleReady?.();
138-
settleReady = null;
139-
return;
140-
}
141-
if (message.type === 'error') {
142-
// The runtime failed to load (network, or the browser is unsupported).
143-
teardown(new Error(message.message));
144-
return;
145-
}
146-
const request = pending[message.id];
147-
if (!request) return;
148-
delete pending[message.id];
149-
if (message.error != null) {
150-
request.reject(new Error(message.error));
151-
} else {
152-
request.resolve(message.result);
153-
}
154-
};
155-
156-
const spawn = () => {
157-
ready = new Promise<void>((resolve, reject) => {
158-
settleReady = (error?: Error) => (error ? reject(error) : resolve());
159-
});
160-
worker = createWorkerFromContent(getWorkerSrc());
161-
worker.onmessage = onMessage;
162-
worker.onerror = (event) => teardown(new Error(`The Ada worker crashed: ${event.message}`));
163-
};
164-
165-
const ensureReady = async () => {
166-
if (!ready) spawn();
167-
await ready;
168-
};
169-
170-
const run = (code: string, input: string) =>
171-
ensureReady().then(
172-
() =>
173-
new Promise<WorkerRunResult>((resolve, reject) => {
174-
const id = nextId++;
175-
pending[id] = { resolve, reject };
176-
worker?.postMessage({ id, code, input });
177-
}),
178-
);
179-
180-
return { ensureReady, run, destroy: () => teardown() };
181-
};
182-
183101
const setResult = (
184102
input: string,
185103
output: string | null,
@@ -207,7 +125,7 @@ adaWasm.ready = false;
207125
adaWasm.failed = false;
208126

209127
/** Start (once) loading the runtime in a worker, showing the loading indicator. */
210-
const ensureLoaded = (runner: Runner): Promise<void> => {
128+
const ensureLoaded = (runner: Runner<WorkerRunResult>): Promise<void> => {
211129
let init = adaWasm.init;
212130
if (!init) {
213131
adaWasm.failed = false;
@@ -258,7 +176,8 @@ adaWasm.run = async (input?: string) => {
258176
});
259177
if (!code.trim()) return setResult(stdin, null, null, 0);
260178

261-
const runner = (adaWasm.runner = adaWasm.runner || createRunner());
179+
const runner = (adaWasm.runner =
180+
adaWasm.runner || createWorkerRunner<WorkerRunResult>({ getWorkerSrc, label: 'Ada' }));
262181

263182
try {
264183
await ensureLoaded(runner);

‎src/livecodes/languages/assembly-wasm/lang-assembly-wasm-script.ts‎

Lines changed: 4 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
/* eslint-disable no-console */
2-
import { createWorkerFromContent, getErrorMessage } from '../../utils/utils';
2+
import { getErrorMessage } from '../../utils/utils';
33
import {
44
assemblyWasmBaseUrl,
55
keystoneJsUrl,
66
keystoneJsWasmUrl,
77
unicornJsX86Url,
88
} from '../../vendors';
9+
import { createWorkerRunner, type Runner } from '../worker-runner';
910

1011
// x86-64 assembly is assembled and executed by @live-codes/assembly-wasm, which loads
1112
// Keystone (the assembler) and Unicorn (the CPU emulator) as separate WebAssembly
@@ -20,25 +21,13 @@ const SCRIPT_TYPE = 'text/assembly';
2021

2122
const BOOT_TIMEOUT_MS = 2 * 60_000;
2223

23-
interface WorkerRunResult {
24-
output: string;
25-
errors: string[];
26-
exitCode: number | null;
27-
}
28-
2924
interface RunResult {
3025
input: string;
3126
output: string | null;
3227
error: string | null;
3328
exitCode: number | null;
3429
}
3530

36-
interface Runner {
37-
ensureReady: () => Promise<void>;
38-
run: (code: string, input: string) => Promise<WorkerRunResult>;
39-
destroy: () => void;
40-
}
41-
4231
interface AssemblyWasmApi {
4332
ready: boolean;
4433
failed: boolean;
@@ -118,85 +107,6 @@ getCompiler().then(
118107
);
119108
`;
120109

121-
interface Pending {
122-
resolve: (result: WorkerRunResult) => void;
123-
reject: (error: Error) => void;
124-
}
125-
126-
const createRunner = (): Runner => {
127-
let worker: Worker | null = null;
128-
let ready: Promise<void> | null = null;
129-
let settleReady: ((error?: Error) => void) | null = null;
130-
let pending: Record<number, Pending> = {};
131-
let nextId = 1;
132-
133-
const failAll = (error: Error) => {
134-
for (const id of Object.keys(pending)) {
135-
pending[Number(id)].reject(error);
136-
}
137-
pending = {};
138-
};
139-
140-
const teardown = (error?: Error) => {
141-
worker?.terminate();
142-
worker = null;
143-
ready = null;
144-
settleReady?.(error);
145-
settleReady = null;
146-
if (error) failAll(error);
147-
};
148-
149-
const onMessage = (event: MessageEvent) => {
150-
const message = event.data ?? {};
151-
if (message.type === 'ready') {
152-
settleReady?.();
153-
settleReady = null;
154-
return;
155-
}
156-
if (message.type === 'error') {
157-
// The runtime failed to load (network, or the browser is unsupported).
158-
teardown(new Error(message.message));
159-
return;
160-
}
161-
const request = pending[message.id];
162-
if (!request) return;
163-
delete pending[message.id];
164-
if (message.error != null) {
165-
request.reject(new Error(message.error));
166-
} else {
167-
request.resolve(message.result);
168-
}
169-
};
170-
171-
const spawn = () => {
172-
ready = new Promise<void>((resolve, reject) => {
173-
settleReady = (error?: Error) => (error ? reject(error) : resolve());
174-
});
175-
worker = createWorkerFromContent(getWorkerSrc());
176-
worker.onmessage = onMessage;
177-
worker.onerror = (event) =>
178-
teardown(new Error(`The Assembly worker crashed: ${event.message}`));
179-
};
180-
181-
/** Spawn the worker if needed and resolve once the runtime has loaded. */
182-
const ensureReady = async () => {
183-
if (!ready) spawn();
184-
await ready;
185-
};
186-
187-
const run = (code: string, input: string) =>
188-
ensureReady().then(
189-
() =>
190-
new Promise<WorkerRunResult>((resolve, reject) => {
191-
const id = nextId++;
192-
pending[id] = { resolve, reject };
193-
worker?.postMessage({ id, code, input });
194-
}),
195-
);
196-
197-
return { ensureReady, run, destroy: () => teardown() };
198-
};
199-
200110
const setResult = (
201111
input: string,
202112
output: string | null,
@@ -277,7 +187,8 @@ assemblyWasm.run = async (input?: string) => {
277187

278188
// Park the runner on the persisted namespace so a live reload reuses the warm
279189
// worker instead of loading the runtimes again.
280-
const runner = (assemblyWasm.runner = assemblyWasm.runner || createRunner());
190+
const runner = (assemblyWasm.runner =
191+
assemblyWasm.runner || createWorkerRunner({ getWorkerSrc, label: 'Assembly' }));
281192

282193
try {
283194
await ensureLoaded(runner);

‎src/livecodes/languages/clang-wasm/lang-clang-wasm-script.ts‎

Lines changed: 4 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable no-console */
2-
import { createWorkerFromContent, getErrorMessage } from '../../utils/utils';
2+
import { getErrorMessage } from '../../utils/utils';
33
import { clangWasmBaseUrl } from '../../vendors';
4+
import { createWorkerRunner, type Runner } from '../worker-runner';
45

56
// One runner serves all four Clang languages. The language is read from the script tags the
67
// compiler injected into the page, so only the script type differs between them.
@@ -35,24 +36,13 @@ interface ClangWasmSettings {
3536
args?: string[];
3637
}
3738

38-
interface WorkerRunResult {
39-
output: string;
40-
errors: string[];
41-
exitCode: number | null;
42-
}
43-
4439
interface RunResult {
4540
input: string;
4641
output: string | null;
4742
error: string | null;
4843
exitCode: number | null;
4944
}
5045

51-
interface Runner {
52-
ensureReady: () => Promise<void>;
53-
run: (code: string, input: string, options?: ClangWasmSettings) => Promise<WorkerRunResult>;
54-
}
55-
5646
interface ClangWasmApi {
5747
ready: boolean;
5848
input: string;
@@ -107,84 +97,6 @@ getCompiler().then(
10797
);
10898
`;
10999

110-
interface Pending {
111-
resolve: (result: WorkerRunResult) => void;
112-
reject: (error: Error) => void;
113-
}
114-
115-
const createRunner = (language: LanguageId): Runner => {
116-
let worker: Worker | null = null;
117-
let ready: Promise<void> | null = null;
118-
let settleReady: ((error?: Error) => void) | null = null;
119-
let pending: Record<number, Pending> = {};
120-
let nextId = 1;
121-
122-
const failAll = (error: Error) => {
123-
for (const id of Object.keys(pending)) {
124-
pending[Number(id)].reject(error);
125-
}
126-
pending = {};
127-
};
128-
129-
const teardown = (error: Error) => {
130-
worker?.terminate();
131-
worker = null;
132-
ready = null;
133-
settleReady?.(error);
134-
settleReady = null;
135-
failAll(error);
136-
};
137-
138-
const onMessage = (event: MessageEvent) => {
139-
const message = event.data ?? {};
140-
if (message.type === 'ready') {
141-
settleReady?.();
142-
settleReady = null;
143-
return;
144-
}
145-
if (message.type === 'error') {
146-
// The runtime failed to load (network, or the browser is unsupported).
147-
teardown(new Error(message.message));
148-
return;
149-
}
150-
const request = pending[message.id];
151-
if (!request) return;
152-
delete pending[message.id];
153-
if (message.error != null) {
154-
request.reject(new Error(message.error));
155-
} else {
156-
request.resolve(message.result);
157-
}
158-
};
159-
160-
const spawn = () => {
161-
ready = new Promise<void>((resolve, reject) => {
162-
settleReady = (error?: Error) => (error ? reject(error) : resolve());
163-
});
164-
worker = createWorkerFromContent(getWorkerSrc(language));
165-
worker.onmessage = onMessage;
166-
worker.onerror = (event) => teardown(new Error(`The Clang worker crashed: ${event.message}`));
167-
};
168-
169-
/** Spawn the worker if needed and resolve once the compiler has loaded. */
170-
const ensureReady = async () => {
171-
if (!ready) spawn();
172-
await ready;
173-
};
174-
175-
const run = (code: string, input: string, options?: ClangWasmSettings) =>
176-
ensureReady().then(
177-
() =>
178-
new Promise<WorkerRunResult>((resolve, reject) => {
179-
const id = nextId++;
180-
pending[id] = { resolve, reject };
181-
worker?.postMessage({ id, code, input, options });
182-
}),
183-
);
184-
185-
return { ensureReady, run };
186-
};
187-
188100
const getLanguage = (): LanguageId | null => {
189101
for (const scriptType of SCRIPT_TYPES) {
190102
if (document.querySelector(`script[type="${scriptType}"]`)) {
@@ -282,7 +194,8 @@ clangWasm.run = async (input?: string) => {
282194
if (!code.trim()) return setResult(stdin, null, null, null);
283195

284196
const runner = (clangWasm.runners[language] =
285-
clangWasm.runners[language] || createRunner(language));
197+
clangWasm.runners[language] ||
198+
createWorkerRunner({ getWorkerSrc: () => getWorkerSrc(language), label: 'Clang' }));
286199

287200
try {
288201
await ensureLoaded(runner);

0 commit comments

Comments
 (0)