diff --git a/CHANGELOG.md b/CHANGELOG.md index 8611ba779c..632732a9a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Fixes +- **Go methods now record whether they are exported.** A method on a Go type (`func (w *writer) Close()`) was always indexed as unexported, whatever the case of its name; only plain functions carried the flag. Both the native kernel and the WebAssembly path now apply Go's rule — an uppercase first letter — to methods too, so a tool asking "can another package call this?" gets the right answer. Re-index after upgrading. - In TypeScript, an awaited call with type arguments like `await fetchApi("/me")` or `await api.get(url)` now links to the function it calls. Before, the call was dropped, so typed fetch wrappers looked like they had no callers. (#2586) - In React, a component rendered at module scope, in a top-level constant or inside a test's `it(…)` callback, like `createRoot(el).render()` or `render()`, now counts as rendered there, so an app's entry reaches its root component and a component's tests show among its callers. (#2587) - In Python, `import a.b as X` written inside a function now links its calls like `X.f()`, as the same import at module level already did. Thanks @Egrn for the report. (#2600) diff --git a/__tests__/extraction.test.ts b/__tests__/extraction.test.ts index ed460dd4c8..b64e1ea50e 100644 --- a/__tests__/extraction.test.ts +++ b/__tests__/extraction.test.ts @@ -13540,6 +13540,29 @@ describe('C/C++ kernel-port preParse blanks (R7a)', () => { } }); + it('Go: a method carries the exportedness of its name, like a function', async () => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'codegraph-go-method-exported-')); + try { + fs.writeFileSync( + path.join(dir, 'log.go'), + 'package log\n\ntype writer struct{}\n\nfunc (w *writer) Close() error { return nil }\n\nfunc (w *writer) flush() {}\n\nfunc Open() *writer { return &writer{} }\n\nfunc helper() {}\n' + ); + const cg = await CodeGraph.init(dir, { index: true }); + try { + const flag = (name: string) => + cg.getNodesByKind('method').concat(cg.getNodesByKind('function')).find((n) => n.name === name)!.isExported; + expect(flag('Close')).toBe(true); + expect(flag('flush')).toBe(false); + expect(flag('Open')).toBe(true); + expect(flag('helper')).toBe(false); + } finally { + cg.close(); + } + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } + }); + it('blankCStatementMacroCalls blanks indented iterator macros, keeps the block', async () => { const { blankCStatementMacroCalls } = await import('../src/extraction/languages/c-cpp'); const src = [ diff --git a/codegraph-kernel/src/go.rs b/codegraph-kernel/src/go.rs index a4557e13ac..eec327f9d9 100644 --- a/codegraph-kernel/src/go.rs +++ b/codegraph-kernel/src/go.rs @@ -524,7 +524,10 @@ impl<'t> Walker<'t> { signature: self.signature_of(node), return_type: self.return_type_of(node), qualified_name: receiver_type.as_ref().map(|r| format!("{r}::{name}")), - ..Extra::default() // extractMethod passes no isExported + // methodsAreTopLevel: a Go method is a top-level declaration, so + // extractMethod gives it the function's exportedness (name case). + is_exported: Some(self.is_exported(node)), + ..Extra::default() }; let Some(row) = self.create_node("method", &name, node, extra) else { return }; diff --git a/src/extraction/tree-sitter.ts b/src/extraction/tree-sitter.ts index e6d627ac3e..6159577f54 100644 --- a/src/extraction/tree-sitter.ts +++ b/src/extraction/tree-sitter.ts @@ -2539,10 +2539,20 @@ export class TreeSitterExtractor { // on every ordinary method (breaks kernel↔wasm parity JSON equality). const isAbstract = this.extractor.isAbstract?.(node) ? true : undefined; const returnType = this.extractor.getReturnType?.(node, this.source); + // A method that is a top-level declaration (Go: `func (r *T) Name()`) has + // the same exportedness rule as a function — the name's case — and a + // consumer asking "can another package name this?" needs it on methods + // too. Class members keep the flag unset: their reachability is the + // class's, and the languages whose isExported walks the parent chain + // (JS/TS) would otherwise re-mark every member of an exported class. + const isExported = this.extractor.methodsAreTopLevel + ? this.extractor.isExported?.(node, this.source) + : undefined; const extraProps: Partial = { docstring, signature, visibility, + isExported, isAsync, isStatic, isAbstract,