mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-19 00:03:33 +00:00
P2: - WASM dual-ownership invariant documented on ASTCache dispose: a Tree must live in AT MOST ONE disposing ASTCache. Native tree-sitter today is unaffected; WASM adoption would require tree.copy() or a non-disposing secondary cache. - mro-processor C3 ordering test: pins EXTENDS-before-IMPLEMENTS parent grouping for classes with interleaved edge additions. Asserts exact MRO ['Base', 'Iface'] — a revert to single-loop insertion-order iteration would produce ['Iface', 'Base'] and fail loudly. - cached-tree parity test: emitPythonScopeCaptures(src, path, T) returns identical CaptureMatch[] to emitPythonScopeCaptures(src, path). Pins the cache-hit path's correctness so a regression that silently returns stale captures would break the test. P3: - Dev-mode cache counters moved from captures.ts to cache-stats.ts. Production hot-path module no longer carries the module-global export surface; PROF gating behavior preserved. - ParseOutput field rename astCache → scopeTreeCache. Clarifies that the surfaced cache is the persistent cross-phase one, not the chunk-local astCache parse-impl clears between chunks. Single consumer (scopeResolutionPhase) updated; no other readers. - ASTCacheReader interface extracted. scopeResolutionPhase now reads the phase dep via a shared type instead of a hand-rolled inline structural shape that could drift from ASTCache's contract. - graph.ts dual-index invariant enforced through writeRel/deleteRel private helpers instead of duplicated add/delete at 3 mutation sites. Adding a new mutation method only needs to call the helpers — forgetting to update one index becomes structurally impossible. Tests: 382/382 unit (incl. 2 new), 191/191 python integration both flag paths. tsc clean.
78 lines
2.9 KiB
TypeScript
78 lines
2.9 KiB
TypeScript
/**
|
|
* Parity guard for the cross-phase tree cache (PHM Unit 5).
|
|
*
|
|
* `emitPythonScopeCaptures(src, path)` re-parses internally;
|
|
* `emitPythonScopeCaptures(src, path, cachedTree)` skips the parse. The
|
|
* two paths MUST return identical `CaptureMatch[]`. A future change
|
|
* that (a) mutates Trees before caching, (b) conditionally branches
|
|
* the capture query on cached vs fresh Trees, or (c) leaks state
|
|
* through module-level caches would break this — and no other test
|
|
* today asserts the equivalence.
|
|
*
|
|
* Keeps the wins from the cache-hit path honest.
|
|
*/
|
|
import { describe, it, expect } from 'vitest';
|
|
import {
|
|
emitPythonScopeCaptures,
|
|
resetPythonCaptureCacheStats,
|
|
getPythonCaptureCacheStats,
|
|
} from '../../../../src/core/ingestion/languages/python/index.js';
|
|
import { getPythonParser } from '../../../../src/core/ingestion/languages/python/query.js';
|
|
|
|
const FIXTURE = `
|
|
from typing import List
|
|
|
|
class Base:
|
|
def greet(self) -> str:
|
|
return "hi"
|
|
|
|
class Child(Base):
|
|
def shout(self, items: List[str]) -> None:
|
|
for item in items:
|
|
print(item.upper())
|
|
|
|
def top(c: Child) -> None:
|
|
c.greet()
|
|
c.shout([])
|
|
`;
|
|
|
|
function normalizeCaptures(caps: readonly Record<string, unknown>[]): unknown[] {
|
|
// CaptureMatch is a Record<tag, Capture>. Compare by structural JSON
|
|
// so Node references don't create false negatives.
|
|
return caps.map((m) => {
|
|
const out: Record<string, unknown> = {};
|
|
for (const [tag, cap] of Object.entries(m)) {
|
|
const c = cap as { range?: unknown; text?: unknown };
|
|
out[tag] = { range: c.range, text: c.text };
|
|
}
|
|
return out;
|
|
});
|
|
}
|
|
|
|
describe('emitPythonScopeCaptures cache-hit parity', () => {
|
|
it('returns identical captures whether cachedTree is supplied or not', () => {
|
|
const fresh = emitPythonScopeCaptures(FIXTURE, 'fixture.py');
|
|
const tree = getPythonParser().parse(FIXTURE);
|
|
const cached = emitPythonScopeCaptures(FIXTURE, 'fixture.py', tree);
|
|
|
|
expect(cached).toHaveLength(fresh.length);
|
|
expect(normalizeCaptures(cached)).toEqual(normalizeCaptures(fresh));
|
|
});
|
|
|
|
it('counters stay at zero baseline after reset regardless of whether PROF is active', () => {
|
|
// The PROF gate is evaluated at module load, so we can't toggle
|
|
// counters on mid-test. What we CAN assert deterministically is
|
|
// that reset zeros the counters and repeated reads yield the same
|
|
// zeroed snapshot (counter API shape invariant).
|
|
resetPythonCaptureCacheStats();
|
|
expect(getPythonCaptureCacheStats()).toEqual({ hits: 0, misses: 0 });
|
|
// Running the emit path should not mutate the counters unless PROF
|
|
// was on at module load. Whichever state, calling reset again must
|
|
// return to zero.
|
|
const tree = getPythonParser().parse(FIXTURE);
|
|
emitPythonScopeCaptures(FIXTURE, 'fixture.py', tree);
|
|
emitPythonScopeCaptures(FIXTURE, 'fixture.py');
|
|
resetPythonCaptureCacheStats();
|
|
expect(getPythonCaptureCacheStats()).toEqual({ hits: 0, misses: 0 });
|
|
});
|
|
});
|