/** * Reference Resolution Orchestrator * * Coordinates all reference resolution strategies. */ import { UnresolvedReference, Edge } from '../types'; import { QueryBuilder } from '../db/queries'; import { UnresolvedRef, ResolvedRef, ResolutionResult, ResolutionContext } from './types'; import { type MaybeYield } from './cooperative-yield'; export * from './types'; /** * Reference Resolver * * Orchestrates reference resolution using multiple strategies. */ export declare class ReferenceResolver { private projectRoot; private queries; private context; private frameworks; private deferredChainRefs; private deferredThisMemberRefs; private razorUsingsCache; private nodeCache; private fileCache; private importMappingCache; private reExportCache; private nameCache; private lowerNameCache; private qualifiedNameCache; private fileLinesCache; private methodMatchCache; private methodOwnerIndexCache; private supertypeGen; private supertypeMemo; /** Invalidate the getSupertypes memo — call when resolved edges may have advanced. */ private advanceSupertypeGeneration; private nodesByKindCache; private knownNames; private knownFiles; private cachesWarmed; private projectAliases; private goModule; private workspacePackages; constructor(projectRoot: string, queries: QueryBuilder); /** * Initialize the resolver (detect frameworks, etc.) */ initialize(): void; /** * Run each framework resolver's cross-file finalization pass and persist * the returned node updates. Idempotent — safe to call after every indexAll * and every incremental sync. Returns the number of nodes updated. * * Caches are cleared before/after so the post-extract pass sees fresh DB * state and downstream queries see the updated names. */ runPostExtract(): number; /** * Pre-build lightweight caches for resolution. * Node lookups are now handled by indexed SQLite queries instead of * loading all nodes into memory (which caused OOM on large codebases). * We cache the set of known symbol names for fast pre-filtering. */ warmCaches(): void; /** * warmCaches for the async resolution entry points: streams the distinct * name set with periodic yields instead of one synchronous `.all()`. On a * multi-million-node index the DISTINCT scan is a solid multi-second block * (measured up to 28s inside `lattice sensor sync` on the Linux kernel index), * long enough to matter to the #850 watchdog on slower hardware. Same * result, same memory — only the event loop keeps turning. */ warmCachesYielding(onYield: MaybeYield): Promise; /** * Clear internal caches */ clearCaches(): void; /** `readFile` through the LRU content cache (null = read failed, also cached). */ private readFileCached; /** * Create the resolution context */ private createContext; /** * Resolve all unresolved references */ resolveAll(unresolvedRefs: UnresolvedReference[], onProgress?: (current: number, total: number) => void): ResolutionResult; /** * Check if a reference name has any possible match in the codebase. * Uses the pre-built knownNames set to skip expensive resolution * for names that definitely don't exist as symbols. */ private hasAnyPossibleMatch; /** * Does `ref.referenceName` match an import declared in its containing * file? Used as a pre-filter escape so re-export chain resolution * still gets a chance when the name has no project-wide declaration. */ private matchesAnyImport; /** * Resolve a single reference */ resolveOne(ref: UnresolvedRef): ResolvedRef | null; /** * Create edges from resolved references */ createEdges(resolved: ResolvedRef[]): Edge[]; /** * Split resolved refs into rows deletable by id and hand-built refs that * must fall back to the key-tuple delete. Rows loaded from the database * carry their row id and are deleted by exactly that id; the key tuple * omits line/col, so it also removes SIBLING rows — the same caller calling * the same callee at other lines — that a later batch hadn't attempted yet: * when a batch boundary split a caller's same-named call sites, the later * sites' edges were silently never created (#1269). */ private static partitionResolvedCleanup; /** * Same row-id precision for parking unresolvable refs as status='failed' * (#1240): the key-tuple fallback would flip same-key sibling rows in later * batches to 'failed' before they were ever attempted, and resolution * outcome can differ per call site (receiver-type inference reads the * ref's line), so a sibling must not inherit this row's failure (#1269). */ private static partitionFailedCleanup; /** * Resolve and persist edges to database */ resolveAndPersist(unresolvedRefs: UnresolvedReference[], onProgress?: (current: number, total: number) => void): ResolutionResult; /** * Yielding counterpart of {@link resolveAndPersist} for a caller-supplied * ref list — used by sync's failed-ref retry pass (#1240). Same persistence * semantics: resolved refs become edges and their rows are deleted; * still-unresolvable refs are (re-)marked failed (a no-op for rows already * in that status). Yields per-ref because sync can run on the daemon's * liveness-watchdog thread (#850/#1091) and a retry set is unbounded when * a large edit lands many popular symbol names at once. */ resolveAndPersistListYielding(refs: UnresolvedReference[]): Promise; /** * Second resolution pass for chained static-factory / fluent calls whose * chained method is defined on a SUPERTYPE the receiver's type conforms to — * a protocol-extension / inherited / default-interface method (#750). The * first pass can't resolve these because `implements`/`extends` edges aren't * built yet; this runs AFTER edges are persisted, so `context.getSupertypes` * (and the conformance fallback in resolveMethodOnType) can walk them. * * Operates only on the leftover unresolved refs that have the `inner().method` * chain shape, for the dotted-chain languages — a small set — and is idempotent * (re-resolving an already-resolved ref is a no-op since it's been deleted). * Returns the number of newly-created edges. */ resolveChainedCallsViaConformance(): Promise; /** * Resolve one batch with a yield checkpoint between EVERY ref so the #850 * liveness heartbeat can fire on a slow/dense batch (#1091). The checkpoint * granularity is per-ref — not per-N-refs — because per-ref cost is unbounded * in the worst case (a collision-heavy method name whose candidate set misses * the LRU re-fetches tens of thousands of rows): any fixed N multiplies that * worst case into the watchdog window, which is how v1.2.0 still got killed * at "Resolving refs" on large Java monorepos (#1122). `maybeYield()` is a * ~ns time check when under budget, so per-ref checkpoints cost nothing. * Behaviourally identical to `resolveAll(batch)`: `warmCaches()` is * idempotent (guarded) and `resolveOne` is independent per ref, so yielding * between refs changes only timing, never which edges get created. */ private resolveBatchYielding; /** * Resolve a list of refs and return everything the ADMISSION side needs to * persist the outcome: resolutions, failures, the deferred post-pass refs * this run produced (drained, so the caller owns routing them), and stats. * This is the resolver-worker entry point — it runs the exact per-ref loop * of resolveBatchYielding, minus the main-thread yields (worker threads have * no watchdog heartbeat to starve). Results are in input order. */ /** * LATTICE_SENSOR_RESOLVE_PROFILE=1: per-outcome wall-clock histogram of * resolveOne, keyed by the winning strategy (`resolvedBy`) or * `fail:` — the §7a.2 "profile the per-ref path" probe. The * kernel-scale batch loop is ~430s and CORE-INVARIANT (835.9s pooled-4-on-8 * ≈ 812.5s sequential-on-2 for the whole superphase), so the next lever is * which CLASS of ref the time belongs to, not more parallelism. Off by * default: the hrtime pair costs ~100ns/ref only when the env is set. */ private resolveProfile; /** * LATTICE_SENSOR_RESOLVE_PROFILE=2 additionally attributes time to the * STRATEGIES inside resolveOne (`stage:||hit/miss` rows in * the same histogram) — i.e. WHICH machinery a failing class of refs pays * for, not just that it fails. =1 keeps the per-outcome rows only. */ private profileStages; private stageAdd; private resolveOneTimed; /** Dump the LATTICE_SENSOR_RESOLVE_PROFILE histogram to stderr (no-op when off). */ dumpResolveProfile(label: string): void; resolveListForAdmission(refs: UnresolvedReference[]): { resolved: ResolvedRef[]; unresolved: UnresolvedRef[]; deferredChain: UnresolvedRef[]; deferredThisMember: UnresolvedRef[]; byMethod: Record; }; /** * The resolver's live ResolutionContext — resolver-pool workers use it to * run synthesis passes against their own read-only connection. */ getResolutionContext(): ResolutionContext; /** * Re-queue deferred post-pass refs produced by resolver workers, preserving * their admission order so resolveChainedCallsViaConformance / * resolveDeferredThisMemberRefs process them exactly as the sequential path * would have. */ appendDeferredFromWorkers(deferredChain: UnresolvedRef[], deferredThisMember: UnresolvedRef[]): void; /** * Resolve and persist in batches to keep memory bounded. * Processes unresolved references in chunks, persisting edges and cleaning * up resolved refs after each batch to avoid accumulating large arrays. */ resolveAndPersistBatched(onProgress?: (current: number, total: number) => void, batchSize?: number, onSynthesisProgress?: (done: number, total: number) => void, parallel?: { dbPath: string; bulkEdgeLoad?: { begin: () => void; end: () => void | Promise; }; /** unresolved_refs index window for the batched loop — the loop only * reads the status index + PK; dropping the sync-path ref indexes cuts * each per-batch DELETE's B-tree work (DatabaseConnection.beginBulkRefLoad). */ refIndexLoad?: { begin: () => void; end: () => void | Promise; }; backpressure?: () => Promise | null; }): Promise; /** * Get detected frameworks */ getDetectedFrameworks(): string[]; /** * Check if reference is to a built-in or external symbol */ private isBuiltInOrExternal; /** * Get file path from node ID */ private getFilePathFromNodeId; /** * Get language from node ID */ private getLanguageFromNodeId; /** * Drop an import/name-strategy resolution that crosses a language family. * Two regimes (mirrors `applyLanguageGate`'s candidate filter): * - `references` (type usage): STRICT — a `Type.member` static read names a * same-family type, never a coincidentally same-named symbol in another * language. Drops any non-same-family target. * - `imports` (import binding / `#include`): both-known — a C++ `#include * "X.h"` must not resolve to a same-named ObjC header on another platform * (basename collision), but a singleton-family / SFC language (`vue` → * `.ts`) importing across is left alone. * Applies to the import (strategy 2) + name-match (strategy 3) results. */ /** * Collect the `@using` namespaces in scope for a `.razor`/`.cshtml` file: its * own `@using` directives plus every `_Imports.razor` from the file's folder up * to the project root (Razor `_Imports` cascade). Cached per file. */ private getRazorUsings; /** * Resolve a Razor/Blazor simple type ref through the file's `@using` * namespaces: `CatalogBrand` + `@using BlazorShared.Models` → the node whose * qualified name is `BlazorShared.Models::CatalogBrand`. Only resolves when the * `@using` set yields exactly ONE type (otherwise it stays ambiguous and falls * through to name-matching). */ private resolveRazorUsing; /** * Resolve a CFML inheritance reference written as a component path (#1152). * Two forms exist in real code: * * - Dotted: `extends="coldbox.system.web.Controller"` — dots are directory * separators from the webroot or a CFML mapping. Mappings live in server * config / Application.cfc, so the leading segments may not exist in the * repo at all (in the coldbox repo itself the path is `system/web/ * Controller.cfc` — the `coldbox.` root IS the repo). Matched by final * segment (the class), corroborated right-to-left against the candidate's * parent directories. * - Relative: `extends="../base"` / `extends="./base"` (the FW/1 style) — * resolved against the referencing file's own directory. * * Conservative by design: a candidate needs at least one corroborating * directory segment (a dotted path whose only same-named class sits in an * unrelated directory is almost always an out-of-repo library supertype — * mxunit/testbox/coldbox-as-dependency), and a corroboration tie yields no * edge. Directory comparison is case-insensitive (CFML path resolution is); * the class segment itself is matched exactly, which real code satisfies — * dotted paths are written to match the on-disk file name. */ private resolveCfmlComponentPath; /** * Resolve a `this.` function-as-value reference (#756/#808) to the * ENCLOSING CLASS's own member — never a same-named symbol elsewhere. The * registration idiom (`btn.on('click', this.handleClick)`) names a member * of the class being defined, so the only valid target shares the * from-symbol's qualified-name scope. Function/method targets only — a * property (a data field, post-#808 classification) yields no edge — same * file required, no fallback of any kind. */ private resolveThisMemberFnRef; /** * Second pass for `this.` refs whose member wasn't on the enclosing * class itself (#808): once implements/extends edges exist, walk the * class's supertypes (transitively, depth-capped) and resolve the member on * the nearest one that declares it — `this.handleSubmit` registered in a * subclass resolves to `FormBase::handleSubmit`. Validated targets only * (function/method kind, same language family); no match → no edge. * Mirrors resolveChainedCallsViaConformance's lifecycle. Returns the number * of newly-created edges. */ resolveDeferredThisMemberRefs(): Promise; private gateLanguage; /** * Drop a FRAMEWORK-strategy resolution that crosses two *known* language * families for a type-usage (`references`) or import-binding (`imports`) * edge. The framework strategy is intentionally ungated for cross-language * bridges, but those legitimate bridges are either `calls` edges (RN/Expo * JS → native) or config↔code edges whose config side (`yaml`/`blade`/…) is * not a known programming-language family. A `references`/`imports` edge * between two *known* families is always a coincidental name collision — the * React/Svelte/Vue PascalCase component resolvers name-match `getNodesByName` * without a language check, so a TS `` ref happily matched a * Kotlin `class TestRunner`. Gating only the both-known-cross-family case * lets config bridges and `calls` bridges through untouched. */ private gateFrameworkLanguage; } /** * Create a reference resolver instance */ export declare function createResolver(projectRoot: string, queries: QueryBuilder): ReferenceResolver; //# sourceMappingURL=index.d.ts.map