Skip to content

Commit

Permalink
Merge 5db5e55 into 1edbbd0
Browse files Browse the repository at this point in the history
  • Loading branch information
kwelch committed May 9, 2019
2 parents 1edbbd0 + 5db5e55 commit b6fd941
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -6,6 +6,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
## [Unreleased]

- [`named`]: ignore Flow `typeof` imports and `type` exports ([#1345], thanks [@loganfsmyth])
- Added new rule [`max-depth`]. ([#1355], thanks [@kwelch])

## [2.17.2] - 2019-04-16

Expand Down
50 changes: 50 additions & 0 deletions docs/rules/max-depth.md
@@ -0,0 +1,50 @@
# import/max-dependencies

Forbid importing into nested folders of a module.

This is a useful rule as import from packages you should stick to the public API (which is typically exported from the root of the package). Depending on specific folders in a package that are not public API exposes you to a greater potential of breaking changes.

### Options

This rule takes the following options:

#### `max`
`max`: The maximum number of nested directories allowed. Anything over will trigger the rule. **Default is 0** if the rule is enabled and no `max` is specified.

You can set the option like this:

```js
"import/max-depth": ["error", {"max": 2}]
```

#### `overridePackages`
`overridePackages`: This allows you to override this rule for specific packages. This can be useful if the packages public API is expected to be imported from nested directories such as `lodash`. This options is declared as an object with the key being set to the name of the package and the value is set to the `max` for that package only.

You can set the option like this:

```js
"import/max-depth": ["error", {"overridePackages": {'lodash': 2}}]
```

## Example

Given a max value of `{"max": 0}`:

### Fail

```js
import map from "lodash/map"
const map = require("lodash/map")
```

### Pass

```js
import "lodash"
import _ from "lodash"
const _ = require("lodash")
```

## When Not To Use It

If you don't care how about imports deeply nested into packages.
1 change: 1 addition & 0 deletions src/index.js
Expand Up @@ -39,6 +39,7 @@ export const rules = {
'no-unassigned-import': require('./rules/no-unassigned-import'),
'no-useless-path-segments': require('./rules/no-useless-path-segments'),
'dynamic-import-chunkname': require('./rules/dynamic-import-chunkname'),
'max-depth': require('./rules/max-depth'),

// export
'exports-last': require('./rules/exports-last'),
Expand Down
83 changes: 83 additions & 0 deletions src/rules/max-depth.js
@@ -0,0 +1,83 @@
import isStaticRequire from '../core/staticRequire'
import docsUrl from '../docsUrl'

const DEFAULT_MAX = 0

const createError = (sourcePath, maxDepth, actualDepth) => (
`Import '${sourcePath}' exceeds max nesting depth of ${maxDepth} (actual: ${actualDepth}).`
)

const checkDepth = (sourceValue, node, context) => {
const {max = DEFAULT_MAX, overridePackages = {}} = context.options[0] || { }
let packageMax = max

if (/^\./.test(sourceValue)) {
// skip relative paths
return
}

let [pkgName, ...dirs] = sourceValue.split('/')

// update variables for scoped packages
if (/^@/.test(pkgName)) {
pkgName = `${pkgName}/${dirs[0]}`
dirs = dirs.slice(1)
}

if (pkgName in overridePackages) {
packageMax = overridePackages[pkgName]
}

if (dirs.length > packageMax) {
context.report({
node,
message: createError(sourceValue, packageMax, dirs.length),
})
}
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
url: docsUrl('max-depth'),
},
schema: [
{
type: 'object',
properties: {
overridePackages: {
type: 'object',
patternProperties: {
'^[a-zA-Z-_@/]+$': {
type: 'integer',
minimum: 0,
},
},
additionalProperties: false,
},
max: {
type: 'integer',
minimum: 0,
},
},
additionalProperties: false,
},
],
},

create: context => {

return {
ImportDeclaration(node) {
checkDepth(node.source.value, node.source, context)
},
CallExpression(node) {
if (isStaticRequire(node)) {
const [ requirePath ] = node.arguments
checkDepth(requirePath.value, node, context)
}
},
}
},
}
72 changes: 72 additions & 0 deletions tests/src/rules/max-depth.js
@@ -0,0 +1,72 @@
import { test } from '../utils'

import { RuleTester } from 'eslint'

const ruleTester = new RuleTester()
, rule = require('rules/max-depth')

ruleTester.run('max-depth', rule, {
valid: [
test({code: 'import "./foo.js"'}),
test({code: 'import "../bar/baz.js"'}),
test({code: 'import a from "./foo.js"'}),
test({code: 'import b from "../bar/baz.js"'}),
test({code: 'const a = require("./foo.js")'}),
test({code: 'const b = require("../bar/baz.js")'}),
test({code: 'import "@scope/foo"'}),
test({code: 'import "@scope/bar-baz"'}),
test({code: 'import "lodash"'}),
test({code: 'import _ from "lodash"'}),
test({code: 'const _ = require("lodash")'}),
test({
code: 'import map from "lodash/map"',
options: [{
overridePackages: {'lodash': 1},
}],
}),
test({
code: 'import map from "lodash/map"',
options: [{
max: 1,
}],
}),
],
invalid: [
test({
code: 'import map from "lodash/map"',
errors: [
'Import \'lodash/map\' exceeds max nesting depth of 0 (actual: 1).',
],
}),
test({
code: 'import map from "lodash/fp/map"',
options: [{
max: 1,
}],
errors: [
'Import \'lodash/fp/map\' exceeds max nesting depth of 1 (actual: 2).',
],
}),
test({
code: 'import map from "lodash/fp/map"',
options: [{
overridePackages: {'lodash': 1},
}],
errors: [
'Import \'lodash/fp/map\' exceeds max nesting depth of 1 (actual: 2).',
],
}),
test({
code: 'import "@scope/foo/file"',
errors: [
'Import \'@scope/foo/file\' exceeds max nesting depth of 0 (actual: 1).',
],
}),
test({
code: 'import "@scope/bar-baz/nested/directory"',
errors: [
'Import \'@scope/bar-baz/nested/directory\' exceeds max nesting depth of 0 (actual: 2).',
],
}),
],
})

0 comments on commit b6fd941

Please sign in to comment.