diff --git a/CHANGELOG.md b/CHANGELOG.md index 63b33fe91..f2f70fedf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,11 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange ### Added - [`newline-after-import`]: add `considerComments` option ([#2399], thanks [@pri1311]) - [`no-cycle`]: add `allowUnsafeDynamicCyclicDependency` option ([#2387], thanks [@GerkinDev]) +- [`no-restricted-paths`]: support arrays for `from` and `target` options ([#2466], thanks [@AdriAt360]) ### Fixed - [`order`]: move nested imports closer to main import entry ([#2396], thanks [@pri1311]) +- [`no-restricted-paths`]: fix an error message ([#2466], thanks [@AdriAt360]) ### Changed - [Tests] `named`: Run all TypeScript test ([#2427], thanks [@ProdigySim]) @@ -990,6 +992,7 @@ for info on changes for earlier releases. [`memo-parser`]: ./memo-parser/README.md +[#2466]: https://github.com/import-js/eslint-plugin-import/pull/2466 [#2440]: https://github.com/import-js/eslint-plugin-import/pull/2440 [#2427]: https://github.com/import-js/eslint-plugin-import/pull/2427 [#2417]: https://github.com/import-js/eslint-plugin-import/pull/2417 @@ -1493,6 +1496,7 @@ for info on changes for earlier releases. [@aberezkin]: https://github.com/aberezkin [@adamborowski]: https://github.com/adamborowski [@adjerbetian]: https://github.com/adjerbetian +[@AdriAt360]: https://github.com/AdriAt360 [@ai]: https://github.com/ai [@aladdin-add]: https://github.com/aladdin-add [@alex-page]: https://github.com/alex-page diff --git a/docs/rules/no-restricted-paths.md b/docs/rules/no-restricted-paths.md index c9390754e..d22a8b3ea 100644 --- a/docs/rules/no-restricted-paths.md +++ b/docs/rules/no-restricted-paths.md @@ -10,16 +10,19 @@ 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, a `from` path, and an optional `except` and `message` attribute. -- `target` is the path where the restricted imports should be applied. It can be expressed by +Each zone consists of the `target` paths, a `from` paths, and an optional `except` and `message` attribute. +- `target` contains the paths where the restricted imports should be applied. It can be expressed by - directory string path that matches all its containing files - glob pattern matching all the targeted files -- `from` path defines the folder that is not allowed to be used in an import. It can be expressed by + - an array of multiple of the two types above +- `from` paths define the folders that are not allowed to be used in an import. It can be expressed by - directory string path that matches all its containing files - glob pattern matching all the files restricted to be imported + - an array of multiple directory string path + - an array of multiple glob patterns - `except` may be defined for a zone, allowing exception paths that would otherwise violate the related `from`. Note that it does not alter the behaviour of `target` in any way. - - in case `from` is a glob pattern, `except` must be an array of glob patterns as well - - in case `from` is a directory path, `except` is relative to `from` and cannot backtrack to a parent directory. + - in case `from` contains only glob patterns, `except` must be an array of glob patterns as well + - in case `from` contains only directory path, `except` is relative to `from` and cannot backtrack to a parent directory - `message` - will be displayed in case of the rule violation. ### Examples @@ -124,3 +127,70 @@ The following import is not considered a problem in `my-project/client/sub-modul ```js import b from './baz' ``` + +--------------- + +Given the following folder structure: + +``` +my-project +└── one + └── a.js + └── b.js +└── two + └── a.js + └── b.js +└── three + └── a.js + └── b.js +``` + +and the current configuration is set to: + +``` +{ + "zones": [ + { + "target": ["./tests/files/restricted-paths/two/*", "./tests/files/restricted-paths/three/*"], + "from": ["./tests/files/restricted-paths/one", "./tests/files/restricted-paths/three"], + } + ] +} +``` + +The following patterns are not considered a problem in `my-project/one/b.js`: + +```js +import a from '../three/a' +``` + +```js +import a from './a' +``` + +The following pattern is not considered a problem in `my-project/two/b.js`: + +```js +import a from './a' +``` + +The following patterns are considered a problem in `my-project/two/a.js`: + +```js +import a from '../one/a' +``` + +```js +import a from '../three/a' +``` + +The following patterns are considered a problem in `my-project/three/b.js`: + +```js +import a from '../one/a' +``` + +```js +import a from './a' +``` + diff --git a/src/rules/no-restricted-paths.js b/src/rules/no-restricted-paths.js index e5bc6bc85..9b17975b5 100644 --- a/src/rules/no-restricted-paths.js +++ b/src/rules/no-restricted-paths.js @@ -3,7 +3,7 @@ import path from 'path'; import resolve from 'eslint-module-utils/resolve'; import moduleVisitor from 'eslint-module-utils/moduleVisitor'; import isGlob from 'is-glob'; -import { Minimatch, default as minimatch } from 'minimatch'; +import { Minimatch } from 'minimatch'; import docsUrl from '../docsUrl'; import importType from '../core/importType'; @@ -29,8 +29,28 @@ module.exports = { items: { type: 'object', properties: { - target: { type: 'string' }, - from: { type: 'string' }, + target: { + oneOf: [ + { type: 'string' }, + { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + minLength: 1, + }, + ], + }, + from: { + oneOf: [ + { type: 'string' }, + { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + minLength: 1, + }, + ], + }, except: { type: 'array', items: { @@ -56,14 +76,19 @@ module.exports = { const basePath = options.basePath || process.cwd(); const currentFilename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename(); const matchingZones = restrictedPaths.filter((zone) => { - const targetPath = path.resolve(basePath, zone.target); + return [].concat(zone.target) + .map(target => path.resolve(basePath, target)) + .some(targetPath => isMatchingTargetPath(currentFilename, targetPath)); + }); + function isMatchingTargetPath(filename, targetPath) { if (isGlob(targetPath)) { - return minimatch(currentFilename, targetPath); + const mm = new Minimatch(targetPath); + return mm.match(filename); } - return containsPath(currentFilename, targetPath); - }); + return containsPath(filename, targetPath); + } function isValidExceptionPath(absoluteFromPath, absoluteExceptionPath) { const relativeExceptionPath = path.relative(absoluteFromPath, absoluteExceptionPath); @@ -71,6 +96,10 @@ module.exports = { return importType(relativeExceptionPath, context) !== 'parent'; } + function areBothGlobPatternAndAbsolutePath(areGlobPatterns) { + return areGlobPatterns.some((isGlob) => isGlob) && areGlobPatterns.some((isGlob) => !isGlob); + } + function reportInvalidExceptionPath(node) { context.report({ node, @@ -78,56 +107,108 @@ module.exports = { }); } + function reportInvalidExceptionMixedGlobAndNonGlob(node) { + context.report({ + node, + message: 'Restricted path `from` must contain either only glob patterns or none', + }); + } + function reportInvalidExceptionGlob(node) { context.report({ node, - message: 'Restricted path exceptions must be glob patterns when`from` is a glob pattern', + message: 'Restricted path exceptions must be glob patterns when `from` contains glob patterns', }); } - const makePathValidator = (zoneFrom, zoneExcept = []) => { - const absoluteFrom = path.resolve(basePath, zoneFrom); - const isGlobPattern = isGlob(zoneFrom); - let isPathRestricted; - let hasValidExceptions; + function computeMixedGlobAndAbsolutePathValidator() { + return { + isPathRestricted: () => true, + hasValidExceptions: false, + reportInvalidException: reportInvalidExceptionMixedGlobAndNonGlob, + }; + } + + function computeGlobPatternPathValidator(absoluteFrom, zoneExcept) { let isPathException; - let reportInvalidException; - if (isGlobPattern) { - const mm = new Minimatch(absoluteFrom); - isPathRestricted = (absoluteImportPath) => mm.match(absoluteImportPath); + const mm = new Minimatch(absoluteFrom); + const isPathRestricted = (absoluteImportPath) => mm.match(absoluteImportPath); + const hasValidExceptions = zoneExcept.every(isGlob); - hasValidExceptions = zoneExcept.every(isGlob); + if (hasValidExceptions) { + const exceptionsMm = zoneExcept.map((except) => new Minimatch(except)); + isPathException = (absoluteImportPath) => exceptionsMm.some((mm) => mm.match(absoluteImportPath)); + } - if (hasValidExceptions) { - const exceptionsMm = zoneExcept.map((except) => new Minimatch(except)); - isPathException = (absoluteImportPath) => exceptionsMm.some((mm) => mm.match(absoluteImportPath)); - } + const reportInvalidException = reportInvalidExceptionGlob; + + return { + isPathRestricted, + hasValidExceptions, + isPathException, + reportInvalidException, + }; + } - reportInvalidException = reportInvalidExceptionGlob; - } else { - isPathRestricted = (absoluteImportPath) => containsPath(absoluteImportPath, absoluteFrom); + function computeAbsolutePathValidator(absoluteFrom, zoneExcept) { + let isPathException; - const absoluteExceptionPaths = zoneExcept - .map((exceptionPath) => path.resolve(absoluteFrom, exceptionPath)); - hasValidExceptions = absoluteExceptionPaths - .every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath)); + const isPathRestricted = (absoluteImportPath) => containsPath(absoluteImportPath, absoluteFrom); - if (hasValidExceptions) { - isPathException = (absoluteImportPath) => absoluteExceptionPaths.some( - (absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath), - ); - } + const absoluteExceptionPaths = zoneExcept + .map((exceptionPath) => path.resolve(absoluteFrom, exceptionPath)); + const hasValidExceptions = absoluteExceptionPaths + .every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath)); - reportInvalidException = reportInvalidExceptionPath; + if (hasValidExceptions) { + isPathException = (absoluteImportPath) => absoluteExceptionPaths.some( + (absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath), + ); } + const reportInvalidException = reportInvalidExceptionPath; + return { isPathRestricted, hasValidExceptions, isPathException, reportInvalidException, }; + } + + function reportInvalidExceptions(validators, node) { + validators.forEach(validator => validator.reportInvalidException(node)); + } + + function reportImportsInRestrictedZone(validators, node, importPath, customMessage) { + validators.forEach(() => { + context.report({ + node, + message: `Unexpected path "{{importPath}}" imported in restricted zone.${customMessage ? ` ${customMessage}` : ''}`, + data: { importPath }, + }); + }); + } + + const makePathValidators = (zoneFrom, zoneExcept = []) => { + const allZoneFrom = [].concat(zoneFrom); + const areGlobPatterns = allZoneFrom.map(isGlob); + + if (areBothGlobPatternAndAbsolutePath(areGlobPatterns)) { + return [computeMixedGlobAndAbsolutePathValidator()]; + } + + const isGlobPattern = areGlobPatterns.every((isGlob) => isGlob); + + return allZoneFrom.map(singleZoneFrom => { + const absoluteFrom = path.resolve(basePath, singleZoneFrom); + + if (isGlobPattern) { + return computeGlobPatternPathValidator(absoluteFrom, zoneExcept); + } + return computeAbsolutePathValidator(absoluteFrom, zoneExcept); + }); }; const validators = []; @@ -141,35 +222,18 @@ module.exports = { matchingZones.forEach((zone, index) => { if (!validators[index]) { - validators[index] = makePathValidator(zone.from, zone.except); - } - - const { - isPathRestricted, - hasValidExceptions, - isPathException, - reportInvalidException, - } = validators[index]; - - if (!isPathRestricted(absoluteImportPath)) { - return; + validators[index] = makePathValidators(zone.from, zone.except); } - if (!hasValidExceptions) { - reportInvalidException(node); - return; - } + const applicableValidatorsForImportPath = validators[index].filter(validator => validator.isPathRestricted(absoluteImportPath)); - const pathIsExcepted = isPathException(absoluteImportPath); - if (pathIsExcepted) { - return; - } + const validatorsWithInvalidExceptions = applicableValidatorsForImportPath.filter(validator => !validator.hasValidExceptions); + reportInvalidExceptions(validatorsWithInvalidExceptions, node); - context.report({ - node, - message: `Unexpected path "{{importPath}}" imported in restricted zone.${zone.message ? ` ${zone.message}` : ''}`, - data: { importPath }, - }); + const applicableValidatorsForImportPathExcludingExceptions = applicableValidatorsForImportPath + .filter(validator => validator.hasValidExceptions) + .filter(validator => !validator.isPathException(absoluteImportPath)); + reportImportsInRestrictedZone(applicableValidatorsForImportPathExcludingExceptions, node, importPath, zone.message); }); } diff --git a/tests/files/restricted-paths/client/one/a.js b/tests/files/restricted-paths/client/one/a.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/files/restricted-paths/server/three/a.js b/tests/files/restricted-paths/server/three/a.js new file mode 100644 index 000000000..e69de29bb diff --git a/tests/src/rules/no-restricted-paths.js b/tests/src/rules/no-restricted-paths.js index 11934599e..d782a1447 100644 --- a/tests/src/rules/no-restricted-paths.js +++ b/tests/src/rules/no-restricted-paths.js @@ -6,88 +6,227 @@ import { test, testFilePath } from '../utils'; const ruleTester = new RuleTester(); ruleTester.run('no-restricted-paths', rule, { - valid: [ + valid: [].concat( 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' } ], - } ], + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/server', + from: './tests/files/restricted-paths/other', + }, + ], + }, + ], }), test({ code: 'import a from "../client/a.js"', filename: testFilePath('./restricted-paths/server/b.js'), - options: [ { - zones: [ { target: '**/*', from: './tests/files/restricted-paths/other' } ], - } ], + options: [ + { + zones: [ + { + target: '**/*', + from: './tests/files/restricted-paths/other', + }, + ], + }, + ], }), test({ code: 'import a from "../client/a.js"', filename: testFilePath('./restricted-paths/client/b.js'), - options: [ { - zones: [ { - target: './tests/files/restricted-paths/!(client)/**/*', - from: './tests/files/restricted-paths/client/**/*', - } ], - } ], + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/!(client)/**/*', + from: './tests/files/restricted-paths/client/**/*', + }, + ], + }, + ], }), 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' } ], - } ], + 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' } ], - } ], + options: [ + { + 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'], - } ], - } ], + 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'], - } ], - } ], + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/server/one', + from: './tests/files/restricted-paths/server', + except: ['./two'], + }, + ], + }, + ], }), test({ code: 'import a from "../one/a.js"', filename: testFilePath('./restricted-paths/server/two-new/a.js'), - options: [ { - zones: [ { - target: './tests/files/restricted-paths/server/two', - from: './tests/files/restricted-paths/server', - except: [], - } ], - } ], + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/server/two', + from: './tests/files/restricted-paths/server', + except: [], + }, + ], + }, + ], }), test({ code: 'import A from "../two/a.js"', filename: testFilePath('./restricted-paths/server/one/a.js'), - options: [ { - zones: [ { - target: '**/*', - from: './tests/files/restricted-paths/server/**/*', - except: ['**/a.js'], - } ], - } ], + options: [ + { + zones: [ + { + target: '**/*', + from: './tests/files/restricted-paths/server/**/*', + except: ['**/a.js'], + }, + ], + }, + ], + }), + + // support of arrays for from and target + // array with single element + 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: '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'], + }, + ], + }, + ], + }), + // array with multiple elements + test({ + code: 'import a from "../one/a.js"', + filename: testFilePath('./restricted-paths/server/two-new/a.js'), + options: [ + { + zones: [ + { + target: ['./tests/files/restricted-paths/server/two', './tests/files/restricted-paths/server/three'], + from: './tests/files/restricted-paths/server', + }, + ], + }, + ], + }), + test({ + code: 'import a from "../one/a.js"', + filename: testFilePath('./restricted-paths/server/two-new/a.js'), + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/server', + from: ['./tests/files/restricted-paths/server/two', './tests/files/restricted-paths/server/three'], + except: [], + }, + ], + }, + ], + }), + // array with multiple glob patterns in from + test({ + code: 'import a from "../client/a.js"', + filename: testFilePath('./restricted-paths/client/b.js'), + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/!(client)/**/*', + from: ['./tests/files/restricted-paths/client/*', './tests/files/restricted-paths/client/one/*'], + }, + ], + }, + ], + }), + // array with mix of glob and non glob patterns in target + test({ + code: 'import a from "../client/a.js"', + filename: testFilePath('./restricted-paths/client/b.js'), + options: [ + { + zones: [ + { + target: ['./tests/files/restricted-paths/!(client)/**/*', './tests/files/restricted-paths/client/a/'], + from: './tests/files/restricted-paths/client/**/*', + }, + ], + }, + ], }), // irrelevant function calls @@ -95,9 +234,17 @@ ruleTester.run('no-restricted-paths', rule, { test({ code: 'notrequire("../server/b.js")', filename: testFilePath('./restricted-paths/client/a.js'), - options: [ { - zones: [ { target: './tests/files/restricted-paths/client', from: './tests/files/restricted-paths/server' } ], - } ] }), + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/client', + from: './tests/files/restricted-paths/server', + }, + ], + }, + ], + }), // no config test({ code: 'require("../server/b.js")' }), @@ -105,42 +252,111 @@ ruleTester.run('no-restricted-paths', rule, { // builtin (ignore) test({ code: 'require("os")' }), - ], + ), - invalid: [ + invalid: [].concat( test({ - code: 'import b from "../server/b.js"', + code: 'import b from "../server/b.js"; // 1', 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, - } ], + 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 b from "../server/b.js"', + code: 'import b from "../server/b.js"; // 2', + 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, + }, + ], + }), + // TODO: fix test on windows + process.platform === 'win32' ? [] : test({ + code: 'import b from "../server/b.js";', + filename: testFilePath('./restricted-paths/client/a.js'), + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/client/*.js', + from: './tests/files/restricted-paths/server', + }, + ], + }, + ], + errors: [ + { + message: 'Unexpected path "../server/b.js" imported in restricted zone.', + line: 1, + column: 15, + }, + ], + }), + test({ + code: 'import b from "../server/b.js"; // 2 ter', 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, - } ], + 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' }, - ], - } ], + 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.', @@ -155,110 +371,344 @@ ruleTester.run('no-restricted-paths', rule, { ], }), test({ - code: 'import b from "../server/b.js"', + code: 'import b from "../server/b.js"; // 3', 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, - } ], + 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, - } ], + 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, + }, + ], }), 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, - } ], + 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: ['./one'], - message: 'Custom message', - } ], - } ], - errors: [ { - message: 'Unexpected path "../two/a.js" imported in restricted zone. Custom message', - line: 1, - column: 15, - } ], + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/server/one', + from: './tests/files/restricted-paths/server', + except: ['./one'], + message: 'Custom message', + }, + ], + }, + ], + errors: [ + { + message: 'Unexpected path "../two/a.js" imported in restricted zone. Custom message', + 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 ' + + 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, - } ], + line: 1, + column: 15, + }, + ], }), test({ code: 'import A from "../two/a.js"', filename: testFilePath('./restricted-paths/server/one/a.js'), - options: [ { - zones: [ { - target: '**/*', - from: './tests/files/restricted-paths/server/**/*', - } ], - } ], - errors: [ { - message: 'Unexpected path "../two/a.js" imported in restricted zone.', - line: 1, - column: 15, - } ], + options: [ + { + zones: [ + { + target: '**/*', + from: './tests/files/restricted-paths/server/**/*', + }, + ], + }, + ], + errors: [ + { + message: 'Unexpected path "../two/a.js" imported in restricted zone.', + line: 1, + column: 15, + }, + ], }), test({ code: 'import A from "../two/a.js"', filename: testFilePath('./restricted-paths/server/one/a.js'), - options: [ { - zones: [ { - target: '**/*', - from: './tests/files/restricted-paths/server/**/*', - except: ['a.js'], - } ], - } ], - errors: [ { - message: 'Restricted path exceptions must be glob patterns when`from` is a glob pattern', - line: 1, - column: 15, - } ], - }), - ], + options: [ + { + zones: [ + { + target: '**/*', + from: './tests/files/restricted-paths/server/**/*', + except: ['a.js'], + }, + ], + }, + ], + errors: [ + { + message: 'Restricted path exceptions must be glob patterns when `from` contains glob patterns', + line: 1, + column: 15, + }, + ], + }), + + // support of arrays for from and target + // array with single element + test({ + code: 'import b from "../server/b.js"; // 4', + 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 b from "../server/b.js"; // 5', + 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, + }, + ], + }), + // array with multiple elements + test({ + code: 'import b from "../server/b.js"; // 6', + filename: testFilePath('./restricted-paths/client/a.js'), + options: [ + { + zones: [ + { + target: ['./tests/files/restricted-paths/client/one', './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 b from "../server/one/b.js"\nimport a from "../server/two/a.js"', + filename: testFilePath('./restricted-paths/client/a.js'), + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/client', + from: ['./tests/files/restricted-paths/server/one', './tests/files/restricted-paths/server/two'], + }, + ], + }, + ], + errors: [ + { + message: 'Unexpected path "../server/one/b.js" imported in restricted zone.', + line: 1, + column: 15, + }, + { + message: 'Unexpected path "../server/two/a.js" imported in restricted zone.', + line: 2, + column: 15, + }, + ], + }), + // array with multiple glob patterns in from + test({ + code: 'import b from "../server/one/b.js"\nimport a from "../server/two/a.js"', + filename: testFilePath('./restricted-paths/client/a.js'), + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/client', + from: ['./tests/files/restricted-paths/server/one/*', './tests/files/restricted-paths/server/two/*'], + }, + ], + }, + ], + errors: [ + { + message: 'Unexpected path "../server/one/b.js" imported in restricted zone.', + line: 1, + column: 15, + }, + { + message: 'Unexpected path "../server/two/a.js" imported in restricted zone.', + line: 2, + column: 15, + }, + ], + }), + // array with mix of glob and non glob patterns in target + test({ + code: 'import b from "../server/b.js"; // 7', + filename: testFilePath('./restricted-paths/client/a.js'), + options: [ + { + zones: [ + { + target: ['./tests/files/restricted-paths/client/one', './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, + }, + ], + }), + // configuration format + test({ + code: 'import A from "../two/a.js"', + filename: testFilePath('./restricted-paths/server/one/a.js'), + options: [ + { + zones: [ + { + target: '**/*', + from: ['./tests/files/restricted-paths/server/**/*'], + except: ['a.js'], + }, + ], + }, + ], + errors: [ + { + message: 'Restricted path exceptions must be glob patterns when `from` contains glob patterns', + line: 1, + column: 15, + }, + ], + }), + test({ + code: 'import b from "../server/one/b.js"', + filename: testFilePath('./restricted-paths/client/a.js'), + options: [ + { + zones: [ + { + target: './tests/files/restricted-paths/client', + from: ['./tests/files/restricted-paths/server/one', './tests/files/restricted-paths/server/two/*'], + }, + ], + }, + ], + errors: [ + { + message: 'Restricted path `from` must contain either only glob patterns or none', + line: 1, + column: 15, + }, + ], + }), + ), });