diff --git a/CHANGELOG.md b/CHANGELOG.md index 1815c4150..7aa064899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -201,6 +201,8 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). #### Symbols, tests and the viewer +- **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. + - **Files under an `e2e/` directory count as tests.** Their calls no longer appear as production callers in Steps, dead-code and test badges. - **Production code under a `samples` or `examples` package path is no longer treated as test code.** A Kotlin or Java project whose package path runs through `com/google/samples/…` (Now in Android, for one) had nearly every file counted as a fixture, so the Map opened on `build-logic`, the entry points hid the app, and dead-code and test badges were wrong. Only the project layout above a `src/` folder decides now; the package path below it never does. diff --git a/__tests__/extraction.test.ts b/__tests__/extraction.test.ts index ad0ba2374..2f2fa7bdd 100644 --- a/__tests__/extraction.test.ts +++ b/__tests__/extraction.test.ts @@ -11671,6 +11671,29 @@ describe('C/C++ kernel-port preParse blanks (R7a)', () => { expect(blankLoneMacroLines(bare)).toBe(bare); }); + 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 66bb352f1..16a78a108 100644 --- a/codegraph-kernel/src/go.rs +++ b/codegraph-kernel/src/go.rs @@ -488,7 +488,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 7ef90c273..e86b0ec49 100644 --- a/src/extraction/tree-sitter.ts +++ b/src/extraction/tree-sitter.ts @@ -1799,10 +1799,20 @@ export class TreeSitterExtractor { const isAsync = this.extractor.isAsync?.(node); const isStatic = this.extractor.isStatic?.(node); 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, returnType,