diff --git a/CHANGELOG.md b/CHANGELOG.md index fb12deb24..82241fca0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ## [Unreleased] +### Added +- add `exclude` option to `import/no-extraneous-dependencies` to allow excluding dependencies from rule. ([#3198], thanks [@sf0rman]) + ## [2.32.0] - 2025-06-20 ### Added diff --git a/docs/rules/no-extraneous-dependencies.md b/docs/rules/no-extraneous-dependencies.md index 848d5bb0d..ce9defb35 100644 --- a/docs/rules/no-extraneous-dependencies.md +++ b/docs/rules/no-extraneous-dependencies.md @@ -20,6 +20,8 @@ Type imports are ignored by default. `bundledDependencies`: If set to `false`, then the rule will show an error when `bundledDependencies` are imported. Defaults to `true`. +`exclude`: If set, then the rule will exclude errors for the matched patterns. Defaults to `undefined`. + You can set the options like this: ```js @@ -57,6 +59,12 @@ folder layouts: "import/no-extraneous-dependencies": ["error", {"packageDir": ['./some-dir/', './root-pkg']}] ``` +You can also exclude errors for specific import paths to support packages that provide its components as nested dependencies. + +```js +"import/no-extraneous-dependencies": ["error", {"exclude": ['@scope/package']}] +``` + ## Rule Details Given the following `package.json`: @@ -69,7 +77,8 @@ Given the following `package.json`: "builtin-modules": "^1.1.1", "lodash.cond": "^4.2.0", "lodash.find": "^4.2.0", - "pkg-up": "^1.0.0" + "pkg-up": "^1.0.0", + "radix-ui": "^1.4.2", }, "devDependencies": { "ava": "^0.13.0", @@ -132,6 +141,10 @@ import type { MyType } from 'foo'; /* eslint import/no-extraneous-dependencies: ["error", {"peerDependencies": true}] */ import react from 'react'; + +/* eslint import/no-extraneous-dependencies: ["error", {"exclude": ['@radix-ui/react-*']}] */ +import { Alert } from "@radix-ui/react-alert-dialog"; +import { Button } from "@radix-ui/react-button"; ``` ## When Not To Use It diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index bf0a1ed47..a060714be 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -177,6 +177,17 @@ function checkDependencyDeclaration(deps, packageName, declarationStatus) { }), newDeclarationStatus); } +function isInExcludeList(packageName, exclude) { + if (!exclude) { + return false; + } + + if (Array.isArray(exclude)) { + return exclude.some((pattern) => minimatch(packageName, pattern)); + } + return minimatch(packageName, exclude); +} + function reportIfMissing(context, deps, depsOptions, node, name) { // Do not report when importing types unless option is enabled if ( @@ -200,6 +211,10 @@ function reportIfMissing(context, deps, depsOptions, node, name) { return; } + if (isInExcludeList(name, depsOptions.exclude)) { + return; + } + const resolved = resolve(name, context); if (!resolved) { return; } @@ -277,6 +292,7 @@ module.exports = { packageDir: { type: ['string', 'array'] }, includeInternal: { type: ['boolean'] }, includeTypes: { type: ['boolean'] }, + exclude: { type: ['string', 'array'] }, }, additionalProperties: false, }, @@ -295,6 +311,7 @@ module.exports = { allowBundledDeps: testConfig(options.bundledDependencies, filename) !== false, verifyInternalDeps: !!options.includeInternal, verifyTypeImports: !!options.includeTypes, + exclude: options.exclude, }; return moduleVisitor((source, node) => { diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 4a465eb39..9b8b2e370 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -188,6 +188,37 @@ ruleTester.run('no-extraneous-dependencies', rule, { }, }, }), + + test({ + code: `import "excluded-package";`, + options: [{ exclude: 'excluded-package' }], + }), + + test({ + code: ` + import "excluded-package"; + import x from "another-package"; + `, + options: [{ exclude: ['excluded-package', 'another-package'] }], + }), + + test({ + code: `import "@scope/excluded-package";`, + options: [{ exclude: '@scope/excluded-*' }], + }), + + test({ + code: `import { item } from "@scope/excluded-package";`, + options: [{ exclude: '@scope/excluded-*' }], + }), + + test({ + code: ` + import { item } from "@scope/some-package"; + import { a, b } from "@scope/another-package" + `, + options: [{ exclude: '@scope/*' }], + }), ], invalid: [ test({