Skip to content

Fix relative TS/JS imports never showing up in deps/imported_by#543

Closed
nsxdavid wants to merge 1 commit into
justrach:mainfrom
nsxdavid:fix/relative-import-resolution
Closed

Fix relative TS/JS imports never showing up in deps/imported_by#543
nsxdavid wants to merge 1 commit into
justrach:mainfrom
nsxdavid:fix/relative-import-resolution

Conversation

@nsxdavid

@nsxdavid nsxdavid commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #541.

Every import with .. in it got thrown out before it hit the dep graph, so all the intra-repo ../foo deps were invisible and imported_by on an internal file came back empty. This resolves relative specifiers against the importing file into real repo paths instead of dropping them.

The skip lived in two places that had drifted: rebuildDepsFor (fresh index) and rebuildDepsFromOutline (snapshot restore). The binary always reloads from a snapshot, so the restore copy is the one that broke it in real use. You could write an in-process test that passed and still have it broken from the CLI. Both now go through one shared resolveDependencyKey, including the same dedup, so a file importing ../foo as both a type and a value doesn't double up after reload.

outline.imports stays raw, the resolved strings are interned in a dep-graph-owned arena, so issue-114 and the other import tests don't change. Also fixes a pre-existing leak in normalizePath that this newly hits under a leak-checking allocator (parts was errdefer-only, so it leaked on the success path).

Red to green: the new test checks depends-on and imported_by for a ../ import. Fails before (relative dep dropped), passes after. Verified through the built binary too, not just in-process. On a two-file repo where role/driver.ts does import type { Bus } from "../bus/event-bus.ts":

Before:

role/driver.ts depends on:
  node:path
bus/event-bus.ts is imported by:
  (none)

After:

role/driver.ts depends on:
  bus/event-bus.ts
  node:path
bus/event-bus.ts is imported by:
  role/driver.ts

Files: src/explore.zig (resolveDependencyKey, rebuildDepsFor, normalizePath), src/snapshot.zig (rebuildDepsFromOutline), src/test_parser.zig.

Off current main, no generated files. Read CONTRIBUTING and followed it.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8c629070f5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/explore.zig
".";
const joined = std.fmt.allocPrint(allocator, "{s}/{s}", .{ dir, raw }) catch return null;
defer allocator.free(joined);
return normalizePath(joined, allocator);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve extensionless TS imports before storing

When a TS/JS file uses the common extensionless form, e.g. import { foo } from "../bus/event-bus", this normalizes and stores the edge as daemon/src/bus/event-bus rather than the indexed file path daemon/src/bus/event-bus.ts. Since getImportedBy only checks the exact queried path and its basename, codedb_deps daemon/src/bus/event-bus.ts still returns no importer for the typical TS/JS case this change is meant to fix. Please resolve relative specs against indexed candidates or the language's extension list before interning the dependency key.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 83fddc0. Extensionless imports now take the importing file's extension, so import { foo } from "../bus/event-bus" from a .ts file resolves to daemon/src/bus/event-bus.ts and lines up with the indexed file. Added a test that indexes that exact extensionless form and checks both depends-on and imported_by resolve.

Went with the importer's extension rather than probing the index, since dep resolution runs per-file during indexing and the target may not be indexed yet, so an index lookup would be order-dependent. The same-language assumption (TS imports TS, JS imports JS) covers the common case without that hazard.

Comment thread src/explore.zig
if (isRelativeImport(spec)) {
const tmp = resolveRelativeImportPath(importer_path, spec, tmp_allocator) orelse return null;
defer tmp_allocator.free(tmp);
return try dep_graph.internString(tmp);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid leaking interned dependency keys on reindex

Each relative import now gets duplicated into str_arena, but replacing deps during watcher reindex only deinitializes the old forward list and removes reverse membership; the arena allocations for the old resolved keys are never reclaimed until the entire Explorer shuts down. In a long-running MCP server, repeatedly editing a TS/JS file with relative imports will grow memory by one interned path per import per rebuild. Consider interning by value with reuse or owning/freeing resolved keys when setDeps replaces a file's deps.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 83fddc0. internString now dedups through a map, so re-resolving the same path returns the same arena slice instead of allocating a fresh one. Re-editing a file with relative imports reuses the existing keys, so growth is bounded by the number of distinct resolved paths, not by rebuilds. Added a test asserting two interns of the same string return the same pointer.

rebuildDepsFor (and its twin on the snapshot-restore path,
rebuildDepsFromOutline) dropped any import specifier containing "..",
so intra-repo relative imports never appeared in deps/imported_by; only
bare specifiers (node:*, package names) survived.

Add one shared resolveDependencyKey() used by both dep-build paths:
relative specifiers resolve against the importing file into repo-rooted
paths. Extensionless imports ("../foo") take the importer's extension so
they line up with the indexed file ("dir/foo.ts"). Resolved keys are
interned in a dep-graph-owned arena with a dedup map, so re-indexing a
file reuses one allocation per path instead of growing the arena. The
snapshot path dedups like the fresh path. outline.imports stays raw, so
issue-114 and the other import tests are unchanged. Also fixes a
pre-existing success-path leak in normalizePath.

Tests cover depends-on/imported_by for both explicit and extensionless
relative imports, and that interned keys are reused.
@nsxdavid nsxdavid force-pushed the fix/relative-import-resolution branch from 8c62907 to 83fddc0 Compare June 7, 2026 00:15
@justrach

justrach commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Hi @nsxdavid — thanks again, another solid one. 🙏 The arena + intern-map ownership is correct (maps freed before the arena in deinit), and you fixed a real leak (normalizePath errdeferdefer) along the way.

Same as the sibling PR: instead of asking you to retarget off main, I've cherry-picked your commit onto release/0.2.5825 as 223d5a2, authorship preserved. Verified: clean apply (only a trivial test-file merge with the other PR), all three issue-2 tests pass, full suite green (725/725).

Tiny non-blocking note if you ever revisit: worth confirming normalizePath returns null on a root-escaping path as the doc-comment says — it's name-only (graph key, never an open()), so purely cosmetic.

Credited as author + in the 0.2.5825 release notes. Closing as landed. Thank you! 🙏

@justrach justrach closed this Jun 7, 2026
justrach added a commit that referenced this pull request Jun 7, 2026
…ps/search/CLI + @nsxdavid credit

Documents the work that landed in 0.2.5825 beyond the original #537/#538/#539
cut (codedb_callpath + PageRank #531, symbol filters, JSON/paths_only output,
TS/JS dep-graph fixes #540/#541/#542/#543/#548, search word-index loading #547,
CLI hardening #528) and credits @nsxdavid. Removes the pre-publish TODO.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Relative imports never show up in deps or imported_by

2 participants