From 49fb596271d9ab2c8ca6c545f58aca71cd4da5e4 Mon Sep 17 00:00:00 2001 From: Parafee41 Date: Fri, 26 Jun 2026 20:37:02 +0800 Subject: [PATCH] fix(ingestion): index generator function declarations (#2305) --- .../src/core/ingestion/tree-sitter-queries.ts | 6 ++ ...pescript-async-generator-functions.test.ts | 28 +++++++++ .../test/unit/tree-sitter-queries.test.ts | 61 +++++++++++++++++++ 3 files changed, 95 insertions(+) create mode 100644 gitnexus/test/integration/typescript-async-generator-functions.test.ts diff --git a/gitnexus/src/core/ingestion/tree-sitter-queries.ts b/gitnexus/src/core/ingestion/tree-sitter-queries.ts index 540ad1c746..d4810f724e 100644 --- a/gitnexus/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus/src/core/ingestion/tree-sitter-queries.ts @@ -27,6 +27,9 @@ export const TYPESCRIPT_QUERIES = ` (function_declaration name: (identifier) @name) @definition.function +(generator_function_declaration + name: (identifier) @name) @definition.function + ; TypeScript overload signatures (function_signature is a separate node type from function_declaration) (function_signature name: (identifier) @name) @definition.function @@ -374,6 +377,9 @@ export const JAVASCRIPT_QUERIES = ` (function_declaration name: (identifier) @name) @definition.function +(generator_function_declaration + name: (identifier) @name) @definition.function + (method_definition name: (property_identifier) @name) @definition.method diff --git a/gitnexus/test/integration/typescript-async-generator-functions.test.ts b/gitnexus/test/integration/typescript-async-generator-functions.test.ts new file mode 100644 index 0000000000..38a8bba084 --- /dev/null +++ b/gitnexus/test/integration/typescript-async-generator-functions.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, it } from 'vitest'; +import { parseFilesWithWorkers } from '../helpers/worker-parse.js'; + +describe('TypeScript async generator functions', () => { + it('indexes exported async function* declarations as Function nodes', async () => { + const { graph } = await parseFilesWithWorkers([ + { + path: 'src/coach.ts', + content: ` + export function userText(input: string): string { + return input.trim(); + } + + export async function* runCoachLoop(input: string): AsyncGenerator { + yield userText(input); + } + `, + }, + ]); + + const functionNames = new Set( + graph.nodes.filter((node) => node.label === 'Function').map((node) => node.properties.name), + ); + + expect(functionNames).toContain('userText'); + expect(functionNames).toContain('runCoachLoop'); + }); +}); diff --git a/gitnexus/test/unit/tree-sitter-queries.test.ts b/gitnexus/test/unit/tree-sitter-queries.test.ts index a6d1b39582..77c119b263 100644 --- a/gitnexus/test/unit/tree-sitter-queries.test.ts +++ b/gitnexus/test/unit/tree-sitter-queries.test.ts @@ -1,4 +1,7 @@ import { describe, it, expect } from 'vitest'; +import Parser from 'tree-sitter'; +import JavaScript from 'tree-sitter-javascript'; +import TypeScript from 'tree-sitter-typescript'; import { TYPESCRIPT_QUERIES, JAVASCRIPT_QUERIES, @@ -15,6 +18,28 @@ import { DART_QUERIES, } from '../../src/core/ingestion/tree-sitter-queries.js'; +function capturedDefinitionFunctionNames( + language: Parameters[0], + querySource: string, + src: string, +): string[] { + const parser = new Parser(); + parser.setLanguage(language); + const query = new Parser.Query(language, querySource); + const tree = parser.parse(src); + const names: string[] = []; + for (const match of query.matches(tree.rootNode)) { + let isFunction = false; + let name: string | undefined; + for (const capture of match.captures) { + if (capture.name === 'definition.function') isFunction = true; + if (capture.name === 'name') name = capture.node.text; + } + if (isFunction && name !== undefined) names.push(name); + } + return names; +} + describe('tree-sitter queries', () => { describe('TypeScript queries', () => { it('captures class declarations', () => { @@ -32,6 +57,22 @@ describe('tree-sitter queries', () => { expect(TYPESCRIPT_QUERIES).toContain('@definition.function'); }); + it('captures async generator function declarations as function definitions', () => { + const names = capturedDefinitionFunctionNames( + TypeScript.typescript as Parameters[0], + TYPESCRIPT_QUERIES, + ` + export function userText() { return ''; } + export async function* runCoachLoop(): AsyncGenerator { + yield 'ready'; + } + `, + ); + + expect(names).toContain('userText'); + expect(names).toContain('runCoachLoop'); + }); + it('captures method definitions', () => { expect(TYPESCRIPT_QUERIES).toContain('method_definition'); expect(TYPESCRIPT_QUERIES).toContain('@definition.method'); @@ -62,6 +103,26 @@ describe('tree-sitter queries', () => { it('does not have interface declarations', () => { expect(JAVASCRIPT_QUERIES).not.toContain('interface_declaration'); }); + + it('captures generator function declarations as function definitions', () => { + const names = capturedDefinitionFunctionNames( + JavaScript as Parameters[0], + JAVASCRIPT_QUERIES, + ` + export function userText() { return ''; } + export async function* runCoachLoop() { + yield 'ready'; + } + export function* plainGen() { + yield 1; + } + `, + ); + + expect(names).toContain('userText'); + expect(names).toContain('runCoachLoop'); + expect(names).toContain('plainGen'); + }); }); describe('Python queries', () => {