eslint-plugin-boundaries 3.1.1 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -1
- package/package.json +13 -10
- package/src/core/dependencyInfo.js +4 -4
- package/src/core/elementsInfo.js +11 -9
- package/src/helpers/messages.js +35 -10
- package/src/helpers/rules.js +42 -14
- package/src/helpers/utils.js +2 -1
- package/src/helpers/validations.js +17 -4
- package/src/index.js +5 -0
- package/src/rules/element-types.js +14 -6
- package/src/rules/entry-point.js +23 -5
- package/src/rules/external.js +91 -19
- package/src/rules/no-ignored.js +1 -1
- package/src/rules/no-private.js +2 -2
- package/src/rules/no-unknown.js +1 -1
- package/src/rules-factories/dependency-rule.js +1 -1
package/README.md
CHANGED
|
@@ -261,7 +261,7 @@ Some rules require extra configuration, and it has to be defined in each specifi
|
|
|
261
261
|
|
|
262
262
|
The docs of each rule contains an specification of their own options, but __the main rules share the format in which the options have to be defined__. The format described here is valid for options of [`element-types`](docs/rules/element-types.md), [`external`](docs/rules/external.md) and [`entry-point`](docs/rules/entry-point.md) rules.
|
|
263
263
|
|
|
264
|
-
Options set an `allow` or `disallow` value by default, and provide an array of rules. Each matching rule will override the default value and the value returned by previous matching rules. So, the final result of the options, once processed for each case, will be `allow` or `disallow`, and this value will be applied by the plugin rule in the
|
|
264
|
+
Options set an `allow` or `disallow` value by default, and provide an array of rules. Each matching rule will override the default value and the value returned by previous matching rules. So, the final result of the options, once processed for each case, will be `allow` or `disallow`, and this value will be applied by the plugin rule in the correspondent way, making it to produce an eslint error or not.
|
|
265
265
|
|
|
266
266
|
```jsonc
|
|
267
267
|
{
|
|
@@ -277,6 +277,8 @@ Options set an `allow` or `disallow` value by default, and provide an array of r
|
|
|
277
277
|
"from": ["helpers"],
|
|
278
278
|
// ...disallow importing this type of elements
|
|
279
279
|
"disallow": ["modules", "components"],
|
|
280
|
+
// ..for this kind of imports (applies only when using TypeScript)
|
|
281
|
+
"importKind": "value",
|
|
280
282
|
// ...and return this custom error message
|
|
281
283
|
"message": "Helpers must not import other thing than helpers"
|
|
282
284
|
},
|
|
@@ -300,6 +302,7 @@ Remember that:
|
|
|
300
302
|
|
|
301
303
|
* __`from/target`__: `<element matchers>` Depending of the rule to which the options are for, the rule will be applied only if the file being analyzed matches with this element matcher (`from`), or the dependency being imported matches with this element matcher (`target`).
|
|
302
304
|
* __`disallow/allow`__: `<value matchers>` If the plugin rule target matches with this, then the result of the rule will be "disallow/allow". Each rule will require a type of value here depending of what it is checking. In the case of the `element-types` rule, for example, another `<element matcher>` has to be provided in order to check the type of the local dependency.
|
|
305
|
+
* __`importKind`__: `<string>` _Optional_. It is useful only when using TypeScript, as it allows to define if the rule applies when the dependency is being imported as a value or as a type. It can be also defined as an array of strings, or a micromatch pattern. Note that possible values to match with are `"value"`, `"type"` or `"typeof"`. For example, you could define that "components" can import "helpers" as a value, but not as a type. So, `import { helper } from "helpers/helper-a"` would be allowed, but `import type { Helper } from "helpers/helper-a"` would be disallowed.
|
|
303
306
|
* __`message`__: `<string>` Optional. If the rule results in an error, the plugin will return this message instead of the default one. Read [error messages](#error-messages) for further info.
|
|
304
307
|
|
|
305
308
|
> Tip: Properties `from/target` and `disallow/allow` can receive a single matcher, or an array of matchers.
|
|
@@ -340,6 +343,7 @@ Available properties in error templates both from `file` or `dependency` are:
|
|
|
340
343
|
* `internalPath`: File path being analyzed or imported. Relative to the element's root path.
|
|
341
344
|
* `source`: Available only for `dependency`. The source of the `import` statement as it is in the code.
|
|
342
345
|
* `parent`: If the element is child of another element, it is also available in this property, which contains correspondent `type`, `internalPath` and captured properties as well.
|
|
346
|
+
* `importKind`: Available only for `dependency` when using TypeScript. It contains the kind of import being analyzed. Possible values are `"value"`, `"type"` or `"typeof"`.
|
|
343
347
|
* ...All captured properties are also available
|
|
344
348
|
|
|
345
349
|
> Tip: Read ["Global settings"](#global-settings) for further info about how to capture values from elements.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-boundaries",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Eslint plugin checking architecture boundaries between elements",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -32,21 +32,24 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"chalk": "4.1.2",
|
|
35
|
-
"eslint-import-resolver-node": "0.3.
|
|
36
|
-
"eslint-module-utils": "2.
|
|
37
|
-
"is-core-module": "2.
|
|
35
|
+
"eslint-import-resolver-node": "0.3.9",
|
|
36
|
+
"eslint-module-utils": "2.8.0",
|
|
37
|
+
"is-core-module": "2.13.0",
|
|
38
38
|
"micromatch": "4.0.5"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "6.4.0",
|
|
42
|
+
"@typescript-eslint/parser": "6.4.0",
|
|
41
43
|
"cross-env": "7.0.3",
|
|
42
|
-
"eslint": "8.
|
|
43
|
-
"eslint-config-prettier": "
|
|
44
|
-
"eslint-
|
|
44
|
+
"eslint": "8.47.0",
|
|
45
|
+
"eslint-config-prettier": "9.0.0",
|
|
46
|
+
"eslint-import-resolver-typescript": "3.6.0",
|
|
47
|
+
"eslint-plugin-prettier": "5.0.0",
|
|
45
48
|
"husky": "8.0.3",
|
|
46
49
|
"is-ci": "3.0.1",
|
|
47
|
-
"jest": "29.
|
|
48
|
-
"lint-staged": "
|
|
49
|
-
"prettier": "
|
|
50
|
+
"jest": "29.6.2",
|
|
51
|
+
"lint-staged": "14.0.0",
|
|
52
|
+
"prettier": "3.0.1"
|
|
50
53
|
},
|
|
51
54
|
"lint-staged": {
|
|
52
55
|
"test/**/*.js": "eslint",
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const { fileInfo, importInfo } = require("./elementsInfo");
|
|
2
2
|
|
|
3
3
|
function getParent(elementInfo) {
|
|
4
|
-
|
|
5
|
-
return parent && parent.elementPath;
|
|
4
|
+
return elementInfo.parents?.[0]?.elementPath;
|
|
6
5
|
}
|
|
7
6
|
|
|
8
7
|
function getCommonAncestor(elementInfoA, elementInfoB) {
|
|
@@ -11,7 +10,7 @@ function getCommonAncestor(elementInfoA, elementInfoB) {
|
|
|
11
10
|
return elementParentA.elementPath === elementParentB.elementPath;
|
|
12
11
|
});
|
|
13
12
|
});
|
|
14
|
-
return commonAncestor
|
|
13
|
+
return commonAncestor?.elementPath;
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
function isUncle(elementA, elementB) {
|
|
@@ -65,12 +64,13 @@ function dependencyRelationship(dependency, element) {
|
|
|
65
64
|
return null;
|
|
66
65
|
}
|
|
67
66
|
|
|
68
|
-
function dependencyInfo(source, context) {
|
|
67
|
+
function dependencyInfo(source, importKind, context) {
|
|
69
68
|
const elementInfo = fileInfo(context);
|
|
70
69
|
const dependency = importInfo(source, context);
|
|
71
70
|
|
|
72
71
|
return {
|
|
73
72
|
...dependency,
|
|
73
|
+
importKind: importKind || "value",
|
|
74
74
|
relationship: dependencyRelationship(dependency, elementInfo),
|
|
75
75
|
isInternal: isInternal(dependency, elementInfo),
|
|
76
76
|
};
|
package/src/core/elementsInfo.js
CHANGED
|
@@ -9,10 +9,7 @@ const { isArray } = require("../helpers/utils");
|
|
|
9
9
|
|
|
10
10
|
const { filesCache, importsCache, elementsCache } = require("./cache");
|
|
11
11
|
|
|
12
|
-
function baseModule(name
|
|
13
|
-
if (path) {
|
|
14
|
-
return null;
|
|
15
|
-
}
|
|
12
|
+
function baseModule(name) {
|
|
16
13
|
if (isScoped(name)) {
|
|
17
14
|
const [scope, packageName] = name.split("/");
|
|
18
15
|
return `${scope}/${packageName}`;
|
|
@@ -129,12 +126,12 @@ function elementTypeAndParents(path, settings) {
|
|
|
129
126
|
path
|
|
130
127
|
.split("/")
|
|
131
128
|
.slice(0, path.split("/").length - lastSegmentMatching)
|
|
132
|
-
.join("/")
|
|
129
|
+
.join("/"),
|
|
133
130
|
);
|
|
134
131
|
}
|
|
135
132
|
const capture = micromatch.capture(
|
|
136
133
|
pattern,
|
|
137
|
-
useFullPathMatch ? path : accumulator.join("/")
|
|
134
|
+
useFullPathMatch ? path : accumulator.join("/"),
|
|
138
135
|
);
|
|
139
136
|
|
|
140
137
|
if (capture && basePatternCapture) {
|
|
@@ -174,7 +171,7 @@ function elementTypeAndParents(path, settings) {
|
|
|
174
171
|
});
|
|
175
172
|
return { accumulator, lastSegmentMatching };
|
|
176
173
|
},
|
|
177
|
-
{ accumulator: [], lastSegmentMatching: 0 }
|
|
174
|
+
{ accumulator: [], lastSegmentMatching: 0 },
|
|
178
175
|
);
|
|
179
176
|
|
|
180
177
|
return {
|
|
@@ -193,6 +190,10 @@ function projectPath(absolutePath) {
|
|
|
193
190
|
}
|
|
194
191
|
}
|
|
195
192
|
|
|
193
|
+
function externalModulePath(source, baseModuleValue) {
|
|
194
|
+
return source.replace(baseModuleValue, "");
|
|
195
|
+
}
|
|
196
|
+
|
|
196
197
|
function importInfo(source, context) {
|
|
197
198
|
const path = projectPath(resolve(source, context));
|
|
198
199
|
const isExternalModule = isExternal(source, path);
|
|
@@ -205,8 +206,9 @@ function importInfo(source, context) {
|
|
|
205
206
|
result = resultCache;
|
|
206
207
|
} else {
|
|
207
208
|
elementCache = elementsCache.load(path, context.settings);
|
|
209
|
+
const baseModuleValue = isExternalModule ? baseModule(source) : null;
|
|
208
210
|
const isBuiltInModule = isBuiltIn(source, path);
|
|
209
|
-
const pathToUse = isExternalModule ?
|
|
211
|
+
const pathToUse = isExternalModule ? externalModulePath(source, baseModuleValue) : path;
|
|
210
212
|
if (elementCache) {
|
|
211
213
|
elementResult = elementCache;
|
|
212
214
|
} else {
|
|
@@ -221,7 +223,7 @@ function importInfo(source, context) {
|
|
|
221
223
|
isLocal: !isExternalModule && !isBuiltInModule,
|
|
222
224
|
isBuiltIn: isBuiltInModule,
|
|
223
225
|
isExternal: isExternalModule,
|
|
224
|
-
baseModule:
|
|
226
|
+
baseModule: baseModuleValue,
|
|
225
227
|
...elementResult,
|
|
226
228
|
};
|
|
227
229
|
|
package/src/helpers/messages.js
CHANGED
|
@@ -22,7 +22,7 @@ function propertiesConcater(properties, index) {
|
|
|
22
22
|
function micromatchPatternMessage(micromatchPatterns, elementCapturedValues) {
|
|
23
23
|
const micromatchPatternsWithValues = micromatchPatternReplacingObjectsValues(
|
|
24
24
|
micromatchPatterns,
|
|
25
|
-
{ from: elementCapturedValues }
|
|
25
|
+
{ from: elementCapturedValues },
|
|
26
26
|
);
|
|
27
27
|
if (isArray(micromatchPatternsWithValues)) {
|
|
28
28
|
if (micromatchPatternsWithValues.length === 1) {
|
|
@@ -60,7 +60,7 @@ function elementMatcherMessage(elementMatcher, elementCapturedValues) {
|
|
|
60
60
|
}
|
|
61
61
|
return `${typeMessage(elementMatcher[0])}${capturedValuesMatcherMessage(
|
|
62
62
|
elementMatcher[1],
|
|
63
|
-
elementCapturedValues
|
|
63
|
+
elementCapturedValues,
|
|
64
64
|
)}`;
|
|
65
65
|
}
|
|
66
66
|
|
|
@@ -85,6 +85,7 @@ function elementPropertiesToReplaceInTemplate(element) {
|
|
|
85
85
|
type: element.type,
|
|
86
86
|
internalPath: element.internalPath,
|
|
87
87
|
source: element.source,
|
|
88
|
+
importKind: element.importKind,
|
|
88
89
|
};
|
|
89
90
|
}
|
|
90
91
|
|
|
@@ -92,39 +93,39 @@ function customErrorMessage(message, file, dependency, report = {}) {
|
|
|
92
93
|
let replacedMessage = replaceObjectValuesInTemplates(
|
|
93
94
|
replaceObjectValuesInTemplates(message, elementPropertiesToReplaceInTemplate(file), "file"),
|
|
94
95
|
elementPropertiesToReplaceInTemplate(dependency),
|
|
95
|
-
"dependency"
|
|
96
|
+
"dependency",
|
|
96
97
|
);
|
|
97
98
|
replacedMessage = replaceObjectValuesInTemplates(
|
|
98
99
|
replaceObjectValuesInTemplates(
|
|
99
100
|
replacedMessage,
|
|
100
101
|
elementPropertiesToReplaceInTemplate(file),
|
|
101
|
-
"from"
|
|
102
|
+
"from",
|
|
102
103
|
),
|
|
103
104
|
elementPropertiesToReplaceInTemplate(dependency),
|
|
104
|
-
"target"
|
|
105
|
+
"target",
|
|
105
106
|
);
|
|
106
107
|
if (file.parents[0]) {
|
|
107
108
|
replacedMessage = replaceObjectValuesInTemplates(
|
|
108
109
|
replacedMessage,
|
|
109
110
|
elementPropertiesToReplaceInTemplate(file.parents[0]),
|
|
110
|
-
"file.parent"
|
|
111
|
+
"file.parent",
|
|
111
112
|
);
|
|
112
113
|
replacedMessage = replaceObjectValuesInTemplates(
|
|
113
114
|
replacedMessage,
|
|
114
115
|
elementPropertiesToReplaceInTemplate(file.parents[0]),
|
|
115
|
-
"from.parent"
|
|
116
|
+
"from.parent",
|
|
116
117
|
);
|
|
117
118
|
}
|
|
118
119
|
if (dependency.parents[0]) {
|
|
119
120
|
replacedMessage = replaceObjectValuesInTemplates(
|
|
120
121
|
replacedMessage,
|
|
121
122
|
elementPropertiesToReplaceInTemplate(dependency.parents[0]),
|
|
122
|
-
"dependency.parent"
|
|
123
|
+
"dependency.parent",
|
|
123
124
|
);
|
|
124
125
|
replacedMessage = replaceObjectValuesInTemplates(
|
|
125
126
|
replacedMessage,
|
|
126
127
|
elementPropertiesToReplaceInTemplate(dependency.parents[0]),
|
|
127
|
-
"target.parent"
|
|
128
|
+
"target.parent",
|
|
128
129
|
);
|
|
129
130
|
}
|
|
130
131
|
return replaceObjectValuesInTemplates(replacedMessage, report, "report");
|
|
@@ -148,13 +149,37 @@ function elementCapturedValuesMessage(capturedValues) {
|
|
|
148
149
|
|
|
149
150
|
function elementMessage(elementInfo) {
|
|
150
151
|
return `of type ${quote(elementInfo.type)}${elementCapturedValuesMessage(
|
|
151
|
-
elementInfo.capturedValues
|
|
152
|
+
elementInfo.capturedValues,
|
|
152
153
|
)}`;
|
|
153
154
|
}
|
|
154
155
|
|
|
156
|
+
function hasToPrintKindMessage(ruleImportKind, dependencyInfo) {
|
|
157
|
+
return ruleImportKind && dependencyInfo.importKind;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function dependencyImportKindMessage(ruleImportKind, dependencyInfo) {
|
|
161
|
+
if (hasToPrintKindMessage(ruleImportKind, dependencyInfo)) {
|
|
162
|
+
return `kind ${quote(dependencyInfo.importKind)} from `;
|
|
163
|
+
}
|
|
164
|
+
return "";
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function dependencyUsageKindMessage(
|
|
168
|
+
ruleImportKind,
|
|
169
|
+
dependencyInfo,
|
|
170
|
+
{ suffix = " ", prefix = "" } = {},
|
|
171
|
+
) {
|
|
172
|
+
if (hasToPrintKindMessage(ruleImportKind, dependencyInfo)) {
|
|
173
|
+
return `${prefix}${dependencyInfo.importKind}${suffix}`;
|
|
174
|
+
}
|
|
175
|
+
return "";
|
|
176
|
+
}
|
|
177
|
+
|
|
155
178
|
module.exports = {
|
|
156
179
|
quote,
|
|
157
180
|
ruleElementMessage,
|
|
158
181
|
customErrorMessage,
|
|
159
182
|
elementMessage,
|
|
183
|
+
dependencyImportKindMessage,
|
|
184
|
+
dependencyUsageKindMessage,
|
|
160
185
|
};
|
package/src/helpers/rules.js
CHANGED
|
@@ -62,9 +62,12 @@ function micromatchPatternReplacingObjectsValues(pattern, object) {
|
|
|
62
62
|
function isObjectMatch(objectWithMatchers, object, objectsWithValuesToReplace) {
|
|
63
63
|
return Object.keys(objectWithMatchers).reduce((isMatch, key) => {
|
|
64
64
|
if (isMatch) {
|
|
65
|
+
if (!object) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
65
68
|
const micromatchPattern = micromatchPatternReplacingObjectsValues(
|
|
66
69
|
objectWithMatchers[key],
|
|
67
|
-
objectsWithValuesToReplace
|
|
70
|
+
objectsWithValuesToReplace,
|
|
68
71
|
);
|
|
69
72
|
return micromatch.isMatch(object[key], micromatchPattern);
|
|
70
73
|
}
|
|
@@ -76,17 +79,23 @@ function rulesMainKey(key) {
|
|
|
76
79
|
return key || FROM;
|
|
77
80
|
}
|
|
78
81
|
|
|
79
|
-
function ruleMatch(ruleMatchers, targetElement, isMatch, fromElement) {
|
|
82
|
+
function ruleMatch(ruleMatchers, targetElement, isMatch, fromElement, importKind) {
|
|
80
83
|
let match = { result: false, report: null };
|
|
81
84
|
const matchers = !isArray(ruleMatchers) ? [ruleMatchers] : ruleMatchers;
|
|
82
85
|
matchers.forEach((matcher) => {
|
|
83
86
|
if (!match.result) {
|
|
84
87
|
if (isArray(matcher)) {
|
|
85
88
|
const [value, captures] = matcher;
|
|
86
|
-
match = isMatch(
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
match = isMatch(
|
|
90
|
+
targetElement,
|
|
91
|
+
value,
|
|
92
|
+
captures,
|
|
93
|
+
{
|
|
94
|
+
from: fromElement.capturedValues,
|
|
95
|
+
target: targetElement.capturedValues,
|
|
96
|
+
},
|
|
97
|
+
importKind,
|
|
98
|
+
);
|
|
90
99
|
} else {
|
|
91
100
|
match = isMatch(
|
|
92
101
|
targetElement,
|
|
@@ -95,7 +104,8 @@ function ruleMatch(ruleMatchers, targetElement, isMatch, fromElement) {
|
|
|
95
104
|
{
|
|
96
105
|
from: fromElement.capturedValues,
|
|
97
106
|
target: targetElement.capturedValues,
|
|
98
|
-
}
|
|
107
|
+
},
|
|
108
|
+
importKind,
|
|
99
109
|
);
|
|
100
110
|
}
|
|
101
111
|
}
|
|
@@ -108,11 +118,11 @@ function isMatchElementKey(
|
|
|
108
118
|
matcher,
|
|
109
119
|
options,
|
|
110
120
|
elementKey,
|
|
111
|
-
elementsToCompareCapturedValues
|
|
121
|
+
elementsToCompareCapturedValues,
|
|
112
122
|
) {
|
|
113
123
|
const isMatch = micromatch.isMatch(
|
|
114
124
|
elementInfo[elementKey],
|
|
115
|
-
micromatchPatternReplacingObjectsValues(matcher, elementsToCompareCapturedValues)
|
|
125
|
+
micromatchPatternReplacingObjectsValues(matcher, elementsToCompareCapturedValues),
|
|
116
126
|
);
|
|
117
127
|
if (isMatch && options) {
|
|
118
128
|
return {
|
|
@@ -124,7 +134,23 @@ function isMatchElementKey(
|
|
|
124
134
|
};
|
|
125
135
|
}
|
|
126
136
|
|
|
127
|
-
function
|
|
137
|
+
function isMatchImportKind(elementInfo, importKind) {
|
|
138
|
+
if (!elementInfo.importKind || !importKind) {
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
return micromatch.isMatch(elementInfo.importKind, importKind);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function isMatchElementType(
|
|
145
|
+
elementInfo,
|
|
146
|
+
matcher,
|
|
147
|
+
options,
|
|
148
|
+
elementsToCompareCapturedValues,
|
|
149
|
+
importKind,
|
|
150
|
+
) {
|
|
151
|
+
if (!isMatchImportKind(elementInfo, importKind)) {
|
|
152
|
+
return { result: false };
|
|
153
|
+
}
|
|
128
154
|
return isMatchElementKey(elementInfo, matcher, options, "type", elementsToCompareCapturedValues);
|
|
129
155
|
}
|
|
130
156
|
|
|
@@ -166,11 +192,11 @@ function elementRulesAllowDependency({
|
|
|
166
192
|
const [result, report, ruleReport] = getElementRules(
|
|
167
193
|
elementToGetRulesFrom(element, dependency, mainKey),
|
|
168
194
|
options,
|
|
169
|
-
mainKey
|
|
195
|
+
mainKey,
|
|
170
196
|
).reduce(
|
|
171
197
|
(allowed, rule) => {
|
|
172
198
|
if (rule.disallow) {
|
|
173
|
-
const match = ruleMatch(rule.disallow, dependency, isMatch, element);
|
|
199
|
+
const match = ruleMatch(rule.disallow, dependency, isMatch, element, rule.importKind);
|
|
174
200
|
if (match.result) {
|
|
175
201
|
return [
|
|
176
202
|
false,
|
|
@@ -180,12 +206,13 @@ function elementRulesAllowDependency({
|
|
|
180
206
|
disallow: rule.disallow,
|
|
181
207
|
index: rule.index,
|
|
182
208
|
message: rule.message || options.message,
|
|
209
|
+
importKind: rule.importKind,
|
|
183
210
|
},
|
|
184
211
|
];
|
|
185
212
|
}
|
|
186
213
|
}
|
|
187
214
|
if (rule.allow) {
|
|
188
|
-
const match = ruleMatch(rule.allow, dependency, isMatch, element);
|
|
215
|
+
const match = ruleMatch(rule.allow, dependency, isMatch, element, rule.importKind);
|
|
189
216
|
if (match.result) {
|
|
190
217
|
return [true, match.report];
|
|
191
218
|
}
|
|
@@ -199,7 +226,7 @@ function elementRulesAllowDependency({
|
|
|
199
226
|
isDefault: true,
|
|
200
227
|
message: options.message,
|
|
201
228
|
},
|
|
202
|
-
]
|
|
229
|
+
],
|
|
203
230
|
);
|
|
204
231
|
return {
|
|
205
232
|
result,
|
|
@@ -218,4 +245,5 @@ module.exports = {
|
|
|
218
245
|
getElementRules,
|
|
219
246
|
rulesMainKey,
|
|
220
247
|
micromatchPatternReplacingObjectsValues,
|
|
248
|
+
isMatchImportKind,
|
|
221
249
|
};
|
package/src/helpers/utils.js
CHANGED
|
@@ -8,7 +8,8 @@ function isArray(object) {
|
|
|
8
8
|
|
|
9
9
|
function replaceObjectValueInTemplate(template, key, value, namespace) {
|
|
10
10
|
const keyToReplace = namespace ? `${namespace}.${key}` : key;
|
|
11
|
-
|
|
11
|
+
const regexp = new RegExp(`\\$\\{${keyToReplace}\\}`, "g");
|
|
12
|
+
return template.replace(regexp, value);
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
function replaceObjectValuesInTemplates(strings, object, namespace) {
|
|
@@ -63,6 +63,19 @@ function rulesOptionsSchema(options = {}) {
|
|
|
63
63
|
[mainKey]: elementsMatcherSchema(),
|
|
64
64
|
allow: elementsMatcherSchema(options.targetMatcherOptions),
|
|
65
65
|
disallow: elementsMatcherSchema(options.targetMatcherOptions),
|
|
66
|
+
importKind: {
|
|
67
|
+
oneOf: [
|
|
68
|
+
{
|
|
69
|
+
type: "string",
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
type: "array",
|
|
73
|
+
items: {
|
|
74
|
+
type: "string",
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
66
79
|
message: {
|
|
67
80
|
type: "string",
|
|
68
81
|
},
|
|
@@ -109,7 +122,7 @@ function validateElements(elements) {
|
|
|
109
122
|
// TODO, remove in next major version
|
|
110
123
|
if (isLegacyType(element)) {
|
|
111
124
|
warnOnce(
|
|
112
|
-
`Defining elements as strings in settings is deprecated. Will be automatically converted, but this feature will be removed in next major versions
|
|
125
|
+
`Defining elements as strings in settings is deprecated. Will be automatically converted, but this feature will be removed in next major versions`,
|
|
113
126
|
);
|
|
114
127
|
} else {
|
|
115
128
|
Object.keys(element).forEach(() => {
|
|
@@ -119,8 +132,8 @@ function validateElements(elements) {
|
|
|
119
132
|
if (element.mode && !VALID_MODES.includes(element.mode)) {
|
|
120
133
|
warnOnce(
|
|
121
134
|
`Invalid mode property in '${ELEMENTS}' setting. Should be one of ${VALID_MODES.join(
|
|
122
|
-
","
|
|
123
|
-
)}. Default value "${VALID_MODES[0]}" will be used instead
|
|
135
|
+
",",
|
|
136
|
+
)}. Default value "${VALID_MODES[0]}" will be used instead`,
|
|
124
137
|
);
|
|
125
138
|
}
|
|
126
139
|
if (!element.pattern || !(isString(element.pattern) || isArray(element.pattern))) {
|
|
@@ -137,7 +150,7 @@ function validateElements(elements) {
|
|
|
137
150
|
function deprecateAlias(aliases) {
|
|
138
151
|
if (aliases) {
|
|
139
152
|
warnOnce(
|
|
140
|
-
`Defining aliases in '${ALIAS}' setting is deprecated. Please use 'import/resolver' setting
|
|
153
|
+
`Defining aliases in '${ALIAS}' setting is deprecated. Please use 'import/resolver' setting`,
|
|
141
154
|
);
|
|
142
155
|
}
|
|
143
156
|
}
|
package/src/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const packageJson = require("../package.json");
|
|
1
2
|
const rules = require("./constants/rules");
|
|
2
3
|
const recommendedConfig = require("./configs/recommended");
|
|
3
4
|
const strictConfig = require("./configs/strict");
|
|
@@ -17,6 +18,10 @@ const importRules = (ruleNames) => {
|
|
|
17
18
|
// export all configs
|
|
18
19
|
|
|
19
20
|
module.exports = {
|
|
21
|
+
meta: {
|
|
22
|
+
name: packageJson.name,
|
|
23
|
+
version: packageJson.version,
|
|
24
|
+
},
|
|
20
25
|
rules: importRules(rules),
|
|
21
26
|
configs: {
|
|
22
27
|
recommended: recommendedConfig,
|
|
@@ -8,7 +8,12 @@ const {
|
|
|
8
8
|
isMatchElementType,
|
|
9
9
|
elementRulesAllowDependency,
|
|
10
10
|
} = require("../helpers/rules");
|
|
11
|
-
const {
|
|
11
|
+
const {
|
|
12
|
+
customErrorMessage,
|
|
13
|
+
ruleElementMessage,
|
|
14
|
+
elementMessage,
|
|
15
|
+
dependencyImportKindMessage,
|
|
16
|
+
} = require("../helpers/messages");
|
|
12
17
|
|
|
13
18
|
function elementRulesAllowDependencyType(element, dependency, options) {
|
|
14
19
|
return elementRulesAllowDependency({
|
|
@@ -26,15 +31,18 @@ function errorMessage(ruleData, file, dependency) {
|
|
|
26
31
|
}
|
|
27
32
|
if (ruleReport.isDefault) {
|
|
28
33
|
return `No rule allowing this dependency was found. File is ${elementMessage(
|
|
29
|
-
file
|
|
34
|
+
file,
|
|
30
35
|
)}. Dependency is ${elementMessage(dependency)}`;
|
|
31
36
|
}
|
|
32
|
-
return `Importing ${
|
|
37
|
+
return `Importing ${dependencyImportKindMessage(
|
|
38
|
+
ruleReport.importKind,
|
|
39
|
+
dependency,
|
|
40
|
+
)}${ruleElementMessage(
|
|
33
41
|
ruleReport.disallow,
|
|
34
|
-
file.capturedValues
|
|
42
|
+
file.capturedValues,
|
|
35
43
|
)} is not allowed in ${ruleElementMessage(
|
|
36
44
|
ruleReport.element,
|
|
37
|
-
file.capturedValues
|
|
45
|
+
file.capturedValues,
|
|
38
46
|
)}. Disallowed in rule ${ruleReport.index + 1}`;
|
|
39
47
|
}
|
|
40
48
|
|
|
@@ -55,5 +63,5 @@ module.exports = dependencyRule(
|
|
|
55
63
|
});
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
|
-
}
|
|
66
|
+
},
|
|
59
67
|
);
|
package/src/rules/entry-point.js
CHANGED
|
@@ -7,10 +7,25 @@ const {
|
|
|
7
7
|
dependencyLocation,
|
|
8
8
|
isMatchElementKey,
|
|
9
9
|
elementRulesAllowDependency,
|
|
10
|
+
isMatchImportKind,
|
|
10
11
|
} = require("../helpers/rules");
|
|
11
|
-
const {
|
|
12
|
+
const {
|
|
13
|
+
customErrorMessage,
|
|
14
|
+
ruleElementMessage,
|
|
15
|
+
elementMessage,
|
|
16
|
+
dependencyUsageKindMessage,
|
|
17
|
+
} = require("../helpers/messages");
|
|
12
18
|
|
|
13
|
-
function isMatchElementInternalPath(
|
|
19
|
+
function isMatchElementInternalPath(
|
|
20
|
+
elementInfo,
|
|
21
|
+
matcher,
|
|
22
|
+
options,
|
|
23
|
+
elementsCapturedValues,
|
|
24
|
+
importKind,
|
|
25
|
+
) {
|
|
26
|
+
if (!isMatchImportKind(elementInfo, importKind)) {
|
|
27
|
+
return { result: false };
|
|
28
|
+
}
|
|
14
29
|
return isMatchElementKey(elementInfo, matcher, options, "internalPath", elementsCapturedValues);
|
|
15
30
|
}
|
|
16
31
|
|
|
@@ -36,8 +51,11 @@ function errorMessage(ruleData, file, dependency) {
|
|
|
36
51
|
}
|
|
37
52
|
return `The entry point '${dependency.internalPath}' is not allowed in ${ruleElementMessage(
|
|
38
53
|
ruleReport.element,
|
|
39
|
-
dependency.capturedValues
|
|
40
|
-
)}
|
|
54
|
+
dependency.capturedValues,
|
|
55
|
+
)}${dependencyUsageKindMessage(ruleReport.importKind, dependency, {
|
|
56
|
+
prefix: " when importing ",
|
|
57
|
+
suffix: "",
|
|
58
|
+
})}. Disallowed in rule ${ruleReport.index + 1}`;
|
|
41
59
|
}
|
|
42
60
|
|
|
43
61
|
module.exports = dependencyRule(
|
|
@@ -62,5 +80,5 @@ module.exports = dependencyRule(
|
|
|
62
80
|
},
|
|
63
81
|
{
|
|
64
82
|
validateRules: { onlyMainKey: true, mainKey: "target" },
|
|
65
|
-
}
|
|
83
|
+
},
|
|
66
84
|
);
|
package/src/rules/external.js
CHANGED
|
@@ -9,19 +9,26 @@ const {
|
|
|
9
9
|
dependencyLocation,
|
|
10
10
|
elementRulesAllowDependency,
|
|
11
11
|
micromatchPatternReplacingObjectsValues,
|
|
12
|
+
isMatchImportKind,
|
|
12
13
|
} = require("../helpers/rules");
|
|
13
|
-
const {
|
|
14
|
+
const {
|
|
15
|
+
customErrorMessage,
|
|
16
|
+
ruleElementMessage,
|
|
17
|
+
elementMessage,
|
|
18
|
+
dependencyUsageKindMessage,
|
|
19
|
+
} = require("../helpers/messages");
|
|
20
|
+
const { isArray } = require("../helpers/utils");
|
|
14
21
|
|
|
15
|
-
function specifiersMatch(specifiers,
|
|
22
|
+
function specifiersMatch(specifiers, specifierOptions, elementsCapturedValues) {
|
|
16
23
|
const importedSpecifiersNames = specifiers
|
|
17
24
|
.filter((specifier) => {
|
|
18
25
|
return specifier.type === "ImportSpecifier" && specifier.imported.name;
|
|
19
26
|
})
|
|
20
27
|
.map((specifier) => specifier.imported.name);
|
|
21
|
-
return
|
|
28
|
+
return specifierOptions.reduce((found, option) => {
|
|
22
29
|
const matcherWithTemplateReplaced = micromatchPatternReplacingObjectsValues(
|
|
23
30
|
option,
|
|
24
|
-
elementsCapturedValues
|
|
31
|
+
elementsCapturedValues,
|
|
25
32
|
);
|
|
26
33
|
if (micromatch.some(importedSpecifiersNames, matcherWithTemplateReplaced)) {
|
|
27
34
|
found.push(option);
|
|
@@ -30,21 +37,60 @@ function specifiersMatch(specifiers, options, elementsCapturedValues) {
|
|
|
30
37
|
}, []);
|
|
31
38
|
}
|
|
32
39
|
|
|
33
|
-
function
|
|
40
|
+
function pathMatch(path, pathOptions, elementsCapturedValues) {
|
|
41
|
+
const pathMatchers = isArray(pathOptions) ? pathOptions : [pathOptions];
|
|
42
|
+
return pathMatchers.reduce((isMatch, option) => {
|
|
43
|
+
if (isMatch) {
|
|
44
|
+
return isMatch;
|
|
45
|
+
}
|
|
46
|
+
const matcherWithTemplateReplaced = micromatchPatternReplacingObjectsValues(
|
|
47
|
+
option,
|
|
48
|
+
elementsCapturedValues,
|
|
49
|
+
);
|
|
50
|
+
if (micromatch.some(path, matcherWithTemplateReplaced)) {
|
|
51
|
+
isMatch = true;
|
|
52
|
+
}
|
|
53
|
+
return isMatch;
|
|
54
|
+
}, false);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isMatchExternalDependency(
|
|
58
|
+
dependency,
|
|
59
|
+
matcher,
|
|
60
|
+
options,
|
|
61
|
+
elementsCapturedValues,
|
|
62
|
+
importKind,
|
|
63
|
+
) {
|
|
34
64
|
const matcherWithTemplatesReplaced = micromatchPatternReplacingObjectsValues(
|
|
35
65
|
matcher,
|
|
36
|
-
elementsCapturedValues
|
|
66
|
+
elementsCapturedValues,
|
|
37
67
|
);
|
|
68
|
+
if (!isMatchImportKind(dependency, importKind)) {
|
|
69
|
+
return { result: false };
|
|
70
|
+
}
|
|
38
71
|
const isMatch = micromatch.isMatch(dependency.baseModule, matcherWithTemplatesReplaced);
|
|
39
72
|
if (isMatch && options && Object.keys(options).length) {
|
|
40
|
-
const
|
|
41
|
-
dependency.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
73
|
+
const isPathMatch = options.path
|
|
74
|
+
? pathMatch(dependency.path, options.path, elementsCapturedValues)
|
|
75
|
+
: true;
|
|
76
|
+
if (isPathMatch && options.specifiers) {
|
|
77
|
+
const specifiersResult = specifiersMatch(
|
|
78
|
+
dependency.specifiers,
|
|
79
|
+
options.specifiers,
|
|
80
|
+
elementsCapturedValues,
|
|
81
|
+
);
|
|
82
|
+
return {
|
|
83
|
+
result: specifiersResult.length > 0,
|
|
84
|
+
report: {
|
|
85
|
+
specifiers: specifiersResult,
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
45
89
|
return {
|
|
46
|
-
result:
|
|
47
|
-
report:
|
|
90
|
+
result: isPathMatch,
|
|
91
|
+
report: {
|
|
92
|
+
path: dependency.path,
|
|
93
|
+
},
|
|
48
94
|
};
|
|
49
95
|
}
|
|
50
96
|
return {
|
|
@@ -61,11 +107,19 @@ function elementRulesAllowExternalDependency(element, dependency, options) {
|
|
|
61
107
|
});
|
|
62
108
|
}
|
|
63
109
|
|
|
110
|
+
function getErrorReportMessage(report) {
|
|
111
|
+
if (report.path) {
|
|
112
|
+
return report.path;
|
|
113
|
+
}
|
|
114
|
+
return report.specifiers.join(", ");
|
|
115
|
+
}
|
|
116
|
+
|
|
64
117
|
function errorMessage(ruleData, file, dependency) {
|
|
65
118
|
const ruleReport = ruleData.ruleReport;
|
|
66
119
|
if (ruleReport.message) {
|
|
67
120
|
return customErrorMessage(ruleReport.message, file, dependency, {
|
|
68
|
-
specifiers: ruleData.report
|
|
121
|
+
specifiers: ruleData.report?.specifiers?.join(", "),
|
|
122
|
+
path: ruleData.report?.path,
|
|
69
123
|
});
|
|
70
124
|
}
|
|
71
125
|
if (ruleReport.isDefault) {
|
|
@@ -76,15 +130,20 @@ function errorMessage(ruleData, file, dependency) {
|
|
|
76
130
|
|
|
77
131
|
const fileReport = `is not allowed in ${ruleElementMessage(
|
|
78
132
|
ruleReport.element,
|
|
79
|
-
file.capturedValues
|
|
133
|
+
file.capturedValues,
|
|
80
134
|
)}. Disallowed in rule ${ruleReport.index + 1}`;
|
|
81
135
|
|
|
82
136
|
if (ruleData.report) {
|
|
83
|
-
return `Usage of
|
|
137
|
+
return `Usage of ${dependencyUsageKindMessage(
|
|
138
|
+
ruleReport.importKind,
|
|
139
|
+
dependency,
|
|
140
|
+
)}'${getErrorReportMessage(ruleData.report)}' from external module '${
|
|
84
141
|
dependency.baseModule
|
|
85
142
|
}' ${fileReport}`;
|
|
86
143
|
}
|
|
87
|
-
return `Usage of
|
|
144
|
+
return `Usage of ${dependencyUsageKindMessage(ruleReport.importKind, dependency, {
|
|
145
|
+
suffix: " from ",
|
|
146
|
+
})}external module '${dependency.baseModule}' ${fileReport}`;
|
|
88
147
|
}
|
|
89
148
|
|
|
90
149
|
module.exports = dependencyRule(
|
|
@@ -101,6 +160,19 @@ module.exports = dependencyRule(
|
|
|
101
160
|
type: "string",
|
|
102
161
|
},
|
|
103
162
|
},
|
|
163
|
+
path: {
|
|
164
|
+
oneOf: [
|
|
165
|
+
{
|
|
166
|
+
type: "string",
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
type: "array",
|
|
170
|
+
items: {
|
|
171
|
+
type: "string",
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
},
|
|
104
176
|
},
|
|
105
177
|
additionalProperties: false,
|
|
106
178
|
},
|
|
@@ -111,7 +183,7 @@ module.exports = dependencyRule(
|
|
|
111
183
|
const ruleData = elementRulesAllowExternalDependency(
|
|
112
184
|
file,
|
|
113
185
|
{ ...dependency, specifiers: node.source.parent.specifiers },
|
|
114
|
-
options
|
|
186
|
+
options,
|
|
115
187
|
);
|
|
116
188
|
if (!ruleData.result) {
|
|
117
189
|
context.report({
|
|
@@ -124,5 +196,5 @@ module.exports = dependencyRule(
|
|
|
124
196
|
},
|
|
125
197
|
{
|
|
126
198
|
validateRules: { onlyMainKey: true },
|
|
127
|
-
}
|
|
199
|
+
},
|
|
128
200
|
);
|
package/src/rules/no-ignored.js
CHANGED
package/src/rules/no-private.js
CHANGED
|
@@ -40,7 +40,7 @@ module.exports = dependencyRule(
|
|
|
40
40
|
dependency.relationship !== "internal" &&
|
|
41
41
|
dependency.relationship !== "child" &&
|
|
42
42
|
dependency.relationship !== "brother" &&
|
|
43
|
-
(!options
|
|
43
|
+
(!options?.allowUncles || dependency.relationship !== "uncle")
|
|
44
44
|
) {
|
|
45
45
|
context.report({
|
|
46
46
|
message: errorMessage(file, dependency, options),
|
|
@@ -51,5 +51,5 @@ module.exports = dependencyRule(
|
|
|
51
51
|
},
|
|
52
52
|
{
|
|
53
53
|
validate: false,
|
|
54
|
-
}
|
|
54
|
+
},
|
|
55
55
|
);
|
package/src/rules/no-unknown.js
CHANGED
|
@@ -21,7 +21,7 @@ module.exports = function (ruleMeta, rule, ruleOptions = {}) {
|
|
|
21
21
|
|
|
22
22
|
return {
|
|
23
23
|
ImportDeclaration: (node) => {
|
|
24
|
-
const dependency = dependencyInfo(node.source.value, context);
|
|
24
|
+
const dependency = dependencyInfo(node.source.value, node.importKind, context);
|
|
25
25
|
|
|
26
26
|
rule({ file, dependency, options, node, context });
|
|
27
27
|
},
|