Skip to content

Commit

Permalink
Implement new rule no-restricted-paths (fixes #155) (#371)
Browse files Browse the repository at this point in the history
  • Loading branch information
lo1tuma authored and benmosher committed Jun 21, 2016
1 parent 66c84ea commit 8141713
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel

## [1.9.0] - 2016-06-10
### Added
- Added new rule [`no-restricted-paths`]. ([#155]/[#371], thanks [@lo1tuma])
- Added support TomDoc comments to [`no-deprecated`]. ([#321], thanks [@josh])
- Added support for loading custom resolvers ([#314], thanks [@le0nik])

Expand Down Expand Up @@ -220,7 +221,9 @@ for info on changes for earlier releases.
[`newline-after-import`]: ./docs/rules/newline-after-import.md
[`no-mutable-exports`]: ./docs/rules/no-mutable-exports.md
[`prefer-default-export`]: ./docs/rules/prefer-default-export.md
[`no-restricted-paths`]: ./docs/rules/no-restricted-paths.md

[#371]: https://github.com/benmosher/eslint-plugin-import/pull/371
[#359]: https://github.com/benmosher/eslint-plugin-import/pull/359
[#343]: https://github.com/benmosher/eslint-plugin-import/pull/343
[#332]: https://github.com/benmosher/eslint-plugin-import/pull/332
Expand Down Expand Up @@ -269,6 +272,7 @@ for info on changes for earlier releases.
[#191]: https://github.com/benmosher/eslint-plugin-import/issues/191
[#189]: https://github.com/benmosher/eslint-plugin-import/issues/189
[#170]: https://github.com/benmosher/eslint-plugin-import/issues/170
[#155]: https://github.com/benmosher/eslint-plugin-import/issues/155
[#119]: https://github.com/benmosher/eslint-plugin-import/issues/119
[#89]: https://github.com/benmosher/eslint-plugin-import/issues/89

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
* Ensure named imports correspond to a named export in the remote file. ([`named`])
* Ensure a default export is present, given a default import. ([`default`])
* Ensure imported namespaces contain dereferenced properties as they are dereferenced. ([`namespace`])
* Restrict which files can be imported in a given folder ([`no-restricted-paths`])

[`no-unresolved`]: ./docs/rules/no-unresolved.md
[`named`]: ./docs/rules/named.md
[`default`]: ./docs/rules/default.md
[`namespace`]: ./docs/rules/namespace.md
[`no-restricted-paths`]: ./docs/rule/no-restricted-paths.md

**Helpful warnings:**

Expand Down
39 changes: 39 additions & 0 deletions docs/rules/no-restricted-paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# no-restricted-paths - Restrict which files can be imported in a given folder

Some projects contain files which are not always meant to be executed in the same environment.
For example consider a web application that contains specific code for the server and some specific code for the browser/client. In this case you don’t want to import server-only files in your client code.

In order to prevent such scenarios this rule allows you to define restricted zones where you can forbid files from imported if they match a specific path.

## Rule Details

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.

### Examples

Given the following folder structure:

```
my-project
├── client
│ └── foo.js
│ └── baz.js
└── server
└── bar.js
```

and the current file being linted is `my-project/client/foo.js`.

The following patterns are considered problems when configuration set to `{ "zones": [ { "target": "./client", "from": "./server" } ] }`:

```js
import bar from '../server/bar';
```

The following patterns are not considered problems when configuration set to `{ "zones": [ { "target": "./client", "from": "./server" } ] }`:

```js
import baz from '../client/baz';
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
},
"dependencies": {
"builtin-modules": "^1.1.1",
"contains-path": "^0.1.0",
"doctrine": "1.2.x",
"es6-map": "^0.1.3",
"es6-set": "^0.1.4",
Expand Down
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export const rules = {
'export': require('./rules/export'),
'no-mutable-exports': require('./rules/no-mutable-exports'),
'extensions': require('./rules/extensions'),
'no-restricted-paths': require('./rules/no-restricted-paths'),

'no-named-as-default': require('./rules/no-named-as-default'),
'no-named-as-default-member': require('./rules/no-named-as-default-member'),
Expand Down
71 changes: 71 additions & 0 deletions src/rules/no-restricted-paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import containsPath from 'contains-path'
import path from 'path'

import resolve from '../core/resolve'
import isStaticRequire from '../core/staticRequire'

module.exports = function noRestrictedPaths(context) {
const options = context.options[0] || {}
const restrictedPaths = options.zones || []
const basePath = options.basePath || process.cwd()
const currentFilename = context.getFilename()
const matchingZones = restrictedPaths.filter((zone) => {
const targetPath = path.resolve(basePath, zone.target)

return containsPath(currentFilename, targetPath)
})

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

if (!absoluteImportPath) {
return
}

matchingZones.forEach((zone) => {
const absoluteFrom = path.resolve(basePath, zone.from)

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

return {
ImportDeclaration(node) {
checkForRestrictedImportPath(node.source.value, node.source)
},
CallExpression(node) {
if (isStaticRequire(node)) {
const [ firstArgument ] = node.arguments

checkForRestrictedImportPath(firstArgument.value, firstArgument)
}
},
}
}

module.exports.schema = [
{
type: 'object',
properties: {
zones: {
type: 'array',
minItems: 1,
items: {
type: 'object',
properties: {
target: { type: 'string' },
from: { type: 'string' },
},
additionalProperties: false,
},
},
basePath: { type: 'string' },
},
additionalProperties: false,
},
]
Empty file.
Empty file.
Empty file.
94 changes: 94 additions & 0 deletions tests/src/rules/no-restricted-paths.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { RuleTester } from 'eslint'
import rule from 'rules/no-restricted-paths'

import { test, testFilePath } from '../utils'

const ruleTester = new RuleTester()

ruleTester.run('no-restricted-paths', rule, {
valid: [
test({
code: 'import a from "../client/a.js"',
filename: testFilePath('./restricted-paths/server/b.js'),
options: [ {
zones: [ { target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/other' } ],
} ],
}),
test({
code: 'const a = require("../client/a.js")',
filename: testFilePath('./restricted-paths/server/b.js'),
options: [ {
zones: [ { target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/other' } ],
} ],
}),
test({
code: 'import b from "../server/b.js"',
filename: testFilePath('./restricted-paths/client/a.js'),
options: [ {
zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/other' } ],
} ],
}),
],

invalid: [
test({
code: 'import b from "../server/b.js"',
filename: testFilePath('./restricted-paths/client/a.js'),
options: [ {
zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' } ],
} ],
errors: [ {
message: 'Unexpected path "../server/b.js" imported in restricted zone.',
line: 1,
column: 15,
} ],
}),
test({
code: 'import a from "../client/a"\nimport c from "./c"',
filename: testFilePath('./restricted-paths/server/b.js'),
options: [ {
zones: [
{ target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/client' },
{ target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/server/c.js' },
],
} ],
errors: [
{
message: 'Unexpected path "../client/a" imported in restricted zone.',
line: 1,
column: 15,
},
{
message: 'Unexpected path "./c" imported in restricted zone.',
line: 2,
column: 15,
},
],
}),
test({
code: 'import b from "../server/b.js"',
filename: testFilePath('./restricted-paths/client/a.js'),
options: [ {
zones: [ { target: './client', from: './server' } ],
basePath: testFilePath('./restricted-paths'),
} ],
errors: [ {
message: 'Unexpected path "../server/b.js" imported in restricted zone.',
line: 1,
column: 15,
} ],
}),
test({
code: 'const b = require("../server/b.js")',
filename: testFilePath('./restricted-paths/client/a.js'),
options: [ {
zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' } ],
} ],
errors: [ {
message: 'Unexpected path "../server/b.js" imported in restricted zone.',
line: 1,
column: 19,
} ],
}),
],
})

0 comments on commit 8141713

Please sign in to comment.