fix: issue-triage quick wins (extraction, MCP probes, gitignore, CJK, impact) (#654)
Batch of small, localized fixes from an open-issue triage: - .codegraph/.gitignore now ignores everything but itself, so the database, daemon.pid, sockets, and logs stop showing up in git status (#492, #484) - MCP server answers resources/list and prompts/list with empty lists instead of -32601, clearing scary log lines in opencode/Codex (#621) - index SAP HANA .xsjs/.xsjslib as JavaScript (#556) and TS .mts/.cts (#366) - visit anonymous AMD/CommonJS/IIFE wrapper bodies so their inner functions and calls are indexed instead of coming up empty (#528) - batch the changed-file lookup so a huge first sync no longer hits "too many SQL variables" (#540) - list files with `git ls-files -z` so non-ASCII/CJK paths survive core.quotepath and are no longer silently skipped (#541) - attach Go methods on generic receivers (*T[P]) to their type (#583, RC1) - impact no longer climbs the structural `contains` edge, so a leaf symbol stops dragging in its sibling methods (#536) - README: explicit `codegraph install` step, run in a new shell (#631) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
2a22f9f55a
commit
ddb1a8f72d
@@ -10,7 +10,7 @@ import * as path from 'path';
|
||||
import * as os from 'os';
|
||||
import { CodeGraph } from '../src';
|
||||
import { extractFromSource, scanDirectory } from '../src/extraction';
|
||||
import { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars, loadAllGrammars } from '../src/extraction/grammars';
|
||||
import { detectLanguage, isLanguageSupported, getSupportedLanguages, initGrammars, loadAllGrammars, isSourceFile } from '../src/extraction/grammars';
|
||||
import { normalizePath } from '../src/utils';
|
||||
|
||||
beforeAll(async () => {
|
||||
@@ -4387,3 +4387,49 @@ void helperFunction(int count) {
|
||||
expect(getSupportedLanguages()).toContain('objc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Regression: issue-specific extraction fixes', () => {
|
||||
it('indexes inner functions of an anonymous AMD/CommonJS module wrapper (#528)', () => {
|
||||
const code = `
|
||||
define(['dep'], function (dep) {
|
||||
function innerHelper(x) { return x + 1; }
|
||||
function compute(y) { return innerHelper(y); }
|
||||
return { compute: compute };
|
||||
});
|
||||
`;
|
||||
const result = extractFromSource('amd-module.js', code);
|
||||
const fns = result.nodes.filter((n) => n.kind === 'function').map((n) => n.name);
|
||||
expect(fns).toContain('innerHelper');
|
||||
expect(fns).toContain('compute');
|
||||
});
|
||||
|
||||
it('attaches Go methods on generic receivers to their type (#583)', () => {
|
||||
const code = `
|
||||
package main
|
||||
|
||||
type Stack[T any] struct { items []T }
|
||||
|
||||
func (s *Stack[T]) Push(v T) { s.items = append(s.items, v) }
|
||||
func (s Stack[T]) Len() int { return len(s.items) }
|
||||
`;
|
||||
const result = extractFromSource('stack.go', code);
|
||||
const methods = result.nodes.filter((n) => n.kind === 'method');
|
||||
expect(methods.find((m) => m.name === 'Push')?.qualifiedName).toBe('Stack::Push');
|
||||
expect(methods.find((m) => m.name === 'Len')?.qualifiedName).toBe('Stack::Len');
|
||||
});
|
||||
|
||||
it('indexes new module extensions: .mts/.cts (TS) and .xsjs/.xsjslib (JS) (#366, #556)', () => {
|
||||
expect(isSourceFile('mod.mts')).toBe(true);
|
||||
expect(isSourceFile('mod.cts')).toBe(true);
|
||||
expect(isSourceFile('service.xsjs')).toBe(true);
|
||||
expect(isSourceFile('lib.xsjslib')).toBe(true);
|
||||
expect(detectLanguage('mod.mts')).toBe('typescript');
|
||||
expect(detectLanguage('service.xsjs')).toBe('javascript');
|
||||
|
||||
// End-to-end: a .mts file is parsed as TS, a .xsjs file as JS.
|
||||
const ts = extractFromSource('mod.mts', 'export function hello(): number { return 1; }');
|
||||
expect(ts.nodes.find((n) => n.name === 'hello' && n.kind === 'function')).toBeDefined();
|
||||
const js = extractFromSource('service.xsjs', 'function handleRequest() { return 1; }');
|
||||
expect(js.nodes.find((n) => n.name === 'handleRequest' && n.kind === 'function')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -54,7 +54,10 @@ describe('CodeGraph Foundation', () => {
|
||||
expect(fs.existsSync(gitignorePath)).toBe(true);
|
||||
|
||||
const content = fs.readFileSync(gitignorePath, 'utf-8');
|
||||
expect(content).toContain('*.db');
|
||||
// Ignore everything in .codegraph/ except this file itself, so transient
|
||||
// files (db, daemon.pid, sockets, logs) never show up in git. (#492, #484)
|
||||
expect(content).toContain('*');
|
||||
expect(content).toContain('!.gitignore');
|
||||
|
||||
cg.close();
|
||||
});
|
||||
|
||||
@@ -309,6 +309,19 @@ export { main };
|
||||
expect(impact.nodes.size).toBeGreaterThan(0);
|
||||
expect(impact.nodes.has(formatValue.id)).toBe(true);
|
||||
});
|
||||
|
||||
it('does not drag in sibling members via the structural contains edge (#536)', () => {
|
||||
const getName = cg.getNodesByKind('method').find((n) => n.name === 'getName');
|
||||
const derived = cg.getNodesByKind('class').find((n) => n.name === 'DerivedClass');
|
||||
expect(getName).toBeDefined();
|
||||
expect(derived).toBeDefined();
|
||||
|
||||
const impact = cg.getImpactRadius(getName!.id, 3);
|
||||
// The containing class must NOT be pulled into impact just because it
|
||||
// *contains* getName — climbing that contains edge would re-expand every
|
||||
// sibling method and explode impact for a leaf symbol. (#536)
|
||||
expect(impact.nodes.has(derived!.id)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findPath()', () => {
|
||||
|
||||
@@ -154,4 +154,30 @@ describe('MCP initialize handshake (issue #172)', () => {
|
||||
expect(json.id).toBe(0);
|
||||
expect(json.result.serverInfo.name).toBe('codegraph');
|
||||
}, 20000);
|
||||
|
||||
it('answers resources/list and prompts/list with empty lists, not -32601 (issue #621)', async () => {
|
||||
child = spawnServer(tempDir);
|
||||
const events = tagStreams(child);
|
||||
sendInitialize(child, tempDir);
|
||||
await waitFor(events, (e) => e.stream === 'stdout', 5000); // initialize reply
|
||||
|
||||
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'resources/list', params: {} }) + '\n');
|
||||
child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'prompts/list', params: {} }) + '\n');
|
||||
|
||||
const replyFor = async (id: number) => {
|
||||
const ev = await waitFor(events, (e) => {
|
||||
if (e.stream !== 'stdout') return false;
|
||||
try { return JSON.parse(e.text).id === id; } catch { return false; }
|
||||
}, 5000);
|
||||
return JSON.parse(ev.text);
|
||||
};
|
||||
|
||||
const resources = await replyFor(1);
|
||||
expect(resources.error).toBeUndefined();
|
||||
expect(resources.result.resources).toEqual([]);
|
||||
|
||||
const prompts = await replyFor(2);
|
||||
expect(prompts.error).toBeUndefined();
|
||||
expect(prompts.result.prompts).toEqual([]);
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user