Skip to content

Commit

Permalink
[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 3aefa79 commit c12cf07
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 6 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]
### Added
- Autofixer for [`no-duplicates`] rule ([#1312], thanks [@lydell])
- [`no-restricted-paths`]: New `except` option per `zone`, allowing exceptions to be defined for a restricted zone.

### Fixed
- [`order`]: Fix interpreting some external modules being interpreted as internal modules ([#793], [#794] thanks [@ephys])
Expand Down
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'
```
55 changes: 50 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,20 @@ 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 +76,37 @@ 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.`,
})
})
}

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 c12cf07

Please sign in to comment.