Skip to content

fix(semantic): recognize .mts/.mjs/.cts/.cjs as source files - #75

Open
gbleu wants to merge 2 commits into
frontops-dev:mainfrom
gbleu:pr/esm-cjs-source-extensions
Open

fix(semantic): recognize .mts/.mjs/.cts/.cjs as source files#75
gbleu wants to merge 2 commits into
frontops-dev:mainfrom
gbleu:pr/esm-cjs-source-extensions

Conversation

@gbleu

@gbleu gbleu commented Jun 24, 2026

Copy link
Copy Markdown

Problem

Files using the explicit ESM/CJS TypeScript & JavaScript extensions — .mts, .mjs, .cts, .cjs — are not recognized as source files. They are classified as assets, so they skip Oxc symbol analysis entirely. As a result, a change to any such file never propagates to the projects that import it: affected reports only the package that directly owns the changed file, silently missing every downstream consumer.

This is invisible in mixed repos — .ts/.tsx analysis keeps working — so the gap only shows up as under-reporting on the .mts-based parts of the graph. In a monorepo whose package entrypoints and cross-package contracts are .mts (e.g. modern NestJS services, native Node ESM packages), that's the entire cross-service dependency layer.

Minimal repro

One package, one file, the same single-symbol change — only the extension differs:

extension affected --debug partition
.ts Partitioned files: 1 source, 0 assets
.tsx 1 source, 0 assets
.mts 0 source, **1 asset**
.mjs 0 source, **1 asset**
.cts 0 source, **1 asset**

With the changed file treated as an asset, the debug log shows Found 0 references to asset "...index.mts" and the consumers are never reached.

Root cause

SOURCE_EXTENSIONS in src/utils.rs (which drives is_source_file → the source/asset partition) lists only ["ts", "tsx", "js", "jsx"]. The same ts | tsx | js | jsx set is hardcoded in the sourceRoot directory walk and the resolver config.

This is the natural completion of #24, which added .js/.jsx.ts/.tsx ESM import resolution but did not cover the .mjs/.cjs runtime-extension variants that TypeScript emits for .mts/.cts sources (export * from "./foo.mjs" where the source is foo.mts).

Fix

  • utils.rs — add mts/mjs/cts/cjs to SOURCE_EXTENSIONS (the source/asset partition)
  • semantic/analyzer.rs — include them in the sourceRoot directory walk
  • semantic/mod.rs — remap .mjs.mts and .cjs.cts in the relative resolver (TS ESM emits runtime extensions), and probe .mts/.mjs/.cts + /index.mts variants
  • semantic/resolve_options.rs — add the extensions and the .mjs/.cjs extension_alias entries to the oxc resolver

Tests

  • New is_source_file assertions for .mts/.mjs/.cts/.cjs
  • New resolver tests: .mjs → .mts remap and /index.mts directory-entry probing
  • Full suite green: 209 passed; 0 failed
  • Verified end-to-end on a 250-package Nx 22 + pnpm production monorepo: changing a consumed symbol in a .mts temporal-contract now correctly marks the importing worker project as affected (previously missed), while projects importing unchanged symbols from the same package remain correctly pruned — i.e. precision is preserved, only the false negatives are fixed.

Summary by CodeRabbit

  • New Features

    • Expanded TypeScript/JavaScript file recognition to include .mts, .mjs, .cts, and .cjs files.
    • Improved module resolution so imports can find matching TypeScript variants and directory index files in more cases.
  • Bug Fixes

    • Workspace analysis now includes modern ESM/CJS file variants, helping them appear in search, navigation, and related tooling.
    • Added coverage for resolving module-style imports and index files to reduce missed references.

Files using the explicit ESM/CJS TypeScript & JavaScript extensions (.mts/.mjs/.cts/.cjs) were classified as assets rather than source, so they skipped Oxc symbol analysis entirely and changes to them never propagated to importers. This silently breaks true-affected detection for any monorepo whose package entrypoints/contracts use these extensions (e.g. modern NestJS services, native Node ESM packages). Completes frontops-dev#24, which added .js/.jsx ESM import resolution but not the .mjs/.cjs runtime-extension variants.

- utils.rs: add mts/mjs/cts/cjs to SOURCE_EXTENSIONS (source/asset partition)
- analyzer.rs: include them in the sourceRoot directory walk
- semantic/mod.rs: remap .mjs->.mts and .cjs->.cts (TS ESM emits runtime exts), probe .mts/.mjs/.cts + /index.mts variants
- resolve_options.rs: add the extensions + .mjs/.cjs extension_alias to the oxc resolver
@coderabbitai

coderabbitai Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@gbleu, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 15 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 736b06a0-9bb3-4e56-814b-f4af32b57d33

📥 Commits

Reviewing files that changed from the base of the PR and between 0d5d752 and d25aa25.

📒 Files selected for processing (2)
  • src/semantic/analyzer.rs
  • src/semantic/reference_finder.rs
📝 Walkthrough

Walkthrough

Source-file detection and module resolution now include .mts/.mjs/.cts/.cjs variants. The analyzer, resolver options, and fallback relative resolver were updated, and tests were added for .mjs remapping and .mts index resolution.

Changes

Extended module extension support

Layer / File(s) Summary
Source extension recognition
src/utils.rs, src/semantic/analyzer.rs, src/core.rs
SOURCE_EXTENSIONS and workspace file filtering now include .mts, .mjs, .cts, and .cjs, and the sourceRoot comment documents the expanded source-typed list.
Resolver options
src/semantic/resolve_options.rs
The resolver extension list now includes .mts, .mjs, .cts, and .cjs, and .mjs/.cjs aliases map to .mts/.cts with fallback to the original extensions.
Fallback relative resolution
src/semantic/mod.rs, src/semantic/reference_finder.rs
simple_resolve_relative now remaps .js/.jsx/.mjs/.cjs to TypeScript variants, probes the expanded extension and index suffix lists, and tests cover .mjs.mts and directory index .mts resolution.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • EladBezalel

Poem

🐇 I hopped through paths both old and new,
.mjs now finds its .mts view.
.cjs and friends join the springtime show,
So source files bloom যেখানে extensions grow.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: treating .mts/.mjs/.cts/.cjs as source files in semantic processing.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

🧹 Nitpick comments (2)
src/semantic/reference_finder.rs (1)

535-582: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a mirror test for .cjs.cts remapping.

The new remap branch for .cjs is unverified in this test set; a small symmetric test would guard the added fallback path.

Suggested test pattern
+#[test]
+fn test_simple_resolve_cjs_to_cts_remapping() {
+  let temp_dir = TempDir::new().expect("Failed to create temp dir");
+  let cwd = temp_dir.path();
+  let src_dir = cwd.join("src");
+  fs::create_dir_all(&src_dir).expect("Failed to create src dir");
+  fs::write(src_dir.join("legacy.cts"), "export const schema = 1;")
+    .expect("Failed to write test file");
+
+  let profiler = Arc::new(Profiler::new(false));
+  let analyzer =
+    WorkspaceAnalyzer::new(vec![], cwd, profiler.clone()).expect("Failed to create analyzer");
+  let reference_finder = ReferenceFinder::new(&analyzer, cwd, profiler);
+
+  let resolved = reference_finder.simple_resolve(src_dir.as_path(), "./legacy.cjs");
+  assert_eq!(resolved, Some(PathBuf::from("src/legacy.cts")));
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/semantic/reference_finder.rs` around lines 535 - 582, Add a symmetric
unit test in ReferenceFinder’s simple_resolve test suite to cover .cjs resolving
to .cts, mirroring the existing .mjs→.mts case. Use
ReferenceFinder::simple_resolve with a temporary workspace containing a .cts
source file and assert the remapped PathBuf is returned, so the new fallback
branch is verified alongside test_simple_resolve_mjs_to_mts_remapping and
test_simple_resolve_index_mts.
src/semantic/analyzer.rs (1)

247-252: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Deduplicate source-extension matching to a single shared definition.

This extension list now exists in multiple places; a future mismatch can silently break affected detection again. Reuse the shared source-file predicate/constant here instead of re-declaring literals.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/semantic/analyzer.rs` around lines 247 - 252, The source-extension
matching logic is duplicated in the analyzer, which risks drifting from the
shared definition. Update the extension check in analyzer.rs to reuse the
existing shared source-file predicate or constant instead of hardcoding the
literals again, using the relevant shared helper/definition that already powers
source-file detection in the codebase.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/semantic/analyzer.rs`:
- Around line 247-252: The source-extension matching logic is duplicated in the
analyzer, which risks drifting from the shared definition. Update the extension
check in analyzer.rs to reuse the existing shared source-file predicate or
constant instead of hardcoding the literals again, using the relevant shared
helper/definition that already powers source-file detection in the codebase.

In `@src/semantic/reference_finder.rs`:
- Around line 535-582: Add a symmetric unit test in ReferenceFinder’s
simple_resolve test suite to cover .cjs resolving to .cts, mirroring the
existing .mjs→.mts case. Use ReferenceFinder::simple_resolve with a temporary
workspace containing a .cts source file and assert the remapped PathBuf is
returned, so the new fallback branch is verified alongside
test_simple_resolve_mjs_to_mts_remapping and test_simple_resolve_index_mts.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ae560e9f-1e51-4b25-a578-f10cd574fcd5

📥 Commits

Reviewing files that changed from the base of the PR and between 8b64617 and 0d5d752.

📒 Files selected for processing (6)
  • src/core.rs
  • src/semantic/analyzer.rs
  • src/semantic/mod.rs
  • src/semantic/reference_finder.rs
  • src/semantic/resolve_options.rs
  • src/utils.rs

Address review feedback:

- analyzer.rs: reuse the shared is_source_file predicate in the sourceRoot walk instead of re-hardcoding the extension literals, so it can't drift from SOURCE_EXTENSIONS (the drift that caused the original bug)
- reference_finder.rs: add test_simple_resolve_cjs_to_cts_remapping mirroring the .mjs->.mts case
@gbleu

gbleu commented Jun 24, 2026

Copy link
Copy Markdown
Author

Thanks for the review — both nitpicks addressed in d25aa25:

  • Dedup extension check (analyzer.rs): the sourceRoot walk now reuses the shared is_source_file predicate instead of re-declaring the extension literals, so it's a single source of truth with SOURCE_EXTENSIONS. This also closes the drift gap that caused the original bug.
  • .cjs → .cts mirror test (reference_finder.rs): added test_simple_resolve_cjs_to_cts_remapping alongside the .mjs → .mts case.

Full suite green: 210 passed; 0 failed.

@shaharkazaz

Copy link
Copy Markdown
Collaborator

@gbleu Thanks for the PR!
Can you verify using the preview release?

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

📦 Preview Release Available

A preview release has been published for commit d25aa25.

Installation

npm install https://github.com/frontops-dev/domino/releases/download/pr-75-d25aa25/front-ops-domino-1.4.0.tgz

Running the preview

npx https://github.com/frontops-dev/domino/releases/download/pr-75-d25aa25/front-ops-domino-1.4.0.tgz affected

Details

@gbleu

gbleu commented Jul 8, 2026

Copy link
Copy Markdown
Author

Thank you @shaharkazaz! Validated the darwin-arm64 binary locally on a small repro, would also need #77 and #78 to drop the fork

@shaharkazaz

Copy link
Copy Markdown
Collaborator

@gbleu approved both workflows there as well, a preview will be up soon.
I'll review them when I can 👍

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.

2 participants