Skip to content

fix(js/ts): count CommonJS require() as an import edge - #750

Open
sandeep-patel-alepo-fifth wants to merge 2 commits into
peteromallet:mainfrom
sandeep-patel-alepo-fifth:fix/js-ts-commonjs-require-imports
Open

sandeep-patel-alepo-fifth wants to merge 2 commits into
peteromallet:mainfrom
sandeep-patel-alepo-fifth:fix/js-ts-commonjs-require-imports

Conversation

@sandeep-patel-alepo-fifth

Copy link
Copy Markdown

Problem

JS_SPEC and TYPESCRIPT_SPEC in desloppify/languages/_framework/treesitter/specs/scripting.py define import_query as ESM-only:

(import_statement source: (string (string_fragment) @path)) @import

ts_build_dep_graph therefore records no edge for require('./x'). On a CommonJS codebase the dependency graph comes out essentially empty and every file reports importer_count == 0.

That count is read by the orphaned, coupling, single_use and test_coverage detectors, so the damage is not limited to one detector.

Repro

Scanning a Strapi 4 backend (1372 JS files: 1128 use require(), 48 use ESM import):

desloppify scan --path ./packages/backend
...
orphaned: 1090 files with zero importers

Spot-checking those 1090 "orphaned" files:

file reported actual importers
src/utils/logger.js orphaned 175
src/utils/chatModel.js orphaned 24
src/utils/tokens.service.js orphaned 22
src/chat/ChatService.js (core orchestrator) orphaned 4

1090 of 1283 production files — 85% — were flagged.

Fix

Add require('...') and dynamic import('...') patterns to both specs. The require pattern uses an (#eq? @_require_fn "require") predicate, so notrequire('./x') and obj.require('./x') do not match. The anchor . in arguments: (arguments . (string ...)) restricts the capture to the first argument.

Same 1372-file backend after the fix:

files: 1372  zero-importer: 503 (36.7%)
  utils/logger.js         importers= 175
  utils/chatModel.js      importers=  24
  utils/tokens.service.js importers=  22
  chat/ChatService.js     importers=   4

The 36.7% residue is framework convention-loaded files (Strapi autoloads src/api/*/{controllers,services,routes}), which is the expected shape for this codebase rather than a graph failure.

Tests

python -m pytest desloppify/tests/ -q → 5810 passed, 3 skipped.

Two failures in test_review_commands.py::TestCmdReviewPrepare::test_do_run_batches_dry_run_generates_packet_and_prompts reproduce on a clean checkout of main at 3a7735d5 and are unrelated to this change.

Added test_js_dep_graph_records_commonjs_require_edges in desloppify/tests/lang/common/test_treesitter_imports_direct.py, covering CommonJS require, ESM import, dynamic import(), and the negative cases.

The JavaScript and TypeScript tree-sitter specs matched only
(import_statement ...), so a CommonJS codebase produced an essentially empty
dependency graph. Every file then reported importer_count == 0, which the
orphaned, coupling, single_use and test_coverage detectors all read.

On a 1372-file Strapi backend (1128 files using require(), 48 using ESM
import) this reported 1090 orphaned files -- including the logger imported by
175 modules and the core chat orchestrator.

Add require('...') and dynamic import('...') patterns to both specs. The
require pattern uses an (#eq? @_require_fn "require") predicate so
notrequire() and obj.require() do not match. Same 1372-file backend now
resolves logger.js to 175 importers and drops the zero-importer set to 36.7%,
the residue being framework convention-loaded files.
ts_build_dep_graph normalized every resolved import with
join(path.resolve(), resolved) whenever it was not absolute. But file_finder
returns paths relative to the current directory, and resolve_js_import derives
its candidate from the source file, so it also returns a cwd-relative path.
Joining that onto the absolute scan root produced a doubled path that never
matched file_set, and every edge was dropped.

Key the lookup on the absolute form of each file_list entry and try the
resolved path as-is before falling back to scan-root-relative, so edges match
whether the resolver returns absolute, cwd-relative or scan-relative paths.
Edges are now stored under the file_list key rather than the normalized
string, keeping imports and importers consistent with the graph keys.

With both fixes, the 1372-file Strapi backend goes from 1090 reported orphans
to 268, and the coupling detector finds 320 single-use candidates where it
previously found 0.

This branch has not been deployed

No deployments
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.

1 participant