diff --git a/docs/rules/no-cycle.md b/docs/rules/no-cycle.md index 8819d6704f..6329bb272e 100644 --- a/docs/rules/no-cycle.md +++ b/docs/rules/no-cycle.md @@ -55,6 +55,26 @@ import { b } from './dep-b.js' // not reported as the cycle is at depth 2 This is not necessarily recommended, but available as a cost/benefit tradeoff mechanism for reducing total project lint time, if needed. +#### `ignoreExternal` + +An `ignoreExternal` option is available to prevent the cycle detection to expand to external modules: + +```js +/*eslint import/no-cycle: [2, { ignoreExternal: true }]*/ + +// dep-a.js +import 'module-b/dep-b.js' + +export function a() { /* ... */ } +``` + +```js +// node_modules/module-b/dep-b.js +import { a } from './dep-a.js' // not reported as this module is external +``` + +Its value is `false` by default, but can be set to `true` for reducing total project lint time, if needed. + ## When Not To Use It This rule is comparatively computationally expensive. If you are pressed for lint @@ -65,5 +85,8 @@ this rule enabled. - [Original inspiring issue](https://github.com/benmosher/eslint-plugin-import/issues/941) - Rule to detect that module imports itself: [`no-self-import`] +- [`import/external-module-folders`] setting [`no-self-import`]: ./no-self-import.md + +[`import/external-module-folders`]: ../../README.md#importexternal-module-folders diff --git a/src/rules/no-cycle.js b/src/rules/no-cycle.js index 62da3643b4..2d238bdd4a 100644 --- a/src/rules/no-cycle.js +++ b/src/rules/no-cycle.js @@ -4,6 +4,7 @@ */ import Exports from '../ExportMap' +import { isExternalModule } from '../core/importType' import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor' import docsUrl from '../docsUrl' @@ -18,6 +19,11 @@ module.exports = { type: 'integer', minimum: 1, }, + ignoreExternal:{ + description: 'ignore external modules', + type: 'boolean', + default: false, + }, })], }, @@ -27,8 +33,13 @@ module.exports = { const options = context.options[0] || {} const maxDepth = options.maxDepth || Infinity + const ignoreModule = (name) => options.ignoreExternal && isExternalModule(name) function checkSourceValue(sourceNode, importer) { + if (ignoreModule(sourceNode.value)) { + return // ignore external modules + } + const imported = Exports.get(sourceNode.value, context) if (importer.importKind === 'type') { @@ -54,6 +65,7 @@ module.exports = { for (let [path, { getter, source }] of m.imports) { if (path === myPath) return true if (traversed.has(path)) continue + if (ignoreModule(source.value)) continue if (route.length + 1 < maxDepth) { untraversed.push({ mget: getter, diff --git a/tests/files/cycles/external-depth-two.js b/tests/files/cycles/external-depth-two.js new file mode 100644 index 0000000000..fbb6bfcbb2 --- /dev/null +++ b/tests/files/cycles/external-depth-two.js @@ -0,0 +1,2 @@ +import { foo } from "cycles/external/depth-one" +export { foo } diff --git a/tests/files/cycles/external/depth-one.js b/tests/files/cycles/external/depth-one.js new file mode 100644 index 0000000000..9caa762505 --- /dev/null +++ b/tests/files/cycles/external/depth-one.js @@ -0,0 +1,2 @@ +import foo from "../depth-zero" +export { foo } diff --git a/tests/src/rules/no-cycle.js b/tests/src/rules/no-cycle.js index df1e6d1433..b1bf8a67dc 100644 --- a/tests/src/rules/no-cycle.js +++ b/tests/src/rules/no-cycle.js @@ -40,6 +40,22 @@ ruleTester.run('no-cycle', rule, { code: 'import { foo, bar } from "./depth-two"', options: [{ maxDepth: 1 }], }), + test({ + code: 'import { foo } from "cycles/external/depth-one"', + options: [{ ignoreExternal: true }], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['external'], + }, + }), + test({ + code: 'import { foo } from "./external-depth-two"', + options: [{ ignoreExternal: true }], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['external'], + }, + }), test({ code: 'import("./depth-two").then(function({ foo }){})', options: [{ maxDepth: 1 }], @@ -63,6 +79,22 @@ ruleTester.run('no-cycle', rule, { code: 'import { foo } from "./depth-one"', errors: [error(`Dependency cycle detected.`)], }), + test({ + code: 'import { foo } from "cycles/external/depth-one"', + errors: [error(`Dependency cycle detected.`)], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['external'], + }, + }), + test({ + code: 'import { foo } from "./external-depth-two"', + errors: [error(`Dependency cycle via cycles/external/depth-one:1`)], + settings: { + 'import/resolver': 'webpack', + 'import/external-module-folders': ['external'], + }, + }), test({ code: 'import { foo } from "./depth-one"', options: [{ maxDepth: 1 }],