Skip to content

Commit

Permalink
[New] no-restricted-paths: Allow exceptions to zones
Browse files Browse the repository at this point in the history
 - Make exceptions relative to from paths, plus enforcement
  • Loading branch information
Ross Solomon authored and ljharb committed Nov 20, 2018
1 parent c28fa7c commit 078b6f7
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
## [Unreleased]
### Added
- [`order`]: added `caseInsensitive` as an additional option to `alphabetize` ([#1586], thanks [@dbrewer5])
- [`no-restricted-paths`]: New `except` option per `zone`, allowing exceptions to be defined for a restricted zone ([#1238], thanks [@rsolomon])

### Fixed
- [`no-unused-modules`]: fix usage of `import/extensions` settings ([#1560], thanks [@stekycz])
Expand Down Expand Up @@ -685,6 +686,7 @@ for info on changes for earlier releases.
[#1277]: https://github.com/benmosher/eslint-plugin-import/pull/1277
[#1257]: https://github.com/benmosher/eslint-plugin-import/pull/1257
[#1253]: https://github.com/benmosher/eslint-plugin-import/pull/1253
[#1238]: https://github.com/benmosher/eslint-plugin-import/pull/1238
[#1235]: https://github.com/benmosher/eslint-plugin-import/pull/1235
[#1234]: https://github.com/benmosher/eslint-plugin-import/pull/1234
[#1232]: https://github.com/benmosher/eslint-plugin-import/pull/1232
Expand Down Expand Up @@ -1051,3 +1053,4 @@ for info on changes for earlier releases.
[@Pessimistress]: https://github.com/Pessimistress
[@stekycz]: https://github.com/stekycz
[@dbrewer5]: https://github.com/dbrewer5
[@rsolomon]: https://github.com/rsolomon
42 changes: 41 additions & 1 deletion docs/rules/no-restricted-paths.md
Expand Up @@ -9,7 +9,7 @@ In order to prevent such scenarios this rule allows you to define restricted zon

This rule has one option. The option is an object containing the definition of all restricted `zones` and the optional `basePath` which is used to resolve relative paths within.
The default value for `basePath` is the current working directory.
Each zone consists of the `target` path and a `from` path. The `target` is the path where the restricted imports should be applied. The `from` path defines the folder that is not allowed to be used in an import.
Each zone consists of the `target` path and a `from` path. The `target` is the path where the restricted imports should be applied. The `from` path defines the folder that is not allowed to be used in an import. An optional `except` may be defined for a zone, allowing exception paths that would otherwise violate the related `from`. Note that `except` is relative to `from` and cannot backtrack to a parent directory.

### Examples

Expand Down Expand Up @@ -37,3 +37,43 @@ The following patterns are not considered problems when configuration set to `{
```js
import baz from '../client/baz';
```

---------------

Given the following folder structure:

```
my-project
├── client
│ └── foo.js
│ └── baz.js
└── server
├── one
│ └── a.js
│ └── b.js
└── two
```

and the current file being linted is `my-project/server/one/a.js`.

and the current configuration is set to:

```
{ "zones": [ {
"target": "./tests/files/restricted-paths/server/one",
"from": "./tests/files/restricted-paths/server",
"except": ["./one"]
} ] }
```

The following pattern is considered a problem:

```js
import a from '../two/a'
```

The following pattern is not considered a problem:

```js
import b from './b'
```
53 changes: 48 additions & 5 deletions src/rules/no-restricted-paths.js
Expand Up @@ -4,6 +4,7 @@ import path from 'path'
import resolve from 'eslint-module-utils/resolve'
import isStaticRequire from '../core/staticRequire'
import docsUrl from '../docsUrl'
import importType from '../core/importType'

module.exports = {
meta: {
Expand All @@ -24,6 +25,13 @@ module.exports = {
properties: {
target: { type: 'string' },
from: { type: 'string' },
except: {
type: 'array',
items: {
type: 'string',
},
uniqueItems: true,
},
},
additionalProperties: false,
},
Expand All @@ -46,6 +54,19 @@ module.exports = {
return containsPath(currentFilename, targetPath)
})

function isValidExceptionPath(absoluteFromPath, absoluteExceptionPath) {
const relativeExceptionPath = path.relative(absoluteFromPath, absoluteExceptionPath)

return importType(relativeExceptionPath, context) !== 'parent'
}

function reportInvalidExceptionPath(node) {
context.report({
node,
message: 'Restricted path exceptions must be descendants of the configured `from` path for that zone.',
})
}

function checkForRestrictedImportPath(importPath, node) {
const absoluteImportPath = resolve(importPath, context)

Expand All @@ -54,14 +75,36 @@ module.exports = {
}

matchingZones.forEach((zone) => {
const exceptionPaths = zone.except || []
const absoluteFrom = path.resolve(basePath, zone.from)

if (containsPath(absoluteImportPath, absoluteFrom)) {
context.report({
node,
message: `Unexpected path "${importPath}" imported in restricted zone.`,
})
if (!containsPath(absoluteImportPath, absoluteFrom)) {
return
}

const absoluteExceptionPaths = exceptionPaths.map((exceptionPath) =>
path.resolve(absoluteFrom, exceptionPath)
)
const hasValidExceptionPaths = absoluteExceptionPaths
.every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath))

if (!hasValidExceptionPaths) {
reportInvalidExceptionPath(node)
return
}

const pathIsExcepted = absoluteExceptionPaths
.some((absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath))

if (pathIsExcepted) {
return
}

context.report({
node,
message: `Unexpected path "{{importPath}}" imported in restricted zone.`,
data: { importPath },
})
})
}

Expand Down
Empty file.
Empty file.
Empty file.
55 changes: 55 additions & 0 deletions tests/src/rules/no-restricted-paths.js
Expand Up @@ -28,6 +28,28 @@ ruleTester.run('no-restricted-paths', rule, {
zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/other' } ],
} ],
}),
test({
code: 'import a from "./a.js"',
filename: testFilePath('./restricted-paths/server/one/a.js'),
options: [ {
zones: [ {
target: './tests/files/restricted-paths/server/one',
from: './tests/files/restricted-paths/server',
except: ['./one'],
} ],
} ],
}),
test({
code: 'import a from "../two/a.js"',
filename: testFilePath('./restricted-paths/server/one/a.js'),
options: [ {
zones: [ {
target: './tests/files/restricted-paths/server/one',
from: './tests/files/restricted-paths/server',
except: ['./two'],
} ],
} ],
}),


// irrelevant function calls
Expand Down Expand Up @@ -107,5 +129,38 @@ ruleTester.run('no-restricted-paths', rule, {
column: 19,
} ],
}),
test({
code: 'import b from "../two/a.js"',
filename: testFilePath('./restricted-paths/server/one/a.js'),
options: [ {
zones: [ {
target: './tests/files/restricted-paths/server/one',
from: './tests/files/restricted-paths/server',
except: ['./one'],
} ],
} ],
errors: [ {
message: 'Unexpected path "../two/a.js" imported in restricted zone.',
line: 1,
column: 15,
} ],
}),
test({
code: 'import b from "../two/a.js"',
filename: testFilePath('./restricted-paths/server/one/a.js'),
options: [ {
zones: [ {
target: './tests/files/restricted-paths/server/one',
from: './tests/files/restricted-paths/server',
except: ['../client/a'],
} ],
} ],
errors: [ {
message: 'Restricted path exceptions must be descendants of the configured ' +
'`from` path for that zone.',
line: 1,
column: 15,
} ],
}),
],
})

0 comments on commit 078b6f7

Please sign in to comment.