mirror of
https://github.com/colbymchenry/codegraph.git
synced 2026-10-23 10:21:40 +08:00
c0f45c7d1c
A C++ call made with `->` on a receiver the calling function or its class declares as a `std::unique_ptr<T>`, `std::shared_ptr<T>` or `std::optional<T>` now resolves on `T`: on the class C++ name lookup finds for it where the call is written, or on a class that class derives from. Before, receiver inference read the type as `unique_ptr`, found no method, and the call fell through to the guess by method name: rocksdb's `std::unique_ptr<Iterator> iter; iter->Valid()` reached `ArenaWrappedDBIter::Valid`. The held type goes through #2407's lookup (`cppClassWritten`, extracted from `matchCppSupertype`), not `resolveMethodOnType` on its last name: rocksdb has 22 classes named `Iterator`, and the one the calls mean inherits its methods from `IteratorBase`, so a last-name lookup reaches `MemTableRep::Iterator`. The method is taken from that class or its bases through the class's own extends edges (`getSupertypeNodes`). A template parameter, a library type or an alias of one gets no edge; a type the project doesn't declare, or an alias the lookup can't follow, leaves the call as it was. The namespace-macro helpers move from name-matcher.ts to cpp-namespaces.ts unchanged, so name-matcher can import cpp-supertypes without a cycle. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
504 lines
22 KiB
TypeScript
504 lines
22 KiB
TypeScript
/**
|
||
* Reference Resolution Types
|
||
*
|
||
* Types for the reference resolution system.
|
||
*/
|
||
|
||
import { EdgeKind, 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[];
|
||
/** `unresolved_refs.id` when loaded from the database — post-pass cleanup
|
||
* targets exactly this row instead of every same-key sibling (#1269). */
|
||
rowId?: number;
|
||
/** The tail a ref that failed to resolve is parked under, when it is not
|
||
* the one its name gives (see `importBindingTail`). */
|
||
nameTail?: 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';
|
||
/**
|
||
* Edge kind the edge should carry when it is NOT the ref's own kind — a
|
||
* framework that turns a `calls` ref into a `navigates` edge, for example.
|
||
* The original kind is still recorded on the edge as `metadata.refKind`, so
|
||
* re-resolution after a target is removed reconstructs the ref faithfully.
|
||
*/
|
||
edgeKind?: EdgeKind;
|
||
/** Extra metadata the strategy wants persisted on the edge (`href`, …). */
|
||
metadata?: Record<string, unknown>;
|
||
/**
|
||
* The OTHER targets, when one reference names several.
|
||
*
|
||
* A navigation whose destination is a conditional reaches every arm —
|
||
* `!isAdmin ? keyword ? '/search/…' : '/page/…' : '/admin/…'` is one call
|
||
* and three screens — and drawing only the first would hide two places the
|
||
* code goes. `createEdges` fans these out into an edge apiece, sharing this
|
||
* resolution's kind and confidence; each carries its own metadata.
|
||
*
|
||
* The reference itself still resolves ONCE, so the resolution pipeline's
|
||
* bookkeeping — cleanup by row id, counts, re-resolution — is unchanged.
|
||
*/
|
||
alsoTargets?: { targetNodeId: string; metadata?: Record<string, unknown> }[];
|
||
}
|
||
|
||
/**
|
||
* 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[];
|
||
/** Whether any node in the file is exported (`getNodesInFile(f).some(n => n.isExported)`), as one indexed probe. */
|
||
fileHasExportedNode?(filePath: string): boolean;
|
||
/** `getNodesInFile(f).filter(n => n.isExported)`, without decoding the rest of the file. */
|
||
getExportedNodesInFile?(filePath: string): Node[];
|
||
/** `getNodesInFile(f).filter(n => n.name === name)`, without decoding the rest of the file. */
|
||
getNodesInFileNamed?(filePath: string, name: 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[];
|
||
/**
|
||
* Stream nodes of a kind one at a time instead of materializing (and, unlike
|
||
* `getNodesByKind`, without populating the resolver's per-kind array cache).
|
||
* For unbounded kinds (`function`, `method`, `struct`) on a symbol-dense
|
||
* project the full array is gigabytes — the dynamic-edge synthesizers must
|
||
* use this so their memory stays O(1) in node count (#610, #1212). Optional
|
||
* so minimal test contexts compile; callers fall back to getNodesByKind.
|
||
*/
|
||
iterateNodesByKind?(kind: Node['kind']): IterableIterator<Node>;
|
||
/** Check if a file exists */
|
||
fileExists(filePath: string): boolean;
|
||
/** Read file content */
|
||
readFile(filePath: string): string | null;
|
||
/** `readFile(filePath)?.includes(needle) ?? false` for an ASCII `needle`, without decoding a file that lacks it. */
|
||
fileContains?(filePath: string, needle: string): boolean;
|
||
/**
|
||
* `readFile(filePath)` split into lines, LRU-cached per file. Receiver-type
|
||
* inference scans source lines for EVERY `receiver.method()` ref; splitting
|
||
* the whole file per ref made that O(refs-in-file × file-size) — ~20% of
|
||
* total index CPU on a Java-heavy repo and a driver of the #1122 watchdog
|
||
* kill on large ones. Optional so external/test contexts compile without it;
|
||
* callers fall back to splitting `readFile` themselves.
|
||
*/
|
||
getFileLines?(filePath: string): string[] | null;
|
||
/**
|
||
* The method-definition nodes matching `typeName::methodName` in the language family —
|
||
* exactly `resolveMethodOnType`'s kind/language/qualifiedName-suffix filter,
|
||
* LRU-cached per (language, type, method). The uncached path re-fetches every
|
||
* node sharing the METHOD name (unbounded — tens of thousands on a collision-
|
||
* heavy Java repo) and re-scans it per ref, the dominant term in the #1122
|
||
* watchdog kill. Cached entries hold only the small filtered result; per-ref
|
||
* disambiguation (import FQN, call-site file) stays in the caller so a cached
|
||
* entry is valid from any call site. Optional for external/test contexts.
|
||
*/
|
||
getMethodMatches?(typeName: string, methodName: string, language: Language): Node[];
|
||
/** 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[];
|
||
/**
|
||
* The nodes one type declaration extends or implements, through its own
|
||
* resolved `implements`/`extends` edges: where getSupertypes unions those of
|
||
* every type sharing a simple name, this is the supertypes of the node
|
||
* `id`. Empty during the first resolution pass, as getSupertypes is.
|
||
* Optional so external/test contexts compile without it.
|
||
*/
|
||
getSupertypeNodes?(id: string): Node[];
|
||
/**
|
||
* 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[];
|
||
/** Import lookup supplied by the coordinator, keeping name matching from
|
||
* importing the import resolver (which itself uses name-matching helpers).
|
||
* Minimal contexts without import resolution may omit this capability. */
|
||
resolveImport?(ref: UnresolvedRef): ResolvedRef | null;
|
||
/**
|
||
* Whether an import specifier, written in `fromFile`, names a module outside
|
||
* the repository: a package that is not a relative path, a tsconfig alias, a
|
||
* workspace member or the repository's own package name, and that resolves
|
||
* to no project file. Supplied by the coordinator, like `resolveImport`.
|
||
*/
|
||
isOutOfRepoImport?(source: string, fromFile: string, language: Language): boolean;
|
||
/**
|
||
* 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;
|
||
/**
|
||
* The aliases of the tsconfig / jsconfig nearest `fromFile` below the
|
||
* project root that declares `paths` — a monorepo app's own `@/*` — or null.
|
||
*/
|
||
getNearestAliases?(fromFile: string): import('./path-aliases').AliasMap | null;
|
||
/**
|
||
* The project-relative directory (`/`-separated, `.` for the root) of
|
||
* the Go package an import path names, when it is a package of one of the
|
||
* project's own modules — the `go.mod` at the root or any `go.mod` above
|
||
* an indexed `.go` file (#2322) — else `null` (the standard library,
|
||
* third-party modules, a project with no `go.mod`). `fromFile`, the
|
||
* importing file, breaks a tie between two modules declaring one path.
|
||
* Used by the Go branch of resolution to tell in-project cross-package
|
||
* imports from outside ones, and to find the package's files.
|
||
*/
|
||
getGoPackageDir?(importPath: string, fromFile?: string): string | 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[];
|
||
/**
|
||
* The import node of every C / C++ `#include`, narrowed to the path it
|
||
* spells and where it is written: what ./cpp-includers builds the include
|
||
* graph from. Optional so minimal contexts compile; it falls back to
|
||
* reading the import nodes themselves.
|
||
*/
|
||
getCppIncludeNodes?(): Array<Pick<Node, 'id' | 'name' | 'filePath' | 'language' | 'startLine' | 'startColumn'>>;
|
||
}
|
||
|
||
/**
|
||
* 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[];
|
||
}
|
||
|
||
/**
|
||
* Nodes a framework can name only from several files at once, with the
|
||
* references that bind each — and how to recognise the ones an earlier run
|
||
* produced, so a run can remove those it no longer wants.
|
||
*/
|
||
export interface CrossFileNodes extends FrameworkExtractionResult {
|
||
/** The kind every one of these nodes has. */
|
||
kind: Node['kind'];
|
||
/** True for a node this pass produces, and for no node extraction does. */
|
||
owns(node: Node): boolean;
|
||
}
|
||
|
||
/**
|
||
* The navigation calls a router binds to its routes — `navigate('/login')`,
|
||
* `router.push('/x')` — described so a sync can find the ones a changed route
|
||
* may answer (see `FrameworkResolver.navigation`).
|
||
*/
|
||
export interface NavigationCalls {
|
||
/**
|
||
* The name tails of the calls `claimsReference` accepts as navigation —
|
||
* `push` for `history.push` — which find them through the failed-tail
|
||
* index; `claimsReference` then decides on the whole name.
|
||
*/
|
||
tails: readonly string[];
|
||
/**
|
||
* The files whose navigation calls can name `route`: the path prefixes of
|
||
* the apps whose table it is in (`''` for every file), or null when it is
|
||
* not one of this router's routes.
|
||
*/
|
||
scope(route: Node, context: ResolutionContext): readonly string[] | null;
|
||
}
|
||
|
||
/**
|
||
* Framework-specific resolver
|
||
*/
|
||
export interface FrameworkResolver {
|
||
/** Framework name */
|
||
name: string;
|
||
/**
|
||
* Languages this framework applies to: `extract()` runs only on files in
|
||
* them, and `resolve()` sees only references written in them (unless
|
||
* `resolveLanguages` says otherwise). If omitted, applies to all languages.
|
||
*/
|
||
languages?: Language[];
|
||
/**
|
||
* The languages whose references `resolve()` sees, when they are not
|
||
* `languages`. For a resolver that reads references in languages it
|
||
* extracts nothing from: SvelteKit's `$lib/…` imports and Svelte 5 runes
|
||
* are written in `.ts` / `.js` modules too, but only a `.svelte` file holds
|
||
* a route. Widening `languages` instead would run `extract()` on those
|
||
* files.
|
||
*/
|
||
resolveLanguages?: readonly Language[];
|
||
/**
|
||
* Packages an app declares when it is built on this framework. When set,
|
||
* `extract()` runs only on files of an app whose package.json — the file's
|
||
* own or an enclosing one — declares one of them: in a monorepo with an Expo
|
||
* app beside a Next.js app, Expo Router must not read the Next app's
|
||
* `app/layout.tsx` as a `/layout` screen. When no manifest in the project
|
||
* declares any, detection found the framework by other evidence and the
|
||
* extractor runs on every file, as before.
|
||
*/
|
||
appDependencies?: readonly string[];
|
||
/** 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.
|
||
* Asked only about references written in the languages `resolve()` sees.
|
||
*/
|
||
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[];
|
||
/**
|
||
* Nodes no single file's `extract()` can decide on — a React Router route
|
||
* table written in one file is a table of routes only because another
|
||
* file hands it to the router. Called after `postExtract` on every index
|
||
* and every sync, it returns the COMPLETE set the framework wants now. The
|
||
* orchestrator inserts the new ones (their references pending, for the
|
||
* resolution that follows), removes the ones an earlier run inserted that
|
||
* are no longer wanted, renames the ones whose name changed, and leaves
|
||
* the rest alone, so an unchanged node keeps its edges. A node lives in the
|
||
* file it is written in, so re-extracting that file drops it until the
|
||
* next run puts it back.
|
||
*/
|
||
crossFileNodes?(context: ResolutionContext): CrossFileNodes;
|
||
/**
|
||
* Set by a router whose `resolve()` binds a navigation call to one of its
|
||
* route nodes. A call whose route did not exist yet was parked as failed, or
|
||
* bound to whatever answered it then (a catch-all, a parameter route), and
|
||
* the retry that matches a failed ref's name tail against the names a sync
|
||
* adds (#1240) never finds it: its name is the router's method, never the
|
||
* route's path. So a sync that adds, removes or renames a route puts the
|
||
* calls this describes back for its resolution sweep.
|
||
*/
|
||
navigation?: NavigationCalls;
|
||
/**
|
||
* The modules whose content `resolve()` reads to answer `ref`, a route
|
||
* node's reference — none for most. A React route that renders a same-file
|
||
* `const Docs = lazy(() => import('./pages/Docs'))` binds to the component
|
||
* that module exports, through any barrel that forwards it, and to the
|
||
* declaration while the module is missing or exports none: the answer
|
||
* changes with those files although no name the reference carries does, so
|
||
* a sync that adds or changes one of them resolves the reference again.
|
||
*/
|
||
lazyModules?(ref: UnresolvedRef, context: ResolutionContext): readonly string[];
|
||
}
|
||
|
||
/**
|
||
* 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;
|
||
}
|
||
| {
|
||
/** `export * as ns from './other'`: only `ns` is exported, the module's members through it. */
|
||
kind: 'namespace';
|
||
exportedName: string;
|
||
source: string;
|
||
};
|
||
|
||
/**
|
||
* Node kinds an `extends`/`implements` edge may legally TARGET — the things a
|
||
* type can actually inherit from or conform to.
|
||
*
|
||
* Kept deliberately wide: `type_alias` because TS `class X implements
|
||
* SomeAliasedObjectType` is valid, `component` because a framework component
|
||
* node stands in for a class, and `module`/`namespace` because whole
|
||
* languages inherit from one — Ruby `include Trackable` targets a `module`,
|
||
* Erlang `-behaviour(gen_server)` targets the behaviour module, which Erlang
|
||
* extraction indexes as a `namespace` (the conformance pass in
|
||
* `resolution/index.ts` makes the same `module` allowance).
|
||
*
|
||
* Everything omitted (`enum_member`, `method`, `field`, `property`,
|
||
* `variable`, `constant`, `function`, `parameter`, `import`, `export`,
|
||
* `file`, `route`) can never be a supertype in any supported language, so an
|
||
* inheritance edge pointing at one is false data.
|
||
*
|
||
* Why this is needed: the name-matcher scores node kind as a BONUS,
|
||
* never a filter, and awards no bonus at all for inheritance refs — so a
|
||
* same-named non-type outranked (or, as the sole candidate, was adopted
|
||
* outright as) the real supertype. Rust `use std::error::Error;` + `impl Error
|
||
* for MapperError {}` bound to the local `MapperError::Error` VARIANT. The
|
||
* supertype is out-of-repo and simply unresolvable; a failed ref is correct.
|
||
*/
|
||
export const SUPERTYPE_TARGET_KINDS = new Set<Node['kind']>([
|
||
'class', 'struct', 'interface', 'trait', 'protocol', 'enum', 'union',
|
||
'type_alias', 'component', 'module', 'namespace',
|
||
]);
|
||
|
||
/** Scala singleton objects are values, unlike inheritable Ruby modules. */
|
||
export function isSupertypeTarget(node: Node): boolean {
|
||
return SUPERTYPE_TARGET_KINDS.has(node.kind) &&
|
||
!(node.language === 'scala' && node.kind === 'module');
|
||
}
|
||
|
||
/** True for the reference kinds that assert an inheritance/conformance relation. */
|
||
export function isInheritanceRef(ref: UnresolvedRef): boolean {
|
||
return ref.referenceKind === 'extends' || ref.referenceKind === 'implements';
|
||
}
|
||
|
||
/**
|
||
* Node kinds an `imports` edge may never TARGET: members that only exist
|
||
* INSIDE a type. No language lets you import a class's property, an
|
||
* interface's method or an enum's variant — you import the type that
|
||
* contains it. The name-matcher has no kind filter, so a bare
|
||
* `import path from 'node:path'` (unresolvable, since the module is external)
|
||
* name-matched an interface property called `path` in an unrelated file.
|
||
*/
|
||
const NON_IMPORTABLE_KINDS = new Set<Node['kind']>([
|
||
'property', 'field', 'method', 'enum_member', 'parameter',
|
||
]);
|
||
|
||
/** Can an `imports` reference legally resolve to this node kind? */
|
||
export function isImportableKind(kind: Node['kind']): boolean {
|
||
return !NON_IMPORTABLE_KINDS.has(kind);
|
||
}
|
||
|
||
/**
|
||
* The signature extraction gives a C/C++ `constant` minted from a
|
||
* function-like `preproc_function_def` (`#define NAME(args) …`, #1838). A
|
||
* macro is a value: it is never a `calls` target, and its presence in a
|
||
* translation unit is what makes `NAME(x)` a macro expansion rather than a
|
||
* call.
|
||
*/
|
||
export const CPP_DEFINE_SIGNATURE = /^\s*#\s*define\b/;
|