Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion gitnexus-web/src/config/supported-languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export enum SupportedLanguages {
Ruby = 'ruby',
Kotlin = 'kotlin',
Swift = 'swift',
}
Dart = 'dart',
}
1 change: 1 addition & 0 deletions gitnexus-web/src/core/ingestion/call-routing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const callRouters = {
[SupportedLanguages.C]: noRouting,
[SupportedLanguages.Ruby]: routeRubyCall,
[SupportedLanguages.Kotlin]: noRouting,
[SupportedLanguages.Dart]: noRouting,
} satisfies Record<SupportedLanguages, CallRouter>;

// ── Result types ────────────────────────────────────────────────────────────
Expand Down
31 changes: 31 additions & 0 deletions gitnexus-web/src/core/ingestion/framework-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,33 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null
return { framework: 'ios-router', entryPointMultiplier: 2.0, reason: 'ios-router' };
}

// ========== DART / FLUTTER ==========

// Flutter main entry point
if (p.endsWith('/main.dart')) {
return { framework: 'flutter', entryPointMultiplier: 3.0, reason: 'flutter-main' };
}

// Flutter screens/pages (high priority - route entry points)
if ((p.includes('/lib/screens/') || p.includes('/lib/pages/')) && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 2.5, reason: 'flutter-screen' };
}

// Flutter BLoC / controllers (state management entry points)
if ((p.includes('/lib/bloc/') || p.includes('/lib/controllers/') || p.includes('/lib/cubit/')) && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 2.0, reason: 'flutter-state-management' };
}

// Flutter services
if (p.includes('/lib/services/') && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 1.8, reason: 'flutter-service' };
}

// Flutter widgets (reusable components)
if (p.includes('/lib/widgets/') && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 1.5, reason: 'flutter-widget' };
}

// ========== GENERIC PATTERNS ==========

// Any language: index files in API folders
Expand Down Expand Up @@ -366,6 +393,10 @@ export const FRAMEWORK_AST_PATTERNS = {
'axum': ['Router::new'],
'rocket': ['#[get', '#[post'],

// Dart/Flutter
'flutter': ['StatelessWidget', 'StatefulWidget', 'BuildContext', 'Widget build',
'ChangeNotifier', 'GetxController', 'Cubit<', 'Bloc<'],

// Swift/iOS
'uikit': ['viewDidLoad', 'viewWillAppear', 'viewDidAppear', 'UIViewController'],
'swiftui': ['@main', 'WindowGroup', 'ContentView', '@StateObject', '@ObservedObject'],
Expand Down
4 changes: 4 additions & 0 deletions gitnexus-web/src/core/ingestion/parsing-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ const isNodeExported = (node: any, name: string, language: string): boolean => {
case 'ruby':
return true;

// Dart: Public if no leading underscore (same convention as Python)
case 'dart':
return !name.startsWith('_');

default:
return false;
}
Expand Down
97 changes: 97 additions & 0 deletions gitnexus-web/src/core/ingestion/tree-sitter-queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,102 @@ export const SWIFT_QUERIES = `
(inheritance_specifier inherits_from: (user_type (type_identifier) @heritage.extends))) @heritage
`;

// Dart queries - works with tree-sitter-dart
export const DART_QUERIES = `
; ── Classes ──────────────────────────────────────────────────────────────────
(class_definition
name: (identifier) @name) @definition.class

; ── Mixins ───────────────────────────────────────────────────────────────────
(mixin_declaration
(identifier) @name) @definition.trait

; ── Extensions ───────────────────────────────────────────────────────────────
(extension_declaration
name: (identifier) @name) @definition.class

; ── Enums ────────────────────────────────────────────────────────────────────
(enum_declaration
name: (identifier) @name) @definition.enum

; ── Type aliases ─────────────────────────────────────────────────────────────
; Anchor "=" after the name to avoid capturing the RHS type
(type_alias
(type_identifier) @name
"=") @definition.type

; ── Top-level functions (parent is program, not method_signature) ────────────
(program
(function_signature
name: (identifier) @name) @definition.function)

; ── Abstract method declarations (function_signature inside class body declaration) ──
(declaration
(function_signature
name: (identifier) @name)) @definition.method

; ── Methods (inside class/mixin/extension bodies) ────────────────────────────
(method_signature
(function_signature
name: (identifier) @name)) @definition.method

; ── Constructors ─────────────────────────────────────────────────────────────
(constructor_signature
name: (identifier) @name) @definition.constructor

; ── Factory constructors ─────────────────────────────────────────────────────
(method_signature
(factory_constructor_signature
(identifier) @name)) @definition.constructor

; ── Getters ──────────────────────────────────────────────────────────────────
(method_signature
(getter_signature
name: (identifier) @name)) @definition.property

; ── Setters ──────────────────────────────────────────────────────────────────
(method_signature
(setter_signature
name: (identifier) @name)) @definition.property

; ── Imports ──────────────────────────────────────────────────────────────────
(import_or_export
(library_import
(import_specification
(configurable_uri) @import.source))) @import

; ── Calls: direct function/constructor calls (identifier immediately before argument_part) ──
(expression_statement
(identifier) @call.name
.
(selector (argument_part))) @call

; ── Calls: method calls (obj.method()) ───────────────────────────────────────
(expression_statement
(selector
(unconditional_assignable_selector
(identifier) @call.name))) @call

; ── Heritage: extends ────────────────────────────────────────────────────────
(class_definition
name: (identifier) @heritage.class
superclass: (superclass
(type_identifier) @heritage.extends)) @heritage

; ── Heritage: implements ─────────────────────────────────────────────────────
(class_definition
name: (identifier) @heritage.class
interfaces: (interfaces
(type_identifier) @heritage.implements)) @heritage.impl

; ── Heritage: with (mixins) ──────────────────────────────────────────────────
(class_definition
name: (identifier) @heritage.class
superclass: (superclass
(mixins
(type_identifier) @heritage.trait))) @heritage
`;

export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
[SupportedLanguages.TypeScript]: TYPESCRIPT_QUERIES,
[SupportedLanguages.JavaScript]: JAVASCRIPT_QUERIES,
Expand All @@ -497,5 +593,6 @@ export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
[SupportedLanguages.Ruby]: RUBY_QUERIES,
[SupportedLanguages.Kotlin]: '', // Kotlin WASM parser not yet available for web
[SupportedLanguages.Swift]: SWIFT_QUERIES,
[SupportedLanguages.Dart]: DART_QUERIES,
};

1 change: 1 addition & 0 deletions gitnexus-web/src/core/ingestion/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const getLanguageFromFilename = (filename: string): SupportedLanguages |
}
// Swift
if (filename.endsWith('.swift')) return SupportedLanguages.Swift;
if (filename.endsWith('.dart')) return SupportedLanguages.Dart;
return null;
};

1 change: 1 addition & 0 deletions gitnexus-web/src/core/tree-sitter/parser-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const getWasmPath = (language: SupportedLanguages, filePath?: string): string =>
[SupportedLanguages.Ruby]: '/wasm/ruby/tree-sitter-ruby.wasm',
[SupportedLanguages.Kotlin]: '', // Kotlin WASM parser not yet available for web
[SupportedLanguages.Swift]: '/wasm/swift/tree-sitter-swift.wasm',
[SupportedLanguages.Dart]: '/wasm/dart/tree-sitter-dart.wasm',
};

return languageFileMap[language];
Expand Down
28 changes: 28 additions & 0 deletions gitnexus/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions gitnexus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
"uuid": "^13.0.0"
},
"optionalDependencies": {
"tree-sitter-dart": "github:UserNobody14/tree-sitter-dart#0fc19c3a57b1109802af41d2b8f60d8835c5da3a",
"tree-sitter-kotlin": "^0.3.8",
"tree-sitter-swift": "0.7.1"
},
Expand Down
3 changes: 2 additions & 1 deletion gitnexus/src/config/supported-languages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,5 @@ export enum SupportedLanguages {
PHP = 'php',
Kotlin = 'kotlin',
Swift = 'swift',
}
Dart = 'dart',
}
14 changes: 14 additions & 0 deletions gitnexus/src/core/ingestion/entry-point-scoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,20 @@ export const ENTRY_POINT_PATTERNS = {
/^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
],
} satisfies Record<SupportedLanguages, RegExp[]>;

/** Pre-computed merged patterns (universal + language-specific) to avoid per-call array allocation. */
Expand Down
3 changes: 3 additions & 0 deletions gitnexus/src/core/ingestion/export-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,6 @@ export const swiftExportChecker: ExportChecker = (node, _name) => {
/** Ruby: all top-level definitions are public (no export syntax). */
export const rubyExportChecker: ExportChecker = (_node, _name) => true;

/** Dart: public if no leading underscore (convention, same as Python). */
export const dartExportChecker: ExportChecker = (_node, name) => !name.startsWith('_');

41 changes: 41 additions & 0 deletions gitnexus/src/core/ingestion/framework-detection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,38 @@ export function detectFrameworkFromPath(filePath: string): FrameworkHint | null
return { framework: 'ios-router', entryPointMultiplier: 2.0, reason: 'ios-router' };
}

// ========== DART / FLUTTER ==========

// Flutter main/app entry points
if (p.endsWith('/main.dart') || p.endsWith('/app.dart')) {
return { framework: 'flutter', entryPointMultiplier: 3.0, reason: 'flutter-main' };
}

// Flutter screens/pages/views (high priority - route entry points)
if ((p.includes('/screens/') || p.includes('/pages/') || p.includes('/views/')) && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 2.5, reason: 'flutter-screen' };
}

// Flutter routes
if (p.includes('/routes/') && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 2.5, reason: 'flutter-routes' };
}

// Flutter BLoC / controllers / presentation (state management entry points)
if ((p.includes('/bloc/') || p.includes('/controllers/') || p.includes('/cubit/') || p.includes('/presentation/')) && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 2.0, reason: 'flutter-state-management' };
}

// Flutter services / domain
if ((p.includes('/services/') || p.includes('/domain/')) && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 1.8, reason: 'flutter-service' };
}

// Flutter widgets (reusable components)
if (p.includes('/widgets/') && p.endsWith('.dart')) {
return { framework: 'flutter', entryPointMultiplier: 1.5, reason: 'flutter-widget' };
}

// ========== GENERIC PATTERNS ==========

// Any language: index files in API folders
Expand Down Expand Up @@ -478,6 +510,11 @@ export const FRAMEWORK_AST_PATTERNS = {
'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 {
Expand Down Expand Up @@ -545,6 +582,10 @@ export const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
{ 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 },
],
} satisfies Record<SupportedLanguages, AstFrameworkPatternConfig[]>;

/** Pre-lowercased patterns for O(1) pattern matching at runtime */
Expand Down
44 changes: 44 additions & 0 deletions gitnexus/src/core/ingestion/import-resolvers/dart.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Dart import resolution.
* Handles package: imports (local packages) and relative imports.
* SDK imports (dart:*) and external packages are skipped.
*/

import type { ImportResult, ResolveCtx } from './types.js';
import { resolveStandard } from './standard.js';
import { SupportedLanguages } from '../../../config/supported-languages.js';

export function resolveDartImport(
rawImportPath: string,
filePath: string,
ctx: ResolveCtx,
): ImportResult {
// Strip surrounding quotes from configurable_uri capture
const stripped = rawImportPath.replace(/^['"]|['"]$/g, '');

// Skip dart: SDK imports (dart:async, dart:io, etc.)
if (stripped.startsWith('dart:')) return null;

// Local package: imports → resolve to lib/<path>
if (stripped.startsWith('package:')) {
const slashIdx = stripped.indexOf('/');
if (slashIdx === -1) return null;
const relPath = stripped.slice(slashIdx + 1);
const candidates = [`lib/${relPath}`, relPath];
const files: string[] = [];
for (const candidate of candidates) {
for (const fp of ctx.allFileList) {
if (fp.endsWith('/' + candidate) || fp === candidate) {
files.push(fp);
break;
}
}
if (files.length > 0) break;
}
if (files.length > 0) return { kind: 'files', files };
return null;
}

// Relative imports — use standard resolution
return resolveStandard(stripped, filePath, ctx, SupportedLanguages.Dart);
}
Loading
Loading