eslint-plugin-boundaries 2.10.2 → 3.1.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 +12 -5
- package/package.json +8 -8
- package/src/core/elementsInfo.js +2 -2
- package/src/helpers/messages.js +22 -3
- package/src/helpers/rules.js +61 -23
- package/src/rules/entry-point.js +4 -4
- package/src/rules/external.js +23 -7
package/README.md
CHANGED
|
@@ -308,10 +308,17 @@ Remember that:
|
|
|
308
308
|
|
|
309
309
|
Elements matchers used in the rules options can have the next formats:
|
|
310
310
|
|
|
311
|
-
* __`<string>`__: Will return `true` when the element type matches with this [`micromatch` pattern](https://github.com/micromatch/micromatch).
|
|
312
|
-
* __`[<string>, <capturedValuesObject>]`__: Will return `true` whe when the element type matches with the first element in the array, and all of the captured values also match. <br/>The `<capturedValuesObject>` has to be an object containing `capture` keys from the [`boundaries/element-types` setting](#boundarieselement-types) of the element as keys, and [`micromatch` patterns](https://github.com/micromatch/micromatch) as values
|
|
311
|
+
* __`<string>`__: Will return `true` when the element type matches with this [`micromatch` pattern](https://github.com/micromatch/micromatch). It [supports templating](#templating) for using values from captured values.
|
|
312
|
+
* __`[<string>, <capturedValuesObject>]`__: Will return `true` whe when the element type matches with the first element in the array, and all of the captured values also match. <br/>The `<capturedValuesObject>` has to be an object containing `capture` keys from the [`boundaries/element-types` setting](#boundarieselement-types) of the element as keys, and [`micromatch` patterns](https://github.com/micromatch/micromatch) as values. (values also support [templating](#templating)) <br/>For example, for an element of type "helpers" with settings as `{ type: "helpers", pattern": "helpers/*/*.js", "capture": ["category", "elementName"]}`, you could write element matchers as:
|
|
313
313
|
* `["helpers", { category: "data", elementName: "parsers"}]`: Will only match with helpers with category "data" and elementName "parsers" (`helpers/data/parsers.js`).
|
|
314
314
|
* `["helpers", { category: "data" }]`: Will match with all helpers with category "data" (`helpers/data/*.js`)
|
|
315
|
+
* `["data-${from.elementName}", { category: "${from.category}" }]`: Will only match with helpers with the type equals to the `elementName` of the file importing plus a `data-` prefix, and the category being equal to the `category` of the file importing the dependency.
|
|
316
|
+
|
|
317
|
+
##### Templating
|
|
318
|
+
|
|
319
|
+
When defining [__Element matchers__](#elements-matchers), the values captured both from the element importing ("from") and from the imported element ("target") are available to be replaced. They are replaced both in the main string and in the `<capturedValuesObject>`.
|
|
320
|
+
|
|
321
|
+
Templates must be defined with the format `${from.CAPTURED_PROPERTY}` or `${target.CAPTURED_PROPERTY}`.
|
|
315
322
|
|
|
316
323
|
##### Error messages
|
|
317
324
|
|
|
@@ -366,7 +373,7 @@ Just to illustrate the high level of customization that the plugin supports, her
|
|
|
366
373
|
"from": ["components"],
|
|
367
374
|
"allow": [
|
|
368
375
|
// allow importing components of the same family
|
|
369
|
-
["components", { "family": "${family}" }],
|
|
376
|
+
["components", { "family": "${from.family}" }],
|
|
370
377
|
// allow importing helpers with captured category "data"
|
|
371
378
|
["helpers", { "category": "data" }],
|
|
372
379
|
]
|
|
@@ -403,10 +410,10 @@ Just to illustrate the high level of customization that the plugin supports, her
|
|
|
403
410
|
"from": [["modules", { "elementName": "page-*" }]],
|
|
404
411
|
"disallow": [
|
|
405
412
|
// disallow importing any type of component not being of family layout
|
|
406
|
-
["components", { "family": "!layout" }]
|
|
413
|
+
["components", { "family": "!layout" }],
|
|
407
414
|
],
|
|
408
415
|
// Custom message only for this specific error
|
|
409
|
-
"message": "Modules with name starting by 'page-' can't import not layout components. You tried to import a component of family ${
|
|
416
|
+
"message": "Modules with name starting by 'page-' can't import not layout components. You tried to import a component of family ${target.family} from a module with name ${from.elementName}"
|
|
410
417
|
}
|
|
411
418
|
]
|
|
412
419
|
}]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-boundaries",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "Eslint plugin checking architecture boundaries between elements",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -34,19 +34,19 @@
|
|
|
34
34
|
"chalk": "4.1.2",
|
|
35
35
|
"eslint-import-resolver-node": "0.3.6",
|
|
36
36
|
"eslint-module-utils": "2.7.4",
|
|
37
|
-
"is-core-module": "2.
|
|
37
|
+
"is-core-module": "2.11.0",
|
|
38
38
|
"micromatch": "4.0.5"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"cross-env": "7.0.3",
|
|
42
|
-
"eslint": "8.
|
|
42
|
+
"eslint": "8.29.0",
|
|
43
43
|
"eslint-config-prettier": "8.5.0",
|
|
44
44
|
"eslint-plugin-prettier": "4.2.1",
|
|
45
|
-
"husky": "8.0.
|
|
45
|
+
"husky": "8.0.2",
|
|
46
46
|
"is-ci": "3.0.1",
|
|
47
|
-
"jest": "29.
|
|
48
|
-
"lint-staged": "13.0.
|
|
49
|
-
"prettier": "2.
|
|
47
|
+
"jest": "29.3.1",
|
|
48
|
+
"lint-staged": "13.0.4",
|
|
49
|
+
"prettier": "2.8.0"
|
|
50
50
|
},
|
|
51
51
|
"lint-staged": {
|
|
52
52
|
"test/**/*.js": "eslint",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"*.js": "eslint"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
|
-
"node": ">=
|
|
57
|
+
"node": ">=14.0.0"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/src/core/elementsInfo.js
CHANGED
|
@@ -195,7 +195,8 @@ function projectPath(absolutePath) {
|
|
|
195
195
|
|
|
196
196
|
function importInfo(source, context) {
|
|
197
197
|
const path = projectPath(resolve(source, context));
|
|
198
|
-
const
|
|
198
|
+
const isExternalModule = isExternal(source, path);
|
|
199
|
+
const resultCache = importsCache.load(isExternalModule ? source : path, context.settings);
|
|
199
200
|
let elementCache;
|
|
200
201
|
let result;
|
|
201
202
|
let elementResult;
|
|
@@ -205,7 +206,6 @@ function importInfo(source, context) {
|
|
|
205
206
|
} else {
|
|
206
207
|
elementCache = elementsCache.load(path, context.settings);
|
|
207
208
|
const isBuiltInModule = isBuiltIn(source, path);
|
|
208
|
-
const isExternalModule = isExternal(source, path);
|
|
209
209
|
const pathToUse = isExternalModule ? null : path;
|
|
210
210
|
if (elementCache) {
|
|
211
211
|
elementResult = elementCache;
|
package/src/helpers/messages.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { isString, isArray, replaceObjectValuesInTemplates } = require("./utils");
|
|
2
|
-
const {
|
|
2
|
+
const { micromatchPatternReplacingObjectsValues } = require("./rules");
|
|
3
3
|
|
|
4
4
|
function quote(str) {
|
|
5
5
|
return `'${str}'`;
|
|
@@ -20,9 +20,9 @@ function propertiesConcater(properties, index) {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
function micromatchPatternMessage(micromatchPatterns, elementCapturedValues) {
|
|
23
|
-
const micromatchPatternsWithValues =
|
|
23
|
+
const micromatchPatternsWithValues = micromatchPatternReplacingObjectsValues(
|
|
24
24
|
micromatchPatterns,
|
|
25
|
-
elementCapturedValues
|
|
25
|
+
{ from: elementCapturedValues }
|
|
26
26
|
);
|
|
27
27
|
if (isArray(micromatchPatternsWithValues)) {
|
|
28
28
|
if (micromatchPatternsWithValues.length === 1) {
|
|
@@ -94,12 +94,26 @@ function customErrorMessage(message, file, dependency, report = {}) {
|
|
|
94
94
|
elementPropertiesToReplaceInTemplate(dependency),
|
|
95
95
|
"dependency"
|
|
96
96
|
);
|
|
97
|
+
replacedMessage = replaceObjectValuesInTemplates(
|
|
98
|
+
replaceObjectValuesInTemplates(
|
|
99
|
+
replacedMessage,
|
|
100
|
+
elementPropertiesToReplaceInTemplate(file),
|
|
101
|
+
"from"
|
|
102
|
+
),
|
|
103
|
+
elementPropertiesToReplaceInTemplate(dependency),
|
|
104
|
+
"target"
|
|
105
|
+
);
|
|
97
106
|
if (file.parents[0]) {
|
|
98
107
|
replacedMessage = replaceObjectValuesInTemplates(
|
|
99
108
|
replacedMessage,
|
|
100
109
|
elementPropertiesToReplaceInTemplate(file.parents[0]),
|
|
101
110
|
"file.parent"
|
|
102
111
|
);
|
|
112
|
+
replacedMessage = replaceObjectValuesInTemplates(
|
|
113
|
+
replacedMessage,
|
|
114
|
+
elementPropertiesToReplaceInTemplate(file.parents[0]),
|
|
115
|
+
"from.parent"
|
|
116
|
+
);
|
|
103
117
|
}
|
|
104
118
|
if (dependency.parents[0]) {
|
|
105
119
|
replacedMessage = replaceObjectValuesInTemplates(
|
|
@@ -107,6 +121,11 @@ function customErrorMessage(message, file, dependency, report = {}) {
|
|
|
107
121
|
elementPropertiesToReplaceInTemplate(dependency.parents[0]),
|
|
108
122
|
"dependency.parent"
|
|
109
123
|
);
|
|
124
|
+
replacedMessage = replaceObjectValuesInTemplates(
|
|
125
|
+
replacedMessage,
|
|
126
|
+
elementPropertiesToReplaceInTemplate(dependency.parents[0]),
|
|
127
|
+
"target.parent"
|
|
128
|
+
);
|
|
110
129
|
}
|
|
111
130
|
return replaceObjectValuesInTemplates(replacedMessage, report, "report");
|
|
112
131
|
}
|
package/src/helpers/rules.js
CHANGED
|
@@ -3,6 +3,7 @@ const micromatch = require("micromatch");
|
|
|
3
3
|
const { isArray, replaceObjectValuesInTemplates } = require("./utils");
|
|
4
4
|
|
|
5
5
|
const REPO_URL = "https://github.com/javierbrea/eslint-plugin-boundaries";
|
|
6
|
+
const FROM = "from";
|
|
6
7
|
|
|
7
8
|
function removePluginNamespace(ruleName) {
|
|
8
9
|
return ruleName.replace("boundaries/", "");
|
|
@@ -44,39 +45,58 @@ function dependencyLocation(node, context) {
|
|
|
44
45
|
};
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
function
|
|
48
|
-
|
|
48
|
+
function micromatchPatternReplacingObjectsValues(pattern, object) {
|
|
49
|
+
let patternToReplace = pattern;
|
|
50
|
+
// Backward compatibility
|
|
51
|
+
if (object.from) {
|
|
52
|
+
patternToReplace = replaceObjectValuesInTemplates(patternToReplace, object.from);
|
|
53
|
+
}
|
|
54
|
+
return Object.keys(object).reduce((replacedPattern, namespace) => {
|
|
55
|
+
if (!object[namespace]) {
|
|
56
|
+
return replacedPattern;
|
|
57
|
+
}
|
|
58
|
+
return replaceObjectValuesInTemplates(replacedPattern, object[namespace], namespace);
|
|
59
|
+
}, patternToReplace);
|
|
49
60
|
}
|
|
50
61
|
|
|
51
|
-
function isObjectMatch(objectWithMatchers, object,
|
|
62
|
+
function isObjectMatch(objectWithMatchers, object, objectsWithValuesToReplace) {
|
|
52
63
|
return Object.keys(objectWithMatchers).reduce((isMatch, key) => {
|
|
53
|
-
if (isMatch
|
|
54
|
-
const micromatchPattern =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
)
|
|
59
|
-
: objectWithMatchers[key];
|
|
64
|
+
if (isMatch) {
|
|
65
|
+
const micromatchPattern = micromatchPatternReplacingObjectsValues(
|
|
66
|
+
objectWithMatchers[key],
|
|
67
|
+
objectsWithValuesToReplace
|
|
68
|
+
);
|
|
60
69
|
return micromatch.isMatch(object[key], micromatchPattern);
|
|
61
70
|
}
|
|
62
71
|
return isMatch;
|
|
63
|
-
},
|
|
72
|
+
}, true);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
function rulesMainKey(key) {
|
|
67
|
-
return key ||
|
|
76
|
+
return key || FROM;
|
|
68
77
|
}
|
|
69
78
|
|
|
70
|
-
function ruleMatch(ruleMatchers,
|
|
79
|
+
function ruleMatch(ruleMatchers, targetElement, isMatch, fromElement) {
|
|
71
80
|
let match = { result: false, report: null };
|
|
72
81
|
const matchers = !isArray(ruleMatchers) ? [ruleMatchers] : ruleMatchers;
|
|
73
82
|
matchers.forEach((matcher) => {
|
|
74
83
|
if (!match.result) {
|
|
75
84
|
if (isArray(matcher)) {
|
|
76
85
|
const [value, captures] = matcher;
|
|
77
|
-
match = isMatch(
|
|
86
|
+
match = isMatch(targetElement, value, captures, {
|
|
87
|
+
from: fromElement.capturedValues,
|
|
88
|
+
target: targetElement.capturedValues,
|
|
89
|
+
});
|
|
78
90
|
} else {
|
|
79
|
-
match = isMatch(
|
|
91
|
+
match = isMatch(
|
|
92
|
+
targetElement,
|
|
93
|
+
matcher,
|
|
94
|
+
{},
|
|
95
|
+
{
|
|
96
|
+
from: fromElement.capturedValues,
|
|
97
|
+
target: targetElement.capturedValues,
|
|
98
|
+
}
|
|
99
|
+
);
|
|
80
100
|
}
|
|
81
101
|
}
|
|
82
102
|
});
|
|
@@ -88,12 +108,15 @@ function isMatchElementKey(
|
|
|
88
108
|
matcher,
|
|
89
109
|
options,
|
|
90
110
|
elementKey,
|
|
91
|
-
|
|
111
|
+
elementsToCompareCapturedValues
|
|
92
112
|
) {
|
|
93
|
-
const isMatch = micromatch.isMatch(
|
|
113
|
+
const isMatch = micromatch.isMatch(
|
|
114
|
+
elementInfo[elementKey],
|
|
115
|
+
micromatchPatternReplacingObjectsValues(matcher, elementsToCompareCapturedValues)
|
|
116
|
+
);
|
|
94
117
|
if (isMatch && options) {
|
|
95
118
|
return {
|
|
96
|
-
result: isObjectMatch(options, elementInfo.capturedValues,
|
|
119
|
+
result: isObjectMatch(options, elementInfo.capturedValues, elementsToCompareCapturedValues),
|
|
97
120
|
};
|
|
98
121
|
}
|
|
99
122
|
return {
|
|
@@ -101,8 +124,8 @@ function isMatchElementKey(
|
|
|
101
124
|
};
|
|
102
125
|
}
|
|
103
126
|
|
|
104
|
-
function isMatchElementType(elementInfo, matcher, options,
|
|
105
|
-
return isMatchElementKey(elementInfo, matcher, options, "type",
|
|
127
|
+
function isMatchElementType(elementInfo, matcher, options, elementsToCompareCapturedValues) {
|
|
128
|
+
return isMatchElementKey(elementInfo, matcher, options, "type", elementsToCompareCapturedValues);
|
|
106
129
|
}
|
|
107
130
|
|
|
108
131
|
function getElementRules(elementInfo, options, mainKey) {
|
|
@@ -118,10 +141,21 @@ function getElementRules(elementInfo, options, mainKey) {
|
|
|
118
141
|
};
|
|
119
142
|
})
|
|
120
143
|
.filter((rule) => {
|
|
121
|
-
return ruleMatch(rule[key], elementInfo, isMatchElementType).result;
|
|
144
|
+
return ruleMatch(rule[key], elementInfo, isMatchElementType, elementInfo).result;
|
|
122
145
|
});
|
|
123
146
|
}
|
|
124
147
|
|
|
148
|
+
function isFromRule(mainKey) {
|
|
149
|
+
return rulesMainKey(mainKey) === FROM;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function elementToGetRulesFrom(element, dependency, mainKey) {
|
|
153
|
+
if (!isFromRule(mainKey)) {
|
|
154
|
+
return dependency;
|
|
155
|
+
}
|
|
156
|
+
return element;
|
|
157
|
+
}
|
|
158
|
+
|
|
125
159
|
function elementRulesAllowDependency({
|
|
126
160
|
element,
|
|
127
161
|
dependency,
|
|
@@ -129,7 +163,11 @@ function elementRulesAllowDependency({
|
|
|
129
163
|
isMatch,
|
|
130
164
|
rulesMainKey: mainKey,
|
|
131
165
|
}) {
|
|
132
|
-
const [result, report, ruleReport] = getElementRules(
|
|
166
|
+
const [result, report, ruleReport] = getElementRules(
|
|
167
|
+
elementToGetRulesFrom(element, dependency, mainKey),
|
|
168
|
+
options,
|
|
169
|
+
mainKey
|
|
170
|
+
).reduce(
|
|
133
171
|
(allowed, rule) => {
|
|
134
172
|
if (rule.disallow) {
|
|
135
173
|
const match = ruleMatch(rule.disallow, dependency, isMatch, element);
|
|
@@ -179,5 +217,5 @@ module.exports = {
|
|
|
179
217
|
elementRulesAllowDependency,
|
|
180
218
|
getElementRules,
|
|
181
219
|
rulesMainKey,
|
|
182
|
-
|
|
220
|
+
micromatchPatternReplacingObjectsValues,
|
|
183
221
|
};
|
package/src/rules/entry-point.js
CHANGED
|
@@ -10,13 +10,13 @@ const {
|
|
|
10
10
|
} = require("../helpers/rules");
|
|
11
11
|
const { customErrorMessage, ruleElementMessage, elementMessage } = require("../helpers/messages");
|
|
12
12
|
|
|
13
|
-
function isMatchElementInternalPath(elementInfo, matcher, options) {
|
|
14
|
-
return isMatchElementKey(elementInfo, matcher, options, "internalPath");
|
|
13
|
+
function isMatchElementInternalPath(elementInfo, matcher, options, elementsCapturedValues) {
|
|
14
|
+
return isMatchElementKey(elementInfo, matcher, options, "internalPath", elementsCapturedValues);
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
function elementRulesAllowEntryPoint(
|
|
17
|
+
function elementRulesAllowEntryPoint(element, dependency, options) {
|
|
18
18
|
return elementRulesAllowDependency({
|
|
19
|
-
element
|
|
19
|
+
element,
|
|
20
20
|
dependency,
|
|
21
21
|
options,
|
|
22
22
|
isMatch: isMatchElementInternalPath,
|
package/src/rules/external.js
CHANGED
|
@@ -5,27 +5,43 @@ const { RULE_EXTERNAL } = require("../constants/settings");
|
|
|
5
5
|
const dependencyRule = require("../rules-factories/dependency-rule");
|
|
6
6
|
|
|
7
7
|
const { rulesOptionsSchema } = require("../helpers/validations");
|
|
8
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
dependencyLocation,
|
|
10
|
+
elementRulesAllowDependency,
|
|
11
|
+
micromatchPatternReplacingObjectsValues,
|
|
12
|
+
} = require("../helpers/rules");
|
|
9
13
|
const { customErrorMessage, ruleElementMessage, elementMessage } = require("../helpers/messages");
|
|
10
14
|
|
|
11
|
-
function specifiersMatch(specifiers, options) {
|
|
15
|
+
function specifiersMatch(specifiers, options, elementsCapturedValues) {
|
|
12
16
|
const importedSpecifiersNames = specifiers
|
|
13
17
|
.filter((specifier) => {
|
|
14
18
|
return specifier.type === "ImportSpecifier" && specifier.imported.name;
|
|
15
19
|
})
|
|
16
20
|
.map((specifier) => specifier.imported.name);
|
|
17
21
|
return options.reduce((found, option) => {
|
|
18
|
-
|
|
22
|
+
const matcherWithTemplateReplaced = micromatchPatternReplacingObjectsValues(
|
|
23
|
+
option,
|
|
24
|
+
elementsCapturedValues
|
|
25
|
+
);
|
|
26
|
+
if (micromatch.some(importedSpecifiersNames, matcherWithTemplateReplaced)) {
|
|
19
27
|
found.push(option);
|
|
20
28
|
}
|
|
21
29
|
return found;
|
|
22
30
|
}, []);
|
|
23
31
|
}
|
|
24
32
|
|
|
25
|
-
function isMatchExternalDependency(dependency, matcher, options) {
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
33
|
+
function isMatchExternalDependency(dependency, matcher, options, elementsCapturedValues) {
|
|
34
|
+
const matcherWithTemplatesReplaced = micromatchPatternReplacingObjectsValues(
|
|
35
|
+
matcher,
|
|
36
|
+
elementsCapturedValues
|
|
37
|
+
);
|
|
38
|
+
const isMatch = micromatch.isMatch(dependency.baseModule, matcherWithTemplatesReplaced);
|
|
39
|
+
if (isMatch && options && Object.keys(options).length) {
|
|
40
|
+
const specifiersResult = specifiersMatch(
|
|
41
|
+
dependency.specifiers,
|
|
42
|
+
options.specifiers,
|
|
43
|
+
elementsCapturedValues
|
|
44
|
+
);
|
|
29
45
|
return {
|
|
30
46
|
result: specifiersResult.length > 0,
|
|
31
47
|
report: specifiersResult,
|