mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-24 11:24:49 +08:00
Three callback-registration shapes deferred from #756/#808, one arc: 1. INHERITED this.X (TS/JS + every this.-routed language): a `this.<member>` registration whose member isn't on the enclosing class defers to a second pass (resolveDeferredThisMemberRefs — in-memory like deferredChainRefs, runs after implements/extends edges persist, same lifecycle as the #750 conformance pass) and resolves up the supertype chain, depth-capped BFS, validated targets only. `bus.on("submit", this.handleSubmit)` in a subclass links to FormBase::handleSubmit; same-named methods on unrelated classes never match. this.-prefixed candidates skip the extraction name gate (an inherited member can't be in definedHere). 2. JAVA/KOTLIN qualified method refs: `Handlers::onMessage` / `OtherClass::handle` emit QUALIFIED names resolved by the scoped suffix-matcher — cross-file capable, gated on the scope name being a same-file type or an imported name (dotted JVM imports now contribute their last segment). `this::m` and `super::m` route through the class-scoped resolver (super rides the supertype pass). References through a VARIABLE (`subscriber::onNext`) deliberately produce nothing — receiver type is unknowable; RxJava's baseline bare capture was resolving these to same-named same-file methods (a test method "registering" an anonymous class's onNext) — the rework drops 18 such wrong edges and keeps the 7 genuine Type::method refs RxJava's main tree actually has. 3. SWIFT enclosing-type scoping (implicit self): bare callback names match methods only of the from-symbol's own type (extension/nested scopes reconciled by suffix), and top-level code never matches methods. Alamofire: −44 wrong edges (parameters like `request`/`data`/`retrier` resolving to same-named methods on unrelated protocols), all verified; the same-class param collision (`task`) remains and is documented. New ResolutionContext.getNodeById lets matchers derive the from-symbol's class scope. Controls: redis/fmt fnref edges byte-identical; excalidraw stable; typeorm +4 genuine inherited-getter dependencies; zero calls edges changed on any of 7 A/B repos; nodes identical everywhere. Kotlin companion-object members extract unqualified (pre-existing) so `Type::companionFn` stays silent rather than guessing — documented. Full suite 1389 passed. EXTRACTION_VERSION 20 → 21 (re-index to benefit). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
244 lines
9.0 KiB
TypeScript
244 lines
9.0 KiB
TypeScript
/**
|
|
* Reference Resolution Types
|
|
*
|
|
* Types for the reference resolution system.
|
|
*/
|
|
|
|
import { Language, Node, ReferenceKind } from '../types';
|
|
|
|
/**
|
|
* An unresolved reference from extraction
|
|
*/
|
|
export interface UnresolvedRef {
|
|
/** ID of the source node containing the reference */
|
|
fromNodeId: string;
|
|
/** The name being referenced */
|
|
referenceName: string;
|
|
/** Type of reference */
|
|
referenceKind: ReferenceKind;
|
|
/** Line where reference occurs */
|
|
line: number;
|
|
/** Column where reference occurs */
|
|
column: number;
|
|
/** File path where reference occurs */
|
|
filePath: string;
|
|
/** Language of the source file */
|
|
language: Language;
|
|
/** Possible qualified names it might resolve to */
|
|
candidates?: string[];
|
|
}
|
|
|
|
/**
|
|
* A resolved reference
|
|
*/
|
|
export interface ResolvedRef {
|
|
/** Original unresolved reference */
|
|
original: UnresolvedRef;
|
|
/** ID of the target node */
|
|
targetNodeId: string;
|
|
/** Confidence score (0-1) */
|
|
confidence: number;
|
|
/** How it was resolved */
|
|
resolvedBy: 'exact-match' | 'import' | 'qualified-name' | 'framework' | 'fuzzy' | 'instance-method' | 'file-path' | 'function-ref';
|
|
}
|
|
|
|
/**
|
|
* Result of resolution attempt
|
|
*/
|
|
export interface ResolutionResult {
|
|
/** Successfully resolved references */
|
|
resolved: ResolvedRef[];
|
|
/** References that couldn't be resolved */
|
|
unresolved: UnresolvedRef[];
|
|
/** Statistics */
|
|
stats: {
|
|
total: number;
|
|
resolved: number;
|
|
unresolved: number;
|
|
byMethod: Record<string, number>;
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Context for resolution - provides access to the graph
|
|
*/
|
|
export interface ResolutionContext {
|
|
/** Get all nodes in a file */
|
|
getNodesInFile(filePath: string): Node[];
|
|
/** Get all nodes by name */
|
|
getNodesByName(name: string): Node[];
|
|
/** Get all nodes by qualified name */
|
|
getNodesByQualifiedName(qualifiedName: string): Node[];
|
|
/** Get all nodes of a kind */
|
|
getNodesByKind(kind: Node['kind']): Node[];
|
|
/** Check if a file exists */
|
|
fileExists(filePath: string): boolean;
|
|
/** Read file content */
|
|
readFile(filePath: string): string | null;
|
|
/** Get project root */
|
|
getProjectRoot(): string;
|
|
/** Get all files */
|
|
getAllFiles(): string[];
|
|
/** Get nodes by lowercase name (O(1) lookup for fuzzy matching) */
|
|
getNodesByLowerName(lowerName: string): Node[];
|
|
/**
|
|
* Direct supertypes of the type named `typeName` (same language): the classes
|
|
* it extends and the interfaces / protocols / traits it implements/conforms to,
|
|
* by simple name. Backed by the resolved `implements`/`extends` edges, so it is
|
|
* EMPTY during the first resolution pass (edges aren't built yet) and populated
|
|
* afterward — the conformance pass uses it to resolve a chained method defined
|
|
* on a supertype the receiver type conforms to (e.g. a protocol-extension
|
|
* method). Optional so external/test contexts compile without it.
|
|
*/
|
|
getSupertypes?(typeName: string, language: Language): string[];
|
|
/**
|
|
* Look up a node by its id. Lets matchers derive the FROM-symbol's
|
|
* enclosing-class scope (Swift implicit-self method scoping, `this.X`
|
|
* member resolution). Optional so external/test contexts compile
|
|
* without it.
|
|
*/
|
|
getNodeById?(id: string): Node | null;
|
|
/** Get cached import mappings for a file */
|
|
getImportMappings(filePath: string, language: Language): ImportMapping[];
|
|
/**
|
|
* Project import-path aliases (tsconfig/jsconfig `paths`). Returns
|
|
* `null` when the project doesn't define any. Cached per resolver
|
|
* instance — safe to call from any resolver code path. Optional so
|
|
* existing test fixtures and external context implementations
|
|
* compile without modification; production resolver implements it.
|
|
*/
|
|
getProjectAliases?(): import('./path-aliases').AliasMap | null;
|
|
/**
|
|
* Go module info from `go.mod` at the project root. Returns `null`
|
|
* when the project has no `go.mod` (non-Go projects, pre-modules
|
|
* Go code, or projects whose modules live in subdirectories). Used
|
|
* by the Go branch of import resolution to distinguish in-module
|
|
* cross-package imports from third-party packages.
|
|
*/
|
|
getGoModule?(): import('./go-module').GoModule | null;
|
|
/**
|
|
* Monorepo workspace member packages, keyed by declared package name.
|
|
* Returns `null` for single-package repos (no `workspaces` field).
|
|
* Lets the resolver treat `@scope/ui/sub` as a local import into the
|
|
* member's directory instead of an external npm package (#629).
|
|
*/
|
|
getWorkspacePackages?(): import('./workspace-packages').WorkspacePackages | null;
|
|
/**
|
|
* Re-exports declared by a file (`export { x } from './other'`,
|
|
* `export * from './other'`). Empty array when the file has none.
|
|
* Optional so older callers compile; the import resolver follows
|
|
* re-export chains when this is provided.
|
|
*/
|
|
getReExports?(filePath: string, language: Language): ReExport[];
|
|
/**
|
|
* List immediate subdirectories of `relativePath` (relative to the
|
|
* project root). Returns an empty array when the path doesn't exist
|
|
* or isn't a directory. Used by framework resolvers that need to
|
|
* walk build-system metadata (e.g. Cargo workspace globs). Optional
|
|
* so external context implementations and test fixtures compile
|
|
* without modification.
|
|
*/
|
|
listDirectories?(relativePath: string): string[];
|
|
/**
|
|
* C/C++ include search directories (relative to project root),
|
|
* extracted from compile_commands.json or discovered by heuristic.
|
|
* Used by resolveCppIncludePath to search -I directories when
|
|
* relative resolution fails. Optional so existing callers compile.
|
|
*/
|
|
getCppIncludeDirs?(): string[];
|
|
}
|
|
|
|
/**
|
|
* Result of framework-specific file extraction.
|
|
*/
|
|
export interface FrameworkExtractionResult {
|
|
/** Framework-specific nodes (e.g. routes) */
|
|
nodes: Node[];
|
|
/** Framework-specific unresolved references (e.g. route -> handler) */
|
|
references: UnresolvedRef[];
|
|
}
|
|
|
|
/**
|
|
* Framework-specific resolver
|
|
*/
|
|
export interface FrameworkResolver {
|
|
/** Framework name */
|
|
name: string;
|
|
/** Languages this framework applies to. If omitted, applies to all languages. */
|
|
languages?: Language[];
|
|
/** Detect if project uses this framework (project-level, called once at startup) */
|
|
detect(context: ResolutionContext): boolean;
|
|
/** Resolve a reference using framework-specific patterns */
|
|
resolve(ref: UnresolvedRef, context: ResolutionContext): ResolvedRef | null;
|
|
/**
|
|
* Opt a reference NAME through the resolver's name-exists pre-filter, even when
|
|
* no node is named that. Needed for dynamic dispatch where the call target is
|
|
* an attribute/descriptor, not a declared symbol (e.g. Django's
|
|
* `self._iterable_class(...)`, React effect callbacks). Returning true lets the
|
|
* ref reach `resolve()` instead of being dropped for having no name match.
|
|
*/
|
|
claimsReference?(name: string): boolean;
|
|
/**
|
|
* Extract framework-specific nodes and references from a file.
|
|
*
|
|
* Returns route nodes, middleware nodes, etc., plus unresolved references
|
|
* that link those nodes to handlers (view classes, controller methods,
|
|
* included modules). Unresolved references flow into the normal resolution
|
|
* pipeline; the framework's own `resolve()` is one of the strategies tried.
|
|
*/
|
|
extract?(filePath: string, content: string): FrameworkExtractionResult;
|
|
/**
|
|
* Cross-file finalization pass, called once after all per-file extraction
|
|
* completes (and again on every incremental sync). Used by frameworks where
|
|
* a symbol's final representation depends on a sibling file the per-file
|
|
* `extract()` never saw — e.g. NestJS's `RouterModule.register([...])`
|
|
* sets route prefixes for controllers declared elsewhere.
|
|
*
|
|
* Implementations return route/etc. nodes with mutated fields (typically
|
|
* `name`); the orchestrator persists each via `updateNode`. The node `id`
|
|
* MUST be preserved so existing edges (route → handler, etc.) stay intact;
|
|
* `qualifiedName` SHOULD be preserved so the pass stays idempotent — a
|
|
* second run can recover the original in-file form from `qualifiedName`.
|
|
*/
|
|
postExtract?(context: ResolutionContext): Node[];
|
|
}
|
|
|
|
/**
|
|
* Import mapping from a file
|
|
*/
|
|
export interface ImportMapping {
|
|
/** Local name used in the file */
|
|
localName: string;
|
|
/** Original exported name (may differ due to aliasing) */
|
|
exportedName: string;
|
|
/** Source module/path */
|
|
source: string;
|
|
/** Whether it's a default import */
|
|
isDefault: boolean;
|
|
/** Whether it's a namespace import (import * as X) */
|
|
isNamespace: boolean;
|
|
/** Resolved file path (if local) */
|
|
resolvedPath?: string;
|
|
}
|
|
|
|
/**
|
|
* Re-export from a file: `export { x } from './other'` or
|
|
* `export * from './other'`. Used by the resolver to chase
|
|
* symbols through barrel files.
|
|
*/
|
|
export type ReExport =
|
|
| {
|
|
kind: 'named';
|
|
/** Name as exported by THIS file. */
|
|
exportedName: string;
|
|
/** Name in the upstream module (differs when renamed: `as`). */
|
|
originalName: string;
|
|
/** Module specifier of the upstream module. */
|
|
source: string;
|
|
}
|
|
| {
|
|
kind: 'wildcard';
|
|
/** Module specifier of the upstream module. */
|
|
source: string;
|
|
};
|