| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283 |
- /**
- * Object-literal registry dispatch synthesizer.
- *
- * A command registry maps keys → handler classes/functions in an object literal, then
- * dispatches by a RUNTIME key (`new registry[command]().execute()`) that static parsing
- * can't follow. The synthesizer links each dispatching method → each registered handler's
- * callable entry. Validates: a class registry resolves to the handler's `.execute` method;
- * the field-initializer form (`commands = {…}` matched against a `this.commands[k]` dispatch);
- * and the dispatch GATE — a look-alike object literal that is only ever accessed statically
- * (never `registry[var]`) yields no edges.
- */
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
- import * as fs from 'node:fs';
- import * as path from 'node:path';
- import * as os from 'node:os';
- import { CodeGraph } from '../src';
- describe('object-registry synthesizer', () => {
- let dir: string;
- beforeEach(() => { dir = fs.mkdtempSync(path.join(os.tmpdir(), 'obj-registry-')); });
- afterEach(() => { fs.rmSync(dir, { recursive: true, force: true }); });
- it('links a dispatcher to each registered command class’s execute method, gated on dynamic dispatch', async () => {
- fs.writeFileSync(
- path.join(dir, 'commands.ts'),
- `export class AddCommand { execute() { return 'add'; } }
- export class RemoveCommand { execute() { return 'remove'; } }
- export class MoveCommand { execute() { return 'move'; } }
- `
- );
- fs.writeFileSync(
- path.join(dir, 'manager.ts'),
- `import { AddCommand, RemoveCommand, MoveCommand } from './commands';
- const Cmd = { ADD: 'add', REMOVE: 'remove', MOVE: 'move' };
- class CommandManager {
- commands = {
- [Cmd.ADD]: AddCommand,
- [Cmd.REMOVE]: RemoveCommand,
- [Cmd.MOVE]: MoveCommand,
- };
- executeCommand(command: string) {
- return new this.commands[command]().execute();
- }
- }
- `
- );
- // A look-alike registry that is NEVER dynamically dispatched (only a static `.add`
- // member access) — must yield NO edges. The dynamic `registry[var]` dispatch is the gate.
- fs.writeFileSync(
- path.join(dir, 'static.ts'),
- `import { AddCommand, RemoveCommand } from './commands';
- const table = { add: AddCommand, remove: RemoveCommand };
- export function direct() { return new table.add().execute(); }
- `
- );
- const cg = await CodeGraph.init(dir, { silent: true });
- await cg.indexAll();
- const db = (cg as any).db.db;
- const rows = db
- .prepare(
- `SELECT s.name source_name, t.name target_name, t.kind target_kind, t.file_path target_file
- FROM edges e
- JOIN nodes s ON s.id = e.source
- JOIN nodes t ON t.id = e.target
- WHERE json_extract(e.metadata,'$.synthesizedBy') = 'object-registry'`
- )
- .all();
- cg.close?.();
- // Exactly the 3 dispatcher→handler-entry edges: executeCommand → {Add,Remove,Move}Command.execute.
- expect(rows.length).toBe(3);
- expect(rows.every((r: any) => r.source_name === 'executeCommand')).toBe(true);
- expect(rows.every((r: any) => r.target_kind === 'method' && r.target_name === 'execute')).toBe(true);
- expect(rows.every((r: any) => /commands\.ts$/.test(r.target_file))).toBe(true);
- // The statically-accessed look-alike registry contributed nothing.
- expect(rows.some((r: any) => /static\.ts$/.test(r.target_file))).toBe(false);
- });
- });
|