Skip to content

Commit 0b982bf

Browse files
committed
style(daemon): oxfmt the interaction response construction guard
1 parent de50360 commit 0b982bf

1 file changed

Lines changed: 51 additions & 87 deletions

File tree

src/daemon/interaction/internal/__tests__/interaction-response-construction-guard.test.ts

Lines changed: 51 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import assert from "node:assert/strict";
2-
import fs from "node:fs";
3-
import path from "node:path";
4-
import { parseSync } from "oxc-parser";
5-
import { test } from "vitest";
1+
import assert from 'node:assert/strict';
2+
import fs from 'node:fs';
3+
import path from 'node:path';
4+
import { parseSync } from 'oxc-parser';
5+
import { test } from 'vitest';
66

77
// ADR 0011 Layer-2 guard: interaction response payloads have exactly ONE
88
// construction site — buildInteractionResponseData in
@@ -18,26 +18,19 @@ import { test } from "vitest";
1818
// stricter, structural rule: each touch case is exactly one `return await
1919
// <touch handler>(...)`, so no response can be constructed there at all.
2020

21-
const INTERACTION_INTERNAL_DIR = path.resolve(import.meta.dirname, "..");
22-
const BUILDER_FILE = "interaction-touch-response.ts";
23-
const DISPATCHER_FILE = "interaction.ts";
24-
const TOUCH_DISPATCH_COMMANDS = [
25-
"press",
26-
"click",
27-
"longpress",
28-
"hover",
29-
"fill",
30-
] as const;
21+
const INTERACTION_INTERNAL_DIR = path.resolve(import.meta.dirname, '..');
22+
const BUILDER_FILE = 'interaction-touch-response.ts';
23+
const DISPATCHER_FILE = 'interaction.ts';
24+
const TOUCH_DISPATCH_COMMANDS = ['press', 'click', 'longpress', 'hover', 'fill'] as const;
3125
const TOUCH_HANDLER_MODULE = /^\.\/interaction-touch[\w-]*\.ts$/;
3226

3327
function touchHandlerSourceFiles(): string[] {
3428
return fs
3529
.readdirSync(INTERACTION_INTERNAL_DIR)
3630
.filter(
3731
(file) =>
38-
(file.startsWith("interaction-touch") ||
39-
file === "interaction-common.ts") &&
40-
file.endsWith(".ts") &&
32+
(file.startsWith('interaction-touch') || file === 'interaction-common.ts') &&
33+
file.endsWith('.ts') &&
4134
file !== BUILDER_FILE,
4235
);
4336
}
@@ -55,18 +48,11 @@ const ALLOWED_RHS = [
5548

5649
function findHandRolledResponseData(source: string): string[] {
5750
// Collapse whitespace so multi-line hand-rolled literals cannot hide.
58-
const collapsed = source.replaceAll(/\s+/g, " ");
51+
const collapsed = source.replaceAll(/\s+/g, ' ');
5952
const offenders: string[] = [];
6053
const assignment = /\bresponseData\s*[:=]\s*/g;
61-
for (
62-
let match = assignment.exec(collapsed);
63-
match;
64-
match = assignment.exec(collapsed)
65-
) {
66-
const rhs = collapsed.slice(
67-
match.index + match[0].length,
68-
match.index + match[0].length + 160,
69-
);
54+
for (let match = assignment.exec(collapsed); match; match = assignment.exec(collapsed)) {
55+
const rhs = collapsed.slice(match.index + match[0].length, match.index + match[0].length + 160);
7056
if (!ALLOWED_RHS.some((pattern) => pattern.test(rhs))) {
7157
offenders.push(rhs.slice(0, 80));
7258
}
@@ -77,11 +63,7 @@ function findHandRolledResponseData(source: string): string[] {
7763
type AstNode = Record<string, unknown> & { type: string };
7864

7965
function isAstNode(value: unknown): value is AstNode {
80-
return (
81-
typeof value === "object" &&
82-
value !== null &&
83-
typeof (value as AstNode).type === "string"
84-
);
66+
return typeof value === 'object' && value !== null && typeof (value as AstNode).type === 'string';
8567
}
8668

8769
function* walkAst(value: unknown): Generator<AstNode> {
@@ -92,14 +74,14 @@ function* walkAst(value: unknown): Generator<AstNode> {
9274
if (!isAstNode(value)) return;
9375
yield value;
9476
for (const [key, child] of Object.entries(value)) {
95-
if (key !== "type") yield* walkAst(child);
77+
if (key !== 'type') yield* walkAst(child);
9678
}
9779
}
9880

9981
function touchHandlerImports(program: AstNode): Set<string> {
10082
const names = new Set<string>();
10183
for (const node of walkAst(program)) {
102-
if (node.type !== "ImportDeclaration") continue;
84+
if (node.type !== 'ImportDeclaration') continue;
10385
const source = node.source as AstNode;
10486
if (!TOUCH_HANDLER_MODULE.test(String(source.value))) continue;
10587
for (const specifier of node.specifiers as AstNode[]) {
@@ -109,13 +91,11 @@ function touchHandlerImports(program: AstNode): Set<string> {
10991
return names;
11092
}
11193

112-
function touchCommandOf(
113-
switchCase: AstNode,
114-
): (typeof TOUCH_DISPATCH_COMMANDS)[number] | null {
94+
function touchCommandOf(switchCase: AstNode): (typeof TOUCH_DISPATCH_COMMANDS)[number] | null {
11595
const literal = switchCase.test;
116-
if (!isAstNode(literal) || literal.type !== "Literal") return null;
96+
if (!isAstNode(literal) || literal.type !== 'Literal') return null;
11797
const command = literal.value;
118-
return typeof command === "string" &&
98+
return typeof command === 'string' &&
11999
(TOUCH_DISPATCH_COMMANDS as readonly string[]).includes(command)
120100
? (command as (typeof TOUCH_DISPATCH_COMMANDS)[number])
121101
: null;
@@ -127,22 +107,21 @@ function delegationOf(
127107
handlers: ReadonlySet<string>,
128108
): { handler: string } | { violation: string } {
129109
const consequent = switchCase.consequent as AstNode[];
130-
if (consequent.length !== 1 || consequent[0]?.type !== "ReturnStatement") {
131-
return { violation: "the case body is not exactly one return statement" };
110+
if (consequent.length !== 1 || consequent[0]?.type !== 'ReturnStatement') {
111+
return { violation: 'the case body is not exactly one return statement' };
132112
}
133113
const awaited = consequent[0].argument;
134-
if (!isAstNode(awaited) || awaited.type !== "AwaitExpression") {
135-
return { violation: "the case does not return an awaited call" };
114+
if (!isAstNode(awaited) || awaited.type !== 'AwaitExpression') {
115+
return { violation: 'the case does not return an awaited call' };
136116
}
137117
const call = awaited.argument;
138-
if (!isAstNode(call) || call.type !== "CallExpression") {
139-
return { violation: "the case does not return an awaited call" };
118+
if (!isAstNode(call) || call.type !== 'CallExpression') {
119+
return { violation: 'the case does not return an awaited call' };
140120
}
141121
const callee = call.callee;
142-
if (!isAstNode(callee) || callee.type !== "Identifier") {
122+
if (!isAstNode(callee) || callee.type !== 'Identifier') {
143123
return {
144-
violation:
145-
"the case calls something other than an imported touch handler",
124+
violation: 'the case calls something other than an imported touch handler',
146125
};
147126
}
148127
const handler = String(callee.name);
@@ -159,43 +138,37 @@ function delegationOf(
159138
* local `responseData`, an inline literal, a second statement — is a violation.
160139
*/
161140
function touchDispatchViolations(source: string): string[] {
162-
const program = parseSync(DISPATCHER_FILE, source)
163-
.program as unknown as AstNode;
141+
const program = parseSync(DISPATCHER_FILE, source).program as unknown as AstNode;
164142
const handlers = touchHandlerImports(program);
165143
const seen = new Map<string, number>();
166144
const violations: string[] = [];
167145
for (const node of walkAst(program)) {
168-
if (node.type !== "SwitchCase") continue;
146+
if (node.type !== 'SwitchCase') continue;
169147
const command = touchCommandOf(node);
170148
if (command === null) continue;
171149
seen.set(command, (seen.get(command) ?? 0) + 1);
172150
const delegation = delegationOf(node, handlers);
173-
if ("violation" in delegation) {
151+
if ('violation' in delegation) {
174152
violations.push(`case '${command}': ${delegation.violation}`);
175153
}
176154
}
177155
for (const command of TOUCH_DISPATCH_COMMANDS) {
178156
const count = seen.get(command) ?? 0;
179157
if (count !== 1)
180-
violations.push(
181-
`case '${command}': expected exactly one case, found ${count}`,
182-
);
158+
violations.push(`case '${command}': expected exactly one case, found ${count}`);
183159
}
184160
return violations;
185161
}
186162

187-
test("interaction responses are only constructed by buildInteractionResponseData", () => {
163+
test('interaction responses are only constructed by buildInteractionResponseData', () => {
188164
const files = touchHandlerSourceFiles();
189165
assert.ok(
190-
files.includes("interaction-touch-press.ts"),
191-
"guard lost sight of interaction-touch-press.ts — update touchHandlerSourceFiles()",
166+
files.includes('interaction-touch-press.ts'),
167+
'guard lost sight of interaction-touch-press.ts — update touchHandlerSourceFiles()',
192168
);
193169
const offenders: string[] = [];
194170
for (const file of files) {
195-
const source = fs.readFileSync(
196-
path.join(INTERACTION_INTERNAL_DIR, file),
197-
"utf8",
198-
);
171+
const source = fs.readFileSync(path.join(INTERACTION_INTERNAL_DIR, file), 'utf8');
199172
for (const offender of findHandRolledResponseData(source)) {
200173
offenders.push(`${file}: responseData = ${offender}...`);
201174
}
@@ -206,48 +179,39 @@ test("interaction responses are only constructed by buildInteractionResponseData
206179
`Hand-rolled interaction responseData found. Route it through ` +
207180
`buildInteractionResponseData (${BUILDER_FILE}) so identity extras ` +
208181
`(evidence, refLabel, selectorChain, hints) cannot be dropped per-branch:\n` +
209-
offenders.map((offender) => ` - ${offender}`).join("\n"),
182+
offenders.map((offender) => ` - ${offender}`).join('\n'),
210183
);
211184
});
212185

213-
test("every touch command case in the dispatcher only delegates to a touch handler", () => {
214-
const source = fs.readFileSync(
215-
path.join(INTERACTION_INTERNAL_DIR, DISPATCHER_FILE),
216-
"utf8",
217-
);
186+
test('every touch command case in the dispatcher only delegates to a touch handler', () => {
187+
const source = fs.readFileSync(path.join(INTERACTION_INTERNAL_DIR, DISPATCHER_FILE), 'utf8');
218188
const violations = touchDispatchViolations(source);
219189
assert.deepEqual(
220190
violations,
221191
[],
222192
`The touch dispatch switch in ${DISPATCHER_FILE} must only delegate. Move the logic into ` +
223193
`an interaction-touch-*.ts handler, which the responseData guard above scans:\n` +
224-
violations.map((violation) => ` - ${violation}`).join("\n"),
194+
violations.map((violation) => ` - ${violation}`).join('\n'),
225195
);
226196
});
227197

228-
test("the guard itself flags a hand-rolled responseData literal", () => {
198+
test('the guard itself flags a hand-rolled responseData literal', () => {
229199
assert.equal(
230-
findHandRolledResponseData(
231-
"const responseData = { ...backendResult, x, y };",
232-
).length,
200+
findHandRolledResponseData('const responseData = { ...backendResult, x, y };').length,
233201
1,
234202
);
235203
assert.equal(
236-
findHandRolledResponseData(
237-
'const responseData = result.kind === "ref" ? { a: 1 } : built;',
238-
).length,
204+
findHandRolledResponseData('const responseData = result.kind === "ref" ? { a: 1 } : built;')
205+
.length,
239206
1,
240207
);
241208
assert.equal(
242209
findHandRolledResponseData(
243-
"const responseData = buildInteractionResponseData({ source }).responseData;",
210+
'const responseData = buildInteractionResponseData({ source }).responseData;',
244211
).length,
245212
0,
246213
);
247-
assert.equal(
248-
findHandRolledResponseData("finalize({ result, responseData });").length,
249-
0,
250-
);
214+
assert.equal(findHandRolledResponseData('finalize({ result, responseData });').length, 0);
251215
});
252216

253217
const DISPATCHER_PREAMBLE = `
@@ -283,11 +247,11 @@ const DELEGATING_CASES = `
283247
return await dispatchFillViaRuntime(params);
284248
`;
285249

286-
test("the dispatcher guard accepts a switch whose touch cases only delegate", () => {
250+
test('the dispatcher guard accepts a switch whose touch cases only delegate', () => {
287251
assert.deepEqual(touchDispatchViolations(dispatcher(DELEGATING_CASES)), []);
288252
});
289253

290-
test("the dispatcher guard rejects a hand-rolled responseData hidden behind a nested switch", () => {
254+
test('the dispatcher guard rejects a hand-rolled responseData hidden behind a nested switch', () => {
291255
const source = dispatcher(
292256
DELEGATING_CASES.replace(
293257
`case 'press':\n return await dispatchTargetedTouchViaRuntime(params, 'press');`,
@@ -308,7 +272,7 @@ test("the dispatcher guard rejects a hand-rolled responseData hidden behind a ne
308272
]);
309273
});
310274

311-
test("the dispatcher guard rejects a touch case that returns something other than a handler call", () => {
275+
test('the dispatcher guard rejects a touch case that returns something other than a handler call', () => {
312276
const inline = dispatcher(
313277
DELEGATING_CASES.replace(
314278
`return await dispatchFillViaRuntime(params);`,
@@ -330,11 +294,11 @@ test("the dispatcher guard rejects a touch case that returns something other tha
330294
]);
331295
});
332296

333-
test("the dispatcher guard notices a touch command that left the switch", () => {
297+
test('the dispatcher guard notices a touch command that left the switch', () => {
334298
const source = dispatcher(
335299
DELEGATING_CASES.replace(
336300
`case 'longpress':\n return await dispatchTargetedTouchViaRuntime(params, 'longpress');`,
337-
"",
301+
'',
338302
),
339303
);
340304
assert.deepEqual(touchDispatchViolations(source), [

0 commit comments

Comments
 (0)