Skip to content

feat(no-extraneous-dependencies): allow package to import itself #309

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from

Conversation

remcohaszing
Copy link
Contributor

@remcohaszing remcohaszing commented Apr 18, 2025

Allow packages to import themselves via the package.json exports field.

If a package tries this, but the exports field is not defined, a new message is reported.

Closes #305


Important

Allow self-imports in no-extraneous-dependencies if exports field is defined in package.json, with error reporting for missing exports.

  • Behavior:
    • Allow self-imports in no-extraneous-dependencies if exports field is defined in package.json.
    • Report selfImport error if a package imports itself without exports field.
  • Code Changes:
    • Update getDependencies() in no-extraneous-dependencies.ts to include name and exports.
    • Modify reportIfMissing() to handle self-import logic.
  • Tests:
    • Add valid test case for self-import with exports in no-extraneous-dependencies.spec.ts.
    • Add invalid test case for self-import without exports in no-extraneous-dependencies.spec.ts.

This description was created by Ellipsis for 4e0dc4f. It will automatically update as commits are pushed.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of self-imports to allow packages to import themselves only if their package.json defines an exports field.
  • Tests
    • Added new test cases to verify correct behavior for self-imports with and without the exports field.
  • Chores
    • Introduced new fixture files to support expanded test coverage for package-named exports.

Allow packages to import themselves via the `package.json` exports
field.

If a package tries this, but the exports field is not defined, a new
message is reported.

Closes un-ts#305
Copy link

changeset-bot bot commented Apr 18, 2025

🦋 Changeset detected

Latest commit: d878aac

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
eslint-plugin-import-x Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link

coderabbitai bot commented Apr 18, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This change updates the import-x/no-extraneous-dependencies ESLint rule to refine how self-imports are handled. The rule now allows a package to import itself only if its package.json includes an exports field. The logic for extracting dependency information is expanded to consider the name and exports fields from package.json. New test cases are added to verify the updated behavior, including scenarios where self-imports are permitted or disallowed based on the presence of the exports field.

Changes

File(s) Change Summary
src/rules/no-extraneous-dependencies.ts Enhanced dependency extraction to include name and exports from package.json. Updated self-import logic and error reporting. Added new message ID for self-import violations.
test/rules/no-extraneous-dependencies.spec.ts Added tests for self-imports, covering both allowed (with exports) and disallowed (without exports) cases.
test/fixtures/package-named-exports/index.js
test/fixtures/package-named-exports/package.json
Introduced a new fixture package with an exports field and a default export for test coverage.
.changeset/cruel-cooks-notice.md Added a changeset entry describing the update to the self-import rule.

Sequence Diagram(s)

sequenceDiagram
    participant ESLintRule as no-extraneous-dependencies
    participant PackageJson as package.json
    participant ImportStatement as Import Statement

    ImportStatement->>ESLintRule: import { foo } from "package-name"
    ESLintRule->>PackageJson: Read name and exports fields
    alt Package name matches import & exports field exists
        ESLintRule-->>ImportStatement: Allow import (no error)
    else Package name matches import & no exports field
        ESLintRule-->>ImportStatement: Report selfImport error
    else Import is not self-import
        ESLintRule-->>ImportStatement: Perform standard dependency check
    end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Assessment against linked issues

Objective Addressed Explanation
Do not report self-imports in no-extraneous-dependencies rule (#305) The rule now allows self-imports only if the exports field exists, which is stricter than the linked issue’s expectation of allowing all self-imports.

Assessment against linked issues: Out-of-scope changes

No out-of-scope changes were detected.

Suggested labels

enhancement

Suggested reviewers

  • JounQin

Poem

A bunny hopped through package land,
With exports in its gentle hand.
"Self-imports now are mostly fine—
Just add 'exports' and you’ll align!
But if you miss that special key,
The rule will scold you, can't you see?
Hop on, dear dev, with code so neat,
Your dependencies now are quite complete!" 🐇✨

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

codesandbox-ci bot commented Apr 18, 2025

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Copy link

Copy link

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

❌ Changes requested. Reviewed everything up to 4e0dc4f in 1 minute and 59 seconds

More details
  • Looked at 215 lines of code in 9 files
  • Skipped 0 files when reviewing.
  • Skipped posting 7 drafted comments based on config settings.
1. src/rules/no-extraneous-dependencies.ts:71
  • Draft comment:
    Initializing packageContent with 'name' and 'exports' properties set to undefined is clear. Consider adding a comment explaining that these fields are merged only once from the first package.json to avoid later overwrites.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50%
    None
2. src/rules/no-extraneous-dependencies.ts:328
  • Draft comment:
    Self-import block: verifies if a package imports itself and reports an error if the 'exports' field is missing. The implementation meets the issue requirements. A short inline comment might help clarify the intention.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50%
    None
3. .changeset/cruel-cooks-notice.md:5
  • Draft comment:
    The changeset notice refers to import-x/no-extraneous-imports but the actual rule is named no-extraneous-dependencies. Please update the notice for consistency.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50%
    Without seeing the actual ESLint plugin code or documentation, I cannot verify if the rule is actually named 'no-extraneous-dependencies'. The changeset is introducing a new feature for a rule named 'import-x/no-extraneous-imports', and we should assume the author knows the correct name of their own rule. The comment makes an assertion without providing evidence.
    I could be wrong if there's established naming conventions for ESLint rules that I'm not aware of, or if there's documentation elsewhere showing the correct rule name.
    Even if there are conventions, we need clear evidence that the current name is wrong. Without such evidence, we should trust the author's knowledge of their own codebase.
    Delete this comment as it makes an assertion about the rule name being wrong without providing any evidence to support the claim.
4. src/rules/no-extraneous-dependencies.ts:44
  • Draft comment:
    Good job adding name and exports to the extracted package fields. This extension is key for handling self-imports.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50%
    None
5. src/rules/no-extraneous-dependencies.ts:97
  • Draft comment:
    Merging the package’s name and exports from the first package.json is reasonable, but note that when multiple packageDirs are provided, only the first one’s name/exports is used. Ensure this merging behavior is as intended.
  • Reason this comment was not posted:
    Comment did not seem useful. Confidence is useful = 0% <= threshold 50%
    The comment is asking the PR author to ensure that the merging behavior is intended, which violates the rule against asking for confirmation of intention. It does not provide a specific suggestion or point out a clear issue with the code.
6. src/rules/no-extraneous-dependencies.ts:329
  • Draft comment:
    The new self-import check is clear: if the imported package name matches the package’s own name and the exports field is missing, a 'selfImport' error is reported. This meets the requirements.
  • Reason this comment was not posted:
    Confidence changes required: 0% <= threshold 50%
    None
7. test/rules/no-extraneous-dependencies.spec.ts:317
  • Draft comment:
    There's a potential typo in the filename 'foo.tes.js' used in the tInvalid test cases (e.g., at line 317). Did you mean 'foo.test.js'? Please verify if this was intentional.
  • Reason this comment was not posted:
    Comment was not on a location in the diff, so it can't be submitted as a review comment.

Workflow ID: wflow_y4n7hpxvA0T9NPK8


Want Ellipsis to fix these issues? Tag @ellipsis-dev in a comment. You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

packageContent.peerDependencies,
packageContent_.peerDependencies,
)
Object.assign(
Copy link

Choose a reason for hiding this comment

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

Merging bundledDependencies using Object.assign may not properly concatenate arrays if multiple package.json files provide them. Consider whether concatenating arrays (e.g. using array spread or concat) might be more appropriate here.

Copy link
Member

@JounQin JounQin May 8, 2025

Choose a reason for hiding this comment

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

Personally I don't like current refactor, it's too verbose to me, only name and exports are special.

@remcohaszing Could you revert it back to previous style? So that I can merge this PR as-is.

Copy link
Member

Choose a reason for hiding this comment

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

Friendly ping @remcohaszing

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What do you mean by reverting to the previous style? This is not a stylistic change.

Copy link
Member

@JounQin JounQin May 26, 2025

Choose a reason for hiding this comment

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

Aren't they? Only name and exports are special, all other keys can key previous style assignment.

Copy link
Member

@JounQin JounQin May 26, 2025

Choose a reason for hiding this comment

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

By the way, should we check whether the importee is truly exported in exports? Example:

import 'fake-pkg/subpath'

/subpath needs to be checked whether available in exports?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

all other keys can key previous style assignment.

I really don’t understand what style you envision. Please elaborate. Do you mean the loop? That doesn’t work. Not all keys are objects that should be shallow merged.

By the way, should we check whether the importee is truly exported in exports?

No. That doesn’t change anything. If fake-pkg uses its own package exports, it uses the exports from its own package.json. It no longer checks node_modules/fake-pkg/package.json. So even if the fake-pkg/subpath export doesn’t exist, it still doesn’t resolve to implicit dependencies.

Copy link
Member

@JounQin JounQin May 26, 2025

Choose a reason for hiding this comment

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

I really don’t understand what style you envision. Please elaborate. Do you mean the loop? That doesn’t work. Not all keys are objects that should be shallow merged.

Loop with check name and exports specially only, all other keys should not be changed.

So even if the fake-pkg/subpath export doesn’t exist, it still doesn’t resolve to implicit dependencies.

I'm not sure to understand, I know <rootDir>/node_modules/fake-pkg/package.json won't be checked, I'm talking about <rootDir>/package.json, and also, when the user installs fake-pkg manually or in its dependency tree implicitly, what means <rootDir>/node_modules/fake-pkg/package.json is present, what'll happen?

Copy link
Collaborator

Choose a reason for hiding this comment

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

i think you're both right

@remcohaszing i think you may be better off having a set of known merged fields, like:

const fieldsToMerge = ['dependencies', 'devDependencies', /* ... */];
for (const field of fieldsToMerge) {
  // if the package manifest has it, merge it into `packageContent[field]`
}

you're right we probably shouldn't blindly merge every key now that not all keys are objects (and that may change in future if we add even more non-object keys).

i think @JounQin is just suggesting you keep the existing "deep merge every key", but you'd have to have a condition in there to skip name and exports. then it'd achieve the same as what you have here

but imo we should just be explicit about the fields we want to merge

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, this PR fell off my radar. I used a loop now.

Copy link

pkg-pr-new bot commented Apr 18, 2025

Open in StackBlitz

npm i https://pkg.pr.new/eslint-plugin-import-x@309

commit: d878aac

Copy link

codecov bot commented Apr 18, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 96.00%. Comparing base (335d9f5) to head (4e0dc4f).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #309      +/-   ##
==========================================
+ Coverage   95.99%   96.00%   +0.01%     
==========================================
  Files          91       91              
  Lines        4744     4756      +12     
  Branches     1785     1791       +6     
==========================================
+ Hits         4554     4566      +12     
  Misses        189      189              
  Partials        1        1              

☔ View full report in Codecov by Sentry.
📢 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.

@@ -297,6 +325,20 @@ function reportIfMissing(
return
}

if (importPackageName === deps.name) {
if (!deps.exports) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm not sure to understand why exports field is required, and is this prevents self-importing issues in source files?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don’t know the rationale behind that decision, but that is how Node.js behaves. You can try it by creating a directory with two files:

package.json:

{
  "name": "pkg",
  "exports": "./index.js"
}

index.js:

require('pkg')

console.log('Hello')

If you run index.js, this logs:

$ node index.js
Hello

If you now remove the exports field, or change it to main, this logs:

$ node index.js 
node:internal/modules/cjs/loader:1228
  throw err;
  ^

Error: Cannot find module 'pkg'
Require stack:
- /home/remco/Downloads/asd/index.js
    at Function._resolveFilename (node:internal/modules/cjs/loader:1225:15)
    at Function._load (node:internal/modules/cjs/loader:1055:27)
    at TracingChannel.traceSync (node:diagnostics_channel:322:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:220:24)
    at Module.require (node:internal/modules/cjs/loader:1311:12)
    at require (node:internal/modules/helpers:136:16)
    at Object.<anonymous> (/home/remco/Downloads/asd/index.js:1:1)
    at Module._compile (node:internal/modules/cjs/loader:1554:14)
    at Object..js (node:internal/modules/cjs/loader:1706:10)
    at Module.load (node:internal/modules/cjs/loader:1289:32) {
  code: 'MODULE_NOT_FOUND',
  requireStack: [ '/home/remco/Downloads/asd/index.js' ]
}

Node.js v22.14.0

Copy link
Member

Choose a reason for hiding this comment

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

Even if it works for self-importing, but I personally still believe it's not a good practice to do like this as circular dependency itself. It should only be allowed in non-source files IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This example is indeed circular, which is a bit silly. This was to keep it minimal. Modules can resolve package-local modules using package.json exports. Tests are one useful example, but I can think of other use cases.

The goal of this PR however is not to discuss whether or not people should do this or when. People can do it. The concept of what a source file is also varies per project. The goal of this PR is to fix a false positives for detecting imports of extraneous modules.

Copy link
Member

@JounQin JounQin Apr 18, 2025

Choose a reason for hiding this comment

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

People can do anything they want without enabling this rule, the ESLint rules are for good practice, not how codes can be used in runtime. Otherwise, running the codes itself already helps you confirming it's working.

But I'd like to hear more voices from @thepassle @SukkaW @Shinigami92 @43081j

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, to summary, import/require via package's own exports is supported by Node, main is not, right?

Correct!

Copy link
Member

@JounQin JounQin May 8, 2025

Choose a reason for hiding this comment

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

@remcohaszing Would you like to combine your proposed import-x/no-own-exports rule into this PR together? Or maybe wait for other reviewers.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That rule seems unrelated to these changes TBH. Also I don’t have personal interest in that rule.

Copy link
Member

Choose a reason for hiding this comment

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

Well, that rule should be added together with this behavior change IMO.

Copy link
Collaborator

Choose a reason for hiding this comment

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

That makes sense to me

We can probably merge this one first @JounQin and one of us can work on the new rule separately.

It looks like this won't change existing behaviour other than making this edge case work. So should be an easy one to land

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/rules/no-extraneous-dependencies.ts (1)

98-114: Don’t use Object.assign to merge arrays; concatenate/deduplicate bundledDependencies

Object.assign on arrays overwrites indices rather than concatenating. This can lose entries when multiple package.json files provide bundledDependencies. Prefer concatenation with optional deduplication.

Apply this diff to handle array merging correctly:

-          const fieldsToMerge = [
-            'dependencies',
-            'devDependencies',
-            'optionalDependencies',
-            'peerDependencies',
-            'bundledDependencies'
-          ] as const
-          for(const field of fieldsToMerge) {
-            Object.assign(
-              packageContent[field],
-              packageContent_[field],
-            )
-          }
+          const fieldsToMerge = [
+            'dependencies',
+            'devDependencies',
+            'optionalDependencies',
+            'peerDependencies',
+          ] as const
+          for (const field of fieldsToMerge) {
+            Object.assign(packageContent[field], packageContent_[field])
+          }
+          if (packageContent_.bundledDependencies.length > 0) {
+            packageContent.bundledDependencies = Array.from(
+              new Set([
+                ...packageContent.bundledDependencies,
+                ...packageContent_.bundledDependencies,
+              ]),
+            )
+          }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4e0dc4f and cac809c.

📒 Files selected for processing (1)
  • src/rules/no-extraneous-dependencies.ts (7 hunks)
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#378
File: src/rules/imports-first.ts:10-19
Timestamp: 2025-06-08T12:09:38.535Z
Learning: The current implementation in eslint-plugin-import-x uses the correct ESLint core DeprecatedInfo structure: deprecatedSince field and replacedBy array with objects containing rule properties that match the ExternalSpecifier type with name and url fields.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#378
File: src/rules/imports-first.ts:10-19
Timestamp: 2025-06-08T12:09:38.535Z
Learning: ESLint core ExternalSpecifier interface has optional name and url string properties. The current eslint-plugin-import-x implementation correctly uses this structure for the rule property within replacedBy arrays in DeprecatedInfo objects.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#362
File: src/utils/create-rule.ts:0-0
Timestamp: 2025-05-31T03:10:38.972Z
Learning: When `rewriteRelativeImportExtensions` is enabled in TypeScript configuration, using `.ts` extensions in import paths is correct and necessary for Node.js ESM compatibility. TypeScript will rewrite these to `.js` during compilation.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#362
File: src/utils/import-declaration.ts:1-2
Timestamp: 2025-05-31T03:11:08.864Z
Learning: TypeScript type-only imports (using `import type`) are stripped during compilation and do not affect production dependencies or bundle size. Only runtime imports need to be considered when managing production dependencies in TypeScript projects.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#385
File: src/rules/prefer-namespace-import.ts:0-0
Timestamp: 2025-06-18T12:49:41.795Z
Learning: The eslint-plugin-import-x project requires Node versions `^18.18.0 || ^20.9.0 || >=21.1.0` as specified in package.json, so modern JavaScript features like String.prototype.at() are safe to use.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#0
File: :0-0
Timestamp: 2025-03-30T09:56:52.604Z
Learning: The project supports both ESM and CommonJS module systems simultaneously, even as it migrates to use ESM as the primary module system. Using `cjsRequire` is part of this strategy to maintain compatibility with both systems.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#271
File: test/rules/no-unused-modules.spec.ts:1528-1532
Timestamp: 2025-03-30T09:06:59.006Z
Learning: The import from 'eslint8.56/use-at-your-own-risk' has incorrect TypeScript types but works correctly at runtime, which is properly handled with a `ts-expect-error` comment.
📚 Learning: 2025-06-08T12:09:38.535Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#378
File: src/rules/imports-first.ts:10-19
Timestamp: 2025-06-08T12:09:38.535Z
Learning: ESLint core ExternalSpecifier interface has optional name and url string properties. The current eslint-plugin-import-x implementation correctly uses this structure for the rule property within replacedBy arrays in DeprecatedInfo objects.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-06-08T12:09:38.535Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#378
File: src/rules/imports-first.ts:10-19
Timestamp: 2025-06-08T12:09:38.535Z
Learning: The current implementation in eslint-plugin-import-x uses the correct ESLint core DeprecatedInfo structure: deprecatedSince field and replacedBy array with objects containing rule properties that match the ExternalSpecifier type with name and url fields.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-31T03:11:08.864Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#362
File: src/utils/import-declaration.ts:1-2
Timestamp: 2025-05-31T03:11:08.864Z
Learning: TypeScript type-only imports (using `import type`) are stripped during compilation and do not affect production dependencies or bundle size. Only runtime imports need to be considered when managing production dependencies in TypeScript projects.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-03-30T09:56:52.604Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#0
File: :0-0
Timestamp: 2025-03-30T09:56:52.604Z
Learning: The project supports both ESM and CommonJS module systems simultaneously, even as it migrates to use ESM as the primary module system. Using `cjsRequire` is part of this strategy to maintain compatibility with both systems.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-30T13:24:29.760Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#272
File: test/rules/no-unresolved.spec.ts:293-325
Timestamp: 2025-05-30T13:24:29.760Z
Learning: Node.js ES6 import statements do not support plain Windows absolute paths like `C:\path\to\file.js` because import specifiers are resolved as URLs, not plain file paths. Plain Windows absolute paths conflict with URL schemes and create ambiguity. For absolute imports on Windows, you must use `file://` URLs instead.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-31T03:10:38.972Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#362
File: src/utils/create-rule.ts:0-0
Timestamp: 2025-05-31T03:10:38.972Z
Learning: When `rewriteRelativeImportExtensions` is enabled in TypeScript configuration, using `.ts` extensions in import paths is correct and necessary for Node.js ESM compatibility. TypeScript will rewrite these to `.js` during compilation.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-07T09:03:58.638Z
Learnt from: 43081j
PR: un-ts/eslint-plugin-import-x#304
File: src/rules/avoid-barrel-files.ts:74-85
Timestamp: 2025-05-07T09:03:58.638Z
Learning: According to the ESTree specification, an `ExportDefaultDeclaration` node always has a `declaration` property, making optional chaining (`?.`) unnecessary when accessing `n.declaration.type`.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-03-30T14:44:11.779Z
Learnt from: SukkaW
PR: un-ts/eslint-plugin-import-x#272
File: src/utils/resolve.ts:0-0
Timestamp: 2025-03-30T14:44:11.779Z
Learning: In eslint-plugin-import-x's node resolver, the `modules` parameter of `createNodeResolver` function accepts both string and string array types, making it flexible when passing values like `moduleDirectory`.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-06-18T15:22:38.532Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#386
File: src/rules/prefer-namespace-import.ts:41-46
Timestamp: 2025-06-18T15:22:38.532Z
Learning: In eslint-plugin-import-x, JounQin prefers to throw on invalid rule options rather than handling them gracefully with try/catch blocks and reporting configuration errors.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-03-30T09:06:59.006Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#271
File: test/rules/no-unused-modules.spec.ts:1528-1532
Timestamp: 2025-03-30T09:06:59.006Z
Learning: The import from 'eslint8.56/use-at-your-own-risk' has incorrect TypeScript types but works correctly at runtime, which is properly handled with a `ts-expect-error` comment.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-30T15:39:53.248Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#357
File: package.json:160-160
Timestamp: 2025-05-30T15:39:53.248Z
Learning: The `typescript-eslint` package (without the `typescript-eslint/` namespace) is the official unified entry package for ESLint flat config, not an incorrect package name.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Socket Security: Pull Request Alerts
🔇 Additional comments (5)
src/rules/no-extraneous-dependencies.ts (5)

44-46: Including name and exports in PackageDeps is correct and necessary

This enables accurate self-import handling without extra file system lookups elsewhere.


74-81: Sane initial shape for packageContent

Explicitly initializing name/exports to undefined keeps merging logic simple and predictable.


130-142: Early-return condition updated to consider name/exports

Good addition; ensures we still operate when only name/exports are relevant (e.g., self-import scenarios) even if dependency maps are empty.


321-333: Self-import handling aligns with Node semantics

Allowing self-imports only when exports is defined is correct; reporting selfImport otherwise addresses the false positive from issue #305.


396-397: MessageId union updated

Adding selfImport to the union keeps typings consistent with the new message.

Comment on lines +431 to 433
selfImport:
"'{{packageName}}' may only import itself if the exports field is defined in package.json",
},
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

New message text added; consider minor polish and docs/tests sync

  • Optional: end the message with a period to match house style if applicable.
  • Ensure the rule docs mention this new behavior/message and tests assert against selfImport.

To verify coverage quickly:


🏁 Script executed:

#!/bin/bash
# Check tests reference the selfImport messageId and that docs mention it.
rg -n "selfImport" -S
rg -n "no-extraneous-dependencies" docs -S || true

Length of output: 2274


Polish selfImport message and sync documentation

The selfImport message needs a terminal period and the docs must include this new error ID.

  • Update the message in src/rules/no-extraneous-dependencies.ts (around line 431):
      - "'{{packageName}}' may only import itself if the exports field is defined in package.json",
      + "'{{packageName}}' may only import itself if the exports field is defined in package.json."
  • In docs/rules/no-extraneous-dependencies.md, add a description/example for the selfImport message under the rule’s “Messages” (or “Examples”) section.
  • No test changes needed—selfImport is already asserted in test/rules/no-extraneous-dependencies.spec.ts.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
selfImport:
"'{{packageName}}' may only import itself if the exports field is defined in package.json",
},
selfImport:
"'{{packageName}}' may only import itself if the exports field is defined in package.json.",
},
🤖 Prompt for AI Agents
In src/rules/no-extraneous-dependencies.ts around lines 431 to 433, add a
terminal period to the selfImport message string to polish it. Then, update
docs/rules/no-extraneous-dependencies.md by adding a description and example for
the selfImport message under the rule’s Messages or Examples section to keep
documentation in sync with the code changes.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/rules/no-extraneous-dependencies.ts (2)

435-437: Polish message punctuation and sync docs

Add a terminal period to align with house style and update rule docs to mention selfImport.

-        "'{{packageName}}' may only import itself if the exports field is defined in package.json",
+        "'{{packageName}}' may only import itself if the exports field is defined in package.json.",

If you want, I can open a docs patch adding a brief explanation and example for selfImport under docs/rules/no-extraneous-dependencies.md.


99-112: Bug: bundledDependencies merge via Object.assign overwrites instead of concatenating

Using Object.assign on arrays will overwrite indices and discard previously merged entries when multiple package.json paths are provided. Concatenate and dedupe instead; keep Object.assign for the object maps.

Apply this diff within the loop:

-          for (const field of fieldsToMerge) {
-            Object.assign(packageContent[field], packageContent_[field])
-          }
+          for (const field of fieldsToMerge) {
+            if (field === 'bundledDependencies') {
+              const left = packageContent.bundledDependencies
+              const right = packageContent_.bundledDependencies
+              packageContent.bundledDependencies =
+                left.length === 0
+                  ? right.slice()
+                  : Array.from(new Set([...left, ...right]))
+            } else {
+              Object.assign(packageContent[field], packageContent_[field])
+            }
+          }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cac809c and d878aac.

📒 Files selected for processing (2)
  • src/rules/no-extraneous-dependencies.ts (7 hunks)
  • test/rules/no-extraneous-dependencies.spec.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • test/rules/no-extraneous-dependencies.spec.ts
🧰 Additional context used
🧠 Learnings (12)
📓 Common learnings
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#378
File: src/rules/imports-first.ts:10-19
Timestamp: 2025-06-08T12:09:38.535Z
Learning: The current implementation in eslint-plugin-import-x uses the correct ESLint core DeprecatedInfo structure: deprecatedSince field and replacedBy array with objects containing rule properties that match the ExternalSpecifier type with name and url fields.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#378
File: src/rules/imports-first.ts:10-19
Timestamp: 2025-06-08T12:09:38.535Z
Learning: ESLint core ExternalSpecifier interface has optional name and url string properties. The current eslint-plugin-import-x implementation correctly uses this structure for the rule property within replacedBy arrays in DeprecatedInfo objects.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#362
File: src/utils/create-rule.ts:0-0
Timestamp: 2025-05-31T03:10:38.972Z
Learning: When `rewriteRelativeImportExtensions` is enabled in TypeScript configuration, using `.ts` extensions in import paths is correct and necessary for Node.js ESM compatibility. TypeScript will rewrite these to `.js` during compilation.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#362
File: src/utils/import-declaration.ts:1-2
Timestamp: 2025-05-31T03:11:08.864Z
Learning: TypeScript type-only imports (using `import type`) are stripped during compilation and do not affect production dependencies or bundle size. Only runtime imports need to be considered when managing production dependencies in TypeScript projects.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#385
File: src/rules/prefer-namespace-import.ts:0-0
Timestamp: 2025-06-18T12:49:41.795Z
Learning: The eslint-plugin-import-x project requires Node versions `^18.18.0 || ^20.9.0 || >=21.1.0` as specified in package.json, so modern JavaScript features like String.prototype.at() are safe to use.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#0
File: :0-0
Timestamp: 2025-03-30T09:56:52.604Z
Learning: The project supports both ESM and CommonJS module systems simultaneously, even as it migrates to use ESM as the primary module system. Using `cjsRequire` is part of this strategy to maintain compatibility with both systems.
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#271
File: test/rules/no-unused-modules.spec.ts:1528-1532
Timestamp: 2025-03-30T09:06:59.006Z
Learning: The import from 'eslint8.56/use-at-your-own-risk' has incorrect TypeScript types but works correctly at runtime, which is properly handled with a `ts-expect-error` comment.
📚 Learning: 2025-06-08T12:09:38.535Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#378
File: src/rules/imports-first.ts:10-19
Timestamp: 2025-06-08T12:09:38.535Z
Learning: ESLint core ExternalSpecifier interface has optional name and url string properties. The current eslint-plugin-import-x implementation correctly uses this structure for the rule property within replacedBy arrays in DeprecatedInfo objects.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-06-08T12:09:38.535Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#378
File: src/rules/imports-first.ts:10-19
Timestamp: 2025-06-08T12:09:38.535Z
Learning: The current implementation in eslint-plugin-import-x uses the correct ESLint core DeprecatedInfo structure: deprecatedSince field and replacedBy array with objects containing rule properties that match the ExternalSpecifier type with name and url fields.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-31T03:11:08.864Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#362
File: src/utils/import-declaration.ts:1-2
Timestamp: 2025-05-31T03:11:08.864Z
Learning: TypeScript type-only imports (using `import type`) are stripped during compilation and do not affect production dependencies or bundle size. Only runtime imports need to be considered when managing production dependencies in TypeScript projects.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-03-30T09:56:52.604Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#0
File: :0-0
Timestamp: 2025-03-30T09:56:52.604Z
Learning: The project supports both ESM and CommonJS module systems simultaneously, even as it migrates to use ESM as the primary module system. Using `cjsRequire` is part of this strategy to maintain compatibility with both systems.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-30T13:24:29.760Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#272
File: test/rules/no-unresolved.spec.ts:293-325
Timestamp: 2025-05-30T13:24:29.760Z
Learning: Node.js ES6 import statements do not support plain Windows absolute paths like `C:\path\to\file.js` because import specifiers are resolved as URLs, not plain file paths. Plain Windows absolute paths conflict with URL schemes and create ambiguity. For absolute imports on Windows, you must use `file://` URLs instead.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-31T03:10:38.972Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#362
File: src/utils/create-rule.ts:0-0
Timestamp: 2025-05-31T03:10:38.972Z
Learning: When `rewriteRelativeImportExtensions` is enabled in TypeScript configuration, using `.ts` extensions in import paths is correct and necessary for Node.js ESM compatibility. TypeScript will rewrite these to `.js` during compilation.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-07T09:03:58.638Z
Learnt from: 43081j
PR: un-ts/eslint-plugin-import-x#304
File: src/rules/avoid-barrel-files.ts:74-85
Timestamp: 2025-05-07T09:03:58.638Z
Learning: According to the ESTree specification, an `ExportDefaultDeclaration` node always has a `declaration` property, making optional chaining (`?.`) unnecessary when accessing `n.declaration.type`.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-03-30T14:44:11.779Z
Learnt from: SukkaW
PR: un-ts/eslint-plugin-import-x#272
File: src/utils/resolve.ts:0-0
Timestamp: 2025-03-30T14:44:11.779Z
Learning: In eslint-plugin-import-x's node resolver, the `modules` parameter of `createNodeResolver` function accepts both string and string array types, making it flexible when passing values like `moduleDirectory`.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-06-18T15:22:38.532Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#386
File: src/rules/prefer-namespace-import.ts:41-46
Timestamp: 2025-06-18T15:22:38.532Z
Learning: In eslint-plugin-import-x, JounQin prefers to throw on invalid rule options rather than handling them gracefully with try/catch blocks and reporting configuration errors.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-03-30T09:06:59.006Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#271
File: test/rules/no-unused-modules.spec.ts:1528-1532
Timestamp: 2025-03-30T09:06:59.006Z
Learning: The import from 'eslint8.56/use-at-your-own-risk' has incorrect TypeScript types but works correctly at runtime, which is properly handled with a `ts-expect-error` comment.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
📚 Learning: 2025-05-30T15:39:53.248Z
Learnt from: JounQin
PR: un-ts/eslint-plugin-import-x#357
File: package.json:160-160
Timestamp: 2025-05-30T15:39:53.248Z
Learning: The `typescript-eslint` package (without the `typescript-eslint/` namespace) is the official unified entry package for ESLint flat config, not an incorrect package name.

Applied to files:

  • src/rules/no-extraneous-dependencies.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Lint and Test with Node.js 22 and ESLint 9 on windows-latest
  • GitHub Check: Lint and Test with Node.js 24 and ESLint 8 on windows-latest
  • GitHub Check: Lint and Test with Node.js 22 and ESLint 8 on windows-latest
  • GitHub Check: Lint and Test with Node.js 22 and ESLint 8.56 on windows-latest
  • GitHub Check: Lint and Test with Node.js 24 and ESLint 8.56 on windows-latest
  • GitHub Check: Lint and Test with Node.js 20 and ESLint 9 on windows-latest
  • GitHub Check: Lint and Test with Node.js 20 and ESLint 8 on windows-latest
  • GitHub Check: Lint and Test with Node.js 18 and ESLint 8.56 on windows-latest
  • GitHub Check: Lint and Test with Node.js 18 and ESLint 8 on windows-latest
  • GitHub Check: Lint and Test with Node.js 20 and ESLint 8 on ubuntu-latest
  • GitHub Check: Lint and Test with Node.js 18 and ESLint 9 on windows-latest
  • GitHub Check: Lint and Test with Node.js 24 and ESLint 8 on ubuntu-latest
  • GitHub Check: Lint and Test with Node.js 18 and ESLint 8 on ubuntu-latest
  • GitHub Check: Lint and Test with Node.js 24 and ESLint 8.56 on ubuntu-latest
  • GitHub Check: Lint and Test with Node.js 22 and ESLint 8 on macos-latest
  • GitHub Check: Lint and Test with Node.js 20 and ESLint 8.56 on ubuntu-latest
  • GitHub Check: Lint and Test with Node.js 18 and ESLint 8.56 on ubuntu-latest
  • GitHub Check: Lint and Test with Node.js 24 and ESLint 8.56 on macos-latest
  • GitHub Check: Lint and Test with Node.js 22 and ESLint 8.56 on macos-latest
  • GitHub Check: Lint and Test with Node.js 18 and ESLint 8 on macos-latest
🔇 Additional comments (4)
src/rules/no-extraneous-dependencies.ts (4)

45-46: Including package name and exports in extracted fields looks good

Capturing name and exports early simplifies later checks. No issues here.


75-77: Initializing name/exports in the accumulator is fine

The accumulator shape matches PackageDeps and prevents conditional checks later.


128-139: Early return condition reads well

Including name or exports as a signal that the package context is meaningful is reasonable. No change requested.


401-402: MessageId addition for selfImport is correct

Enum updated consistently with new message usage.

Comment on lines +319 to +331
if (importPackageName === deps.name) {
if (!deps.exports) {
context.report({
node,
messageId: 'selfImport',
data: {
packageName,
},
})
}

return
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Harden self-import detection using the resolved package name

Currently, self-import is detected via importPackageName === deps.name. If a resolver aliases the bare specifier to a different package/path, this could incorrectly treat a non-self import as self and skip reporting. Prefer using realPackageName when available.

-  if (importPackageName === deps.name) {
+  const isSelfImport = (realPackageName ?? importPackageName) === deps.name
+  if (isSelfImport) {
     if (!deps.exports) {
       context.report({
         node,
         messageId: 'selfImport',
         data: {
           packageName,
         },
       })
     }
 
     return
   }

Note: This preserves existing behavior but avoids false positives when aliasing is involved.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/rules/no-extraneous-dependencies.ts between lines 319 and 331, the
self-import detection uses importPackageName === deps.name, which can cause
false positives if a resolver aliases the import. To fix this, update the
condition to use realPackageName when available instead of importPackageName for
comparison with deps.name, ensuring more accurate self-import detection while
preserving existing behavior.

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.

no-extraneous-dependencies should not report the package itself
3 participants