Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epidemian
GitHub Repository: epidemian/eslint-plugin-import
Path: blob/main/src/rules/no-restricted-paths.js
829 views
1
import path from 'path';
2
3
import resolve from 'eslint-module-utils/resolve';
4
import moduleVisitor from 'eslint-module-utils/moduleVisitor';
5
import isGlob from 'is-glob';
6
import { Minimatch, default as minimatch } from 'minimatch';
7
import docsUrl from '../docsUrl';
8
import importType from '../core/importType';
9
10
const containsPath = (filepath, target) => {
11
const relative = path.relative(target, filepath);
12
return relative === '' || !relative.startsWith('..');
13
};
14
15
module.exports = {
16
meta: {
17
type: 'problem',
18
docs: {
19
url: docsUrl('no-restricted-paths'),
20
},
21
22
schema: [
23
{
24
type: 'object',
25
properties: {
26
zones: {
27
type: 'array',
28
minItems: 1,
29
items: {
30
type: 'object',
31
properties: {
32
target: { type: 'string' },
33
from: { type: 'string' },
34
except: {
35
type: 'array',
36
items: {
37
type: 'string',
38
},
39
uniqueItems: true,
40
},
41
message: { type: 'string' },
42
},
43
additionalProperties: false,
44
},
45
},
46
basePath: { type: 'string' },
47
},
48
additionalProperties: false,
49
},
50
],
51
},
52
53
create: function noRestrictedPaths(context) {
54
const options = context.options[0] || {};
55
const restrictedPaths = options.zones || [];
56
const basePath = options.basePath || process.cwd();
57
const currentFilename = context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename();
58
const matchingZones = restrictedPaths.filter((zone) => {
59
const targetPath = path.resolve(basePath, zone.target);
60
61
if (isGlob(targetPath)) {
62
return minimatch(currentFilename, targetPath);
63
}
64
65
return containsPath(currentFilename, targetPath);
66
});
67
68
function isValidExceptionPath(absoluteFromPath, absoluteExceptionPath) {
69
const relativeExceptionPath = path.relative(absoluteFromPath, absoluteExceptionPath);
70
71
return importType(relativeExceptionPath, context) !== 'parent';
72
}
73
74
function reportInvalidExceptionPath(node) {
75
context.report({
76
node,
77
message: 'Restricted path exceptions must be descendants of the configured `from` path for that zone.',
78
});
79
}
80
81
function reportInvalidExceptionGlob(node) {
82
context.report({
83
node,
84
message: 'Restricted path exceptions must be glob patterns when`from` is a glob pattern',
85
});
86
}
87
88
const makePathValidator = (zoneFrom, zoneExcept = []) => {
89
const absoluteFrom = path.resolve(basePath, zoneFrom);
90
const isGlobPattern = isGlob(zoneFrom);
91
let isPathRestricted;
92
let hasValidExceptions;
93
let isPathException;
94
let reportInvalidException;
95
96
if (isGlobPattern) {
97
const mm = new Minimatch(absoluteFrom);
98
isPathRestricted = (absoluteImportPath) => mm.match(absoluteImportPath);
99
100
hasValidExceptions = zoneExcept.every(isGlob);
101
102
if (hasValidExceptions) {
103
const exceptionsMm = zoneExcept.map((except) => new Minimatch(except));
104
isPathException = (absoluteImportPath) => exceptionsMm.some((mm) => mm.match(absoluteImportPath));
105
}
106
107
reportInvalidException = reportInvalidExceptionGlob;
108
} else {
109
isPathRestricted = (absoluteImportPath) => containsPath(absoluteImportPath, absoluteFrom);
110
111
const absoluteExceptionPaths = zoneExcept
112
.map((exceptionPath) => path.resolve(absoluteFrom, exceptionPath));
113
hasValidExceptions = absoluteExceptionPaths
114
.every((absoluteExceptionPath) => isValidExceptionPath(absoluteFrom, absoluteExceptionPath));
115
116
if (hasValidExceptions) {
117
isPathException = (absoluteImportPath) => absoluteExceptionPaths.some(
118
(absoluteExceptionPath) => containsPath(absoluteImportPath, absoluteExceptionPath),
119
);
120
}
121
122
reportInvalidException = reportInvalidExceptionPath;
123
}
124
125
return {
126
isPathRestricted,
127
hasValidExceptions,
128
isPathException,
129
reportInvalidException,
130
};
131
};
132
133
const validators = [];
134
135
function checkForRestrictedImportPath(importPath, node) {
136
const absoluteImportPath = resolve(importPath, context);
137
138
if (!absoluteImportPath) {
139
return;
140
}
141
142
matchingZones.forEach((zone, index) => {
143
if (!validators[index]) {
144
validators[index] = makePathValidator(zone.from, zone.except);
145
}
146
147
const {
148
isPathRestricted,
149
hasValidExceptions,
150
isPathException,
151
reportInvalidException,
152
} = validators[index];
153
154
if (!isPathRestricted(absoluteImportPath)) {
155
return;
156
}
157
158
if (!hasValidExceptions) {
159
reportInvalidException(node);
160
return;
161
}
162
163
const pathIsExcepted = isPathException(absoluteImportPath);
164
if (pathIsExcepted) {
165
return;
166
}
167
168
context.report({
169
node,
170
message: `Unexpected path "{{importPath}}" imported in restricted zone.${zone.message ? ` ${zone.message}` : ''}`,
171
data: { importPath },
172
});
173
});
174
}
175
176
return moduleVisitor((source) => {
177
checkForRestrictedImportPath(source.value, source);
178
}, { commonjs: true });
179
},
180
};
181
182