refactor(ingestion): consolidate per-language patterns into LanguageProvider

Move entry-point name patterns and AST framework detection patterns from
shared maps in entry-point-scoring.ts and framework-detection.ts into each
LanguageProvider. The shared files now build their lookup tables dynamically
from the provider registry at module load.

This aligns with the architecture principle that shared pipeline code must
not name languages. Adding a new language no longer requires modifying
entry-point-scoring.ts or framework-detection.ts — the provider file is
the single source of truth for all language-specific data.

New LanguageProvider fields:
  - entryPointPatterns: RegExp[] (default: [])
  - astFrameworkPatterns: AstFrameworkPatternConfig[] (default: [])
This commit is contained in:
Christian C. Berclaz 2026-05-02 17:24:12 +02:00
parent 26b39560ce
commit 0b84496cfd
No known key found for this signature in database
18 changed files with 538 additions and 599 deletions

View file

@ -12,18 +12,15 @@
import { detectFrameworkFromPath } from './framework-detection.js';
import { SupportedLanguages } from 'gitnexus-shared';
import { providers } from './languages/index.js';
// ============================================================================
// NAME PATTERNS - All 13 supported languages
// NAME PATTERNS
// ============================================================================
/**
* Common entry point naming patterns by language.
* These patterns indicate functions that are likely feature entry points.
*
* Universal patterns are separated from per-language patterns so the per-language
* table can use `satisfies Record<SupportedLanguages, RegExp[]>` for compile-time
* exhaustiveness — the compiler catches any missing language entry.
* Universal entry point naming patterns shared across all languages.
* Per-language patterns live on each LanguageProvider.entryPointPatterns.
*/
const UNIVERSAL_ENTRY_POINT_PATTERNS: RegExp[] = [
/^(main|init|bootstrap|start|run|setup|configure)$/i,
@ -40,201 +37,11 @@ const UNIVERSAL_ENTRY_POINT_PATTERNS: RegExp[] = [
/^emit[A-Z]/, // emitEvent
];
export const ENTRY_POINT_PATTERNS = {
// JavaScript/TypeScript
[SupportedLanguages.JavaScript]: [
/^use[A-Z]/, // React hooks (useEffect, etc.)
],
[SupportedLanguages.TypeScript]: [
/^use[A-Z]/, // React hooks
],
// Python
[SupportedLanguages.Python]: [
/^app$/, // Flask/FastAPI app
/^(get|post|put|delete|patch)_/i, // REST conventions
/^api_/, // API functions
/^view_/, // Django views
],
// Java
[SupportedLanguages.Java]: [
/^do[A-Z]/, // doGet, doPost (Servlets)
/^create[A-Z]/, // Factory patterns
/^build[A-Z]/, // Builder patterns
/Service$/, // UserService
],
// Kotlin
[SupportedLanguages.Kotlin]: [
/^on(Create|Start|Resume|Pause|Stop|Destroy)$/, // Android lifecycle
/^do[A-Z]/, // doGet, doPost (shared JVM Servlet pattern)
/^create[A-Z]/, // Factory patterns
/^build[A-Z]/, // Builder patterns
/ViewModel$/, // MVVM pattern (Android)
/^module$/, // Ktor module entry point
/Service$/, // Service classes
],
// C#
[SupportedLanguages.CSharp]: [
/^(Get|Post|Put|Delete|Patch)/, // ASP.NET action methods
/Action$/, // MVC actions
/^On[A-Z]/, // Event handlers / Blazor lifecycle
/Async$/, // Async entry points
/^Configure$/, // Startup.Configure
/^ConfigureServices$/, // Startup.ConfigureServices
/^Handle$/, // MediatR / generic handler
/^Execute$/, // Command pattern
/^Invoke$/, // Middleware Invoke
/^Map[A-Z]/, // Minimal API MapGet, MapPost
/Service$/, // Service classes
/^Seed/, // Database seeding
],
// Go
[SupportedLanguages.Go]: [
/Handler$/, // http.Handler pattern
/^Serve/, // ServeHTTP
/^New[A-Z]/, // Constructor pattern (returns new instance)
/^Make[A-Z]/, // Make functions
],
// Rust
[SupportedLanguages.Rust]: [
/^(get|post|put|delete)_handler$/i,
/^handle_/, // handle_request
/^new$/, // Constructor pattern
/^run$/, // run entry point
/^spawn/, // Async spawn
],
// C - explicit main() boost plus common C entry point conventions
[SupportedLanguages.C]: [
/^main$/, // THE entry point
/^init_/, // init_server, init_client
/_init$/, // module_init, server_init
/^start_/, // start_server
/_start$/, // thread_start
/^run_/, // run_loop
/_run$/, // event_run
/^stop_/, // stop_server
/_stop$/, // service_stop
/^open_/, // open_connection
/_open$/, // file_open
/^close_/, // close_connection
/_close$/, // socket_close
/^create_/, // create_session
/_create$/, // object_create
/^destroy_/, // destroy_session
/_destroy$/, // object_destroy
/^handle_/, // handle_request
/_handler$/, // signal_handler
/_callback$/, // event_callback
/^cmd_/, // tmux: cmd_new_window, cmd_attach_session
/^server_/, // server_start, server_loop
/^client_/, // client_connect
/^session_/, // session_create
/^window_/, // window_resize (tmux)
/^key_/, // key_press
/^input_/, // input_parse
/^output_/, // output_write
/^notify_/, // notify_client
/^control_/, // control_start
],
// C++ - same as C plus OOP/template patterns
[SupportedLanguages.CPlusPlus]: [
/^main$/, // THE entry point
/^init_/,
/_init$/,
/^Create[A-Z]/, // Factory patterns
/^create_/,
/^Run$/, // Run methods
/^run$/,
/^Start$/, // Start methods
/^start$/,
/^handle_/,
/_handler$/,
/_callback$/,
/^OnEvent/, // Event callbacks
/^on_/,
/::Run$/, // Class::Run
/::Start$/, // Class::Start
/::Init$/, // Class::Init
/::Execute$/, // Class::Execute
],
// Swift / iOS
[SupportedLanguages.Swift]: [
/^viewDidLoad$/, // UIKit lifecycle
/^viewWillAppear$/, // UIKit lifecycle
/^viewDidAppear$/, // UIKit lifecycle
/^viewWillDisappear$/, // UIKit lifecycle
/^viewDidDisappear$/, // UIKit lifecycle
/^application\(/, // AppDelegate methods
/^scene\(/, // SceneDelegate methods
/^body$/, // SwiftUI View.body
/Coordinator$/, // Coordinator pattern
/^sceneDidBecomeActive$/, // SceneDelegate lifecycle
/^sceneWillResignActive$/, // SceneDelegate lifecycle
/^didFinishLaunchingWithOptions$/, // AppDelegate
/ViewController$/, // ViewController classes
/^configure[A-Z]/, // Configuration methods
/^setup[A-Z]/, // Setup methods
/^makeBody$/, // SwiftUI ViewModifier
],
// PHP / Laravel
[SupportedLanguages.PHP]: [
/Controller$/, // UserController (class name convention)
/^handle$/, // Job::handle(), Listener::handle()
/^execute$/, // Command::execute()
/^boot$/, // ServiceProvider::boot()
/^register$/, // ServiceProvider::register()
/^__invoke$/, // Invokable controllers/actions
/^(index|show|store|update|destroy|create|edit)$/, // RESTful resource methods
/^(get|post|put|delete|patch)[A-Z]/, // Explicit HTTP method actions
/^run$/, // Command/Job run()
/^fire$/, // Event fire()
/^dispatch$/, // Dispatchable jobs
/Service$/, // UserService (Service layer)
/Repository$/, // UserRepository (Repository pattern)
/^find$/, // Repository::find()
/^findAll$/, // Repository::findAll()
/^save$/, // Repository::save()
/^delete$/, // Repository::delete()
],
// Ruby
[SupportedLanguages.Ruby]: [
/^call$/, // Service objects (MyService.call)
/^perform$/, // Background jobs (Sidekiq, ActiveJob)
/^execute$/, // Command pattern
],
// Dart / Flutter
[SupportedLanguages.Dart]: [
/^main$/, // App entry
/^build$/, // Widget.build — fundamental Flutter render entry point
/^createState$/, // StatefulWidget.createState
/^initState$/, // State lifecycle initialization
/^dispose$/, // State lifecycle teardown
/^didChangeDependencies$/, // State lifecycle — InheritedWidget changes
/^didUpdateWidget$/, // State lifecycle — widget rebuild with new config
/^runApp$/, // App entry point
/^onEvent$/, // BLoC event handler
/^mapEventToState$/, // Legacy BLoC pattern
],
[SupportedLanguages.Vue]: [], // Vue uses TypeScript queries — entry points handled via TS patterns
[SupportedLanguages.Cobol]: [], // Standalone regex processor — no tree-sitter entry points
} satisfies Record<SupportedLanguages, RegExp[]>;
/** Pre-computed merged patterns (universal + language-specific) to avoid per-call array allocation. */
/** Pre-computed merged patterns (universal + language-specific) from providers. */
const MERGED_ENTRY_POINT_PATTERNS = Object.fromEntries(
Object.values(SupportedLanguages).map((lang) => [
Object.entries(providers).map(([lang, provider]) => [
lang,
[...UNIVERSAL_ENTRY_POINT_PATTERNS, ...(ENTRY_POINT_PATTERNS[lang] ?? [])],
[...UNIVERSAL_ENTRY_POINT_PATTERNS, ...(provider.entryPointPatterns ?? [])],
]),
) as Record<SupportedLanguages, RegExp[]>;

View file

@ -11,6 +11,8 @@
*/
import { SupportedLanguages } from 'gitnexus-shared';
import type { AstFrameworkPatternConfig } from './language-provider.js';
import { providers } from './languages/index.js';
// ============================================================================
// TYPES
@ -518,395 +520,14 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null
// AST-BASED FRAMEWORK DETECTION
// ============================================================================
/**
* Patterns that indicate framework entry points within code definitions.
* These are matched against AST node text (class/method/function declaration text).
*/
export const FRAMEWORK_AST_PATTERNS = {
// JavaScript/TypeScript decorators
nestjs: ['@Controller', '@Get', '@Post', '@Put', '@Delete', '@Patch'],
'expo-router': [
'router.push',
'router.replace',
'router.navigate',
'useRouter',
'useLocalSearchParams',
'useSegments',
'expo-router',
],
express: ['app.get', 'app.post', 'app.put', 'app.delete', 'router.get', 'router.post'],
// Python decorators
fastapi: ['@app.get', '@app.post', '@app.put', '@app.delete', '@router.get'],
flask: ['@app.route', '@blueprint.route'],
// Java annotations
spring: ['@RestController', '@Controller', '@GetMapping', '@PostMapping', '@RequestMapping'],
jaxrs: ['@Path', '@GET', '@POST', '@PUT', '@DELETE'],
// C# attributes
aspnet: [
'[ApiController]',
'[HttpGet]',
'[HttpPost]',
'[HttpPut]',
'[HttpDelete]',
'[Route]',
'[Authorize]',
'[AllowAnonymous]',
],
signalr: ['[HubMethodName]', ': Hub', ': Hub<'],
blazor: ['@page', '[Parameter]', '@inject'],
efcore: ['DbContext', 'DbSet<', 'OnModelCreating'],
// Go patterns (function signatures include framework types)
'go-http': [
'http.Handler',
'http.HandlerFunc',
'ServeHTTP',
'http.ResponseWriter',
'http.Request',
],
gin: ['gin.Context', 'gin.Default', 'gin.New'],
echo: ['echo.Context', 'echo.New'],
fiber: ['fiber.Ctx', 'fiber.New', 'fiber.App'],
'go-grpc': ['grpc.Server', 'RegisterServer', 'pb.Unimplemented'],
// ORM patterns
prisma: ['prisma.', 'PrismaClient', '@prisma/client'],
supabase: ['supabase.from', 'createClient', '@supabase/supabase-js'],
// PHP/Laravel
laravel: [
'Route::get',
'Route::post',
'Route::put',
'Route::delete',
'Route::resource',
'Route::apiResource',
'#[Route(',
],
// Rust macros (proc-macro attributes in definition text)
actix: ['#[get', '#[post', '#[put', '#[delete', '#[actix_web', 'HttpRequest', 'HttpResponse'],
axum: ['Router::new', 'axum::extract', 'axum::routing'],
rocket: ['#[get', '#[post', '#[launch', 'rocket::'],
tokio: ['#[tokio::main]', '#[tokio::test]'],
// C++ patterns (Qt, Boost)
qt: [
'Q_OBJECT',
'Q_INVOKABLE',
'Q_PROPERTY',
'Q_SIGNALS',
'Q_SLOTS',
'Q_SIGNAL',
'Q_SLOT',
'QWidget',
'QApplication',
],
// Swift/iOS
uikit: [
'viewDidLoad',
'viewWillAppear',
'viewDidAppear',
'UIViewController',
'@IBOutlet',
'@IBAction',
'@objc',
],
swiftui: [
'@main',
'WindowGroup',
'ContentView',
'@StateObject',
'@ObservedObject',
'@EnvironmentObject',
'@Published',
],
vapor: ['app.get', 'app.post', 'req.content.decode', 'Vapor'],
// Ruby patterns (class-level macros in definition text)
rails: [
'ApplicationController',
'ApplicationRecord',
'ActiveRecord::Base',
'before_action',
'after_action',
'has_many',
'belongs_to',
'has_one',
'validates',
],
sinatra: ['Sinatra::Base', 'Sinatra::Application'],
// Dart/Flutter
flutter: [
'StatelessWidget',
'StatefulWidget',
'BuildContext',
'Widget build',
'ChangeNotifier',
'GetxController',
'Cubit<',
'Bloc<',
'ConsumerWidget',
],
riverpod: ['@riverpod', 'ref.watch', 'ref.read', 'AsyncNotifier', 'Notifier'],
};
interface AstFrameworkPatternConfig {
framework: string;
entryPointMultiplier: number;
reason: string;
patterns: string[];
}
export const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
[SupportedLanguages.JavaScript]: [
{
framework: 'nestjs',
entryPointMultiplier: 3.2,
reason: 'nestjs-decorator',
patterns: FRAMEWORK_AST_PATTERNS.nestjs,
},
{
framework: 'expo-router',
entryPointMultiplier: 2.5,
reason: 'expo-router-navigation',
patterns: FRAMEWORK_AST_PATTERNS['expo-router'],
},
],
[SupportedLanguages.TypeScript]: [
{
framework: 'nestjs',
entryPointMultiplier: 3.2,
reason: 'nestjs-decorator',
patterns: FRAMEWORK_AST_PATTERNS.nestjs,
},
{
framework: 'expo-router',
entryPointMultiplier: 2.5,
reason: 'expo-router-navigation',
patterns: FRAMEWORK_AST_PATTERNS['expo-router'],
},
],
[SupportedLanguages.Python]: [
{
framework: 'fastapi',
entryPointMultiplier: 3.0,
reason: 'fastapi-decorator',
patterns: FRAMEWORK_AST_PATTERNS.fastapi,
},
{
framework: 'flask',
entryPointMultiplier: 2.8,
reason: 'flask-decorator',
patterns: FRAMEWORK_AST_PATTERNS.flask,
},
],
[SupportedLanguages.Java]: [
{
framework: 'spring',
entryPointMultiplier: 3.2,
reason: 'spring-annotation',
patterns: FRAMEWORK_AST_PATTERNS.spring,
},
{
framework: 'jaxrs',
entryPointMultiplier: 3.0,
reason: 'jaxrs-annotation',
patterns: FRAMEWORK_AST_PATTERNS.jaxrs,
},
],
[SupportedLanguages.Kotlin]: [
{
framework: 'spring-kotlin',
entryPointMultiplier: 3.2,
reason: 'spring-kotlin-annotation',
patterns: FRAMEWORK_AST_PATTERNS.spring,
},
{
framework: 'jaxrs',
entryPointMultiplier: 3.0,
reason: 'jaxrs-annotation',
patterns: FRAMEWORK_AST_PATTERNS.jaxrs,
},
{
framework: 'ktor',
entryPointMultiplier: 2.8,
reason: 'ktor-routing',
patterns: ['routing', 'embeddedServer', 'Application.module'],
},
{
framework: 'android-kotlin',
entryPointMultiplier: 2.5,
reason: 'android-annotation',
patterns: ['@AndroidEntryPoint', 'AppCompatActivity', 'Fragment('],
},
],
[SupportedLanguages.CSharp]: [
{
framework: 'aspnet',
entryPointMultiplier: 3.2,
reason: 'aspnet-attribute',
patterns: FRAMEWORK_AST_PATTERNS.aspnet,
},
{
framework: 'signalr',
entryPointMultiplier: 2.8,
reason: 'signalr-attribute',
patterns: FRAMEWORK_AST_PATTERNS.signalr,
},
{
framework: 'blazor',
entryPointMultiplier: 2.5,
reason: 'blazor-attribute',
patterns: FRAMEWORK_AST_PATTERNS.blazor,
},
{
framework: 'efcore',
entryPointMultiplier: 2.0,
reason: 'efcore-pattern',
patterns: FRAMEWORK_AST_PATTERNS.efcore,
},
],
[SupportedLanguages.PHP]: [
{
framework: 'laravel',
entryPointMultiplier: 3.0,
reason: 'php-route-attribute',
patterns: FRAMEWORK_AST_PATTERNS.laravel,
},
],
[SupportedLanguages.Go]: [
{
framework: 'go-http',
entryPointMultiplier: 2.5,
reason: 'go-http-handler',
patterns: FRAMEWORK_AST_PATTERNS['go-http'],
},
{
framework: 'gin',
entryPointMultiplier: 3.0,
reason: 'gin-handler',
patterns: FRAMEWORK_AST_PATTERNS.gin,
},
{
framework: 'echo',
entryPointMultiplier: 3.0,
reason: 'echo-handler',
patterns: FRAMEWORK_AST_PATTERNS.echo,
},
{
framework: 'fiber',
entryPointMultiplier: 3.0,
reason: 'fiber-handler',
patterns: FRAMEWORK_AST_PATTERNS.fiber,
},
{
framework: 'go-grpc',
entryPointMultiplier: 2.8,
reason: 'grpc-service',
patterns: FRAMEWORK_AST_PATTERNS['go-grpc'],
},
],
[SupportedLanguages.Rust]: [
{
framework: 'actix-web',
entryPointMultiplier: 3.0,
reason: 'actix-attribute',
patterns: FRAMEWORK_AST_PATTERNS.actix,
},
{
framework: 'axum',
entryPointMultiplier: 3.0,
reason: 'axum-routing',
patterns: FRAMEWORK_AST_PATTERNS.axum,
},
{
framework: 'rocket',
entryPointMultiplier: 3.0,
reason: 'rocket-attribute',
patterns: FRAMEWORK_AST_PATTERNS.rocket,
},
{
framework: 'tokio',
entryPointMultiplier: 2.5,
reason: 'tokio-runtime',
patterns: FRAMEWORK_AST_PATTERNS.tokio,
},
],
[SupportedLanguages.C]: [], // C has no framework-specific AST patterns (POSIX/socket patterns are in entry-point-scoring)
[SupportedLanguages.CPlusPlus]: [
{
framework: 'qt',
entryPointMultiplier: 2.8,
reason: 'qt-macro',
patterns: FRAMEWORK_AST_PATTERNS.qt,
},
],
[SupportedLanguages.Swift]: [
{
framework: 'uikit',
entryPointMultiplier: 2.5,
reason: 'uikit-lifecycle',
patterns: FRAMEWORK_AST_PATTERNS.uikit,
},
{
framework: 'swiftui',
entryPointMultiplier: 2.8,
reason: 'swiftui-pattern',
patterns: FRAMEWORK_AST_PATTERNS.swiftui,
},
{
framework: 'vapor',
entryPointMultiplier: 3.0,
reason: 'vapor-routing',
patterns: FRAMEWORK_AST_PATTERNS.vapor,
},
],
[SupportedLanguages.Ruby]: [
{
framework: 'rails',
entryPointMultiplier: 3.0,
reason: 'rails-pattern',
patterns: FRAMEWORK_AST_PATTERNS.rails,
},
{
framework: 'sinatra',
entryPointMultiplier: 2.8,
reason: 'sinatra-pattern',
patterns: FRAMEWORK_AST_PATTERNS.sinatra,
},
],
[SupportedLanguages.Dart]: [
{
framework: 'flutter',
entryPointMultiplier: 2.5,
reason: 'flutter-widget',
patterns: FRAMEWORK_AST_PATTERNS.flutter,
},
{
framework: 'riverpod',
entryPointMultiplier: 2.8,
reason: 'riverpod-pattern',
patterns: FRAMEWORK_AST_PATTERNS.riverpod,
},
],
[SupportedLanguages.Vue]: [], // Vue uses TypeScript AST framework detection
[SupportedLanguages.Cobol]: [], // Standalone regex processor — no AST framework patterns
} satisfies Record<SupportedLanguages, AstFrameworkPatternConfig[]>;
/** Pre-lowercased patterns for O(1) pattern matching at runtime */
const AST_PATTERNS_LOWERED: Record<
string,
Array<{ framework: string; entryPointMultiplier: number; reason: string; patterns: string[] }>
> = Object.fromEntries(
Object.entries(AST_FRAMEWORK_PATTERNS_BY_LANGUAGE).map(([lang, cfgs]) => [
/** Pre-lowercased patterns for O(1) pattern matching at runtime — built from providers. */
const AST_PATTERNS_LOWERED: Record<string, AstFrameworkPatternConfig[]> = Object.fromEntries(
Object.entries(providers).map(([lang, provider]) => [
lang,
cfgs.map((cfg) => ({ ...cfg, patterns: cfg.patterns.map((p) => p.toLowerCase()) })),
(provider.astFrameworkPatterns ?? []).map((cfg) => ({
...cfg,
patterns: cfg.patterns.map((p) => p.toLowerCase()),
})),
]),
);

View file

@ -78,6 +78,14 @@ export type ImportSemantics =
| 'namespace'
| 'explicit-reexport';
/** Configuration for AST-based framework detection patterns. */
export interface AstFrameworkPatternConfig {
framework: string;
entryPointMultiplier: number;
reason: string;
patterns: string[];
}
/**
* Everything a language needs to provide.
* Required fields must be explicitly set; optional fields have defaults
@ -89,6 +97,16 @@ interface LanguageProviderConfig {
/** File extensions that map to this language (e.g., ['.ts', '.tsx']) */
readonly extensions: readonly string[];
/** Entry-point function name patterns specific to this language.
* Merged with universal patterns at runtime for process detection scoring.
* Default: [] (only universal patterns apply). */
readonly entryPointPatterns?: readonly RegExp[];
/** AST-based framework detection patterns for this language.
* Used by detectFrameworkFromAST to identify framework entry points.
* Default: [] (no AST framework detection for this language). */
readonly astFrameworkPatterns?: readonly AstFrameworkPatternConfig[];
// ── Parser ────────────────────────────────────────────────────────
/** Parse strategy: 'tree-sitter' (default) uses AST parsing via tree-sitter.
* 'standalone' means the language has its own regex-based processor and

View file

@ -12,6 +12,7 @@ import { SupportedLanguages } from 'gitnexus-shared';
import { createClassExtractor } from '../class-extractors/generic.js';
import { cClassConfig, cppClassConfig } from '../class-extractors/configs/c-cpp.js';
import { defineLanguage } from '../language-provider.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { typeConfig as cCppConfig } from '../type-extractors/c-cpp.js';
import { cCppExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
@ -317,6 +318,38 @@ const cppLabelOverride: NonNullable<LanguageProvider['labelOverride']> = (
export const cProvider = defineLanguage({
id: SupportedLanguages.C,
extensions: ['.c'],
entryPointPatterns: [
/^main$/,
/^init_/,
/_init$/,
/^start_/,
/_start$/,
/^run_/,
/_run$/,
/^stop_/,
/_stop$/,
/^open_/,
/_open$/,
/^close_/,
/_close$/,
/^create_/,
/_create$/,
/^destroy_/,
/_destroy$/,
/^handle_/,
/_handler$/,
/_callback$/,
/^cmd_/,
/^server_/,
/^client_/,
/^session_/,
/^window_/,
/^key_/,
/^input_/,
/^output_/,
/^notify_/,
/^control_/,
],
treeSitterQueries: C_QUERIES,
typeConfig: cCppConfig,
exportChecker: cCppExportChecker,
@ -338,6 +371,44 @@ export const cProvider = defineLanguage({
export const cppProvider = defineLanguage({
id: SupportedLanguages.CPlusPlus,
extensions: ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hxx', '.hh'],
entryPointPatterns: [
/^main$/,
/^init_/,
/_init$/,
/^Create[A-Z]/,
/^create_/,
/^Run$/,
/^run$/,
/^Start$/,
/^start$/,
/^handle_/,
/_handler$/,
/_callback$/,
/^OnEvent/,
/^on_/,
/::Run$/,
/::Start$/,
/::Init$/,
/::Execute$/,
],
astFrameworkPatterns: [
{
framework: 'qt',
entryPointMultiplier: 2.8,
reason: 'qt-macro',
patterns: [
'Q_OBJECT',
'Q_INVOKABLE',
'Q_PROPERTY',
'Q_SIGNALS',
'Q_SLOTS',
'Q_SIGNAL',
'Q_SLOT',
'QWidget',
'QApplication',
],
},
],
treeSitterQueries: CPP_QUERIES,
typeConfig: cCppConfig,
exportChecker: cCppExportChecker,

View file

@ -16,6 +16,8 @@ export const cobolProvider = defineLanguage({
id: SupportedLanguages.Cobol,
parseStrategy: 'standalone',
extensions: [], // COBOL files detected by cobol-processor's isCobolFile/isJclFile
entryPointPatterns: [],
astFrameworkPatterns: [],
treeSitterQueries: '',
typeConfig: {
declarationNodeTypes: new Set(),

View file

@ -16,6 +16,7 @@ import { createImportResolver } from '../import-resolvers/resolver-factory.js';
import { csharpImportConfig } from '../import-resolvers/configs/csharp.js';
import { extractCSharpNamedBindings } from '../named-bindings/csharp.js';
import { CSHARP_QUERIES } from '../tree-sitter-queries.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { csharpCallConfig } from '../call-extractors/configs/csharp.js';
import { createFieldExtractor } from '../field-extractors/generic.js';
@ -135,6 +136,55 @@ const BUILT_INS: ReadonlySet<string> = new Set([
export const csharpProvider = defineLanguage({
id: SupportedLanguages.CSharp,
extensions: ['.cs'],
entryPointPatterns: [
/^(Get|Post|Put|Delete|Patch)/,
/Action$/,
/^On[A-Z]/,
/Async$/,
/^Configure$/,
/^ConfigureServices$/,
/^Handle$/,
/^Execute$/,
/^Invoke$/,
/^Map[A-Z]/,
/Service$/,
/^Seed/,
],
astFrameworkPatterns: [
{
framework: 'aspnet',
entryPointMultiplier: 3.2,
reason: 'aspnet-attribute',
patterns: [
'[ApiController]',
'[HttpGet]',
'[HttpPost]',
'[HttpPut]',
'[HttpDelete]',
'[Route]',
'[Authorize]',
'[AllowAnonymous]',
],
},
{
framework: 'signalr',
entryPointMultiplier: 2.8,
reason: 'signalr-attribute',
patterns: ['[HubMethodName]', ': Hub', ': Hub<'],
},
{
framework: 'blazor',
entryPointMultiplier: 2.5,
reason: 'blazor-attribute',
patterns: ['@page', '[Parameter]', '@inject'],
},
{
framework: 'efcore',
entryPointMultiplier: 2.0,
reason: 'efcore-pattern',
patterns: ['DbContext', 'DbSet<', 'OnModelCreating'],
},
] satisfies AstFrameworkPatternConfig[],
treeSitterQueries: CSHARP_QUERIES,
typeConfig: csharpConfig,
exportChecker: csharpExportChecker,

View file

@ -17,6 +17,7 @@ import { SupportedLanguages } from 'gitnexus-shared';
import { createClassExtractor } from '../class-extractors/generic.js';
import { dartClassConfig } from '../class-extractors/configs/dart.js';
import { defineLanguage } from '../language-provider.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { typeConfig as dartConfig } from '../type-extractors/dart.js';
import { dartExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
@ -93,6 +94,42 @@ const BUILT_INS: ReadonlySet<string> = new Set([
export const dartProvider = defineLanguage({
id: SupportedLanguages.Dart,
extensions: ['.dart'],
entryPointPatterns: [
/^main$/,
/^build$/,
/^createState$/,
/^initState$/,
/^dispose$/,
/^didChangeDependencies$/,
/^didUpdateWidget$/,
/^runApp$/,
/^onEvent$/,
/^mapEventToState$/,
],
astFrameworkPatterns: [
{
framework: 'flutter',
entryPointMultiplier: 2.5,
reason: 'flutter-widget',
patterns: [
'StatelessWidget',
'StatefulWidget',
'BuildContext',
'Widget build',
'ChangeNotifier',
'GetxController',
'Cubit<',
'Bloc<',
'ConsumerWidget',
],
},
{
framework: 'riverpod',
entryPointMultiplier: 2.8,
reason: 'riverpod-pattern',
patterns: ['@riverpod', 'ref.watch', 'ref.read', 'AsyncNotifier', 'Notifier'],
},
],
treeSitterQueries: DART_QUERIES,
typeConfig: dartConfig,
exportChecker: dartExportChecker,

View file

@ -18,6 +18,7 @@ import { goExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
import { goImportConfig } from '../import-resolvers/configs/go.js';
import { GO_QUERIES } from '../tree-sitter-queries.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { createFieldExtractor } from '../field-extractors/generic.js';
import { goConfig as goFieldConfig } from '../field-extractors/configs/go.js';
import { createMethodExtractor } from '../method-extractors/generic.js';
@ -32,6 +33,45 @@ import { goHeritageConfig } from '../heritage-extractors/configs/go.js';
export const goProvider = defineLanguage({
id: SupportedLanguages.Go,
extensions: ['.go'],
entryPointPatterns: [/Handler$/, /^Serve/, /^New[A-Z]/, /^Make[A-Z]/],
astFrameworkPatterns: [
{
framework: 'go-http',
entryPointMultiplier: 2.5,
reason: 'go-http-handler',
patterns: [
'http.Handler',
'http.HandlerFunc',
'ServeHTTP',
'http.ResponseWriter',
'http.Request',
],
},
{
framework: 'gin',
entryPointMultiplier: 3.0,
reason: 'gin-handler',
patterns: ['gin.Context', 'gin.Default', 'gin.New'],
},
{
framework: 'echo',
entryPointMultiplier: 3.0,
reason: 'echo-handler',
patterns: ['echo.Context', 'echo.New'],
},
{
framework: 'fiber',
entryPointMultiplier: 3.0,
reason: 'fiber-handler',
patterns: ['fiber.Ctx', 'fiber.New', 'fiber.App'],
},
{
framework: 'go-grpc',
entryPointMultiplier: 2.8,
reason: 'grpc-service',
patterns: ['grpc.Server', 'RegisterServer', 'pb.Unimplemented'],
},
] satisfies AstFrameworkPatternConfig[],
treeSitterQueries: GO_QUERIES,
typeConfig: goConfig,
exportChecker: goExportChecker,

View file

@ -11,6 +11,7 @@ import { SupportedLanguages } from 'gitnexus-shared';
import { createClassExtractor } from '../class-extractors/generic.js';
import { javaClassConfig } from '../class-extractors/configs/jvm.js';
import { defineLanguage } from '../language-provider.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { javaTypeConfig } from '../type-extractors/jvm.js';
import { javaExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
@ -30,6 +31,27 @@ import { createHeritageExtractor } from '../heritage-extractors/generic.js';
export const javaProvider = defineLanguage({
id: SupportedLanguages.Java,
extensions: ['.java'],
entryPointPatterns: [/^do[A-Z]/, /^create[A-Z]/, /^build[A-Z]/, /Service$/],
astFrameworkPatterns: [
{
framework: 'spring',
entryPointMultiplier: 3.2,
reason: 'spring-annotation',
patterns: [
'@RestController',
'@Controller',
'@GetMapping',
'@PostMapping',
'@RequestMapping',
],
},
{
framework: 'jaxrs',
entryPointMultiplier: 3.0,
reason: 'jaxrs-annotation',
patterns: ['@Path', '@GET', '@POST', '@PUT', '@DELETE'],
},
] satisfies AstFrameworkPatternConfig[],
treeSitterQueries: JAVA_QUERIES,
typeConfig: javaTypeConfig,
exportChecker: javaExportChecker,

View file

@ -18,6 +18,7 @@ import { kotlinImportConfig } from '../import-resolvers/configs/jvm.js';
import { extractKotlinNamedBindings } from '../named-bindings/kotlin.js';
import { appendKotlinWildcard } from '../import-resolvers/jvm.js';
import { KOTLIN_QUERIES } from '../tree-sitter-queries.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import type { SyntaxNode } from '../utils/ast-helpers.js';
import { createCallExtractor } from '../call-extractors/generic.js';
import { kotlinCallConfig } from '../call-extractors/configs/jvm.js';
@ -105,6 +106,47 @@ const BUILT_INS: ReadonlySet<string> = new Set([
export const kotlinProvider = defineLanguage({
id: SupportedLanguages.Kotlin,
extensions: ['.kt', '.kts'],
entryPointPatterns: [
/^on(Create|Start|Resume|Pause|Stop|Destroy)$/,
/^do[A-Z]/,
/^create[A-Z]/,
/^build[A-Z]/,
/ViewModel$/,
/^module$/,
/Service$/,
],
astFrameworkPatterns: [
{
framework: 'spring-kotlin',
entryPointMultiplier: 3.2,
reason: 'spring-kotlin-annotation',
patterns: [
'@RestController',
'@Controller',
'@GetMapping',
'@PostMapping',
'@RequestMapping',
],
},
{
framework: 'jaxrs',
entryPointMultiplier: 3.0,
reason: 'jaxrs-annotation',
patterns: ['@Path', '@GET', '@POST', '@PUT', '@DELETE'],
},
{
framework: 'ktor',
entryPointMultiplier: 2.8,
reason: 'ktor-routing',
patterns: ['routing', 'embeddedServer', 'Application.module'],
},
{
framework: 'android-kotlin',
entryPointMultiplier: 2.5,
reason: 'android-annotation',
patterns: ['@AndroidEntryPoint', 'AppCompatActivity', 'Fragment('],
},
] satisfies AstFrameworkPatternConfig[],
treeSitterQueries: KOTLIN_QUERIES,
typeConfig: kotlinTypeConfig,
exportChecker: kotlinExportChecker,

View file

@ -10,6 +10,7 @@ import { SupportedLanguages } from 'gitnexus-shared';
import { createClassExtractor } from '../class-extractors/generic.js';
import { phpClassConfig } from '../class-extractors/configs/php.js';
import { defineLanguage } from '../language-provider.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { typeConfig as phpConfig } from '../type-extractors/php.js';
import { phpExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
@ -239,6 +240,41 @@ function isPhpRouteFile(filePath: string): boolean {
export const phpProvider = defineLanguage({
id: SupportedLanguages.PHP,
extensions: ['.php', '.phtml', '.php3', '.php4', '.php5', '.php8'],
entryPointPatterns: [
/Controller$/,
/^handle$/,
/^execute$/,
/^boot$/,
/^register$/,
/^__invoke$/,
/^(index|show|store|update|destroy|create|edit)$/,
/^(get|post|put|delete|patch)[A-Z]/,
/^run$/,
/^fire$/,
/^dispatch$/,
/Service$/,
/Repository$/,
/^find$/,
/^findAll$/,
/^save$/,
/^delete$/,
],
astFrameworkPatterns: [
{
framework: 'laravel',
entryPointMultiplier: 3.0,
reason: 'php-route-attribute',
patterns: [
'Route::get',
'Route::post',
'Route::put',
'Route::delete',
'Route::resource',
'Route::apiResource',
'#[Route(',
],
},
],
treeSitterQueries: PHP_QUERIES,
typeConfig: phpConfig,
exportChecker: phpExportChecker,

View file

@ -15,6 +15,7 @@ import { SupportedLanguages } from 'gitnexus-shared';
import { createClassExtractor } from '../class-extractors/generic.js';
import { pythonClassConfig } from '../class-extractors/configs/python.js';
import { defineLanguage } from '../language-provider.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { typeConfig as pythonConfig } from '../type-extractors/python.js';
import { pythonExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
@ -106,6 +107,21 @@ function normalizePythonStringLiteral(text: string): string | undefined {
export const pythonProvider = defineLanguage({
id: SupportedLanguages.Python,
extensions: ['.py'],
entryPointPatterns: [/^app$/, /^(get|post|put|delete|patch)_/i, /^api_/, /^view_/],
astFrameworkPatterns: [
{
framework: 'fastapi',
entryPointMultiplier: 3.0,
reason: 'fastapi-decorator',
patterns: ['@app.get', '@app.post', '@app.put', '@app.delete', '@router.get'],
},
{
framework: 'flask',
entryPointMultiplier: 2.8,
reason: 'flask-decorator',
patterns: ['@app.route', '@blueprint.route'],
},
] satisfies AstFrameworkPatternConfig[],
treeSitterQueries: PYTHON_QUERIES,
typeConfig: pythonConfig,
exportChecker: pythonExportChecker,

View file

@ -12,6 +12,7 @@ import type { NodeLabel } from 'gitnexus-shared';
import { createClassExtractor } from '../class-extractors/generic.js';
import { rubyClassConfig } from '../class-extractors/configs/ruby.js';
import { defineLanguage } from '../language-provider.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import type { SyntaxNode } from '../utils/ast-helpers.js';
import { typeConfig as rubyConfig } from '../type-extractors/ruby.js';
import { routeRubyCall } from '../call-routing.js';
@ -151,6 +152,31 @@ const rubyResolveEnclosingOwner = (node: SyntaxNode): SyntaxNode | null => {
export const rubyProvider = defineLanguage({
id: SupportedLanguages.Ruby,
extensions: ['.rb', '.rake', '.gemspec'],
entryPointPatterns: [/^call$/, /^perform$/, /^execute$/],
astFrameworkPatterns: [
{
framework: 'rails',
entryPointMultiplier: 3.0,
reason: 'rails-pattern',
patterns: [
'ApplicationController',
'ApplicationRecord',
'ActiveRecord::Base',
'before_action',
'after_action',
'has_many',
'belongs_to',
'has_one',
'validates',
],
},
{
framework: 'sinatra',
entryPointMultiplier: 2.8,
reason: 'sinatra-pattern',
patterns: ['Sinatra::Base', 'Sinatra::Application'],
},
],
treeSitterQueries: RUBY_QUERIES,
typeConfig: rubyConfig,
exportChecker: rubyExportChecker,

View file

@ -22,6 +22,7 @@ import { createImportResolver } from '../import-resolvers/resolver-factory.js';
import { rustImportConfig } from '../import-resolvers/configs/rust.js';
import { extractRustNamedBindings } from '../named-bindings/rust.js';
import { RUST_QUERIES } from '../tree-sitter-queries.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { createFieldExtractor } from '../field-extractors/generic.js';
import { rustConfig as rustFieldConfig } from '../field-extractors/configs/rust.js';
import { createMethodExtractor } from '../method-extractors/generic.js';
@ -121,6 +122,41 @@ const BUILT_INS: ReadonlySet<string> = new Set([
export const rustProvider = defineLanguage({
id: SupportedLanguages.Rust,
extensions: ['.rs'],
entryPointPatterns: [/^(get|post|put|delete)_handler$/i, /^handle_/, /^new$/, /^run$/, /^spawn/],
astFrameworkPatterns: [
{
framework: 'actix-web',
entryPointMultiplier: 3.0,
reason: 'actix-attribute',
patterns: [
'#[get',
'#[post',
'#[put',
'#[delete',
'#[actix_web',
'HttpRequest',
'HttpResponse',
],
},
{
framework: 'axum',
entryPointMultiplier: 3.0,
reason: 'axum-routing',
patterns: ['Router::new', 'axum::extract', 'axum::routing'],
},
{
framework: 'rocket',
entryPointMultiplier: 3.0,
reason: 'rocket-attribute',
patterns: ['#[get', '#[post', '#[launch', 'rocket::'],
},
{
framework: 'tokio',
entryPointMultiplier: 2.5,
reason: 'tokio-runtime',
patterns: ['#[tokio::main]', '#[tokio::test]'],
},
] satisfies AstFrameworkPatternConfig[],
treeSitterQueries: RUST_QUERIES,
typeConfig: rustConfig,
exportChecker: rustExportChecker,

View file

@ -15,6 +15,7 @@ import type { NodeLabel, SymbolDefinition } from 'gitnexus-shared';
import { createClassExtractor } from '../class-extractors/generic.js';
import { swiftClassConfig } from '../class-extractors/configs/swift.js';
import { defineLanguage } from '../language-provider.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { typeConfig as swiftConfig } from '../type-extractors/swift.js';
import { swiftExportChecker } from '../export-detection.js';
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
@ -259,6 +260,60 @@ const BUILT_INS: ReadonlySet<string> = new Set([
export const swiftProvider = defineLanguage({
id: SupportedLanguages.Swift,
extensions: ['.swift'],
entryPointPatterns: [
/^viewDidLoad$/,
/^viewWillAppear$/,
/^viewDidAppear$/,
/^viewWillDisappear$/,
/^viewDidDisappear$/,
/^application\(/,
/^scene\(/,
/^body$/,
/Coordinator$/,
/^sceneDidBecomeActive$/,
/^sceneWillResignActive$/,
/^didFinishLaunchingWithOptions$/,
/ViewController$/,
/^configure[A-Z]/,
/^setup[A-Z]/,
/^makeBody$/,
],
astFrameworkPatterns: [
{
framework: 'uikit',
entryPointMultiplier: 2.5,
reason: 'uikit-lifecycle',
patterns: [
'viewDidLoad',
'viewWillAppear',
'viewDidAppear',
'UIViewController',
'@IBOutlet',
'@IBAction',
'@objc',
],
},
{
framework: 'swiftui',
entryPointMultiplier: 2.8,
reason: 'swiftui-pattern',
patterns: [
'@main',
'WindowGroup',
'ContentView',
'@StateObject',
'@ObservedObject',
'@EnvironmentObject',
'@Published',
],
},
{
framework: 'vapor',
entryPointMultiplier: 3.0,
reason: 'vapor-routing',
patterns: ['app.get', 'app.post', 'req.content.decode', 'Vapor'],
},
],
treeSitterQueries: SWIFT_QUERIES,
typeConfig: swiftConfig,
exportChecker: swiftExportChecker,

View file

@ -10,6 +10,7 @@
import { SupportedLanguages } from 'gitnexus-shared';
import type { NodeLabel } from 'gitnexus-shared';
import { defineLanguage } from '../language-provider.js';
import type { AstFrameworkPatternConfig } from '../language-provider.js';
import { createClassExtractor } from '../class-extractors/generic.js';
import {
typescriptClassConfig,
@ -217,6 +218,29 @@ export const BUILT_INS: ReadonlySet<string> = new Set([
export const typescriptProvider = defineLanguage({
id: SupportedLanguages.TypeScript,
extensions: ['.ts', '.tsx'],
entryPointPatterns: [/^use[A-Z]/],
astFrameworkPatterns: [
{
framework: 'nestjs',
entryPointMultiplier: 3.2,
reason: 'nestjs-decorator',
patterns: ['@Controller', '@Get', '@Post', '@Put', '@Delete', '@Patch'],
},
{
framework: 'expo-router',
entryPointMultiplier: 2.5,
reason: 'expo-router-navigation',
patterns: [
'router.push',
'router.replace',
'router.navigate',
'useRouter',
'useLocalSearchParams',
'useSegments',
'expo-router',
],
},
] satisfies AstFrameworkPatternConfig[],
treeSitterQueries: TYPESCRIPT_QUERIES,
typeConfig: typescriptConfig,
exportChecker: tsExportChecker,
@ -256,6 +280,29 @@ export const typescriptProvider = defineLanguage({
export const javascriptProvider = defineLanguage({
id: SupportedLanguages.JavaScript,
extensions: ['.js', '.jsx'],
entryPointPatterns: [/^use[A-Z]/],
astFrameworkPatterns: [
{
framework: 'nestjs',
entryPointMultiplier: 3.2,
reason: 'nestjs-decorator',
patterns: ['@Controller', '@Get', '@Post', '@Put', '@Delete', '@Patch'],
},
{
framework: 'expo-router',
entryPointMultiplier: 2.5,
reason: 'expo-router-navigation',
patterns: [
'router.push',
'router.replace',
'router.navigate',
'useRouter',
'useLocalSearchParams',
'useSegments',
'expo-router',
],
},
] satisfies AstFrameworkPatternConfig[],
treeSitterQueries: JAVASCRIPT_QUERIES,
typeConfig: typescriptConfig,
exportChecker: tsExportChecker,

View file

@ -68,6 +68,8 @@ const vueClassExtractor = createClassExtractor(vueClassConfig);
export const vueProvider = defineLanguage({
id: SupportedLanguages.Vue,
extensions: ['.vue'],
entryPointPatterns: [],
astFrameworkPatterns: [],
treeSitterQueries: TYPESCRIPT_QUERIES,
typeConfig: typescriptConfig,
exportChecker: tsExportChecker,

View file

@ -2,8 +2,8 @@ import { describe, it, expect } from 'vitest';
import {
detectFrameworkFromPath,
detectFrameworkFromAST,
FRAMEWORK_AST_PATTERNS,
} from '../../src/core/ingestion/framework-detection.js';
import { providers } from '../../src/core/ingestion/languages/index.js';
describe('detectFrameworkFromPath', () => {
describe('Next.js', () => {
@ -359,24 +359,26 @@ describe('detectFrameworkFromAST', () => {
});
});
describe('FRAMEWORK_AST_PATTERNS', () => {
it('has patterns for all expected frameworks', () => {
const expectedFrameworks = [
describe('provider astFrameworkPatterns', () => {
it('covers all expected frameworks across providers', () => {
const expectedFrameworks = new Set([
'nestjs',
'expo-router',
'express',
'fastapi',
'flask',
'spring',
'jaxrs',
'aspnet',
'signalr',
'blazor',
'efcore',
'go-http',
'gin',
'echo',
'fiber',
'go-grpc',
'laravel',
'actix',
'actix-web',
'axum',
'rocket',
'tokio',
@ -386,12 +388,21 @@ describe('FRAMEWORK_AST_PATTERNS', () => {
'vapor',
'rails',
'sinatra',
];
'spring-kotlin',
'ktor',
'android-kotlin',
'flutter',
'riverpod',
]);
const foundFrameworks = new Set<string>();
for (const provider of Object.values(providers)) {
for (const cfg of provider.astFrameworkPatterns ?? []) {
foundFrameworks.add(cfg.framework);
expect(cfg.patterns.length).toBeGreaterThan(0);
}
}
for (const fw of expectedFrameworks) {
expect(FRAMEWORK_AST_PATTERNS).toHaveProperty(fw);
expect(
FRAMEWORK_AST_PATTERNS[fw as keyof typeof FRAMEWORK_AST_PATTERNS].length,
).toBeGreaterThan(0);
expect(foundFrameworks.has(fw), `missing framework: ${fw}`).toBe(true);
}
});
});