Skip to content

Commit

Permalink
no-cycle: initial docs + maxDepth option
Browse files Browse the repository at this point in the history
  • Loading branch information
benmosher committed Mar 25, 2018
1 parent 864dbcf commit 6933fa4
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 10 deletions.
37 changes: 37 additions & 0 deletions docs/rules/no-cycle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# import/no-cycle

Ensures that there is no resolvable path back to this module via its dependencies.

By default, this rule only detects cycles for ES6 imports, but see the [`no-unresolved` options](./no-unresolved.md#options) as this rule also supports the same `commonjs` and `amd` flags. However, these flags only impact which import types are _linted_; the
import/export infrastructure only registers `import` statements in dependencies, so
cycles created by `require` within imported modules may not be detected.

This includes cycles of depth 1 (imported module imports me) to `Infinity`.

```js
// dep-b.js
import './dep-a.js'

export function b() { /* ... */ }

// dep-a.js
import { b } from './dep-b.js' // reported: Dependency cycle detected.
```

This rule does _not_ detect imports that resolve directly to the linted module;
for that, see [`no-self-import`].


## Rule Details

## When Not To Use It

This rule is computationally expensive. If you are pressed for lint time, or don't
think you have an issue with dependency cycles, you may not want this rule enabled.

## Further Reading

- [Original inspiring issue](https://github.com/benmosher/eslint-plugin-import/issues/941)
- Rule to detect that module imports itself: [`no-self-import`]

[`no-self-import`]: ./no-self-import.md
24 changes: 17 additions & 7 deletions src/rules/no-cycle.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,29 @@
*/

import Exports from '../ExportMap'
import moduleVisitor, { optionsSchema } from 'eslint-module-utils/moduleVisitor'
import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisitor'
import docsUrl from '../docsUrl'

// todo: cache cycles / deep relationships for faster repeat evaluation
module.exports = {
meta: {
docs: { url: docsUrl('no-cycle') },
schema: [optionsSchema],
schema: [makeOptionsSchema({
maxDepth:{
description: 'maximum dependency depth to traverse',
type: 'integer',
minimum: 1,
},
})],
},

create: function (context) {
const myPath = context.getFilename()
if (myPath === '<text>') return // can't cycle-check a non-file

const options = context.options[0] || {}
const maxDepth = options.maxDepth || Infinity

function checkSourceValue(sourceNode, importer) {
const imported = Exports.get(sourceNode.value, context)

Expand All @@ -29,19 +38,20 @@ module.exports = {
return // no-self-import territory
}

const untraversed = [{imported, route:[]}]
const untraversed = [{mget: () => imported, route:[]}]
const traversed = new Set()
function detectCycle({imported: m, route}) {
function detectCycle({mget, route}) {
const m = mget()
if (m == null) return
if (traversed.has(m.path)) return
traversed.add(m.path)

for (let [path, { getter, source }] of m.imports) {
if (path === myPath) return true
if (traversed.has(path)) continue
const deeper = getter()
if (deeper != null) {
if (route.length + 1 < maxDepth) {
untraversed.push({
imported: deeper,
mget: getter,
route: route.concat(source),
})
}
Expand Down
3 changes: 0 additions & 3 deletions utils/moduleVisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,6 @@ exports.default = function visitModules(visitor, options) {
/**
* make an options schema for the module visitor, optionally
* adding extra fields.
* @param {[type]} additionalProperties [description]
* @return {[type]} [description]
*/
function makeOptionsSchema(additionalProperties) {
const base = {
Expand Down

0 comments on commit 6933fa4

Please sign in to comment.