Skip to content

fix: no-unused-modules correctly handles export { X as default } syntax - #3244

Draft
whatfontisthis wants to merge 1 commit into
import-js:mainfrom
whatfontisthis:fix/no-unused-modules-export-as-default
Draft

whatfontisthis wants to merge 1 commit into
import-js:mainfrom
whatfontisthis:fix/no-unused-modules-export-as-default

Conversation

@whatfontisthis

Copy link
Copy Markdown

Summary

Fixes #3216.

When a file uses export { testHandler as default } syntax (exporting a local binding as the default export), the no-unused-modules rule incorrectly flagged the default export as unused even when it was imported correctly via import handler from './module' in another file.

  • Root cause: In updateExportUsage, when scanning ExportNamedDeclaration specifiers, the raw exported name "default" was added to newExportIdentifiers. However, the exports map uses IMPORT_DEFAULT_SPECIFIER ("ImportDefaultSpecifier") as the key for default exports. This mismatch meant the existing whereUsed data (populated during doPreparation) was dropped from the map on the first lint pass, and the export was stored under the wrong key "default". On the next lint of the same file, checkUsage looked up IMPORT_DEFAULT_SPECIFIER and found nothing, incorrectly reporting the export as unused.
  • Fix: Normalize "default"IMPORT_DEFAULT_SPECIFIER when building newExportIdentifiers in updateExportUsage, consistent with how EXPORT_DEFAULT_DECLARATION is already handled on the line above.

Changes

  • src/rules/no-unused-modules.js: Normalize "default" exported specifier name to IMPORT_DEFAULT_SPECIFIER in updateExportUsage.
  • tests/src/rules/no-unused-modules.js: Add regression test that lints the file twice (second lint triggers the bug — doPreparation is skipped due to caching, exposing the corrupted export map).
  • tests/files/no-unused-modules/export-as-default/: New fixture files (test-export.js, usage.js) for the test.

Test plan

  • New test fails before the fix and passes after
  • Full no-unused-modules test suite passes: BABEL_ENV=test node_modules/.bin/nyc node_modules/.bin/mocha tests/src/rules/no-unused-modules.js → 157 passing, 1 pending, 0 failing

🤖 Generated with Claude Code

…syntax

In updateExportUsage, specifiers exported as "default" were added to
newExportIdentifiers using the raw string "default", but the exports map
stores default exports under the IMPORT_DEFAULT_SPECIFIER key
("ImportDefaultSpecifier"). This mismatch caused the whereUsed data to
be lost on the first lint pass, making the rule incorrectly report the
default export as unused on subsequent lints.

Fixes import-js#3216

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@codecov

codecov Bot commented Apr 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.52%. Comparing base (3a99e4c) to head (5a6c4b0).
⚠️ Report is 53 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3244      +/-   ##
==========================================
+ Coverage   95.50%   95.52%   +0.02%     
==========================================
  Files          83       83              
  Lines        3690     3690              
  Branches     1333     1333              
==========================================
+ Hits         3524     3525       +1     
+ Misses        166      165       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@whatfontisthis

Copy link
Copy Markdown
Author

Note on the codecov/project check: codecov/patch passes — all lines introduced by this fix are covered by the new regression test. The project-level coverage gate appears to be a pre-existing threshold issue unrelated to this change. Happy to add more tests if the maintainers think that would help.

@LaishGlenberg

Copy link
Copy Markdown

Can we please get this merged? I am getting this problem on non default module exports as well. No amount of configuration or testing could get it to work.

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

Reviewed 5a6c4b0e1f52c883d688586e783a49631651fbd4. Normalizing the exported default name to the same map key used during preparation preserves whereUsed across repeated lint passes. I reproduced the new regression on base 3a99e4c8d3bfd2cd466353d11784eb06dad9b166 (152 passing, 1 pending, 1 failing); the focused head run passes 153 tests, with 1 pending.

An independent Linter probe passes all 13 observations on head, versus 6 failures on base: three consecutive lint passes for a used default export, an unused named export beside a used default, an unused default beside a used named export, and a quoted "default" specifier. Removing the consumer import also correctly makes the default export unused. This supports the scoped default-export cache fix; it does not establish that every named-export report in the linked issue is resolved.

With ESLint 8.57.1, npm test passes 3,015 tests (1 pending), including posttest lint, generated documentation checks, markdownlint and build. With ESLint 9.39.5, the test phase passes 3,011 tests (1 pending); its posttest is blocked by the repository's legacy lint configuration/development plugin compatibility.

The upstream check list still contains two failed old-ESLint matrix jobs and cancelled Windows jobs. Their run log endpoint returns HTTP 410, so I have not established their causes and am not claiming an entirely green CI matrix.

Review and local validation performed with Codex.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

no-unused-modules erroneously flags as default exports

3 participants