Skip to content

fix(resolution): honor jsconfig.json and compilerOptions.baseUrl (#2153) - #2176

Closed
Rishet11 wants to merge 2 commits into
Graphify-Labs:v8from
Rishet11:fix/2153-jsconfig-baseurl
Closed

fix(resolution): honor jsconfig.json and compilerOptions.baseUrl (#2153)#2176
Rishet11 wants to merge 2 commits into
Graphify-Labs:v8from
Rishet11:fix/2153-jsconfig-baseurl

Conversation

@Rishet11

Copy link
Copy Markdown
Contributor

Closes #2153

Problem

Non-relative imports produced no edge in a Rails/webpacker project, so every module under baseUrl was orphaned and affected answered nothing.

There are two defects, and the first is not quite the one the issue describes. baseUrl is read today, at resolution.py:146-147:

base_url = compiler_options.get("baseUrl") or "."
paths_base = base_dir / base_url

but only as the base that paths targets resolve against. _read_tsconfig_aliases returns only paths entries, so a config declaring baseUrl and no paths — exactly the webpacker layout — yields an empty alias map and every bare specifier dies.

The second is as reported: _load_tsconfig_aliases probed only tsconfig.json, never jsconfig.json, even though json_config.py already indexes jsconfig.json. That is why the config's own nodes appear in the graph while resolution ignores the file.

Fix

  • _find_js_config walks up probing tsconfig.json then jsconfig.json, tsconfig winning within a directory — matching tsc and editors, which consult jsconfig.json only when there is no tsconfig.json.
  • _load_tsconfig_base_url exposes the resolved baseUrl directory, cached per config path.
  • _resolve_tsconfig_alias gains base_url=None, used as a resolution root of last resort: only when no declared alias matches. Threaded through static resolution and the three regex-rescue dynamic-import paths (Svelte, Astro, TS/TSX), since the issue reports both.

Two deliberate design choices worth flagging for review:

baseUrl is not modelled as a synthesized "*" alias. That was the tempting one-line version, and it is wrong: "*" scores (1, 0) in _match_tsconfig_alias, while a declared non-wildcard directory-prefix alias scores (2, -len(prefix)). Since _resolve_tsconfig_alias picks the smallest tuple, (1, 0) < (2, -4) means an implicit "*" would silently shadow a declared alias like "@lib": ["src/lib"] and regress #1269. Keeping baseUrl out of the specificity contest entirely is what makes existing precedence provably untouched. test_declared_directory_prefix_alias_still_wins pins this.

The fallback returns a candidate only when it is a real file on disk. The declared-alias path deliberately returns a non-resolving first candidate to preserve phantom/external-edge behavior; doing that for baseUrl would turn import React from 'react' into a fabricated <baseUrl>/react edge. test_external_package_not_fabricated_under_baseurl pins this.

Verification

End-to-end on the reported layout (jsconfig.json with "baseUrl": "app/javascript", packs/dashboard.js importing 'mods/Widget.js').

Before — dashboard.js has no outbound edges at all:

edges into Widget: 1
   app_javascript_mods_widget -> app_javascript_mods_widget_widget ( contains )
--- what dashboard points at instead:
(nothing)

After:

edges into Widget: 3
   app_javascript_mods_widget -> app_javascript_mods_widget_widget ( contains )
   app_javascript_packs_dashboard -> app_javascript_mods_widget ( re_exports )
   app_javascript_packs_dashboard -> app_javascript_mods_widget_widget ( imports )
Check Result
New tests before the fix 7 of 11 fail (real negative controls)
New tests after 11 passed
Full suite, base 66d8110 15 failed, 3620 passed
Full suite, this branch 15 failed, 3631 passed
Failure-set diff identical, no new failures
ruff check passed
python -m tools.skillgen --check OK, 134 artifacts match

The 15 pre-existing failures are unrelated (test_ollama.py backend detection and similar), identical before and after, measured with -p no:randomly in the same env.

Four of the eleven tests pass both before and after by design — they are preservation guards: declared paths and directory-prefix aliases are not shadowed, relative imports are untouched, an absent baseUrl changes nothing, and an external package is not fabricated.

Known gap

baseUrl inherited through an extends chain is not followed — _load_tsconfig_base_url reads it only from the directly-found config, whereas alias loading does follow extends. In that case the fallback simply does not apply, degrading to today's behavior (unresolved import) rather than producing a wrong edge. I left it out to keep this diff focused on the reported case, but it is an asymmetry and I'm happy to follow the extends chain for baseUrl here if you'd prefer it in one PR.

Rishet11 added 2 commits July 25, 2026 06:33
…phify-Labs#2153)

Non-relative imports produced no edge in a Rails/webpacker project, so
every module under `baseUrl` was orphaned and `affected` answered nothing.

Two defects. First, only `tsconfig.json` was probed, never
`jsconfig.json` — the plain-JS spelling of the same file, which
json_config.py already indexes, so the config's nodes appeared in the
graph while resolution ignored it entirely. Second, `baseUrl` was
consumed only as the base that `paths` targets resolve against, so a
config declaring `baseUrl` and NO `paths` produced an empty alias map and
every bare specifier died.

`_find_js_config` now probes both names, tsconfig winning within a
directory as tsc and editors do. `baseUrl` is exposed separately and used
as a resolution root of LAST RESORT, tried only when no declared alias
matches, so `paths` precedence (Graphify-Labs#1269, Graphify-Labs#927, Graphify-Labs#1531) is untouched. It is
deliberately not modelled as a synthesized `*` alias: that would score
(1, 0) in _match_tsconfig_alias and beat a declared non-wildcard
directory-prefix alias at (2, -len), silently shadowing it. The fallback
also returns a candidate only when it is a real file, so an external
package import is not fabricated into a <baseUrl>/<pkg> edge.

Threaded through the three regex-rescue dynamic-import paths (Svelte,
Astro, TS/TSX) as well as static resolution, since the issue reports both.
…Labs#2153)

Eleven cases: the webpacker repro (jsconfig + baseUrl, no paths) for
static, dynamic and extensionless specifiers; the same for tsconfig;
tsconfig winning over jsconfig in one directory; and four preservation
guards that pass before and after — declared paths and directory-prefix
aliases are not shadowed, relative imports are untouched, an absent
baseUrl changes nothing, and an external package is not fabricated.
Copilot AI review requested due to automatic review settings July 25, 2026 01:04

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@safishamsi

Copy link
Copy Markdown
Collaborator

Thanks @Rishet11. Shipped in v0.9.27 (cherry-picked to v8 to keep your authorship). Closed-unmerged here, but it's in the release: https://github.com/Graphify-Labs/graphify/releases/tag/v0.9.27

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.

JS: jsconfig.json baseUrl not honoured — non-relative imports (static AND dynamic) produce no edge, orphaning every module in a Rails/webpacker app

3 participants