-
-
Notifications
You must be signed in to change notification settings - Fork 53
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
base: master
Are you sure you want to change the base?
feat(no-extraneous-dependencies): allow package to import itself #309
Conversation
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
🦋 Changeset detectedLatest commit: d878aac The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
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 |
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughThis change updates the Changes
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
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changesNo out-of-scope changes were detected. Suggested labels
Suggested reviewers
Poem
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 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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
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. |
|
There was a problem hiding this 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 in9
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%
<= threshold50%
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%
<= threshold50%
None
3. .changeset/cruel-cooks-notice.md:5
- Draft comment:
The changeset notice refers toimport-x/no-extraneous-imports
but the actual rule is namedno-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 addingname
andexports
to the extracted package fields. This extension is key for handling self-imports. - Reason this comment was not posted:
Confidence changes required:0%
<= threshold50%
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%
<= threshold50%
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%
<= threshold50%
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Friendly ping @remcohaszing
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
commit: |
Codecov ReportAll modified and coverable lines are covered by tests ✅
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. 🚀 New features to boost your workflow:
|
@@ -297,6 +325,20 @@ function reportIfMissing( | |||
return | |||
} | |||
|
|||
if (importPackageName === deps.name) { | |||
if (!deps.exports) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ownexports
is supported by Node,main
is not, right?
Correct!
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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/deduplicatebundledDependencies
Object.assign
on arrays overwrites indices rather than concatenating. This can lose entries when multiple package.json files providebundledDependencies
. 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
📒 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
: Includingname
andexports
in PackageDeps is correct and necessaryThis enables accurate self-import handling without extra file system lookups elsewhere.
74-81
: Sane initial shape forpackageContent
Explicitly initializing
name
/exports
toundefined
keeps merging logic simple and predictable.
130-142
: Early-return condition updated to considername
/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 semanticsAllowing self-imports only when
exports
is defined is correct; reportingselfImport
otherwise addresses the false positive from issue #305.
396-397
: MessageId union updatedAdding
selfImport
to the union keeps typings consistent with the new message.
selfImport: | ||
"'{{packageName}}' may only import itself if the exports field is defined in package.json", | ||
}, |
There was a problem hiding this comment.
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.
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.
There was a problem hiding this 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 docsAdd 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 concatenatingUsing 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
📒 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 goodCapturing name and exports early simplifies later checks. No issues here.
75-77
: Initializing name/exports in the accumulator is fineThe accumulator shape matches PackageDeps and prevents conditional checks later.
128-139
: Early return condition reads wellIncluding name or exports as a signal that the package context is meaningful is reasonable. No change requested.
401-402
: MessageId addition for selfImport is correctEnum updated consistently with new message usage.
if (importPackageName === deps.name) { | ||
if (!deps.exports) { | ||
context.report({ | ||
node, | ||
messageId: 'selfImport', | ||
data: { | ||
packageName, | ||
}, | ||
}) | ||
} | ||
|
||
return | ||
} |
There was a problem hiding this comment.
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.
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
ifexports
field is defined inpackage.json
, with error reporting for missingexports
.no-extraneous-dependencies
ifexports
field is defined inpackage.json
.selfImport
error if a package imports itself withoutexports
field.getDependencies()
inno-extraneous-dependencies.ts
to includename
andexports
.reportIfMissing()
to handle self-import logic.exports
inno-extraneous-dependencies.spec.ts
.exports
inno-extraneous-dependencies.spec.ts
.This description was created by
for 4e0dc4f. It will automatically update as commits are pushed.
Summary by CodeRabbit