mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-09 22:33:39 +00:00
* fix: consolidate C/C++/C#/Rust language support from 6 overlapping PRs Merges fixes from PRs #163, #170, #178, #216, #227, #234 into a single coherent changeset with shared modules and deduplication. Phase 0 — Pre-merge consolidation: - Extract isNodeExported to shared export-detection.ts module - Extract TREE_SITTER_BUFFER_SIZE to shared constants.ts with adaptive sizing - Consolidate FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise from duplicated call-processor.ts and parse-worker.ts into shared utils.ts - Add query compilation smoke tests for all 12 languages Language fixes: - fix(c/cpp): isExported checks static linkage instead of returning false - fix(c/cpp): .h files parsed as C++ (tree-sitter-cpp is superset of C) - fix(c/cpp): expanded entry point patterns (~30 new for C, ~18 for C++) - fix(cpp): add typedef, union, macro, prototype, inline method queries - fix(c#): isExported scans sibling modifiers instead of parent walk - fix(c#): heritage queries use correct base_list AST structure - fix(c#): add framework detection, import resolution, entry point scoring - fix(rust): isExported scans sibling visibility_modifier in declaration - fix(builtins): remove open/read/write/close (real C POSIX syscalls) - fix(buffer): adaptive bufferSize (2x fileSize, 512KB-32MB range) - feat(ts/js): add call_expression query patterns for const assignments Deduplication: - call-processor.ts: -226 lines (uses shared utils) - parse-worker.ts: -320 lines (uses shared utils) - parsing-processor.ts: -156 lines (uses shared export-detection) * perf: fix review findings — hoist Sets, deduplicate DEFINITION_CAPTURE_KEYS - Hoist CSHARP_DECL_TYPES and RUST_DECL_TYPES to module-level constants in export-detection.ts (was allocating new Set on every isNodeExported call) - Extract DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures to shared utils.ts (was duplicated in parsing-processor.ts and parse-worker.ts) - Pre-compute merged entry point patterns to avoid per-call array spread in calculateEntryPointScore * test: add C, C++, and Tree-sitter buffer size tests * fix: C/C++/Rust review findings + comprehensive test coverage (+72 tests) Source fixes: - Add Rust built-in noise (unwrap, clone, into, collect, panic, etc.) - C++ anonymous namespace → internal linkage (not exported) - Replace .text regex with storage_class_specifier child scan (perf) - Raise file skip threshold from 512KB to 32MB (TREE_SITTER_MAX_BUFFER) - Export TREE_SITTER_MAX_BUFFER from constants.ts - Add C++ double pointer query patterns to CPP_QUERIES - Add C#: record_struct, record_class, file_scoped_namespace to decl types - Add Rust: union_item to visibility scanning set Tests (214 → 286): - ingestion-utils: +24 (Rust/C# noise, pointer/ref/destructor extraction, buffer) - parsing: +36 (real AST C/C++ static/namespace, Rust/C#/Java/PHP/Swift edge cases) - tree-sitter-languages: +12 (query accuracy for C/C++/C#/Rust captures)
254 lines
9.5 KiB
TypeScript
254 lines
9.5 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { calculateEntryPointScore, isTestFile, isUtilityFile } from '../../src/core/ingestion/entry-point-scoring.js';
|
|
|
|
describe('calculateEntryPointScore', () => {
|
|
describe('base scoring', () => {
|
|
it('returns 0 for functions with no outgoing calls', () => {
|
|
const result = calculateEntryPointScore('handler', 'typescript', true, 0, 0);
|
|
expect(result.score).toBe(0);
|
|
expect(result.reasons).toContain('no-outgoing-calls');
|
|
});
|
|
|
|
it('calculates base score as calleeCount / (callerCount + 1)', () => {
|
|
const result = calculateEntryPointScore('doStuff', 'typescript', false, 0, 5);
|
|
// base = 5 / (0 + 1) = 5, no export bonus, no name bonus
|
|
expect(result.score).toBe(5);
|
|
});
|
|
|
|
it('reduces score for functions with many callers', () => {
|
|
const few = calculateEntryPointScore('doStuff', 'typescript', false, 1, 5);
|
|
const many = calculateEntryPointScore('doStuff', 'typescript', false, 10, 5);
|
|
expect(few.score).toBeGreaterThan(many.score);
|
|
});
|
|
});
|
|
|
|
describe('export multiplier', () => {
|
|
it('applies 2.0 multiplier for exported functions', () => {
|
|
const exported = calculateEntryPointScore('doStuff', 'typescript', true, 0, 4);
|
|
const notExported = calculateEntryPointScore('doStuff', 'typescript', false, 0, 4);
|
|
expect(exported.score).toBe(notExported.score * 2);
|
|
expect(exported.reasons).toContain('exported');
|
|
});
|
|
|
|
it('does not add exported reason when not exported', () => {
|
|
const result = calculateEntryPointScore('doStuff', 'typescript', false, 0, 4);
|
|
expect(result.reasons).not.toContain('exported');
|
|
});
|
|
});
|
|
|
|
describe('universal name patterns', () => {
|
|
it.each([
|
|
'main', 'init', 'bootstrap', 'start', 'run', 'setup', 'configure',
|
|
])('recognizes "%s" as entry point pattern', (name) => {
|
|
const result = calculateEntryPointScore(name, 'typescript', false, 0, 3);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it.each([
|
|
'handleLogin', 'handleSubmit', 'onClick', 'onSubmit',
|
|
'RequestHandler', 'UserController',
|
|
'processPayment', 'executeQuery', 'performAction',
|
|
'dispatchEvent', 'triggerAction', 'fireEvent', 'emitEvent',
|
|
])('recognizes "%s" as entry point pattern', (name) => {
|
|
const result = calculateEntryPointScore(name, 'typescript', false, 0, 3);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('applies 1.5x name multiplier for entry patterns', () => {
|
|
const matching = calculateEntryPointScore('handleLogin', 'typescript', false, 0, 4);
|
|
const plain = calculateEntryPointScore('doStuff', 'typescript', false, 0, 4);
|
|
// matching gets 1.5x, plain gets 1.0x
|
|
expect(matching.score).toBe(plain.score * 1.5);
|
|
});
|
|
});
|
|
|
|
describe('language-specific patterns', () => {
|
|
it('recognizes React hooks for TypeScript', () => {
|
|
const result = calculateEntryPointScore('useEffect', 'typescript', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes React hooks for JavaScript', () => {
|
|
const result = calculateEntryPointScore('useState', 'javascript', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes Python REST patterns', () => {
|
|
const result = calculateEntryPointScore('get_users', 'python', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes Java servlet patterns', () => {
|
|
const result = calculateEntryPointScore('doGet', 'java', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes Go handler patterns', () => {
|
|
const result = calculateEntryPointScore('NewServer', 'go', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes Rust entry patterns', () => {
|
|
const result = calculateEntryPointScore('handle_request', 'rust', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes Swift UIKit lifecycle', () => {
|
|
const result = calculateEntryPointScore('viewDidLoad', 'swift', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes Swift SwiftUI body', () => {
|
|
const result = calculateEntryPointScore('body', 'swift', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes PHP Laravel patterns', () => {
|
|
// __invoke starts with '_' which matches utility pattern first
|
|
const result = calculateEntryPointScore('handle', 'php', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes PHP RESTful resource methods', () => {
|
|
const result = calculateEntryPointScore('index', 'php', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes C# ASP.NET patterns', () => {
|
|
const result = calculateEntryPointScore('GetUsers', 'csharp', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
it('recognizes C main entry point', () => {
|
|
const result = calculateEntryPointScore('main', 'c', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
// C-specific patterns
|
|
it.each([
|
|
'init_server', 'server_init', 'start_server', 'handle_request',
|
|
'signal_handler', 'event_callback', 'cmd_new_window', 'server_start',
|
|
'client_connect', 'session_create', 'window_resize',
|
|
])('recognizes C pattern "%s"', (name) => {
|
|
const result = calculateEntryPointScore(name, 'c', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
|
|
// C++-specific patterns
|
|
it.each([
|
|
'CreateInstance', 'create_session', 'Run', 'run', 'Start', 'start',
|
|
'OnEventReceived', 'on_click',
|
|
])('recognizes C++ pattern "%s"', (name) => {
|
|
const result = calculateEntryPointScore(name, 'cpp', false, 0, 2);
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
});
|
|
|
|
describe('utility pattern penalty', () => {
|
|
it.each([
|
|
'getUser', 'setName', 'isValid', 'hasPermission', 'canEdit',
|
|
'formatDate', 'parseJSON', 'validateInput',
|
|
'toString', 'fromJSON', 'encodeBase64', 'serializeData',
|
|
'cloneDeep', 'mergeObjects',
|
|
])('penalizes utility function "%s"', (name) => {
|
|
const result = calculateEntryPointScore(name, 'typescript', false, 0, 3);
|
|
expect(result.reasons).toContain('utility-pattern');
|
|
// 0.3 multiplier
|
|
const plain = calculateEntryPointScore('doStuff', 'typescript', false, 0, 3);
|
|
expect(result.score).toBeLessThan(plain.score);
|
|
});
|
|
|
|
it('penalizes private-by-convention functions', () => {
|
|
const result = calculateEntryPointScore('_internal', 'typescript', false, 0, 3);
|
|
expect(result.reasons).toContain('utility-pattern');
|
|
});
|
|
});
|
|
|
|
describe('framework detection from path', () => {
|
|
it('boosts Next.js page entry points', () => {
|
|
const result = calculateEntryPointScore('render', 'typescript', true, 0, 3, 'pages/users.tsx');
|
|
expect(result.reasons.some(r => r.includes('framework:'))).toBe(true);
|
|
expect(result.score).toBeGreaterThan(0);
|
|
});
|
|
|
|
it('does not apply framework bonus for non-framework paths', () => {
|
|
const result = calculateEntryPointScore('render', 'typescript', true, 0, 3, 'src/lib/utils.ts');
|
|
expect(result.reasons.every(r => !r.includes('framework:'))).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('combined scoring', () => {
|
|
it('multiplies all factors together', () => {
|
|
// handleLogin: entry pattern (1.5x) + exported (2.0x) + base
|
|
const result = calculateEntryPointScore('handleLogin', 'typescript', true, 0, 4, 'routes/auth.ts');
|
|
expect(result.score).toBeGreaterThan(0);
|
|
expect(result.reasons).toContain('exported');
|
|
expect(result.reasons).toContain('entry-pattern');
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('isTestFile', () => {
|
|
it.each([
|
|
'src/utils.test.ts',
|
|
'src/utils.spec.ts',
|
|
'__tests__/utils.ts',
|
|
'__mocks__/api.ts',
|
|
'src/test/integration/db.ts',
|
|
'src/tests/unit/helper.ts',
|
|
'src/testing/setup.ts',
|
|
'lib/test_utils.py',
|
|
'pkg/handler_test.go',
|
|
'src/test/java/com/example/Test.java',
|
|
'MyViewTests.swift',
|
|
'MyViewTest.swift',
|
|
'UITests/LoginTest.swift',
|
|
'App.Tests/MyTest.cs',
|
|
'tests/Feature/UserTest.php',
|
|
'tests/Unit/AuthSpec.php',
|
|
])('returns true for test file "%s"', (filePath) => {
|
|
expect(isTestFile(filePath)).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
'src/utils.ts',
|
|
'src/controllers/auth.ts',
|
|
'src/main.py',
|
|
'cmd/server.go',
|
|
'src/main/java/App.java',
|
|
])('returns false for non-test file "%s"', (filePath) => {
|
|
expect(isTestFile(filePath)).toBe(false);
|
|
});
|
|
|
|
it('normalizes Windows backslashes', () => {
|
|
expect(isTestFile('src\\__tests__\\utils.ts')).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('isUtilityFile', () => {
|
|
it.each([
|
|
'src/utils/format.ts',
|
|
'src/util/helpers.ts',
|
|
'src/helpers/date.ts',
|
|
'src/helper/string.ts',
|
|
'src/common/types.ts',
|
|
'src/shared/constants.ts',
|
|
'src/lib/crypto.ts',
|
|
'src/utils.ts',
|
|
'src/utils.js',
|
|
'src/helpers.ts',
|
|
'lib/date_utils.py',
|
|
'lib/date_helpers.py',
|
|
])('returns true for utility file "%s"', (filePath) => {
|
|
expect(isUtilityFile(filePath)).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
'src/controllers/auth.ts',
|
|
'src/routes/api.ts',
|
|
'src/main.ts',
|
|
'src/app.ts',
|
|
])('returns false for non-utility file "%s"', (filePath) => {
|
|
expect(isUtilityFile(filePath)).toBe(false);
|
|
});
|
|
});
|