mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
105 lines
4.2 KiB
TypeScript
105 lines
4.2 KiB
TypeScript
/**
|
|
* U5 — TypeScript emits `@reference.receiver-chain` for a receiver that is
|
|
* itself an expression.
|
|
*
|
|
* Capture-level, not resolution-level: nothing consumes the field yet, so these
|
|
* assert what is written, and that nothing extra is.
|
|
*/
|
|
|
|
import { describe, it, expect } from 'vitest';
|
|
import { emitTsScopeCaptures } from '../../src/core/ingestion/languages/typescript/index.js';
|
|
|
|
/** The chain captures produced for a source, keyed by the member being called. */
|
|
function chainsFor(src: string): Record<string, string> {
|
|
const out: Record<string, string> = {};
|
|
for (const match of emitTsScopeCaptures(src, 'main.ts')) {
|
|
const chain = match['@reference.receiver-chain'];
|
|
const name = match['@reference.name'];
|
|
if (chain === undefined || name === undefined) continue;
|
|
out[name.text] = chain.text;
|
|
}
|
|
return out;
|
|
}
|
|
|
|
const MODELS = `class Address { save(): void {} }
|
|
class User { name = ''; address = new Address(); save(): void {} }
|
|
class Service {
|
|
getUser(): User { return new User(); }
|
|
getTyped<T>(): User { return new User(); }
|
|
}
|
|
declare const svc: Service;
|
|
declare const repos: User[];
|
|
`;
|
|
|
|
describe('TypeScript receiver-chain capture', () => {
|
|
it('emits a chain for a plain call-chain receiver', () => {
|
|
expect(chainsFor(`${MODELS}\nsvc.getUser().save();\n`)).toMatchObject({
|
|
save: '2|svc|cgetUser',
|
|
});
|
|
});
|
|
|
|
it('emits a mixed call/field chain base-first', () => {
|
|
expect(chainsFor(`${MODELS}\nsvc.getUser().address.save();\n`)).toMatchObject({
|
|
save: '2|svc|cgetUser|faddress',
|
|
});
|
|
});
|
|
|
|
it('emits a chain for an optional-chained receiver — one of the shapes that resolves to nothing today', () => {
|
|
expect(chainsFor(`${MODELS}\nsvc?.getUser().save();\n`)).toMatchObject({
|
|
save: '2|svc|cgetUser',
|
|
});
|
|
});
|
|
|
|
it('emits a chain for an explicit-type-argument receiver', () => {
|
|
expect(chainsFor(`${MODELS}\nsvc.getTyped<User>().save();\n`)).toMatchObject({
|
|
save: '2|svc|cgetTyped',
|
|
});
|
|
});
|
|
|
|
it('emits a name-free index step for a subscript receiver', () => {
|
|
// The declaration `repos: User[]` at the top of MODELS exists for this.
|
|
// A subscript receiver contains neither `.` nor `(`, so Case 0's old
|
|
// punctuation gate never fired and this call was INVISIBLE — no edge and no
|
|
// recorded drop. The chain is what makes it visible; the `i` step carries no
|
|
// name because a subscript key is a value, not a member.
|
|
expect(chainsFor(`${MODELS}\nrepos[0].save();\n`)).toMatchObject({ save: '2|repos|i' });
|
|
});
|
|
|
|
it('emits one index step per subscript, so a nested container is not flattened', () => {
|
|
// `User[][]` reduces to the same `User` a single `User[]` does, so the STEP
|
|
// COUNT is the only thing that distinguishes `nested[0]` (still a container)
|
|
// from `nested[0][1]` (an element).
|
|
const src = `${MODELS}\ndeclare const nested: User[][];\nnested[0][1].save();\n`;
|
|
expect(chainsFor(src)).toMatchObject({ save: '2|nested|i|i' });
|
|
});
|
|
|
|
it('emits no chain for a bare-name receiver — there is nothing to fold', () => {
|
|
expect(chainsFor(`${MODELS}\nconst u = new User();\nu.save();\n`)).toEqual({});
|
|
});
|
|
|
|
it('emits no chain for a property read or write receiver (CALL_TAGS gate)', () => {
|
|
// Without the gate these pay the walk and the bytes for a field no
|
|
// call-site resolver reads.
|
|
const chains = chainsFor(
|
|
`${MODELS}\nsvc.getUser().name = 'x';\nconst n = svc.getUser().name;\n`,
|
|
);
|
|
expect(chains).not.toHaveProperty('name');
|
|
});
|
|
|
|
it('emits a plain string payload, so the capture stays structured-clone safe', () => {
|
|
const chains = chainsFor(`${MODELS}\nsvc.getUser().save();\n`);
|
|
expect(typeof chains.save).toBe('string');
|
|
});
|
|
|
|
it('emits NO chain past MAX_CHAIN_DEPTH rather than a truncated one', () => {
|
|
// `extractMixedChain` reports no base receiver when it stops early, and a
|
|
// partial chain is missing exactly the head that decides the final type —
|
|
// encoding it would turn an untypeable receiver into a wrongly-typed one.
|
|
const deep = `${MODELS}
|
|
class C5 { e(): C5 { return this; } f(): void {} }
|
|
declare const a: C5;
|
|
a.e().e().e().e().f();
|
|
`;
|
|
expect(chainsFor(deep)).not.toHaveProperty('f');
|
|
});
|
|
});
|