Skip to content

Commit 4c1c429

Browse files
committed
fix(registry): attribute CLI dispatches by occurrence, not command name
Subtracting attributed command NAMES let a stray dispatch hide behind a routed one that names the same command, so the gate's totality claim did not hold. Dispatch sites now carry their source offset and attribution subtracts occurrences. Part of #1739 (wave 0)
1 parent 8ad1292 commit 4c1c429

1 file changed

Lines changed: 57 additions & 22 deletions

File tree

src/core/command-descriptor/__tests__/platform-execution-cli-route.test.ts

Lines changed: 57 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,16 @@ const registryKind: PlatformExecutionKindOf = (command) =>
3636

3737
type RouteDispatch = Readonly<{ route: string; dispatched: string }>;
3838

39+
/**
40+
* One dispatch expression, identified by source position. Attribution subtracts
41+
* occurrences: two dispatches of the same command are two sites, so a stray one
42+
* cannot be absorbed by a routed one that happens to name the same command.
43+
*/
44+
type DispatchSite = Readonly<{ command: string; offset: number }>;
45+
3946
type RouteScan = Readonly<{
4047
dispatches: readonly RouteDispatch[];
41-
/** Dispatch literals no `command === '<name>'` route could claim. */
48+
/** Commands dispatched at a site no `command === '<name>'` route could claim. */
4249
unattributed: readonly string[];
4350
}>;
4451

@@ -88,10 +95,15 @@ function routedCommandOf(test: unknown): string | undefined {
8895
}
8996

9097
/** `command: 'x'`, `command: INTERNAL_COMMANDS.x`, `command: PUBLIC_COMMANDS.x`. */
91-
function dispatchedCommandOf(node: AstNode): string | undefined {
98+
function dispatchSiteOf(node: AstNode): DispatchSite | undefined {
9299
if (node.type !== 'Property' || node['computed'] === true) return undefined;
93100
if (identifierName(node['key']) !== 'command') return undefined;
94-
return dispatchTargetOf(node['value']);
101+
const command = dispatchTargetOf(node['value']);
102+
if (command === undefined) return undefined;
103+
// A node without a source position cannot be matched against an attributed
104+
// site, so -1 keeps it distinct from every real offset and it stays unclaimed.
105+
const start = node['start'];
106+
return { command, offset: typeof start === 'number' ? start : -1 };
95107
}
96108

97109
function dispatchTargetOf(value: unknown): string | undefined {
@@ -115,57 +127,60 @@ function localFunctionsByName(program: unknown): Map<string, AstNode> {
115127
return functions;
116128
}
117129

118-
/** Dispatch literals reachable from `scope`, following calls to same-module functions. */
130+
/** Dispatch sites reachable from `scope`, following calls to same-module functions. */
119131
function reachableDispatches(
120132
scope: unknown,
121133
functions: ReadonlyMap<string, AstNode>,
122134
visited: Set<string>,
123-
): Set<string> {
124-
const found = new Set<string>();
135+
): DispatchSite[] {
136+
const found: DispatchSite[] = [];
125137
const calls: string[] = [];
126138
walk(scope, (node) => {
127-
const dispatched = dispatchedCommandOf(node);
128-
if (dispatched !== undefined) found.add(dispatched);
139+
const site = dispatchSiteOf(node);
140+
if (site !== undefined) found.push(site);
129141
if (node.type !== 'CallExpression') return;
130142
const callee = identifierName(node['callee']);
131143
if (callee !== undefined && functions.has(callee)) calls.push(callee);
132144
});
133145
for (const name of calls) {
134146
if (visited.has(name)) continue;
135147
visited.add(name);
136-
for (const dispatched of reachableDispatches(functions.get(name), functions, visited)) {
137-
found.add(dispatched);
138-
}
148+
found.push(...reachableDispatches(functions.get(name), functions, visited));
139149
}
140150
return found;
141151
}
142152

143153
function scanCliRouteDispatches(sourceText: string): RouteScan {
144154
const program = parseSync('cli.ts', sourceText).program;
145155
const functions = localFunctionsByName(program);
156+
const edges = new Set<string>();
146157
const dispatches: RouteDispatch[] = [];
147-
const attributed = new Set<string>();
158+
const attributedOffsets = new Set<number>();
148159

149160
walk(program, (node) => {
150161
if (node.type !== 'IfStatement') return;
151162
const route = routedCommandOf(node['test']);
152163
if (route === undefined) return;
153-
for (const dispatched of reachableDispatches(node['consequent'], functions, new Set())) {
154-
dispatches.push({ route, dispatched });
155-
attributed.add(dispatched);
164+
for (const site of reachableDispatches(node['consequent'], functions, new Set())) {
165+
attributedOffsets.add(site.offset);
166+
const edge = `${route} ${site.command}`;
167+
if (edges.has(edge)) continue;
168+
edges.add(edge);
169+
dispatches.push({ route, dispatched: site.command });
156170
}
157171
});
158172

159-
const all = new Set<string>();
173+
const allSites: DispatchSite[] = [];
160174
walk(program, (node) => {
161-
const dispatched = dispatchedCommandOf(node);
162-
if (dispatched !== undefined) all.add(dispatched);
175+
const site = dispatchSiteOf(node);
176+
if (site !== undefined) allSites.push(site);
163177
});
164178

165-
return {
166-
dispatches,
167-
unattributed: [...all].filter((command) => !attributed.has(command)).sort(),
168-
};
179+
const unclaimed = allSites
180+
.filter(({ offset }) => !attributedOffsets.has(offset))
181+
.map(({ command }) => command);
182+
183+
return { dispatches, unattributed: [...new Set(unclaimed)].sort() };
169184
}
170185

171186
function dominanceFailures(scan: RouteScan, kindOf: PlatformExecutionKindOf): string[] {
@@ -215,4 +230,24 @@ describe('platform-execution coherence across CLI route delegation', () => {
215230
`;
216231
expect(scanCliRouteDispatches(planted).unattributed).toEqual(['runtime']);
217232
});
233+
234+
test('planted red: a stray dispatch sharing a routed target name is still reported', () => {
235+
const planted = `
236+
async function runCli(argv, deps) {
237+
if (command === 'react-devtools') {
238+
await runReactDevtoolsCli(ctx, deps);
239+
return;
240+
}
241+
await deps.sendToDaemon({ command: INTERNAL_COMMANDS.runtime, positionals: ['stray'] });
242+
}
243+
async function runReactDevtoolsCli(ctx, deps) {
244+
await deps.sendToDaemon({ command: INTERNAL_COMMANDS.runtime, positionals: [] });
245+
}
246+
`;
247+
const scan = scanCliRouteDispatches(planted);
248+
249+
// Both halves matter: the routed occurrence is claimed, the stray one is not.
250+
expect(scan.dispatches).toEqual([{ route: 'react-devtools', dispatched: 'runtime' }]);
251+
expect(scan.unattributed).toEqual(['runtime']);
252+
});
218253
});

0 commit comments

Comments
 (0)