diff --git a/CHANGELOG.md b/CHANGELOG.md index 61c468bda..4b8ca92c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - In Go, calls through a method receiver or parameter of an unexported type, the usual shape of gRPC and HTTP handlers (`s.service.AddItem()` inside `func (s *server) Create()`), now resolve, and always within that type's own package: another package's `server` with a same-named method no longer takes the call, and a method the type gets from a struct or interface it embeds is found too. Re-index Go projects after upgrading. Thanks @GoDiao for the report and the fix. (#2323) - In Go, calls into a module whose `go.mod` is not at the project root now resolve, whether it is a `server/` backend next to a `web/` frontend or one of several modules side by side as in etcd, so calls like `store.New()` and `s.db.CreateItem()` find their targets instead of being treated as calls into a third-party package. A name written through a package, like a `job.OPCommand` result type or a field of type `artifact.Manager`, now links to that package's symbol rather than a same-named one elsewhere, which also corrects links in projects with a single `go.mod`. Re-index Go projects after upgrading. Thanks @GoDiao for the report and @danusha2345 for the fix. (#2322) - In Go, an exported type written without a package, like `Node` in `func Walk(v Visitor, node Node)`, now links to the type of that name in its own package, whether it is a struct, an interface or another kind of type. Before, it could link to a struct of the same name in an unrelated package, so prometheus's PromQL parser functions pointed at the Kubernetes discovery `Node` struct instead of the parser's own `Node` interface, or, for a result type like `Appender` in `func (f *fanout) Appender(…) Appender`, to the method itself. A type from a package outside your project, like `apiv1.Node` or `http.Handler`, no longer links to a struct of the same name in the file that uses it; a variadic parameter like `...storage.Filter` now links to that package's type; a route handler written as a method value, like `h.Follow` in an Echo or Gin app, links to that method rather than a model struct named `Follow`; and a method called on another call's result or on an indexed value, like `err[i].Error()` or a chained `.String()`, is no longer recorded as creating a struct of the same name. Re-index Go projects after upgrading. +- In Go, a function or method passed as a value, like `sync.Pool{New: wm.new}`, now shows the function that passes it among its callers and impact even when it is named `new`, `nil`, `None`, `self` or a handful of similar words, or when a line break or comment sits beside the dot. Before, such a value was skipped, so a method used only that way looked unused. Re-index Go projects after upgrading. - Indexing a project that includes large bundled JavaScript files, such as a copy of pdf.js or d3, is fast again: since 1.6.2, resolving the calls in a JavaScript or TypeScript file re-read the file's text above each call, so a single bundled library could add many seconds to an index. The graph it builds is unchanged. Thanks @bompus for the report. (#2334) - The time `codegraph init` and `codegraph index` print beside the node and edge counts now covers the whole run, resolving references and linking included, and `codegraph sync` reports its whole run the same way. Before, it counted only reading and parsing the files, which can be a small part of an index, so a slow index looked fast. Thanks @bompus for the report. (#2334) - In Rust, code that uses an enum only through its variants, like `mode::Mode::A` in an expression, a `Mode::B =>` match arm or `Self::A` inside the enum's own `impl`, now shows up in that enum's callers and impact, linked to the enum that actually declares the variant rather than to a same-named type elsewhere. Standard-library variants like `Ordering::Less` or `Option::Some` and associated items like `Foo::new()` or `Foo::MAX` don't count as using a project enum. Re-index Rust projects after upgrading. Thanks @mg-mg-mg for the report and @danusha2345. (#2328) diff --git a/__tests__/kernel-tsjs-parity.test.ts b/__tests__/kernel-tsjs-parity.test.ts index 866d80d30..57fec8649 100644 --- a/__tests__/kernel-tsjs-parity.test.ts +++ b/__tests__/kernel-tsjs-parity.test.ts @@ -383,6 +383,30 @@ class Consumer: expect(result.unresolvedReferences.some(r => r.referenceKind === 'calls' && r.referenceName === 'c.Fetch')).toBe(true); }); + it.each(['LF', 'CRLF'])('Go selector values stoplist the whole name, not the field (%s)', (ending) => { + // The stoplist holds bare words like Python's `None`; `raft.None` (etcd) + // and cgo's `C.NULL` are still values. The name is operand + field, so a + // break or comment after the dot keeps it; one inside the operand doesn't. + const source = `package demo +import "example.com/raft" +type Store struct{} +func (s *Store) new() any { return nil } +func wire(s *Store) { + Submit(raft.None, C.NULL, None, nil) + pool := sync.Pool{New: s.new} + Submit(raft. + Next, raft./* c */ Prev, raft.// c + Last) + Submit(s. + inner.Fetch) + _ = pool +} +`; + const result = assertParity('values.go', ending === 'CRLF' ? source.replace(/\n/g, '\r\n') : source, 'go'); + const names = result.unresolvedReferences.filter(r => r.referenceKind === 'function_ref').map(r => r.referenceName); + expect(names.sort()).toEqual(['C.NULL', 'raft.Last', 'raft.Next', 'raft.None', 'raft.Prev', 's.new']); + }); + it.each(REAL_SOURCES)('real source parity: %s', (rel) => { const file = path.join(__dirname, '..', rel); assertParity(rel, fs.readFileSync(file, 'utf8'), 'typescript'); diff --git a/codegraph-kernel/src/go.rs b/codegraph-kernel/src/go.rs index efc67971b..3a93bacf1 100644 --- a/codegraph-kernel/src/go.rs +++ b/codegraph-kernel/src/go.rs @@ -1028,18 +1028,20 @@ impl<'t> Walker<'t> { row: p.row, }); } - // #1820: preserve the receiver of a method value. + // #1820: preserve the receiver of a method value. The name is + // rebuilt from operand and field, as normalizeSpecial does, so a + // comment or line break beside the dot keeps the candidate. + // NAME_STOPLIST applies to the whole name and holds no dotted + // word, so it never drops one: `raft.None` is a candidate even + // though a bare `None` is not. "selector_expression" => { - let field = v - .child_by_field_name("field") - .or_else(|| v.named_child(v.named_child_count().saturating_sub(1))); - let Some(field) = field else { return }; - let name = self.text(field); - if name.is_empty() || is_stoplisted(name) { + let (Some(operand), Some(field)) = + (v.child_by_field_name("operand"), v.child_by_field_name("field")) + else { return; - } - let value = self.text(v); - if !value.split('.').all(|part| { + }; + let name = format!("{}.{}", self.text(operand), self.text(field)); + if !name.split('.').all(|part| { !part.is_empty() && part.chars().enumerate().all(|(i, c)| { c == '_' || c.is_ascii_alphabetic() || (i > 0 && c.is_ascii_digit()) }) @@ -1047,7 +1049,7 @@ impl<'t> Walker<'t> { let p = field.start_position(); self.fn_ref_cands.push(Cand { from, - name: value.to_string(), + name, line: p.row as u32 + 1, column_byte: field.start_byte(), row: p.row,