Land the six-file fix from upstream PR #1691 by danusha2345 (pr-1691 at 6d0e80d52ae953b22d615bd20c4a4c7758814e60), preserving wasm/native extraction parity and exclusive field-type resolution. Preserve coexistence with the #1566 Map/collection fix merged in #1790, including nested holder.values.get coverage and the unchanged #1566 Unreleased changelog bullet. EXTRACTION_VERSION remains unchanged. Align the existing chained-receiver regression with the fix: a declared service field calls its method, while an anonymous field type does not bind to unrelated same-named project functions. Verified on Linux with Node 22.19.0: - Rebuilt the native kernel and TypeScript/browser distribution. - Both backends change Outbox::send -> Outbox::send into Outbox::send -> Mailer::send, keep Relay::forward -> Mailer::send, and store no self-edges in the issue repro. - Wasm: 224 tests passed; native kernel: 255 tests passed, no skips. - All 10 #1566 resolution cases pass on each backend, plus all four nested-receiver extraction parity cases. Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
102 lines
3.7 KiB
TypeScript
102 lines
3.7 KiB
TypeScript
/**
|
|
* A TS/JS member call reached through a host namespace — `chrome.storage.local
|
|
* .get(k)`, `document.body.querySelector(s)` — ends in a platform API. Emitting
|
|
* the bare method name for it let every such call exact-match whatever project
|
|
* symbol shared the name, so a storage wrapper's `get` called itself (#1707).
|
|
* Those are dropped, as are untyped identifier chains (#1566). The existing
|
|
* `window.MyNs.run()` and `this.<field>.m()` paths remain outside that guard.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import * as fs from 'fs';
|
|
import * as os from 'os';
|
|
import * as path from 'path';
|
|
import { CodeGraph } from '../src';
|
|
|
|
let dir: string;
|
|
let cg: CodeGraph;
|
|
|
|
beforeAll(async () => {
|
|
dir = fs.mkdtempSync(path.join(os.tmpdir(), 'cg-1707-'));
|
|
const w = (rel: string, body: string) => fs.writeFileSync(path.join(dir, rel), body);
|
|
w(
|
|
'storage.ts',
|
|
'declare const chrome: any;\n' +
|
|
'export const DraftHubStorage = {\n' +
|
|
' async get(key: string): Promise<unknown> {\n' +
|
|
' const result = await chrome.storage.local.get([key]);\n' +
|
|
' return result[key];\n' +
|
|
' },\n' +
|
|
'};\n'
|
|
);
|
|
w(
|
|
'dom.ts',
|
|
'export function querySelector(sel: string): string { return sel; }\n' +
|
|
'export function findRow(): unknown {\n' +
|
|
' return document.body.querySelector("tr");\n' +
|
|
'}\n'
|
|
);
|
|
w(
|
|
'service.ts',
|
|
'declare const window: any;\n' +
|
|
'export function ping(): string { return "pong"; }\n' +
|
|
'export function viaGlobal(): string {\n' +
|
|
' return window.MyNs.ping();\n' +
|
|
'}\n' +
|
|
'export class PingService { ping(): string { return "service"; } }\n' +
|
|
'export class Runner {\n' +
|
|
' constructor(private svc: PingService) {}\n' +
|
|
' run(): string { return this.svc.ping(); }\n' +
|
|
'}\n' +
|
|
'export class AnonymousRunner {\n' +
|
|
' constructor(private svc: { ping(): string }) {}\n' +
|
|
' run(): string { return this.svc.ping(); }\n' +
|
|
'}\n'
|
|
);
|
|
cg = await CodeGraph.init(dir, { index: true });
|
|
cg.resolveReferences();
|
|
});
|
|
|
|
afterAll(() => {
|
|
cg.destroy();
|
|
try {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
} catch {
|
|
// Windows can still hold the SQLite handle for a moment; the OS temp dir is swept anyway.
|
|
}
|
|
});
|
|
|
|
const fn = (name: string, file: string) =>
|
|
cg.getNodesByKind('function').find((n) => n.name === name && n.filePath === file)!;
|
|
const method = (qn: string) => cg.getNodesByKind('method').find((n) => n.qualifiedName === qn)!;
|
|
const callTargets = (id: string) =>
|
|
cg
|
|
.getOutgoingEdges(id)
|
|
.filter((e) => e.kind === 'calls')
|
|
.map((e) => e.target);
|
|
|
|
describe('TS/JS call through a host-global chain (#1707)', () => {
|
|
it('does not make a storage wrapper call itself through chrome.storage.local.get', () => {
|
|
const get = fn('get', 'storage.ts');
|
|
expect(get).toBeDefined();
|
|
expect(callTargets(get.id)).not.toContain(get.id);
|
|
});
|
|
|
|
it('does not bind document.body.querySelector to a same-named project function', () => {
|
|
expect(callTargets(fn('findRow', 'dom.ts').id)).not.toContain(
|
|
fn('querySelector', 'dom.ts').id
|
|
);
|
|
});
|
|
|
|
it('keeps a chain rooted at a project value — window.MyNs.m() and this.<field>.m()', () => {
|
|
const ping = fn('ping', 'service.ts').id;
|
|
expect(callTargets(fn('viaGlobal', 'service.ts').id)).toContain(ping);
|
|
expect(callTargets(method('Runner::run').id)).toEqual([method('PingService::ping').id]);
|
|
});
|
|
|
|
it('does not guess a same-named project target for an anonymous field type (#1496)', () => {
|
|
// Neither the top-level ping nor PingService::ping establishes what svc is.
|
|
expect(callTargets(method('AnonymousRunner::run').id)).toEqual([]);
|
|
});
|
|
});
|