Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- React Router routes kept in a table of their own are now indexed: the ASP.NET Core React template's `AppRoutes` array that `App.js` maps into `<Route {...rest}>`, a `routes` array imported into `useRoutes(routes)` or `createBrowserRouter(routes)` or returned by a function as in `useRoutes(routes(isLoggedIn))`, route objects kept one per file and listed in `createBrowserRouter([MainRoutes, LoginRoutes])`, and routes written in place in `useRoutes([…])`. Before, route objects were read only from a file that itself creates a data router, so these apps had no routes, and their `navigate('/login')` calls and `<Link to="/counter">` links led nowhere. Each route links to the page its `element` renders, past a guard like `<ProtectedRoute>` or `<AdminWrapper>`, and through `lazy(() => import(…))` to the page it loads rather than a same-named page elsewhere in the repository. An `index: true` route is the page at its parent's address, and a parent route around others counts as their layout. A `{ path, element }` list that nothing hands to the router, such as a menu, still makes no routes. Re-index React projects after upgrading.

- In Go, a call through a parameter or local variable named like a standard-library package, such as `context`, `trace`, `printer` or `token`, now links to the method it calls. Before, every call written `context.…` or `printer.…` was taken for a call into the standard library and linked to nothing: gin's `context.AbortWithError(…)` inside `func(context *Context)`, harbor's `context.IsAuthenticated()` after `context := NewSecurityContext(…)`, etcd's `printer.DBHashKV(…)` and `trace.Step(…)`. The variable's declaration decides the method: the type it is declared as, or the package of the project function its value comes from. A variable holding a value from outside your project, like `scanner := bufio.NewScanner(r)` or `url, err := url.Parse(raw)`, still links to nothing, and one whose type can't be told links only to a method no other type in the project has, never to one picked because it matches the variable's name. Re-index Go projects after upgrading.
- In Go, a local declared from a function of a project package that shares its name, like harbor's `log := log.G(ctx)`, now links `log.G` to that function and the later `log.Errorf` to the method of the logger it returns. Before, both linked to nothing: the new local hid the package from its own line, and a project package named like a standard-library one, such as `log` or `context`, was taken for the standard library. A direct call into such a project package, `log.G(ctx)` with no local, now links too. Re-index Go projects after upgrading.
- React Router routes written as JSX now read `<Route index>` the way route objects do: inside `<Route path="/" element={<Layout />}>`, `<Route index element={<Home />} />` is the page at `/`, and `Layout` counts as the layout of every route nested in it, as does a guard with no path of its own like `<Route element={<RequireAuth />}>`. Before, the index route was never read, so `/` led to the layout itself and a `<Route path="/dashboard">` holding only an index route led to nothing. A route's `element` written on lines of its own, the way Prettier formats a long one, or wrapped in `<Suspense fallback={<Loader />}>`, now links to the page it shows rather than to nothing or to the fallback, and a path written in braces, like `path={"agents"}`, is read like any other. Re-index React projects after upgrading.

- A React Router route whose `lazy` loader picks its page from the module it loads now links to that page: `lazy: async () => { const { TestTab } = await import('./tabs'); return { Component: TestTab }; }`, `const { default: Component } = await import('./pages/Login')`, `import('./reports').then((m) => ({ Component: m.ReportList }))`, `({ Component: (await import('./reports')).ReportPage })`, React Router 7's `lazy: { Component: async () => (await import('./show')).Show }`, and a page the loader shows inside a guard, like `element: <AgeGate><FireworksPage /></AgeGate>`. Before, such a route linked to the module's default export, else its `Component` export, which is a different page or none at all, as with luci-go's tabs loaded from a folder that forwards them. A loader written as a method, `async lazy() { … }`, is now read too, so its route is no longer missing, and a loader that hands React Router only a `loader` no longer makes the module's default export the route's page or the layout of the routes inside it. Re-index React projects after upgrading.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ C# property accessors and expression-bodied properties contribute calls and refe

C# field and property initializers retain their calls and references under the member that owns them. Target-typed `new()` resolves relative declared types through enclosing namespaces and honors `global::` qualification without requiring a redundant `using`. VB.NET resolves typed receivers, enclosing and inherited members, and field or property reads through values and Shared types without choosing unrelated project declarations. Names beginning with keywords, such as `SharedCache`, `Dimension` and `NewItem`, retain their declarations.

Go declaration comments attach to the declared type, and defined types retain references to their named component types. Assertion calls follow the asserted project type and each declared return in a method chain. Local bindings shadow package imports. C/C++ types defined beside variables retain their members and comments; nested template arguments preserve receiver ownership, and leading `::` names require a visible global declaration. C++ declaration attributes and macros preserve source positions. Multiplication operands remain intact, and anonymous pointer declarations share their first declared type name. Macro-opened namespaces combine with ordinary nested scopes when resolving included owners. Renamed and default imports are retried when their module appears during sync. Named-only components are not default exports; explicit forwarded defaults retain their target. Whitespace before a type argument does not create a JSX rendering edge. React JSX respects component imports and local shadowing; lazy routes follow component exports through local barrels. Named flow paths prefer an equally long path containing more requested symbols.
Go declaration comments attach to the declared type, and defined types retain references to their named component types. Assertion calls follow the asserted project type and each declared return in a method chain. Local bindings shadow package imports, except on the right of the `:=` that declares them, and a project package named like a standard-library one is not taken for it. C/C++ types defined beside variables retain their members and comments; nested template arguments preserve receiver ownership, and leading `::` names require a visible global declaration. C++ declaration attributes and macros preserve source positions. Multiplication operands remain intact, and anonymous pointer declarations share their first declared type name. Macro-opened namespaces combine with ordinary nested scopes when resolving included owners. Renamed and default imports are retried when their module appears during sync. Named-only components are not default exports; explicit forwarded defaults retain their target. Whitespace before a type argument does not create a JSX rendering edge. React JSX respects component imports and local shadowing; lazy routes follow component exports through local barrels. Named flow paths prefer an equally long path containing more requested symbols.

The native resolver includes upstream's Go import-name assumptions, type-position checks, alias method forwarding and embedded interface method sets. Chained Go calls follow declared return types at each fluent step; an unproven receiver stays unresolved instead of borrowing an unrelated method by name. C++ bases use enclosing-scope directives, parent-scope lookup and translation-unit visibility; included test-named headers remain eligible, while declared external library receivers avoid unrelated project methods. Incremental sync retries newly available Liquid targets, lazy route modules and navigation destinations. Navigation in an imported route-table file also survives removing and restoring its destination. Route-table nodes and their references commit together, so a failed reference write can be retried without leaving an incomplete route. React layout links exclude Vue and Angular routes. C++ receiver declaration scans exclude multiline raw-string contents.

Expand Down
29 changes: 25 additions & 4 deletions __tests__/go-stdlib-named-locals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,19 @@ func Refresh(ctx context.Context) {
log := log.G(ctx)
log.Errorf("refresh quota: %v", ctx)
}

func Handle(ctx context.Context) {
_ = log.G(ctx)
}

func Later(ctx context.Context) {
log := log.G(ctx); log.Errorf("later")
}

func Pair(ctx context.Context) {
logger, log := log.G(ctx), 0
logger.Errorf("pair %d", log)
}
`,
// kubernetes' post-start hooks: the hook context gets Done from the
// standard library's context it embeds, not from a project type.
Expand Down Expand Up @@ -427,11 +440,19 @@ describe('a Go parameter or local named like a standard-library package', () =>
expect(reachedAt('etcdmain/grpc_proxy.go', 'tls.ClientConfig()')).toEqual(['transport/tls.go:TLSInfo::ClientConfig']);
});

// Not ported to the native resolver: a local declared as `log := log.G(ctx)`
// shadows the imported `log` package from its own line, and `log.G` and the
// later `log.Errorf` both stay unresolved. `it.fails` turns red once they resolve.
it.fails('calls the Logger method of a local that takes the name of the project package that hands it out', () => {
// `log := log.G(ctx)`: the new local takes the name of the imported project
// package from its own line, but the right of `:=` still means the package,
// so `log.G` calls the package's function and the later `log.Errorf` calls the
// method of the `*Logger` it returns.
it('calls the Logger method of a local that takes the name of the project package that hands it out', () => {
expect(reachedAt('quota/util.go', 'log := log.G(')).toEqual(['lib/log/log.go:G']);
expect(reachedAt('quota/util.go', 'log.Errorf(')).toEqual(['lib/log/log.go:Logger::Errorf']);
// Nothing shadows it in `Handle`, and the import is the project's, not the standard library's.
expect(reachedAt('quota/util.go', '_ = log.G(')).toEqual(['lib/log/log.go:G']);
// After the `;` the line is a later statement: `log` is the local there.
expect(reachedAt('quota/util.go', 'log.Errorf("later")').sort()).toEqual(['lib/log/log.go:G', 'lib/log/log.go:Logger::Errorf']);
// A name the line declares beside the receiver is shadowed from its line too.
expect(reachedAt('quota/util.go', 'logger.Errorf(')).toEqual(['lib/log/log.go:Logger::Errorf']);
});

it('takes the type named like the variable among that package\'s', () => {
Expand Down
10 changes: 8 additions & 2 deletions codegraph-kernel/src/resolve/bound.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1422,11 +1422,17 @@ Ok(self.dart_member_of(type_name, method, r, false)?
let name = caps[2].to_string();
let mut factory_site = site.clone();
factory_site.line = binding.line;
// The call sits on the declaration line, at the factory's name.
factory_site.column = declaration[..caps.get(2).map_or(0, |m| m.start())].encode_utf16().count() as i64;
factory_site.reference_name = name.clone();
let factory_binding = innermost_binding(
// `log := log.G(ctx)`, `logger, log := log.G(ctx), 0`: on the
// right of `:=`, a name the line declares is what it named before.
let factory_root = name.split('.').next().unwrap_or(&name);
let factory_binding = innermost_binding_unless(
&site_bindings,
name.split('.').next().unwrap_or(&name),
factory_root,
Some(binding.line),
names.iter().any(|n| n == factory_root),
Comment thread
coderabbitai[bot] marked this conversation as resolved.
)
.cloned();
let mut callee: Option<Arc<KNode>> = None;
Expand Down
21 changes: 20 additions & 1 deletion codegraph-kernel/src/resolve/member_fn_ref.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,26 @@ impl KernelResolver {
/// parameter, a local)? Files without binding rows keep the import.
pub(super) fn is_shadowed_import(&mut self, name: &str, r: &ResolveRefIn) -> Res<bool> {
let rows = self.bindings(&r.file_path)?;
Ok(innermost_binding(&rows, name, Some(r.line)).is_some_and(|b| b.kind != "import"))
let before = self.go_declares_on_line(name, r, &rows);
Ok(innermost_binding_unless(&rows, name, Some(r.line), before).is_some_and(|b| b.kind != "import"))
}

/// Go: does `r` sit on the right of a `:=` that declares `name`? The name
/// there is not yet the new local. The right side runs to the next `;`:
/// a call after it is a later statement and sees the local.
pub(super) fn go_declares_on_line(&mut self, name: &str, r: &ResolveRefIn, rows: &[KBinding]) -> bool {
let declared = r.language == "go"
&& innermost_binding(rows, name, Some(r.line)).is_some_and(|b| b.kind == "local" && b.line == r.line);
Comment thread
bompus marked this conversation as resolved.
let Some(text) = declared
.then(|| self.read_file(&r.file_path).and_then(|ls| ls.get((r.line - 1).max(0) as usize).cloned()))
.flatten()
else {
return false;
};
let Some(at) = text.find(":=") else { return false };
Comment thread
bompus marked this conversation as resolved.
let end = text[at..].find(';').map_or(text.len(), |i| at + i);
Comment thread
bompus marked this conversation as resolved.
let col = super::names::js_unit_to_byte(&text, r.column.max(0) as usize);
text[..at].split(',').any(|n| n.trim() == name) && col > at && col < end
}

/// The Python receiver arms: `self`/`cls`, `self.field`, a local or
Expand Down
21 changes: 20 additions & 1 deletion codegraph-kernel/src/resolve/prefilter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ impl KernelResolver {
if let Some(dot) = name.find('.') {
if dot > 0 && GO_STDLIB_PACKAGES.contains(&name[..dot])
&& !self.is_shadowed_import(&name[..dot], r).unwrap_or(false)
&& self.go_imported_package_dir(&r.file_path, &name[..dot]).ok().flatten().is_none()
&& self.go_asserted_local(&name[..dot], r).ok().flatten().is_none() {
return true;
}
Expand Down Expand Up @@ -591,12 +592,29 @@ pub(super) fn innermost_binding<'a>(
rows: &'a [KBinding],
name: &str,
line: Option<i64>,
) -> Option<&'a KBinding> {
innermost_binding_unless(rows, name, line, false)
}

/// `innermost_binding` that, when `before_declaration`, passes over a local
/// declared on `line` itself. A local's scope spans its whole block, so on
/// its own line the narrowest row is the new local, while the right of a Go
/// `:=` still sees what the name meant before: `log := log.G(ctx)` calls the
/// imported package's `G`.
pub(super) fn innermost_binding_unless<'a>(
rows: &'a [KBinding],
name: &str,
line: Option<i64>,
before_declaration: bool,
) -> Option<&'a KBinding> {
let mut best: Option<&KBinding> = None;
for r in rows {
if r.name != name {
continue;
}
if before_declaration && r.kind == "local" && Some(r.line) == line {
continue;
}
if let Some(l) = line {
if l < r.scope_start || l > r.scope_end {
continue;
Expand All @@ -614,7 +632,8 @@ impl KernelResolver {
/// not replace a value; a future local still supplies shadowing evidence.
pub(super) fn receiver_binding(&mut self, name: &str, site: &ResolveRefIn) -> Res<Option<KBinding>> {
let rows = self.bindings(&site.file_path)?;
let Some(binding) = innermost_binding(&rows, name, Some(site.line)) else { return Ok(None) };
let before = self.go_declares_on_line(name, site, &rows);
let Some(binding) = innermost_binding_unless(&rows, name, Some(site.line), before) else { return Ok(None) };
if site.language != "python" {
return Ok(Some(binding.clone()));
}
Expand Down
Loading