-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathno-internal-modules.js
144 lines (123 loc) · 4.44 KB
/
no-internal-modules.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import minimatch from 'minimatch';
import resolve from 'eslint-module-utils/resolve';
import importType from '../core/importType';
import moduleVisitor from 'eslint-module-utils/moduleVisitor';
import docsUrl from '../docsUrl';
module.exports = {
meta: {
type: 'suggestion',
docs: {
category: 'Static analysis',
description: 'Forbid importing the submodules of other modules.',
url: docsUrl('no-internal-modules'),
},
schema: [
{
anyOf: [
{
type: 'object',
properties: {
allow: {
type: 'array',
items: {
type: 'string',
},
},
},
additionalProperties: false,
},
{
type: 'object',
properties: {
forbid: {
type: 'array',
items: {
type: 'string',
},
},
},
additionalProperties: false,
},
],
},
],
},
create: function noReachingInside(context) {
const options = context.options[0] || {};
const allowRegexps = (options.allow || []).map((p) => minimatch.makeRe(p));
const forbidRegexps = (options.forbid || []).map((p) => minimatch.makeRe(p));
// minimatch patterns are expected to use / path separators, like import
// statements, so normalize paths to use the same
function normalizeSep(somePath) {
return somePath.split('\\').join('/');
}
function toSteps(somePath) {
return normalizeSep(somePath)
.split('/')
.filter((step) => step && step !== '.')
.reduce((acc, step) => {
if (step === '..') {
return acc.slice(0, -1);
}
return acc.concat(step);
}, []);
}
// test if reaching to this destination is allowed
function reachingAllowed(importPath) {
return allowRegexps.some((re) => re.test(importPath));
}
// test if reaching to this destination is forbidden
function reachingForbidden(importPath) {
return forbidRegexps.some((re) => re.test(importPath));
}
function isAllowViolation(importPath) {
const steps = toSteps(importPath);
const nonScopeSteps = steps.filter((step) => step.indexOf('@') !== 0);
if (nonScopeSteps.length <= 1) { return false; }
// before trying to resolve, see if the raw import (with relative
// segments resolved) matches an allowed pattern
const justSteps = steps.join('/');
if (reachingAllowed(justSteps) || reachingAllowed(`/${justSteps}`)) { return false; }
// if the import statement doesn't match directly, try to match the
// resolved path if the import is resolvable
const resolved = resolve(importPath, context);
if (!resolved || reachingAllowed(normalizeSep(resolved))) { return false; }
// this import was not allowed by the allowed paths, and reaches
// so it is a violation
return true;
}
function isForbidViolation(importPath) {
const steps = toSteps(importPath);
// before trying to resolve, see if the raw import (with relative
// segments resolved) matches a forbidden pattern
const justSteps = steps.join('/');
if (reachingForbidden(justSteps) || reachingForbidden(`/${justSteps}`)) { return true; }
// if the import statement doesn't match directly, try to match the
// resolved path if the import is resolvable
const resolved = resolve(importPath, context);
if (resolved && reachingForbidden(normalizeSep(resolved))) { return true; }
// this import was not forbidden by the forbidden paths so it is not a violation
return false;
}
// find a directory that is being reached into, but which shouldn't be
const isReachViolation = options.forbid ? isForbidViolation : isAllowViolation;
function checkImportForReaching(importPath, node) {
const potentialViolationTypes = ['parent', 'index', 'sibling', 'external', 'internal'];
if (
potentialViolationTypes.indexOf(importType(importPath, context)) !== -1
&& isReachViolation(importPath)
) {
context.report({
node,
message: `Reaching to "${importPath}" is not allowed.`,
});
}
}
return moduleVisitor(
(source) => {
checkImportForReaching(source.value, source);
},
{ commonjs: true },
);
},
};