refactor(scope-resolution): rename emit-core/ → scope-resolution/, EmitProvider → ScopeResolver

Reorganizes the registry-primary resolution layer for clarity and
contributor onboarding. Driven by feedback that "emit" was triple-
overloaded (graph-edge emission + tree-sitter capture extraction +
the provider name itself), and the flat 16-file emit-core/ folder
mixed five concerns.

External research (rust-analyzer hir-def/nameres, Pyright analyzer/,
TypeScript binder/checker, Roslyn Binder, IntelliJ Resolver, swc
semantic/, biome semantic/, semgrep naming/, JDT Binding, clangd
Sema) consistently uses **the phase name** for this layer, never an
output verb. "Scope resolution" matches our pipeline-phase name, the
plan, and the RFC.

## Folder rename

  emit-core/                              → scope-resolution/
  ├── (16 flat files)                     → ├── contract/scope-resolver.ts
                                            ├── pipeline/{run,registry,phase}.ts
                                            ├── passes/{receiver-bound-calls,
                                            │           free-call-fallback,
                                            │           compound-receiver,
                                            │           imported-return-types,
                                            │           mro}.ts
                                            ├── graph-bridge/{node-lookup,ids,
                                            │                 edges,references-to-edges,
                                            │                 imports-to-edges,
                                            │                 method-dispatch}.ts
                                            └── scope/{walkers,namespace-targets}.ts

Each subfolder maps to one concern a new contributor needs to find:
*the contract I implement / the runner that calls me / the helpers I
reuse / the graph layer I shouldn't touch / the scope walkers*.

## Symbol renames

  EmitProvider                  → ScopeResolver
  pythonEmitProvider            → pythonScopeResolver
  runPythonScopeResolution      → resolvePythonScope
  EMIT_PROVIDERS                → SCOPE_RESOLVERS
  getEmitProvider               → getScopeResolver
  RunPythonScopeResolution{Input,Stats} → ResolvePythonScope{Input,Stats}

## File renames (per-language)

  languages/python/emit/index.ts → languages/python/scope-resolver.ts
  languages/python/emit-captures.ts → languages/python/captures.ts
                                     (kills the parse-side "emit" collision)

## Mechanics

- Used `git mv` for all files so blame history is preserved.
- Updated ~30 import lines across 18 files plus the pipeline-phases
  barrel and pipeline.ts.
- Updated JSDoc cross-references throughout to match the new vocabulary.

Verification:
- REGISTRY_PRIMARY_PYTHON=0 (legacy): 191/191.
- REGISTRY_PRIMARY_PYTHON=1 (registry): 191/191.
- Default (post MIGRATED_LANGUAGES flip): 191/191.
- tsc --noEmit clean.

Migration cookbook in `scope-resolution/contract/scope-resolver.ts`
JSDoc points the next-language porter at all the new names and
folder locations.
This commit is contained in:
Gergo Magyar 2026-04-20 13:56:05 +01:00
parent cbc941461c
commit c3d0f0ae92
25 changed files with 207 additions and 186 deletions

View file

@ -0,0 +1 @@
{"sessionId":"67109eaf-0d84-41ff-9cdf-1cd9715e8283","pid":2680,"acquiredAt":1776588137183}

View file

@ -1,57 +0,0 @@
/**
* `emit-core/` — language-agnostic graph-feeding primitives for the
* registry-primary scope-resolution pipeline.
*
* Responsibility boundary:
* - `gitnexus-shared/src/scope-resolution/` owns the semantic model
* (indexes, ParsedFile, registries, TypeRef, Reference).
* - `emit-core/` owns the bridge from that model to the legacy
* `KnowledgeGraph` edge format. Lives here (CLI, not shared)
* because graph bridging depends on `KnowledgeGraph` + `generateId`
* which are CLI-local.
* - `languages/<lang>/emit/` owns per-language post-passes that
* compose these primitives with language-specific captures and
* strategies (MRO, ownership, receiver conventions).
*
* Next-consumer contract: when the next language provider migrates
* (TypeScript #927, JavaScript #928, etc.), it imports from this
* module's public surface and never re-implements any of these
* functions. See the per-file JSDoc for per-function reuse notes.
*/
export {
buildGraphNodeLookup,
isLinkableLabel,
type GraphNodeLookup,
} from './graph-node-lookup.js';
export { resolveCallerGraphId, resolveDefGraphId, simpleQualifiedName } from './graph-id.js';
export { mapReferenceKindToEdgeType, tryEmitEdge } from './emit-edge.js';
export { emitReferencesViaLookup } from './emit-references.js';
export { emitImportEdges } from './emit-imports.js';
export {
findReceiverTypeBinding,
findClassBindingInScope,
findCallableBindingInScope,
findEnclosingClassDef,
findExportedDefByName,
findOwnedMember,
findExportedDef,
populateClassOwnedMembers,
} from './scope-walkers.js';
export { emitFreeCallFallback } from './emit-free-call.js';
export { emitReceiverBoundCalls } from './emit-receiver-bound.js';
export {
matchingOpenParen,
resolveCompoundReceiverClass,
type ResolveCompoundReceiverOptions,
} from './emit-compound-receiver.js';
export { followChainPostFinalize, propagateImportedReturnTypes } from './propagate-return-types.js';
export { collectNamespaceTargets } from './namespace-targets.js';
export { buildPopulatedMethodDispatch } from './method-dispatch-bridge.js';
export type { ArityVerdict, EmitProvider, LinearizeStrategy } from './emit-provider.js';
export { buildMro, defaultLinearize } from './build-mro.js';
export {
runScopeResolution,
type RunScopeResolutionInput,
type RunScopeResolutionStats,
} from './orchestrator.js';

View file

@ -1,27 +0,0 @@
/**
* Per-language `EmitProvider` registry — the lookup the generic
* `scopeResolutionPhase` uses to pick the right provider for each
* migrated language.
*
* Adding a language is two lines: implement an `EmitProvider` in
* `languages/<lang>/emit/index.ts` and register it here. The phase
* picks it up automatically — no workflow changes, no per-language
* pipeline phase file.
*/
import { SupportedLanguages } from 'gitnexus-shared';
import type { EmitProvider } from './emit-core/emit-provider.js';
import { pythonEmitProvider } from './languages/python/emit/index.js';
/** Map of `SupportedLanguages` → `EmitProvider`. The phase iterates
* this map intersected with `MIGRATED_LANGUAGES` (the per-language
* flag set) so adding a provider here without flipping the flag is
* safe — the provider sits idle until the language is migrated. */
export const EMIT_PROVIDERS: ReadonlyMap<SupportedLanguages, EmitProvider> = new Map<
SupportedLanguages,
EmitProvider
>([[SupportedLanguages.Python, pythonEmitProvider]]);
export function getEmitProvider(lang: SupportedLanguages): EmitProvider | undefined {
return EMIT_PROVIDERS.get(lang);
}

View file

@ -12,7 +12,7 @@
* - `ast-utils.ts` — generic `SyntaxNode` helpers
* - `import-decomposer.ts` — `import a, b` / `from m import x, y` → one match per name
* - `receiver-binding.ts` — synthesize `self`/`cls` type bindings on methods
* - `emit-captures.ts` — `emitPythonScopeCaptures` (top-level orchestrator)
* - `captures.ts` — `emitPythonScopeCaptures` (top-level orchestrator)
* - `interpret.ts` — capture-match → `ParsedImport` / `ParsedTypeBinding`
* - `merge-bindings.ts` — Python LEGB precedence
* - `arity.ts` — Python arity check (`*args`, `**kwargs`, defaults)
@ -21,7 +21,7 @@
*/
export { PYTHON_SCOPE_QUERY } from './query.js';
export { emitPythonScopeCaptures } from './emit-captures.js';
export { emitPythonScopeCaptures } from './captures.js';
export { interpretPythonImport, interpretPythonTypeBinding } from './interpret.js';
export { pythonMergeBindings } from './merge-bindings.js';
export { pythonArityCompatibility } from './arity.js';

View file

@ -1,14 +1,14 @@
/**
* Python `EmitProvider` and the `runPythonScopeResolution` entry point.
* Python `ScopeResolver` and the `resolvePythonScope` entry point.
*
* The provider is a thin wiring object — Python's specific bits
* (super recognizer, LEGB merge precedence, Python's relative-import
* resolver, the simplified MRO walk) plug into the generic
* `runScopeResolution` orchestrator from `emit-core/`.
* `runScopeResolution` orchestrator from `scope-resolution/`.
*
* Migration reference: when bringing up the next language
* (TypeScript / Java / Kotlin / Ruby), copy this file's structure —
* implement the 6 required `EmitProvider` fields, optionally toggle
* implement the 6 required `ScopeResolver` fields, optionally toggle
* the 2 booleans, and call `runScopeResolution(input, provider)`.
*/
@ -19,19 +19,19 @@ import {
defaultLinearize,
populateClassOwnedMembers,
runScopeResolution,
type EmitProvider,
type ScopeResolver,
type RunScopeResolutionInput,
type RunScopeResolutionStats,
} from '../../../emit-core/index.js';
import { pythonProvider } from '../../python.js';
} from '../../scope-resolution/index.js';
import { pythonProvider } from '../python.js';
import {
pythonArityCompatibility,
pythonMergeBindings,
resolvePythonImportTarget,
type PythonResolveContext,
} from '../index.js';
} from './index.js';
const pythonEmitProvider: EmitProvider = {
const pythonScopeResolver: ScopeResolver = {
language: SupportedLanguages.Python,
languageProvider: pythonProvider,
importEdgeReason: 'python-scope: import',
@ -77,13 +77,11 @@ const pythonEmitProvider: EmitProvider = {
propagatesReturnTypesAcrossImports: true,
};
export { pythonEmitProvider };
export { pythonScopeResolver };
export interface RunPythonScopeResolutionInput extends RunScopeResolutionInput {}
export interface RunPythonScopeResolutionStats extends RunScopeResolutionStats {}
export interface ResolvePythonScopeInput extends RunScopeResolutionInput {}
export interface ResolvePythonScopeStats extends RunScopeResolutionStats {}
export function runPythonScopeResolution(
input: RunPythonScopeResolutionInput,
): RunPythonScopeResolutionStats {
return runScopeResolution(input, pythonEmitProvider);
export function resolvePythonScope(input: ResolvePythonScopeInput): ResolvePythonScopeStats {
return runScopeResolution(input, pythonScopeResolver);
}

View file

@ -16,7 +16,10 @@ export { routesPhase, type RoutesOutput, type RouteEntry } from './routes.js';
export { toolsPhase, type ToolsOutput, type ToolDef } from './tools.js';
export { ormPhase, type ORMOutput } from './orm.js';
export { crossFilePhase, type CrossFileOutput } from './cross-file.js';
export { scopeResolutionPhase, type ScopeResolutionOutput } from './scope-resolution.js';
export {
scopeResolutionPhase,
type ScopeResolutionOutput,
} from '../scope-resolution/pipeline/phase.js';
export { mroPhase, type MROOutput } from './mro.js';
export { communitiesPhase, type CommunitiesOutput } from './communities.js';
export { processesPhase, type ProcessesOutput } from './processes.js';

View file

@ -1,20 +1,20 @@
/**
* `EmitProvider` — the per-language contract consumed by the generic
* `ScopeResolver` — the per-language contract consumed by the generic
* scope-resolution orchestrator (`runScopeResolution`).
*
* ## Migration cookbook (next language)
*
* To add a language to the registry-primary path:
*
* 1. Implement `EmitProvider` in
* `gitnexus/src/core/ingestion/languages/<lang>/emit/index.ts`.
* 1. Implement `ScopeResolver` in
* `gitnexus/src/core/ingestion/languages/<lang>/scope-resolver.ts`.
* Six required fields (language, languageProvider,
* importEdgeReason, resolveImportTarget, mergeBindings,
* arityCompatibility, buildMro, populateOwners, isSuperReceiver)
* plus two optional booleans (propagatesReturnTypesAcrossImports,
* fieldFallbackOnMethodLookup).
* 2. Export a thin entry point:
* `runYourLangScopeResolution(input) = runScopeResolution(input, yourEmitProvider)`.
* `runYourLangScopeResolution(input) = runScopeResolution(input, yourScopeResolver)`.
* 3. Register the provider in
* `gitnexus/src/core/ingestion/emit-providers-registry.ts`.
* 4. Add `SupportedLanguages.YourLang` to `MIGRATED_LANGUAGES` in
@ -28,7 +28,7 @@
* change. The generic `scopeResolutionPhase` and the CI parity
* workflow auto-discover everything via `MIGRATED_LANGUAGES`.
*
* ## EmitProvider vs LanguageProvider
* ## ScopeResolver vs LanguageProvider
*
* The codebase has two provider contracts. Their lifecycles differ:
*
@ -37,7 +37,7 @@
* scopes, interpret imports / typeBindings. ~40 fields covering
* both legacy and new pipelines. Consumed by `ScopeExtractor`,
* once per file at extract time.
* - `EmitProvider` (this file) is the **emit-side** contract — how
* - `ScopeResolver` (this file) is the **emit-side** contract — how
* the resolution pipeline dispatches references to graph edges.
* 8 fields total. Consumed by `runScopeResolution`, once per
* workspace at resolve time.
@ -51,8 +51,8 @@
*
* ## Reference implementation
*
* `gitnexus/src/core/ingestion/languages/python/emit/index.ts` —
* `pythonEmitProvider` is the canonical example. Read that file when
* `gitnexus/src/core/ingestion/languages/python/scope-resolver.ts` —
* `pythonScopeResolver` is the canonical example. Read that file when
* migrating a new language; this interface lists the fields that
* implementation populates.
*
@ -81,9 +81,9 @@ import type {
SupportedLanguages,
SymbolDefinition,
} from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import type { GraphNodeLookup } from './graph-node-lookup.js';
import { LanguageProvider } from '../language-provider.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
import { LanguageProvider } from '../../language-provider.js';
/** A LinearizeStrategy receives the full ancestor map so C3-style
* algorithms (which need to merge each parent's MRO) can implement
@ -95,10 +95,10 @@ export type LinearizeStrategy = (
parentsByDefId: ReadonlyMap<string, readonly string[]>,
) => string[];
/** Result of `EmitProvider.arityCompatibility` — mirrors `RegistryProviders.arityCompatibility`. */
/** Result of `ScopeResolver.arityCompatibility` — mirrors `RegistryProviders.arityCompatibility`. */
export type ArityVerdict = 'compatible' | 'unknown' | 'incompatible';
export interface EmitProvider {
export interface ScopeResolver {
/** Identity for telemetry + per-language flag check. */
readonly language: SupportedLanguages;

View file

@ -16,10 +16,10 @@
*/
import type { Reference, ScopeId, SymbolDefinition } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import type { GraphNodeLookup } from './graph-node-lookup.js';
import { resolveCallerGraphId, resolveDefGraphId } from './graph-id.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
import { resolveCallerGraphId, resolveDefGraphId } from '../graph-bridge/ids.js';
/**
* Map a `Reference.kind` to a graph edge type. `import-use` is dropped

View file

@ -18,9 +18,9 @@
*/
import type { ScopeId, SymbolDefinition } from 'gitnexus-shared';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import { generateId } from '../../../lib/utils.js';
import { isLinkableLabel, type GraphNodeLookup } from './graph-node-lookup.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import { generateId } from '../../../../lib/utils.js';
import { isLinkableLabel, type GraphNodeLookup } from '../graph-bridge/node-lookup.js';
/** Look up a `SymbolDefinition` in the graph node lookup by file+name. */
export function resolveDefGraphId(

View file

@ -13,9 +13,9 @@
*/
import type { ImportEdge, ScopeId } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import { generateId } from '../../../lib/utils.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import { generateId } from '../../../../lib/utils.js';
export function emitImportEdges(
graph: KnowledgeGraph,

View file

@ -13,7 +13,7 @@
*/
import type { NodeLabel } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
export type GraphNodeLookup = ReadonlyMap<string, string>;

View file

@ -19,11 +19,11 @@
*/
import type { Reference, ScopeId } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import { resolveCallerGraphId, resolveDefGraphId } from './graph-id.js';
import { mapReferenceKindToEdgeType } from './emit-edge.js';
import type { GraphNodeLookup } from './graph-node-lookup.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import { resolveCallerGraphId, resolveDefGraphId } from '../graph-bridge/ids.js';
import { mapReferenceKindToEdgeType } from '../graph-bridge/edges.js';
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
/**
* Optional opaque skip key — providers may pre-emit edges (e.g. via

View file

@ -0,0 +1,76 @@
/**
* `scope-resolution/` — registry-primary call/reference resolution.
*
* Public surface, grouped by concern. New language migrations should
* read `contract/scope-resolver.ts` first — it lists every hook the
* generic pipeline needs from a per-language adapter.
*
* Folder layout:
* - `contract/` — `ScopeResolver` interface + shared types
* - `pipeline/` — orchestrator, registry, pipeline-phase wrapper
* - `passes/` — reference-resolution passes (receiver-bound,
* free-call fallback, compound-receiver, MRO,
* cross-file return-type propagation)
* - `graph-bridge/` — CLI-local layer that translates resolved
* references into `KnowledgeGraph` edges
* - `scope/` — generic scope-chain walkers + namespace targets
*
* The `scope/` and `passes/` content is pure logic with no graph
* dependency; if a future consumer (gitnexus-web) needs the resolver
* without the graph, those two folders are the natural promotion path
* to `gitnexus-shared/`.
*/
// ── Contract ──────────────────────────────────────────────────────────────
export type { ArityVerdict, LinearizeStrategy, ScopeResolver } from './contract/scope-resolver.js';
// ── Pipeline ──────────────────────────────────────────────────────────────
export {
runScopeResolution,
type RunScopeResolutionInput,
type RunScopeResolutionStats,
} from './pipeline/run.js';
export { SCOPE_RESOLVERS, getScopeResolver } from './pipeline/registry.js';
// ── Passes ────────────────────────────────────────────────────────────────
export { emitReceiverBoundCalls } from './passes/receiver-bound-calls.js';
export { emitFreeCallFallback } from './passes/free-call-fallback.js';
export {
matchingOpenParen,
resolveCompoundReceiverClass,
type ResolveCompoundReceiverOptions,
} from './passes/compound-receiver.js';
export {
followChainPostFinalize,
propagateImportedReturnTypes,
} from './passes/imported-return-types.js';
export { buildMro, defaultLinearize } from './passes/mro.js';
// ── Graph bridge (CLI-local) ──────────────────────────────────────────────
export {
buildGraphNodeLookup,
isLinkableLabel,
type GraphNodeLookup,
} from './graph-bridge/node-lookup.js';
export {
resolveCallerGraphId,
resolveDefGraphId,
simpleQualifiedName,
} from './graph-bridge/ids.js';
export { mapReferenceKindToEdgeType, tryEmitEdge } from './graph-bridge/edges.js';
export { emitReferencesViaLookup } from './graph-bridge/references-to-edges.js';
export { emitImportEdges } from './graph-bridge/imports-to-edges.js';
export { buildPopulatedMethodDispatch } from './graph-bridge/method-dispatch.js';
// ── Scope walkers ─────────────────────────────────────────────────────────
export {
findReceiverTypeBinding,
findClassBindingInScope,
findCallableBindingInScope,
findEnclosingClassDef,
findExportedDefByName,
findOwnedMember,
findExportedDef,
populateClassOwnedMembers,
} from './scope/walkers.js';
export { collectNamespaceTargets } from './scope/namespace-targets.js';

View file

@ -13,7 +13,7 @@
* receiver class has no `methodName`, walk its fields and try the
* lookup on each field's type. Useful for dynamically-typed languages
* (Python). Strictly-typed languages should pass
* `fieldFallbackOnMethodLookup: false` via `EmitProvider`.
* `fieldFallbackOnMethodLookup: false` via `ScopeResolver`.
*
* Generic for any C-family language (`.` member access, `()` call
* syntax). Languages with non-C-family syntax (Ruby blocks, COBOL)
@ -22,12 +22,12 @@
import type { ParsedFile, ScopeId, SymbolDefinition, TypeRef } from 'gitnexus-shared';
import type { Scope } from 'gitnexus-shared';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import {
findClassBindingInScope,
findExportedDefByName,
findReceiverTypeBinding,
} from './scope-walkers.js';
} from '../scope/walkers.js';
/** Max depth for compound-receiver chain resolution (`a().b().c().d()`).
* Practical code rarely exceeds 3-4 hops; the cap prevents

View file

@ -13,16 +13,16 @@
* dedup semantics (what the `default-params` / `variadic` / `overload`
* fixtures expect). Member calls keep position-based dedup elsewhere.
*
* Generic; promoted from `python-scope-emit.ts` per the emit-core
* Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
* generalization plan.
*/
import type { ParsedFile, Reference, ScopeId } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import type { GraphNodeLookup } from './graph-node-lookup.js';
import { resolveCallerGraphId, resolveDefGraphId } from './graph-id.js';
import { findCallableBindingInScope } from './scope-walkers.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
import { resolveCallerGraphId, resolveDefGraphId } from '../graph-bridge/ids.js';
import { findCallableBindingInScope } from '../scope/walkers.js';
export function emitFreeCallFallback(
graph: KnowledgeGraph,

View file

@ -2,7 +2,7 @@
* Cross-file return-type typeBinding propagation + post-finalize
* chain re-follow.
*
* **Why this lives in emit-core:** the algorithm is language-agnostic.
* **Why this lives in scope-resolution:** the algorithm is language-agnostic.
* Every language with cross-file callable imports needs the same
* mirror-binding step, otherwise `u = f(); u.save()` only resolves
* when `f` is in the same file as the call.
@ -14,12 +14,12 @@
* populated) but BEFORE `resolveReferenceSites` (so resolution
* sees the propagated types).
*
* Generic; promoted from `python-scope-emit.ts` per the emit-core
* Generic; promoted from `languages/python/scope-resolver.ts` per the scope-resolution
* generalization plan.
*/
import type { ParsedFile, Scope, ScopeId, TypeRef } from 'gitnexus-shared';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
/** Max chain depth for the post-finalize re-follow. */
const RECHAIN_MAX_DEPTH = 8;

View file

@ -20,10 +20,10 @@
*/
import type { ParsedFile } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import type { GraphNodeLookup } from './graph-node-lookup.js';
import type { LinearizeStrategy } from './emit-provider.js';
import { resolveDefGraphId } from './graph-id.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
import type { LinearizeStrategy } from '../contract/scope-resolver.js';
import { resolveDefGraphId } from '../graph-bridge/ids.js';
/**
* Build an MRO map keyed by scope-resolution Class `DefId`.

View file

@ -1,6 +1,6 @@
/**
* Receiver-bound CALLS / ACCESSES emit pass — generic 7-case
* dispatcher consuming `EmitProvider` for the language-specific bits
* dispatcher consuming `ScopeResolver` for the language-specific bits
* (super recognizer, field-fallback toggle).
*
* **Contract Invariant I4 — case order is load-bearing.** The cases
@ -31,26 +31,26 @@
import type { ParsedFile, SymbolDefinition } from 'gitnexus-shared';
import type { Scope } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import type { EmitProvider } from './emit-provider.js';
import type { GraphNodeLookup } from './graph-node-lookup.js';
import { collectNamespaceTargets } from './namespace-targets.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import type { ScopeResolver } from '../contract/scope-resolver.js';
import type { GraphNodeLookup } from '../graph-bridge/node-lookup.js';
import { collectNamespaceTargets } from '../scope/namespace-targets.js';
import {
findClassBindingInScope,
findEnclosingClassDef,
findExportedDef,
findOwnedMember,
findReceiverTypeBinding,
} from './scope-walkers.js';
import { tryEmitEdge } from './emit-edge.js';
import { resolveCompoundReceiverClass } from './emit-compound-receiver.js';
} from '../scope/walkers.js';
import { tryEmitEdge } from '../graph-bridge/edges.js';
import { resolveCompoundReceiverClass } from '../passes/compound-receiver.js';
/** Subset of `EmitProvider` consumed by this pass. Accepting the
/** Subset of `ScopeResolver` consumed by this pass. Accepting the
* subset rather than the full provider keeps tests and partial
* refactors lighter — callers only need to populate what we read. */
export type ReceiverBoundProviderSubset = Pick<
EmitProvider,
ScopeResolver,
'isSuperReceiver' | 'fieldFallbackOnMethodLookup'
>;

View file

@ -4,7 +4,7 @@
* Generic registry-primary resolution phase (RFC #909 Ring 4).
*
* For every language in `MIGRATED_LANGUAGES` (per-language flag set)
* whose provider is registered in `EMIT_PROVIDERS`:
* whose provider is registered in `SCOPE_RESOLVERS`:
* 1. Filter scanned files by language extension.
* 2. Read file contents.
* 3. Drive the scope-based pipeline end-to-end via the generic
@ -16,7 +16,7 @@
* primary, so we don't double-emit edges from both code paths.
*
* Adding a language is two changes:
* - Implement `EmitProvider` in `languages/<lang>/emit/index.ts`
* - Implement `ScopeResolver` in `languages/<lang>/emit/index.ts`
* and register it in `emit-providers-registry.ts`.
* - Add the language to `MIGRATED_LANGUAGES` in
* `registry-primary-flag.ts`.
@ -27,15 +27,15 @@
* @writes graph (IMPORTS, CALLS, ACCESSES, INHERITS, USES)
*/
import type { PipelinePhase, PipelineContext, PhaseResult } from './types.js';
import { getPhaseOutput } from './types.js';
import type { StructureOutput } from './structure.js';
import { isRegistryPrimary } from '../registry-primary-flag.js';
import type { PipelinePhase, PipelineContext, PhaseResult } from '../../pipeline-phases/types.js';
import { getPhaseOutput } from '../../pipeline-phases/types.js';
import type { StructureOutput } from '../../pipeline-phases/structure.js';
import { isRegistryPrimary } from '../../registry-primary-flag.js';
import { SupportedLanguages, getLanguageFromFilename } from 'gitnexus-shared';
import { readFileContents } from '../filesystem-walker.js';
import { runScopeResolution } from '../emit-core/index.js';
import { EMIT_PROVIDERS } from '../emit-providers-registry.js';
import { isDev } from '../utils/env.js';
import { readFileContents } from '../../filesystem-walker.js';
import { runScopeResolution } from './run.js';
import { SCOPE_RESOLVERS } from './registry.js';
import { isDev } from '../../utils/env.js';
export interface ScopeResolutionOutput {
/** True when at least one language ran. */
@ -92,7 +92,7 @@ export const scopeResolutionPhase: PipelinePhase<ScopeResolutionOutput> = {
}
>();
for (const [lang, provider] of EMIT_PROVIDERS) {
for (const [lang, provider] of SCOPE_RESOLVERS) {
if (!isRegistryPrimary(lang)) continue;
const langFiles = scannedFiles.filter((f) => getLanguageFromFilename(f.path) === lang);

View file

@ -0,0 +1,27 @@
/**
* Per-language `ScopeResolver` registry — the lookup the generic
* `scopeResolutionPhase` uses to pick the right resolver for each
* migrated language.
*
* Adding a language is two lines: implement a `ScopeResolver` in
* `languages/<lang>/scope-resolver.ts` and register it here. The
* phase picks it up automatically — no workflow changes, no
* per-language pipeline phase file.
*/
import { SupportedLanguages } from 'gitnexus-shared';
import type { ScopeResolver } from '../contract/scope-resolver.js';
import { pythonScopeResolver } from '../../languages/python/scope-resolver.js';
/** Map of `SupportedLanguages` → `ScopeResolver`. The phase iterates
* this map intersected with `MIGRATED_LANGUAGES` (the per-language
* flag set) so adding a resolver here without flipping the flag is
* safe — the resolver sits idle until the language is migrated. */
export const SCOPE_RESOLVERS: ReadonlyMap<SupportedLanguages, ScopeResolver> = new Map<
SupportedLanguages,
ScopeResolver
>([[SupportedLanguages.Python, pythonScopeResolver]]);
export function getScopeResolver(lang: SupportedLanguages): ScopeResolver | undefined {
return SCOPE_RESOLVERS.get(lang);
}

View file

@ -17,25 +17,25 @@
* KnowledgeGraph
*
* Per-language entry points (e.g. `runPythonScopeResolution` in
* `languages/python/emit/index.ts`) construct an `EmitProvider` and
* `languages/python/scope-resolver.ts`) construct an `ScopeResolver` and
* delegate here.
*
* Plan: `docs/plans/2026-04-20-001-refactor-emit-pipeline-generalization-plan.md`.
*/
import type { ParsedFile, RegistryProviders } from 'gitnexus-shared';
import type { KnowledgeGraph } from '../../graph/types.js';
import { extractParsedFile } from '../scope-extractor-bridge.js';
import { finalizeScopeModel } from '../finalize-orchestrator.js';
import { resolveReferenceSites, type ResolveStats } from '../resolve-references.js';
import { buildGraphNodeLookup } from './graph-node-lookup.js';
import { buildPopulatedMethodDispatch } from './method-dispatch-bridge.js';
import { propagateImportedReturnTypes } from './propagate-return-types.js';
import { emitReceiverBoundCalls } from './emit-receiver-bound.js';
import { emitFreeCallFallback } from './emit-free-call.js';
import { emitReferencesViaLookup } from './emit-references.js';
import { emitImportEdges } from './emit-imports.js';
import type { EmitProvider } from './emit-provider.js';
import type { KnowledgeGraph } from '../../../graph/types.js';
import { extractParsedFile } from '../../scope-extractor-bridge.js';
import { finalizeScopeModel } from '../../finalize-orchestrator.js';
import { resolveReferenceSites, type ResolveStats } from '../../resolve-references.js';
import { buildGraphNodeLookup } from '../graph-bridge/node-lookup.js';
import { buildPopulatedMethodDispatch } from '../graph-bridge/method-dispatch.js';
import { propagateImportedReturnTypes } from '../passes/imported-return-types.js';
import { emitReceiverBoundCalls } from '../passes/receiver-bound-calls.js';
import { emitFreeCallFallback } from '../passes/free-call-fallback.js';
import { emitReferencesViaLookup } from '../graph-bridge/references-to-edges.js';
import { emitImportEdges } from '../graph-bridge/imports-to-edges.js';
import type { ScopeResolver } from '../contract/scope-resolver.js';
export interface RunScopeResolutionInput {
readonly graph: KnowledgeGraph;
@ -54,7 +54,7 @@ export interface RunScopeResolutionStats {
export function runScopeResolution(
input: RunScopeResolutionInput,
provider: EmitProvider,
provider: ScopeResolver,
): RunScopeResolutionStats {
const { graph, files } = input;
const onWarn = input.onWarn ?? (() => {});

View file

@ -20,7 +20,7 @@
*/
import type { ParsedFile } from 'gitnexus-shared';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
export function collectNamespaceTargets(
parsed: ParsedFile,

View file

@ -20,8 +20,8 @@
*/
import type { ParsedFile, ScopeId, SymbolDefinition, TypeRef } from 'gitnexus-shared';
import type { ScopeResolutionIndexes } from '../model/scope-resolution-indexes.js';
import { simpleQualifiedName } from './graph-id.js';
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
import { simpleQualifiedName } from '../graph-bridge/ids.js';
/**
* Walk the scope chain from `startScope` looking for a typeBinding