feat(ingestion): add heritage extractor config+factory pattern with per-language configs

- Create heritage-types.ts with HeritageExtractionConfig, HeritageExtractor, HeritageInfo interfaces
- Create heritage-extractors/generic.ts with createHeritageExtractor() factory
- Create 11 per-language config files in heritage-extractors/configs/
- Add heritageExtractor field to LanguageProviderConfig interface
- Wire createHeritageExtractor() in all 16 language providers
- Replace inline heritage capture handling in parse-worker with provider.heritageExtractor
- Add extractFromCall support for Ruby call-based heritage (include/extend/prepend)
- Replace inline heritage logic in heritage-processor extractExtractedHeritageFromFiles
- Ruby heritage config absorbs mixin routing logic from CallRouter

Agent-Logs-Url: https://github.com/abhigyanpatwari/GitNexus/sessions/b55df80d-758a-48cd-96de-a7657bdc2acb

Co-authored-by: magyargergo <11230420+magyargergo@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot] 2026-04-16 19:01:43 +00:00 • committed by GitHub
parent 541e4f7ce6
commit 835e69c53e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 534 additions and 54 deletions

View file

@ -0,0 +1,25 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/c-cpp.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* C heritage extraction config.
*
* C has no class inheritance — no heritage captures expected.
* Config exists for completeness in the provider wiring.
*/
export const cHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.C,
};
/**
* C++ heritage extraction config.
*
* C++ has standard extends heritage through tree-sitter captures
* (class A : public B). Multiple inheritance uses leftmost-base MRO
* at the resolution layer. No special extraction hooks needed.
*/
export const cppHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.CPlusPlus,
};

View file

@ -0,0 +1,15 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/csharp.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* C# heritage extraction config.
*
* C# has standard extends/implements heritage through tree-sitter
* captures. Interface detection uses I-prefix convention (handled
* by heritage resolution strategy, not here).
*/
export const csharpHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.CSharp,
};

View file

@ -0,0 +1,14 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/dart.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* Dart heritage extraction config.
*
* Dart has standard extends/implements/with heritage through tree-sitter
* captures. No special extraction hooks needed.
*/
export const dartHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Dart,
};

View file

@ -0,0 +1,27 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/go.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* Go heritage extraction config.
*
* Go struct embedding: the tree-sitter query matches ALL field_declarations
* with type_identifier, but only anonymous fields (no name) are embedded.
* Named fields like `Breed string` also match — skip them.
*
* The shouldSkipExtends hook checks if the extends node's parent is a
* field_declaration with a named field child, indicating a regular
* (non-embedded) field that should not produce a heritage record.
*/
export const goHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Go,
shouldSkipExtends(extendsNode) {
const fieldDecl = extendsNode.parent;
return (
fieldDecl?.type === 'field_declaration' &&
fieldDecl.childForFieldName?.('name') != null
);
},
};

View file

@ -0,0 +1,24 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/jvm.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* Java heritage extraction config.
*
* Java has standard extends/implements heritage through tree-sitter
* captures. No special extraction hooks needed.
*/
export const javaHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Java,
};
/**
* Kotlin heritage extraction config.
*
* Kotlin uses ':' for both extends and implements (delegation markers
* are tree-sitter captures). No special extraction hooks needed.
*/
export const kotlinHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Kotlin,
};

View file

@ -0,0 +1,14 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/php.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* PHP heritage extraction config.
*
* PHP has standard extends/implements heritage through tree-sitter
* captures. No special extraction hooks needed.
*/
export const phpHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.PHP,
};

View file

@ -0,0 +1,15 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/python.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* Python heritage extraction config.
*
* Python has standard extends heritage through tree-sitter captures
* (class A(B, C)). Multiple inheritance is handled by C3 MRO at
* the resolution layer. No special extraction hooks needed.
*/
export const pythonHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Python,
};

View file

@ -0,0 +1,72 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/ruby.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig, HeritageInfo } from '../../heritage-types.js';
import type { SyntaxNode } from '../../utils/ast-helpers.js';
/**
* Maximum parent depth for enclosing class/module walk.
* Prevents runaway walks on malformed/deeply-nested ASTs.
*/
const MAX_PARENT_DEPTH = 50;
/**
* Walk up the AST from a call node to find the enclosing class or module name.
* Ruby include/extend/prepend calls must be inside a class or module body.
*/
function findEnclosingClassName(callNode: SyntaxNode): string | null {
let current = callNode.parent;
let depth = 0;
while (current && ++depth <= MAX_PARENT_DEPTH) {
if (current.type === 'class' || current.type === 'module') {
const nameNode = current.childForFieldName?.('name');
if (nameNode) return nameNode.text;
}
current = current.parent;
}
return null;
}
/** Ruby heritage call names that express mixin inclusion. */
const RUBY_HERITAGE_CALL_NAMES: ReadonlySet<string> = new Set([
'include',
'extend',
'prepend',
]);
/**
* Ruby heritage extraction config.
*
* Ruby has no tree-sitter heritage captures — inheritance is expressed
* through the class definition syntax (`class A < B`) which produces
* standard @heritage.extends captures, and through method calls
* (include/extend/prepend) which are call-based heritage.
*
* The callBasedHeritage hook absorbs the mixin routing logic that was
* previously in call-routing.ts (routeRubyCall).
*/
export const rubyHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Ruby,
callBasedHeritage: {
callNames: RUBY_HERITAGE_CALL_NAMES,
extract(calledName, callNode, _filePath): HeritageInfo[] {
const enclosingClass = findEnclosingClassName(callNode);
if (!enclosingClass) return [];
const results: HeritageInfo[] = [];
const argList = callNode.childForFieldName?.('arguments');
for (const arg of argList?.children ?? []) {
if (arg.type === 'constant' || arg.type === 'scope_resolution') {
results.push({
className: enclosingClass,
parentName: arg.text,
kind: calledName, // 'include' | 'extend' | 'prepend'
});
}
}
return results;
},
},
};

View file

@ -0,0 +1,15 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/rust.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* Rust heritage extraction config.
*
* Rust uses impl Trait for Struct syntax. Heritage captures include
* heritage.trait for trait implementations. No special extraction
* hooks needed beyond the standard capture handling.
*/
export const rustHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Rust,
};

View file

@ -0,0 +1,16 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/swift.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* Swift heritage extraction config.
*
* Swift uses ':' for protocol conformance and class inheritance.
* The heritageDefaultEdge ('IMPLEMENTS') and resolution strategy
* are handled at the provider/resolution layer. No special
* extraction hooks needed.
*/
export const swiftHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.Swift,
};

View file

@ -0,0 +1,24 @@
// gitnexus/src/core/ingestion/heritage-extractors/configs/typescript-javascript.ts
import { SupportedLanguages } from 'gitnexus-shared';
import type { HeritageExtractionConfig } from '../../heritage-types.js';
/**
* TypeScript heritage extraction config.
*
* TypeScript has standard extends/implements heritage through tree-sitter
* captures. No special extraction hooks needed.
*/
export const typescriptHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.TypeScript,
};
/**
* JavaScript heritage extraction config.
*
* JavaScript has extends heritage (class A extends B). No implements
* keyword in plain JS. No special extraction hooks needed.
*/
export const javascriptHeritageConfig: HeritageExtractionConfig = {
language: SupportedLanguages.JavaScript,
};

View file

@ -0,0 +1,73 @@
// gitnexus/src/core/ingestion/heritage-extractors/generic.ts
/**
* Generic table-driven heritage extractor factory.
*
* Follows the same config+factory pattern as method-extractors/generic.ts,
* field-extractors/generic.ts, call-extractors/generic.ts, and
* variable-extractors/generic.ts.
*
* Define a HeritageExtractionConfig per language and generate extractors
* from configs. The factory creates a HeritageExtractor whose behaviour
* is entirely driven by HeritageExtractionConfig.
*/
import type { CaptureMap } from '../language-provider.js';
import type {
HeritageExtractionConfig,
HeritageExtractor,
HeritageExtractorContext,
HeritageInfo,
} from '../heritage-types.js';
import type { SyntaxNode } from '../utils/ast-helpers.js';
/**
* Create a HeritageExtractor from a declarative config.
*/
export function createHeritageExtractor(config: HeritageExtractionConfig): HeritageExtractor {
const callNameSet = config.callBasedHeritage?.callNames;
return {
language: config.language,
extract(captureMap: CaptureMap, context: HeritageExtractorContext): HeritageInfo[] {
const classNode = captureMap['heritage.class'];
if (!classNode) return [];
const className = classNode.text;
const results: HeritageInfo[] = [];
const extendsNode = captureMap['heritage.extends'];
if (extendsNode) {
if (!config.shouldSkipExtends?.(extendsNode)) {
results.push({ className, parentName: extendsNode.text, kind: 'extends' });
}
}
const implementsNode = captureMap['heritage.implements'];
if (implementsNode) {
results.push({ className, parentName: implementsNode.text, kind: 'implements' });
}
const traitNode = captureMap['heritage.trait'];
if (traitNode) {
results.push({ className, parentName: traitNode.text, kind: 'trait-impl' });
}
return results;
},
...(callNameSet
? {
extractFromCall(
calledName: string,
callNode: SyntaxNode,
context: HeritageExtractorContext,
): HeritageInfo[] | null {
if (!callNameSet.has(calledName)) return null;
return config.callBasedHeritage!.extract(calledName, callNode, context.filePath);
},
}
: {}),
};
}

View file

@ -407,36 +407,20 @@ export async function extractExtractedHeritageFromFiles(
});
if (captureMap['heritage.class']) {
if (captureMap['heritage.extends']) {
const extendsNode = captureMap['heritage.extends'];
const fieldDecl = extendsNode.parent;
const isNamedField =
fieldDecl?.type === 'field_declaration' && fieldDecl.childForFieldName('name');
if (!isNamedField) {
if (provider.heritageExtractor) {
const heritageItems = provider.heritageExtractor.extract(captureMap, {
filePath: file.path,
language,
});
for (const item of heritageItems) {
out.push({
filePath: file.path,
className: captureMap['heritage.class'].text,
parentName: captureMap['heritage.extends'].text,
kind: 'extends',
className: item.className,
parentName: item.parentName,
kind: item.kind,
});
}
}
if (captureMap['heritage.implements']) {
out.push({
filePath: file.path,
className: captureMap['heritage.class'].text,
parentName: captureMap['heritage.implements'].text,
kind: 'implements',
});
}
if (captureMap['heritage.trait']) {
out.push({
filePath: file.path,
className: captureMap['heritage.class'].text,
parentName: captureMap['heritage.trait'].text,
kind: 'trait-impl',
});
}
}
}
}

View file

@ -0,0 +1,108 @@
// gitnexus/src/core/ingestion/heritage-types.ts
/**
* Types for the language-agnostic heritage extraction pipeline.
*
* Follows the same pattern as call-types.ts, variable-types.ts, and
* method-types.ts: defines the domain interfaces consumed by
* createHeritageExtractor() and the per-language configs.
*
* Heritage extraction handles extends/implements/trait-impl captures from
* tree-sitter queries, plus call-based heritage for languages like Ruby
* (include/extend/prepend expressed as method calls).
*/
import type { SupportedLanguages } from 'gitnexus-shared';
import type { SyntaxNode } from './utils/ast-helpers.js';
import type { CaptureMap } from './language-provider.js';
// ---------------------------------------------------------------------------
// Extracted result
// ---------------------------------------------------------------------------
/**
* Per-match heritage extraction result. The parse worker adds filePath to
* produce the final {@link ExtractedHeritage} that enters the resolution
* pipeline (heritage-processor.ts / heritage-map.ts).
*/
export interface HeritageInfo {
className: string;
parentName: string;
/** 'extends' | 'implements' | 'trait-impl' | 'include' | 'extend' | 'prepend' */
kind: string;
}
// ---------------------------------------------------------------------------
// Context
// ---------------------------------------------------------------------------
export interface HeritageExtractorContext {
filePath: string;
language: SupportedLanguages;
}
// ---------------------------------------------------------------------------
// Extractor interface (produced by createHeritageExtractor)
// ---------------------------------------------------------------------------
export interface HeritageExtractor {
readonly language: SupportedLanguages;
/**
* Extract heritage records from tree-sitter @heritage.* captures.
*
* @param captureMap The capture map from a single tree-sitter match
* @param context File path and language context
* @returns Array of heritage records (may be empty if captures don't match)
*/
extract(captureMap: CaptureMap, context: HeritageExtractorContext): HeritageInfo[];
/**
* Extract heritage from a call node (for languages where heritage is
* expressed as method calls, e.g., Ruby include/extend/prepend).
*
* @param calledName The method name (e.g. 'include', 'extend', 'prepend')
* @param callNode The tree-sitter call AST node
* @param context File path and language context
* @returns Heritage records if the call is heritage-related, or null to
* fall through to the call router / normal call handling.
*/
extractFromCall?(
calledName: string,
callNode: SyntaxNode,
context: HeritageExtractorContext,
): HeritageInfo[] | null;
}
// ---------------------------------------------------------------------------
// Config interface (one per language / language group)
// ---------------------------------------------------------------------------
export interface HeritageExtractionConfig {
language: SupportedLanguages;
/**
* Called for heritage.extends captures. Return true to skip this extends
* capture. Used by Go to skip named struct fields that match the
* field_declaration pattern but are not anonymous embeddings.
*
* Default: never skip (all extends captures are valid).
*/
shouldSkipExtends?: (extendsNode: SyntaxNode) => boolean;
/**
* Call-based heritage extraction for languages where heritage is expressed
* as method calls (e.g., Ruby include/extend/prepend).
*
* callNames: set of method names that trigger heritage extraction.
* extract: extract heritage items from the call node + method name.
*/
callBasedHeritage?: {
readonly callNames: ReadonlySet<string>;
extract(
calledName: string,
callNode: SyntaxNode,
filePath: string,
): HeritageInfo[];
};
}

View file

@ -16,6 +16,7 @@ import type { CallExtractor } from './call-types.js';
import type { ClassExtractor } from './class-types.js';
import type { ExportChecker } from './export-detection.js';
import type { FieldExtractor } from './field-extractor.js';
import type { HeritageExtractor } from './heritage-types.js';
import type { MethodExtractor } from './method-types.js';
import type { VariableExtractor } from './variable-types.js';
import type { ImportResolverFn } from './import-resolvers/types.js';
@ -179,6 +180,11 @@ interface LanguageProviderConfig {
* Uses the same provider-driven strategy pattern as method/field extraction so
* namespace/package/module rules stay language-specific. */
readonly classExtractor?: ClassExtractor;
/** Heritage extractor for extracting extends/implements/trait-impl relationships
* from tree-sitter @heritage.* captures and call-based heritage (e.g., Ruby
* include/extend/prepend). Produced by createHeritageExtractor() with a
* per-language HeritageExtractionConfig. Default: undefined (inline fallback). */
readonly heritageExtractor?: HeritageExtractor;
/** Extract a semantic description for a definition node (e.g., PHP Eloquent
* property arrays, relation method descriptions).
* Default: undefined (no description extraction). */

View file

@ -43,6 +43,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { cVariableConfig, cppVariableConfig } from '../variable-extractors/configs/c-cpp.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { cCallConfig, cppCallConfig } from '../call-extractors/configs/c-cpp.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { cHeritageConfig, cppHeritageConfig } from '../heritage-extractors/configs/c-cpp.js';
const C_BUILT_INS: ReadonlySet<string> = new Set([
'printf',
@ -329,6 +331,7 @@ export const cProvider = defineLanguage({
}),
variableExtractor: createVariableExtractor(cVariableConfig),
classExtractor: cClassExtractor,
heritageExtractor: createHeritageExtractor(cHeritageConfig),
labelOverride: cppLabelOverride,
builtInNames: C_BUILT_INS,
});
@ -350,6 +353,7 @@ export const cppProvider = defineLanguage({
}),
variableExtractor: createVariableExtractor(cppVariableConfig),
classExtractor: cppClassExtractor,
heritageExtractor: createHeritageExtractor(cppHeritageConfig),
labelOverride: cppLabelOverride,
builtInNames: C_BUILT_INS,
});

View file

@ -24,6 +24,8 @@ import { createMethodExtractor } from '../method-extractors/generic.js';
import { csharpMethodConfig } from '../method-extractors/configs/csharp.js';
import { createVariableExtractor } from '../variable-extractors/generic.js';
import { csharpVariableConfig } from '../variable-extractors/configs/csharp.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { csharpHeritageConfig } from '../heritage-extractors/configs/csharp.js';
const BUILT_INS: ReadonlySet<string> = new Set([
'Console',
@ -135,5 +137,6 @@ export const csharpProvider = defineLanguage({
methodExtractor: createMethodExtractor(csharpMethodConfig),
variableExtractor: createVariableExtractor(csharpVariableConfig),
classExtractor: createClassExtractor(csharpClassConfig),
heritageExtractor: createHeritageExtractor(csharpHeritageConfig),
builtInNames: BUILT_INS,
});

View file

@ -30,6 +30,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { dartVariableConfig } from '../variable-extractors/configs/dart.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { dartCallConfig } from '../call-extractors/configs/dart.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { dartHeritageConfig } from '../heritage-extractors/configs/dart.js';
/**
* Resolve the enclosing function from a `function_body` node by looking at its
@ -102,6 +104,7 @@ export const dartProvider = defineLanguage({
methodExtractor: createMethodExtractor(dartMethodConfig),
variableExtractor: createVariableExtractor(dartVariableConfig),
classExtractor: createClassExtractor(dartClassConfig),
heritageExtractor: createHeritageExtractor(dartHeritageConfig),
enclosingFunctionFinder: dartEnclosingFunctionFinder,
builtInNames: BUILT_INS,
});

View file

@ -26,6 +26,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { goVariableConfig } from '../variable-extractors/configs/go.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { goCallConfig } from '../call-extractors/configs/go.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { goHeritageConfig } from '../heritage-extractors/configs/go.js';
export const goProvider = defineLanguage({
id: SupportedLanguages.Go,
@ -40,4 +42,5 @@ export const goProvider = defineLanguage({
methodExtractor: createMethodExtractor(goMethodConfig),
variableExtractor: createVariableExtractor(goVariableConfig),
classExtractor: createClassExtractor(goClassConfig),
heritageExtractor: createHeritageExtractor(goHeritageConfig),
});

View file

@ -25,6 +25,8 @@ import { createMethodExtractor } from '../method-extractors/generic.js';
import { javaMethodConfig } from '../method-extractors/configs/jvm.js';
import { createVariableExtractor } from '../variable-extractors/generic.js';
import { javaVariableConfig } from '../variable-extractors/configs/jvm.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { javaHeritageConfig } from '../heritage-extractors/configs/jvm.js';
export const javaProvider = defineLanguage({
id: SupportedLanguages.Java,
@ -41,4 +43,5 @@ export const javaProvider = defineLanguage({
methodExtractor: createMethodExtractor(javaMethodConfig),
variableExtractor: createVariableExtractor(javaVariableConfig),
classExtractor: createClassExtractor(javaClassConfig),
heritageExtractor: createHeritageExtractor(javaHeritageConfig),
});

View file

@ -27,6 +27,8 @@ import { createMethodExtractor } from '../method-extractors/generic.js';
import { kotlinMethodConfig } from '../method-extractors/configs/jvm.js';
import { createVariableExtractor } from '../variable-extractors/generic.js';
import { kotlinVariableConfig } from '../variable-extractors/configs/jvm.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { kotlinHeritageConfig } from '../heritage-extractors/configs/jvm.js';
/** Check if a Kotlin function_declaration capture is inside a class_body (i.e., a method).
* Kotlin grammar uses function_declaration for both top-level functions and class methods.
@ -116,6 +118,7 @@ export const kotlinProvider = defineLanguage({
methodExtractor: createMethodExtractor(kotlinMethodConfig),
variableExtractor: createVariableExtractor(kotlinVariableConfig),
classExtractor: createClassExtractor(kotlinClassConfig),
heritageExtractor: createHeritageExtractor(kotlinHeritageConfig),
builtInNames: BUILT_INS,
labelOverride: (functionNode, defaultLabel) => {
if (defaultLabel !== 'Function') return defaultLabel;

View file

@ -26,6 +26,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { phpVariableConfig } from '../variable-extractors/configs/php.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { phpCallConfig } from '../call-extractors/configs/php.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { phpHeritageConfig } from '../heritage-extractors/configs/php.js';
const BUILT_INS: ReadonlySet<string> = new Set([
'echo',
@ -248,6 +250,7 @@ export const phpProvider = defineLanguage({
methodExtractor: createMethodExtractor(phpMethodConfig),
variableExtractor: createVariableExtractor(phpVariableConfig),
classExtractor: createClassExtractor(phpClassConfig),
heritageExtractor: createHeritageExtractor(phpHeritageConfig),
descriptionExtractor: phpDescriptionExtractor,
isRouteFile: isPhpRouteFile,
builtInNames: BUILT_INS,

View file

@ -28,6 +28,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { pythonVariableConfig } from '../variable-extractors/configs/python.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { pythonCallConfig } from '../call-extractors/configs/python.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { pythonHeritageConfig } from '../heritage-extractors/configs/python.js';
const BUILT_INS: ReadonlySet<string> = new Set([
'print',
@ -74,5 +76,6 @@ export const pythonProvider = defineLanguage({
methodExtractor: createMethodExtractor(pythonMethodConfig),
variableExtractor: createVariableExtractor(pythonVariableConfig),
classExtractor: createClassExtractor(pythonClassConfig),
heritageExtractor: createHeritageExtractor(pythonHeritageConfig),
builtInNames: BUILT_INS,
});

View file

@ -27,6 +27,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { rubyVariableConfig } from '../variable-extractors/configs/ruby.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { rubyCallConfig } from '../call-extractors/configs/ruby.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { rubyHeritageConfig } from '../heritage-extractors/configs/ruby.js';
/** Ruby method/singleton_method: extract name from 'name' field, label as Method. */
const rubyExtractFunctionName = (
@ -137,5 +139,6 @@ export const rubyProvider = defineLanguage({
}),
variableExtractor: createVariableExtractor(rubyVariableConfig),
classExtractor: createClassExtractor(rubyClassConfig),
heritageExtractor: createHeritageExtractor(rubyHeritageConfig),
builtInNames: BUILT_INS,
});

View file

@ -30,6 +30,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { rustVariableConfig } from '../variable-extractors/configs/rust.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { rustCallConfig } from '../call-extractors/configs/rust.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { rustHeritageConfig } from '../heritage-extractors/configs/rust.js';
/** Rust impl_item: find the function_item child and extract its name as a Method. */
const rustExtractFunctionName = (
@ -134,5 +136,6 @@ export const rustProvider = defineLanguage({
}),
variableExtractor: createVariableExtractor(rustVariableConfig),
classExtractor: createClassExtractor(rustClassConfig),
heritageExtractor: createHeritageExtractor(rustHeritageConfig),
builtInNames: BUILT_INS,
});

View file

@ -30,6 +30,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { swiftVariableConfig } from '../variable-extractors/configs/swift.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { swiftCallConfig } from '../call-extractors/configs/swift.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { swiftHeritageConfig } from '../heritage-extractors/configs/swift.js';
/**
* Group Swift files by SPM target for implicit module visibility.
@ -254,6 +256,7 @@ export const swiftProvider = defineLanguage({
}),
variableExtractor: createVariableExtractor(swiftVariableConfig),
classExtractor: createClassExtractor(swiftClassConfig),
heritageExtractor: createHeritageExtractor(swiftHeritageConfig),
implicitImportWirer: wireSwiftImplicitImports,
builtInNames: BUILT_INS,
});

View file

@ -43,6 +43,11 @@ import {
typescriptCallConfig,
javascriptCallConfig,
} from '../call-extractors/configs/typescript-javascript.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import {
typescriptHeritageConfig,
javascriptHeritageConfig,
} from '../heritage-extractors/configs/typescript-javascript.js';
/**
* TypeScript/JavaScript: arrow_function and function_expression get their name
@ -182,6 +187,7 @@ export const typescriptProvider = defineLanguage({
}),
variableExtractor: createVariableExtractor(typescriptVariableConfig),
classExtractor: createClassExtractor(typescriptClassConfig),
heritageExtractor: createHeritageExtractor(typescriptHeritageConfig),
builtInNames: BUILT_INS,
});
@ -201,5 +207,6 @@ export const javascriptProvider = defineLanguage({
}),
variableExtractor: createVariableExtractor(javascriptVariableConfig),
classExtractor: createClassExtractor(javascriptClassConfig),
heritageExtractor: createHeritageExtractor(javascriptHeritageConfig),
builtInNames: BUILT_INS,
});

View file

@ -27,6 +27,8 @@ import { createVariableExtractor } from '../variable-extractors/generic.js';
import { typescriptVariableConfig } from '../variable-extractors/configs/typescript-javascript.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { typescriptCallConfig } from '../call-extractors/configs/typescript-javascript.js';
import { createHeritageExtractor } from '../heritage-extractors/generic.js';
import { typescriptHeritageConfig } from '../heritage-extractors/configs/typescript-javascript.js';
const VUE_SPECIFIC_BUILT_INS = [
'ref',
@ -76,5 +78,6 @@ export const vueProvider = defineLanguage({
fieldExtractor: typescriptFieldExtractor,
variableExtractor: createVariableExtractor(typescriptVariableConfig),
classExtractor: vueClassExtractor,
heritageExtractor: createHeritageExtractor(typescriptHeritageConfig),
builtInNames: VUE_BUILT_INS,
});

View file

@ -1699,6 +1699,26 @@ const processFileGroup = (
if (callNameNode) {
const calledName = callNameNode.text;
// Check heritage extractor for call-based heritage (e.g., Ruby include/extend/prepend)
if (provider.heritageExtractor?.extractFromCall) {
const heritageItems = provider.heritageExtractor.extractFromCall(
calledName,
captureMap['call']!,
{ filePath: file.path, language },
);
if (heritageItems && heritageItems.length > 0) {
for (const item of heritageItems) {
result.heritage.push({
filePath: file.path,
className: item.className,
parentName: item.parentName,
kind: item.kind,
});
}
continue;
}
}
// Dispatch: route language-specific calls (heritage, properties, imports)
const routed = callRouter?.(calledName, captureMap['call']);
if (routed) {
@ -1878,40 +1898,24 @@ const processFileGroup = (
continue;
}
// Extract heritage (extends/implements)
// Extract heritage (extends/implements) via provider heritage extractor
if (captureMap['heritage.class']) {
if (captureMap['heritage.extends']) {
// Go struct embedding: the query matches ALL field_declarations with
// type_identifier, but only anonymous fields (no name) are embedded.
// Named fields like `Breed string` also match — skip them.
const extendsNode = captureMap['heritage.extends'];
const fieldDecl = extendsNode.parent;
const isNamedField =
fieldDecl?.type === 'field_declaration' && fieldDecl.childForFieldName('name');
if (!isNamedField) {
if (provider.heritageExtractor) {
const heritageItems = provider.heritageExtractor.extract(captureMap, {
filePath: file.path,
language,
});
for (const item of heritageItems) {
result.heritage.push({
filePath: file.path,
className: captureMap['heritage.class'].text,
parentName: captureMap['heritage.extends'].text,
kind: 'extends',
className: item.className,
parentName: item.parentName,
kind: item.kind,
});
}
}
if (captureMap['heritage.implements']) {
result.heritage.push({
filePath: file.path,
className: captureMap['heritage.class'].text,
parentName: captureMap['heritage.implements'].text,
kind: 'implements',
});
}
if (captureMap['heritage.trait']) {
result.heritage.push({
filePath: file.path,
className: captureMap['heritage.class'].text,
parentName: captureMap['heritage.trait'].text,
kind: 'trait-impl',
});
if (heritageItems.length > 0) {
continue;
}
}
if (
captureMap['heritage.extends'] ||