From f6aa376f5055896893fbe055f8b716a18f69fff4 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Sat, 13 Aug 2016 00:43:46 -0400 Subject: [PATCH 1/5] Add test --- tests/src/rules/max-dependencies.js | 78 +++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/src/rules/max-dependencies.js diff --git a/tests/src/rules/max-dependencies.js b/tests/src/rules/max-dependencies.js new file mode 100644 index 000000000..7377c1451 --- /dev/null +++ b/tests/src/rules/max-dependencies.js @@ -0,0 +1,78 @@ +import { test } from '../utils' + +import { RuleTester } from 'eslint' + +const ruleTester = new RuleTester() + , rule = require('rules/max-dependencies') + +ruleTester.run('max-dependencies', rule, { + valid: [ + test({ code: 'import "./foo.js"' }), + + test({ code: 'import "./foo.js"; import "./bar.js";', + options: [{ + max: 2, + }], + }), + + test({ code: 'import "./foo.js"; import "./bar.js"; const a = require("./foo.js"); const b = require("./bar.js");', + options: [{ + max: 2, + }], + }), + + test({ code: 'import {x, y, z} from "./foo"'}), + ], + invalid: [ + test({ + code: 'import { x } from \'./foo\'; import { y } from \'./foo\'; import {z} from \'./bar\';', + options: [{ + max: 1, + }], + errors: [ + 'Maximum number of dependencies (1) exceeded.', + ], + }), + + test({ + code: 'import { x } from \'./foo\'; import { y } from \'./bar\'; import { z } from \'./baz\';', + options: [{ + max: 2, + }], + errors: [ + 'Maximum number of dependencies (2) exceeded.', + ], + }), + + test({ + code: 'import { x } from \'./foo\'; require("./bar"); import { z } from \'./baz\';', + options: [{ + max: 2, + }], + errors: [ + 'Maximum number of dependencies (2) exceeded.', + ], + }), + + test({ + code: 'import { x } from \'./foo\'; import { z } from \'./foo\'; require("./bar"); const path = require("path");', + options: [{ + max: 2, + }], + errors: [ + 'Maximum number of dependencies (2) exceeded.', + ], + }), + + test({ + code: 'import type { x } from \'./foo\'; import type { y } from \'./bar\'', + parser: 'babel-eslint', + options: [{ + max: 1, + }], + errors: [ + 'Maximum number of dependencies (1) exceeded.', + ], + }), + ], +}) From 5eafd099c32a3fd151ce145a25ef16cb08fddf62 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Sat, 13 Aug 2016 00:44:16 -0400 Subject: [PATCH 2/5] Add rule with docs --- docs/rules/max-dependencies.md | 44 ++++++++++++++++++++++++++++++ src/rules/max-dependencies.js | 49 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 docs/rules/max-dependencies.md create mode 100644 src/rules/max-dependencies.js diff --git a/docs/rules/max-dependencies.md b/docs/rules/max-dependencies.md new file mode 100644 index 000000000..78911f232 --- /dev/null +++ b/docs/rules/max-dependencies.md @@ -0,0 +1,44 @@ +# max-dependencies + +Forbid modules to have too many dependencies (`import` or `require` statements). + +This is a useful rule because a module with too many dependencies is code smell, and usually indicates the module is doing too much and/or should be broken up into smaller modules. + +Importing multiple named exports from a single module will only count once (e.g. `import {x, y, z} from './foo'` will only count as a single dependency). + +### Options + +This rule takes the following option: + +`max`: The maximum number of dependencies allowed. Anything over will trigger the rule. **Default is 10** if the rule is enabled and no `max` is specified. + +You can set the option like this: + +```js +"import/max-dependencies": ["error", {"max": 10}] +``` + + +## Example + +Given a max value of `{"max": 2}`: + +### Fail + +```js +import a from './a'; // 1 +const b = require('./b'); // 2 +import c from './c'; // 3 - exceeds max! +``` + +### Pass + +```js +import a from './a'; // 1 +const anotherA = require('./a'); // still 1 +import {x, y, z} from './foo'; // 2 +``` + +## When Not To Use It + +If you don't care how many dependencies a module has. diff --git a/src/rules/max-dependencies.js b/src/rules/max-dependencies.js new file mode 100644 index 000000000..396e21e54 --- /dev/null +++ b/src/rules/max-dependencies.js @@ -0,0 +1,49 @@ +import Set from 'es6-set' +import isStaticRequire from '../core/staticRequire' + +const DEFAULT_MAX = 10 + +const countDependencies = (dependencies, lastNode, context) => { + const {max} = context.options[0] || { max: DEFAULT_MAX } + + if (dependencies.size > max) { + context.report( + lastNode, + `Maximum number of dependencies (${max}) exceeded.` + ) + } +} + +module.exports = context => { + const dependencies = new Set() // keep track of dependencies + let lastNode // keep track of the last node to report on + + return { + ImportDeclaration(node) { + dependencies.add(node.source.value) + lastNode = node.source + }, + + CallExpression(node) { + if (isStaticRequire(node)) { + const [ requirePath ] = node.arguments + dependencies.add(requirePath.value) + lastNode = node + } + }, + + 'Program:exit': function () { + countDependencies(dependencies, lastNode, context) + }, + } +} + +module.exports.schema = [ + { + 'type': 'object', + 'properties': { + 'max': { 'type': 'number' }, + }, + 'additionalProperties': false, + }, +] From 90ff3b86c41eb457137be93cbfcf6a964f3bf151 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Sat, 13 Aug 2016 00:44:33 -0400 Subject: [PATCH 3/5] Add to src index and main README --- README.md | 4 +++- src/index.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b396daf3b..249ba36ee 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`no-amd`]: ./docs/rules/no-amd.md [`no-nodejs-modules`]: ./docs/rules/no-nodejs-modules.md + **Style guide:** * Ensure all imports appear before other statements ([`imports-first`]) @@ -61,6 +62,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a * Enforce a convention in module import order ([`order`]) * Enforce a newline after import statements ([`newline-after-import`]) * Prefer a default export if module exports a single name ([`prefer-default-export`]) +* Limit the maximum number of dependencies a module can have. ([`max-dependencies`]) [`imports-first`]: ./docs/rules/imports-first.md [`no-duplicates`]: ./docs/rules/no-duplicates.md @@ -69,7 +71,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a [`order`]: ./docs/rules/order.md [`newline-after-import`]: ./docs/rules/newline-after-import.md [`prefer-default-export`]: ./docs/rules/prefer-default-export.md - +[`max-dependencies`]: ./docs/rules/max-dependencies.md ## Installation diff --git a/src/index.js b/src/index.js index 52d5668c5..16d23da7e 100644 --- a/src/index.js +++ b/src/index.js @@ -16,6 +16,7 @@ export const rules = { 'no-amd': require('./rules/no-amd'), 'no-duplicates': require('./rules/no-duplicates'), 'imports-first': require('./rules/imports-first'), + 'max-dependencies': require('./rules/max-dependencies'), 'no-extraneous-dependencies': require('./rules/no-extraneous-dependencies'), 'no-nodejs-modules': require('./rules/no-nodejs-modules'), 'order': require('./rules/order'), From 27c7675e39d324960d557fd27fde3bed800c2178 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Sun, 21 Aug 2016 23:22:53 -0400 Subject: [PATCH 4/5] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f0a5a615e..8d0349328 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel ### Fixed - [`namespace`] exception for get property from `namespace` import, which are re-export from commonjs module ([#499] fixes [#416], thanks [@wKich]) +## [1.14.0] - 2016-08-21 +### Added +- [`max-dependencies`] for specifying the maximum number of dependencies (both `import` and `require`) a module can have. (see [#489], thanks [@tizmagik]) + ## [1.13.0] - 2016-08-11 ### Added - `allowComputed` option for [`namespace`] rule. If set to `true`, won't report @@ -293,11 +297,13 @@ for info on changes for earlier releases. [`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md [`prefer-default-export`]: ./docs/rules/prefer-default-export.md [`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md +[`max-dependencies`]: ./docs/rules/max-dependencies.md [#509]: https://github.com/benmosher/eslint-plugin-import/pull/509 [#508]: https://github.com/benmosher/eslint-plugin-import/pull/508 [#503]: https://github.com/benmosher/eslint-plugin-import/pull/503 [#499]: https://github.com/benmosher/eslint-plugin-import/pull/499 +[#489]: https://github.com/benmosher/eslint-plugin-import/pull/489 [#461]: https://github.com/benmosher/eslint-plugin-import/pull/461 [#444]: https://github.com/benmosher/eslint-plugin-import/pull/444 [#428]: https://github.com/benmosher/eslint-plugin-import/pull/428 @@ -430,3 +436,4 @@ for info on changes for earlier releases. [@zloirock]: https://github.com/zloirock [@rhys-vdw]: https://github.com/rhys-vdw [@wKich]: https://github.com/wKich +[@tizmagik]: https://github.com/tizmagik From 159034c59fe260d190d045eb0b3f1ffc13b80c26 Mon Sep 17 00:00:00 2001 From: Jeremy Gayed Date: Wed, 31 Aug 2016 16:34:04 -0400 Subject: [PATCH 5/5] a code smell --- docs/rules/max-dependencies.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rules/max-dependencies.md b/docs/rules/max-dependencies.md index 78911f232..f4aa2a57a 100644 --- a/docs/rules/max-dependencies.md +++ b/docs/rules/max-dependencies.md @@ -2,7 +2,7 @@ Forbid modules to have too many dependencies (`import` or `require` statements). -This is a useful rule because a module with too many dependencies is code smell, and usually indicates the module is doing too much and/or should be broken up into smaller modules. +This is a useful rule because a module with too many dependencies is a code smell, and usually indicates the module is doing too much and/or should be broken up into smaller modules. Importing multiple named exports from a single module will only count once (e.g. `import {x, y, z} from './foo'` will only count as a single dependency).