diff --git a/CHANGELOG.md b/CHANGELOG.md index 822efcebb..8ca234567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Added - support `parseForESLint` from custom parser ([#1435], thanks [@JounQin]) +- [`no-extraneous-dependencies`]: Implement support for [bundledDependencies](https://npm.github.io/using-pkgs-docs/package-json/types/bundleddependencies.html) ([#1436], thanks [@schmidsi])) ### Fixed - `default`: make error message less confusing ([#1470], thanks [@golopot]) @@ -609,6 +610,7 @@ for info on changes for earlier releases. [#1472]: https://github.com/benmosher/eslint-plugin-import/pull/1472 [#1470]: https://github.com/benmosher/eslint-plugin-import/pull/1470 +[#1436]: https://github.com/benmosher/eslint-plugin-import/pull/1436 [#1435]: https://github.com/benmosher/eslint-plugin-import/pull/1435 [#1425]: https://github.com/benmosher/eslint-plugin-import/pull/1425 [#1419]: https://github.com/benmosher/eslint-plugin-import/pull/1419 @@ -986,3 +988,4 @@ for info on changes for earlier releases. [@lencioni]: https://github.com/lencioni [@JounQin]: https://github.com/JounQin [@atikenny]: https://github.com/atikenny +[@schmidsi]: https://github.com/schmidsi diff --git a/docs/rules/no-extraneous-dependencies.md b/docs/rules/no-extraneous-dependencies.md index 2b66aa25c..295590ccd 100644 --- a/docs/rules/no-extraneous-dependencies.md +++ b/docs/rules/no-extraneous-dependencies.md @@ -1,6 +1,6 @@ # import/no-extraneous-dependencies: Forbid the use of extraneous packages -Forbid the import of external modules that are not declared in the `package.json`'s `dependencies`, `devDependencies`, `optionalDependencies` or `peerDependencies`. +Forbid the import of external modules that are not declared in the `package.json`'s `dependencies`, `devDependencies`, `optionalDependencies`, `peerDependencies`, or `bundledDependencies`. The closest parent `package.json` will be used. If no `package.json` is found, the rule will not lint anything. This behaviour can be changed with the rule option `packageDir`. Modules have to be installed for this rule to work. @@ -15,6 +15,8 @@ This rule supports the following options: `peerDependencies`: If set to `false`, then the rule will show an error when `peerDependencies` are imported. Defaults to `false`. +`bundledDependencies`: If set to `false`, then the rule will show an error when `bundledDependencies` are imported. Defaults to `true`. + You can set the options like this: ```js @@ -70,7 +72,10 @@ Given the following `package.json`: }, "peerDependencies": { "react": ">=15.0.0 <16.0.0" - } + }, + "bundledDependencies": [ + "@generated/foo", + ] } ``` @@ -90,6 +95,10 @@ var test = require('ava'); /* eslint import/no-extraneous-dependencies: ["error", {"optionalDependencies": false}] */ import isArray from 'lodash.isarray'; var isArray = require('lodash.isarray'); + +/* eslint import/no-extraneous-dependencies: ["error", {"bundledDependencies": false}] */ +import foo from '"@generated/foo"'; +var foo = require('"@generated/foo"'); ``` @@ -103,6 +112,7 @@ var foo = require('./foo'); import test from 'ava'; import find from 'lodash.find'; import isArray from 'lodash.isarray'; +import foo from '"@generated/foo"'; /* eslint import/no-extraneous-dependencies: ["error", {"peerDependencies": true}] */ import react from 'react'; diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 647481a37..1351029cc 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -11,12 +11,19 @@ function hasKeys(obj = {}) { return Object.keys(obj).length > 0 } +function arrayOrKeys(arrayOrObject) { + return Array.isArray(arrayOrObject) ? arrayOrObject : Object.keys(arrayOrObject) +} + function extractDepFields(pkg) { return { dependencies: pkg.dependencies || {}, devDependencies: pkg.devDependencies || {}, optionalDependencies: pkg.optionalDependencies || {}, peerDependencies: pkg.peerDependencies || {}, + // BundledDeps should be in the form of an array, but object notation is also supported by + // `npm`, so we convert it to an array if it is an object + bundledDependencies: arrayOrKeys(pkg.bundleDependencies || pkg.bundledDependencies || []) } } @@ -28,6 +35,7 @@ function getDependencies(context, packageDir) { devDependencies: {}, optionalDependencies: {}, peerDependencies: {}, + bundledDependencies: [], } if (packageDir && packageDir.length > 0) { @@ -63,6 +71,7 @@ function getDependencies(context, packageDir) { packageContent.devDependencies, packageContent.optionalDependencies, packageContent.peerDependencies, + packageContent.bundledDependencies, ].some(hasKeys)) { return null } @@ -121,11 +130,13 @@ function reportIfMissing(context, deps, depsOptions, node, name) { const isInDevDeps = deps.devDependencies[packageName] !== undefined const isInOptDeps = deps.optionalDependencies[packageName] !== undefined const isInPeerDeps = deps.peerDependencies[packageName] !== undefined + const isInBundledDeps = deps.bundledDependencies.indexOf(packageName) !== -1 if (isInDeps || (depsOptions.allowDevDeps && isInDevDeps) || (depsOptions.allowPeerDeps && isInPeerDeps) || - (depsOptions.allowOptDeps && isInOptDeps) + (depsOptions.allowOptDeps && isInOptDeps) || + (depsOptions.allowBundledDeps && isInBundledDeps) ) { return } @@ -169,6 +180,7 @@ module.exports = { 'devDependencies': { 'type': ['boolean', 'array'] }, 'optionalDependencies': { 'type': ['boolean', 'array'] }, 'peerDependencies': { 'type': ['boolean', 'array'] }, + 'bundledDependencies': { 'type': ['boolean', 'array'] }, 'packageDir': { 'type': ['string', 'array'] }, }, 'additionalProperties': false, @@ -185,6 +197,7 @@ module.exports = { allowDevDeps: testConfig(options.devDependencies, filename) !== false, allowOptDeps: testConfig(options.optionalDependencies, filename) !== false, allowPeerDeps: testConfig(options.peerDependencies, filename) !== false, + allowBundledDeps: testConfig(options.bundledDependencies, filename) !== false, } // todo: use module visitor from module-utils core diff --git a/tests/files/bundled-dependencies/as-array-bundle-deps/node_modules/@generated/bar/index.js b/tests/files/bundled-dependencies/as-array-bundle-deps/node_modules/@generated/bar/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/bundled-dependencies/as-array-bundle-deps/node_modules/@generated/foo/index.js b/tests/files/bundled-dependencies/as-array-bundle-deps/node_modules/@generated/foo/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/bundled-dependencies/as-array-bundle-deps/package.json b/tests/files/bundled-dependencies/as-array-bundle-deps/package.json new file mode 100644 index 000000000..ef9c675ed --- /dev/null +++ b/tests/files/bundled-dependencies/as-array-bundle-deps/package.json @@ -0,0 +1,4 @@ +{ + "dummy": true, + "bundleDependencies": ["@generated/foo"] +} diff --git a/tests/files/bundled-dependencies/as-object/node_modules/@generated/bar/index.js b/tests/files/bundled-dependencies/as-object/node_modules/@generated/bar/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/bundled-dependencies/as-object/node_modules/@generated/foo/index.js b/tests/files/bundled-dependencies/as-object/node_modules/@generated/foo/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/bundled-dependencies/as-object/package.json b/tests/files/bundled-dependencies/as-object/package.json new file mode 100644 index 000000000..1a5baff5a --- /dev/null +++ b/tests/files/bundled-dependencies/as-object/package.json @@ -0,0 +1,4 @@ +{ + "dummy": true, + "bundledDependencies": {"@generated/foo": "latest"} +} diff --git a/tests/files/bundled-dependencies/race-condition/node_modules/@generated/bar/index.js b/tests/files/bundled-dependencies/race-condition/node_modules/@generated/bar/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/bundled-dependencies/race-condition/node_modules/@generated/foo/index.js b/tests/files/bundled-dependencies/race-condition/node_modules/@generated/foo/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/bundled-dependencies/race-condition/package.json b/tests/files/bundled-dependencies/race-condition/package.json new file mode 100644 index 000000000..827ecc583 --- /dev/null +++ b/tests/files/bundled-dependencies/race-condition/package.json @@ -0,0 +1,5 @@ +{ + "dummy": true, + "bundledDependencies": {"@generated/bar": "latest"}, + "bundleDependencies": ["@generated/foo"] +} diff --git a/tests/files/node_modules/@generated/bar/index.js b/tests/files/node_modules/@generated/bar/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/node_modules/@generated/foo/index.js b/tests/files/node_modules/@generated/foo/index.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/package.json b/tests/files/package.json index 0a60f28d3..0ca8e7773 100644 --- a/tests/files/package.json +++ b/tests/files/package.json @@ -15,5 +15,6 @@ }, "optionalDependencies": { "lodash.isarray": "^4.0.0" - } + }, + "bundledDependencies": ["@generated/foo"] } diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index b9d24580e..b50f9923b 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -18,6 +18,9 @@ const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-type const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo') const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package') const packageDirWithEmpty = path.join(__dirname, '../../files/empty') +const packageDirBundleDeps = path.join(__dirname, '../../files/bundled-dependencies/as-array-bundle-deps') +const packageDirBundledDepsAsObject = path.join(__dirname, '../../files/bundled-dependencies/as-object') +const packageDirBundledDepsRaceCondition = path.join(__dirname, '../../files/bundled-dependencies/race-condition') ruleTester.run('no-extraneous-dependencies', rule, { valid: [ @@ -106,6 +109,19 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import rightpad from "right-pad";', options: [{packageDir: [packageDirMonoRepoRoot, packageDirMonoRepoWithNested]}], }), + test({ code: 'import foo from "@generated/foo"'}), + test({ + code: 'import foo from "@generated/foo"', + options: [{packageDir: packageDirBundleDeps}], + }), + test({ + code: 'import foo from "@generated/foo"', + options: [{packageDir: packageDirBundledDepsAsObject}], + }), + test({ + code: 'import foo from "@generated/foo"', + options: [{packageDir: packageDirBundledDepsRaceCondition}], + }), ], invalid: [ test({ @@ -289,5 +305,19 @@ ruleTester.run('no-extraneous-dependencies', rule, { message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it", }], }), + test({ + code: 'import bar from "@generated/bar"', + errors: ["'@generated/bar' should be listed in the project's dependencies. Run 'npm i -S @generated/bar' to add it"], + }), + test({ + code: 'import foo from "@generated/foo"', + options: [{bundledDependencies: false}], + errors: ["'@generated/foo' should be listed in the project's dependencies. Run 'npm i -S @generated/foo' to add it"], + }), + test({ + code: 'import bar from "@generated/bar"', + options: [{packageDir: packageDirBundledDepsRaceCondition}], + errors: ["'@generated/bar' should be listed in the project's dependencies. Run 'npm i -S @generated/bar' to add it"], + }), ], })