Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[no-restricted-paths] Add options to ignore certain import kinds #1900

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions docs/rules/no-restricted-paths.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ In order to prevent such scenarios this rule allows you to define restricted zon
## 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. 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.
It may also specify an `allowedImportKinds`.
The default value for `basePath` is the current working directory and the default value for `allowedImportKinds` is an empty array.

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. Additionally an override of `allowedImportKinds` can be given for each zone.
You may also specify an optional `message` for a zone, which will be displayed in case of the rule violation.

### Examples
Expand Down Expand Up @@ -64,7 +66,8 @@ and the current configuration is set to:
"target": "./tests/files/restricted-paths/server/one",
"from": "./tests/files/restricted-paths/server",
"except": ["./one"]
} ] }
} ],
"allowedImportKinds": ["type"] }
```

The following pattern is considered a problem:
Expand All @@ -73,8 +76,12 @@ The following pattern is considered a problem:
import a from '../two/a'
```

The following pattern is not considered a problem:
The following patterns are not considered a problem:

```js
import b from './b'
```

```ts
import type T from '../two/a'
```
32 changes: 25 additions & 7 deletions src/rules/no-restricted-paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import isStaticRequire from '../core/staticRequire'
import docsUrl from '../docsUrl'
import importType from '../core/importType'

const allowedImportKindsSchema = {
type: 'array',
items: {
type: 'string',
},
uniqueItems: true,
}

Comment on lines +9 to +16
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i do think we should restrict this for now to known import types. we can extend it in the future if needed, but we can't lock it down so easily.

module.exports = {
meta: {
type: 'problem',
Expand All @@ -32,12 +40,14 @@ module.exports = {
},
uniqueItems: true,
},
allowedImportKinds: allowedImportKindsSchema,
message: { type: 'string' },
},
additionalProperties: false,
},
},
basePath: { type: 'string' },
allowedImportKinds: allowedImportKindsSchema,
},
additionalProperties: false,
},
Expand All @@ -48,6 +58,7 @@ module.exports = {
const options = context.options[0] || {}
const restrictedPaths = options.zones || []
const basePath = options.basePath || process.cwd()
const allowedImportKinds = options.allowedImportKinds || []
const currentFilename = context.getFilename()
const matchingZones = restrictedPaths.filter((zone) => {
const targetPath = path.resolve(basePath, zone.target)
Expand All @@ -68,8 +79,8 @@ module.exports = {
})
}

function checkForRestrictedImportPath(importPath, node) {
const absoluteImportPath = resolve(importPath, context)
function checkForRestrictedImportPath(importPathNode, importNode) {
const absoluteImportPath = resolve(importPathNode.value, context)

if (!absoluteImportPath) {
return
Expand All @@ -90,7 +101,7 @@ module.exports = {
.every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath))

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

Expand All @@ -101,23 +112,30 @@ module.exports = {
return
}

const typeIsExpected = (zone.allowedImportKinds || allowedImportKinds)
.some((kind) => kind === importNode.importKind)

if (typeIsExpected) {
return
}

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

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

checkForRestrictedImportPath(firstArgument.value, firstArgument)
checkForRestrictedImportPath(firstArgument, node)
}
},
}
Expand Down
68 changes: 68 additions & 0 deletions tests/src/rules/no-restricted-paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,17 @@ ruleTester.run('no-restricted-paths', rule, {

// builtin (ignore)
test({ code: 'require("os")' }),

// type imports
test({
code: 'import type a from "../client/a.js"',
parser: require.resolve('babel-eslint'),
filename: testFilePath('./restricted-paths/server/b.js'),
options: [ {
allowedImportKinds: ['type'],
zones: [ { target: './tests/files/restricted-paths/server', from: './tests/files/restricted-paths/client' } ],
} ],
}),
],

invalid: [
Expand Down Expand Up @@ -179,5 +190,62 @@ ruleTester.run('no-restricted-paths', rule, {
column: 15,
} ],
}),

// type imports
test({
code: 'import type T from "../server/b.js";',
parser: require.resolve('babel-eslint'),
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: 20,
} ],
}),
test({
code: `
import type { T } from "../server/b.js";
import b from "../server/b.js";
`,
parser: require.resolve('babel-eslint'),
filename: testFilePath('./restricted-paths/client/a.js'),
options: [ {
allowedImportKinds: ['type'],
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: 3,
column: 23,
} ],
}),
test({
code: `
import type { T } from "../server/b.js";
import b from "../server/b.js";
`,
parser: require.resolve('babel-eslint'),
filename: testFilePath('./restricted-paths/client/a.js'),
options: [ {
allowedImportKinds: ['type'],
zones: [ {
target: './tests/files/restricted-paths/client',
from: './tests/files/restricted-paths/server',
allowedImportKinds: [],
} ],
} ],
errors: [ {
message: 'Unexpected path "../server/b.js" imported in restricted zone.',
line: 2,
column: 32,
}, {
message: 'Unexpected path "../server/b.js" imported in restricted zone.',
line: 3,
column: 23,
} ],
}),
],
})