eslint-plugin-boundaries 2.6.0 → 2.9.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 +20 -4
- package/package.json +8 -8
- package/src/helpers/messages.js +28 -4
- package/src/helpers/validations.js +8 -13
- package/src/rules/element-types.js +1 -4
- package/src/rules/entry-point.js +27 -12
- package/src/rules/external.js +34 -17
- package/src/rules/no-private.js +13 -2
package/README.md
CHANGED
|
@@ -259,9 +259,9 @@ Some rules require extra configuration, and it has to be defined in each specifi
|
|
|
259
259
|
|
|
260
260
|
#### Main format of rules options
|
|
261
261
|
|
|
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
|
|
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
|
|
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 correspondant way, making it to produce an eslint error or not.
|
|
265
265
|
|
|
266
266
|
```jsonc
|
|
267
267
|
{
|
|
@@ -300,7 +300,7 @@ Remember that:
|
|
|
300
300
|
|
|
301
301
|
* __`from/target`__: `<element matchers>` Depending of the rule to which the options are for, the rule will be applied only if the file being analized matches with this element matcher (`from`), or the dependency being imported matches with this element matcher (`target`).
|
|
302
302
|
* __`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.
|
|
303
|
-
* __`message`__: `<string>` Optional. If the rule results in an error, the plugin will return this message.
|
|
303
|
+
* __`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
304
|
|
|
305
305
|
> Tip: Properties `from/target` and `disallow/allow` can receive a single matcher, or an array of matchers.
|
|
306
306
|
|
|
@@ -317,7 +317,7 @@ Elements matchers used in the rules options can have the next formats:
|
|
|
317
317
|
|
|
318
318
|
The plugin returns a different default message for each rule, check the documentation of each one for further info. But some rules support defining custom messages in their configuration, as seen in ["Main format of rules options"](#main-format-of-rules-options).
|
|
319
319
|
|
|
320
|
-
When defining custom messages, it is possible to provide information about the
|
|
320
|
+
When defining custom messages, it is possible to provide information about the current file or dependency. Use `${file.PROPERTY}` or `${dependency.PROPERTY}`, and it will be replaced by the correspondent captured value from the file or the dependency:
|
|
321
321
|
|
|
322
322
|
```jsonc
|
|
323
323
|
{
|
|
@@ -327,8 +327,24 @@ When defining custom messages, it is possible to provide information about the t
|
|
|
327
327
|
}
|
|
328
328
|
```
|
|
329
329
|
|
|
330
|
+
Available properties in error templates both from `file` or `dependency` are:
|
|
331
|
+
|
|
332
|
+
* `type`: Element's type.
|
|
333
|
+
* `internalPath`: File path being analyzed or imported. Relative to the element's root path.
|
|
334
|
+
* `source`: Available only for `dependency`. The source of the `import` statement as it is in the code.
|
|
335
|
+
* `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.
|
|
336
|
+
* ...All captured properties are also available
|
|
337
|
+
|
|
330
338
|
> Tip: Read ["Global settings"](#global-settings) for further info about how to capture values from elements.
|
|
331
339
|
|
|
340
|
+
Some rules also provide extra information about the reported error. For example, `no-external` rules provides information about detected forbidden specifiers. This information is available using `${report.PROPERTY}`. Check each rule's documentation to know which report properties it provides:
|
|
341
|
+
|
|
342
|
+
```jsonc
|
|
343
|
+
{
|
|
344
|
+
"message": "Do not import ${report.specifiers} from ${dependency.source} in helpers"
|
|
345
|
+
}
|
|
346
|
+
```
|
|
347
|
+
|
|
332
348
|
##### Advanced example of a rule configuration
|
|
333
349
|
|
|
334
350
|
Just to illustrate the high level of customization that the plugin supports, here is an example of advanced options for the `boundaries/element-types` rule based on the previous global `elements` settings example:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-boundaries",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"description": "Eslint plugin checking architecture boundaries between elements",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -32,21 +32,21 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"eslint-import-resolver-node": "0.3.6",
|
|
35
|
-
"eslint-module-utils": "2.7.
|
|
35
|
+
"eslint-module-utils": "2.7.3",
|
|
36
36
|
"chalk": "4.1.2",
|
|
37
|
-
"is-core-module": "2.8.
|
|
37
|
+
"is-core-module": "2.8.1",
|
|
38
38
|
"micromatch": "4.0.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"cross-env": "7.0.3",
|
|
42
|
-
"eslint": "8.
|
|
43
|
-
"eslint-config-prettier": "8.
|
|
42
|
+
"eslint": "8.9.0",
|
|
43
|
+
"eslint-config-prettier": "8.4.0",
|
|
44
44
|
"eslint-plugin-prettier": "4.0.0",
|
|
45
45
|
"husky": "7.0.4",
|
|
46
46
|
"is-ci": "3.0.1",
|
|
47
|
-
"jest": "27.
|
|
48
|
-
"lint-staged": "
|
|
49
|
-
"prettier": "2.
|
|
47
|
+
"jest": "27.5.1",
|
|
48
|
+
"lint-staged": "12.3.4",
|
|
49
|
+
"prettier": "2.5.1"
|
|
50
50
|
},
|
|
51
51
|
"lint-staged": {
|
|
52
52
|
"test/**/*.js": "eslint",
|
package/src/helpers/messages.js
CHANGED
|
@@ -79,12 +79,36 @@ function ruleElementMessage(elementPatterns, elementCapturedValues) {
|
|
|
79
79
|
return elementMatcherMessage(elementPatterns, elementCapturedValues);
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
function
|
|
83
|
-
return
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
function elementPropertiesToReplaceInTemplate(element) {
|
|
83
|
+
return {
|
|
84
|
+
...element.capturedValues,
|
|
85
|
+
type: element.type,
|
|
86
|
+
internalPath: element.internalPath,
|
|
87
|
+
source: element.source,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function customErrorMessage(message, file, dependency, report = {}) {
|
|
92
|
+
let replacedMessage = replaceObjectValuesInTemplates(
|
|
93
|
+
replaceObjectValuesInTemplates(message, elementPropertiesToReplaceInTemplate(file), "file"),
|
|
94
|
+
elementPropertiesToReplaceInTemplate(dependency),
|
|
86
95
|
"dependency"
|
|
87
96
|
);
|
|
97
|
+
if (file.parents[0]) {
|
|
98
|
+
replacedMessage = replaceObjectValuesInTemplates(
|
|
99
|
+
replacedMessage,
|
|
100
|
+
elementPropertiesToReplaceInTemplate(file.parents[0]),
|
|
101
|
+
"file.parent"
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
if (dependency.parents[0]) {
|
|
105
|
+
replacedMessage = replaceObjectValuesInTemplates(
|
|
106
|
+
replacedMessage,
|
|
107
|
+
elementPropertiesToReplaceInTemplate(dependency.parents[0]),
|
|
108
|
+
"dependency.parent"
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
return replaceObjectValuesInTemplates(replacedMessage, report, "report");
|
|
88
112
|
}
|
|
89
113
|
|
|
90
114
|
function elementCapturedValuesMessage(capturedValues) {
|
|
@@ -42,12 +42,15 @@ function elementsMatcherSchema(matcherOptions = DEFAULT_MATCHER_OPTIONS) {
|
|
|
42
42
|
};
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
function rulesOptionsSchema(options) {
|
|
45
|
+
function rulesOptionsSchema(options = {}) {
|
|
46
46
|
const mainKey = rulesMainKey(options.rulesMainKey);
|
|
47
|
-
|
|
47
|
+
return [
|
|
48
48
|
{
|
|
49
49
|
type: "object",
|
|
50
50
|
properties: {
|
|
51
|
+
message: {
|
|
52
|
+
type: "string",
|
|
53
|
+
},
|
|
51
54
|
default: {
|
|
52
55
|
type: "string",
|
|
53
56
|
enum: ["allow", "disallow"],
|
|
@@ -60,6 +63,9 @@ function rulesOptionsSchema(options) {
|
|
|
60
63
|
[mainKey]: elementsMatcherSchema(),
|
|
61
64
|
allow: elementsMatcherSchema(options.targetMatcherOptions),
|
|
62
65
|
disallow: elementsMatcherSchema(options.targetMatcherOptions),
|
|
66
|
+
message: {
|
|
67
|
+
type: "string",
|
|
68
|
+
},
|
|
63
69
|
},
|
|
64
70
|
additionalProperties: false,
|
|
65
71
|
anyOf: [
|
|
@@ -79,17 +85,6 @@ function rulesOptionsSchema(options) {
|
|
|
79
85
|
additionalProperties: false,
|
|
80
86
|
},
|
|
81
87
|
];
|
|
82
|
-
if (options.customMessage) {
|
|
83
|
-
schema[0].properties.message = {
|
|
84
|
-
type: "string",
|
|
85
|
-
};
|
|
86
|
-
}
|
|
87
|
-
if (options.customRuleMessage) {
|
|
88
|
-
schema[0].properties.rules.items.properties.message = {
|
|
89
|
-
type: "string",
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
return schema;
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
function isValidElementTypesMatcher(matcher, settings) {
|
|
@@ -42,10 +42,7 @@ module.exports = dependencyRule(
|
|
|
42
42
|
{
|
|
43
43
|
ruleName: RULE_ELEMENT_TYPES,
|
|
44
44
|
description: `Check allowed dependencies between element types`,
|
|
45
|
-
schema: rulesOptionsSchema(
|
|
46
|
-
customMessage: true,
|
|
47
|
-
customRuleMessage: true,
|
|
48
|
-
}),
|
|
45
|
+
schema: rulesOptionsSchema(),
|
|
49
46
|
},
|
|
50
47
|
function ({ dependency, file, node, context, options }) {
|
|
51
48
|
if (dependency.isLocal && !dependency.isIgnored && dependency.type && !dependency.isInternal) {
|
package/src/rules/entry-point.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
isMatchElementKey,
|
|
9
9
|
elementRulesAllowDependency,
|
|
10
10
|
} = require("../helpers/rules");
|
|
11
|
+
const { customErrorMessage, ruleElementMessage, elementMessage } = require("../helpers/messages");
|
|
11
12
|
|
|
12
13
|
function isMatchElementInternalPath(elementInfo, matcher, options) {
|
|
13
14
|
return isMatchElementKey(elementInfo, matcher, options, "internalPath");
|
|
@@ -20,7 +21,23 @@ function elementRulesAllowEntryPoint(element, dependency, options) {
|
|
|
20
21
|
options,
|
|
21
22
|
isMatch: isMatchElementInternalPath,
|
|
22
23
|
rulesMainKey: "target",
|
|
23
|
-
})
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function errorMessage(ruleData, file, dependency) {
|
|
28
|
+
const ruleReport = ruleData.ruleReport;
|
|
29
|
+
if (ruleReport.message) {
|
|
30
|
+
return customErrorMessage(ruleReport.message, file, dependency);
|
|
31
|
+
}
|
|
32
|
+
if (ruleReport.isDefault) {
|
|
33
|
+
return `No rule allows the entry point '${
|
|
34
|
+
dependency.internalPath
|
|
35
|
+
}' in dependencies ${elementMessage(dependency)}`;
|
|
36
|
+
}
|
|
37
|
+
return `The entry point '${dependency.internalPath}' is not allowed in ${ruleElementMessage(
|
|
38
|
+
ruleReport.element,
|
|
39
|
+
dependency.capturedValues
|
|
40
|
+
)}. Disallowed in rule ${ruleReport.index + 1}`;
|
|
24
41
|
}
|
|
25
42
|
|
|
26
43
|
module.exports = dependencyRule(
|
|
@@ -32,17 +49,15 @@ module.exports = dependencyRule(
|
|
|
32
49
|
}),
|
|
33
50
|
},
|
|
34
51
|
function ({ dependency, file, node, context, options }) {
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
...dependencyLocation(node, context),
|
|
45
|
-
});
|
|
52
|
+
if (!dependency.isIgnored && dependency.type && !dependency.isInternal) {
|
|
53
|
+
const ruleData = elementRulesAllowEntryPoint(file, dependency, options);
|
|
54
|
+
if (!ruleData.result) {
|
|
55
|
+
context.report({
|
|
56
|
+
message: errorMessage(ruleData, file, dependency),
|
|
57
|
+
node: node,
|
|
58
|
+
...dependencyLocation(node, context),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
46
61
|
}
|
|
47
62
|
},
|
|
48
63
|
{
|
package/src/rules/external.js
CHANGED
|
@@ -6,6 +6,7 @@ const dependencyRule = require("../rules-factories/dependency-rule");
|
|
|
6
6
|
|
|
7
7
|
const { rulesOptionsSchema } = require("../helpers/validations");
|
|
8
8
|
const { dependencyLocation, elementRulesAllowDependency } = require("../helpers/rules");
|
|
9
|
+
const { customErrorMessage, ruleElementMessage, elementMessage } = require("../helpers/messages");
|
|
9
10
|
|
|
10
11
|
function specifiersMatch(specifiers, options) {
|
|
11
12
|
const importedSpecifiersNames = specifiers
|
|
@@ -44,6 +45,32 @@ function elementRulesAllowExternalDependency(element, dependency, options) {
|
|
|
44
45
|
});
|
|
45
46
|
}
|
|
46
47
|
|
|
48
|
+
function errorMessage(ruleData, file, dependency) {
|
|
49
|
+
const ruleReport = ruleData.ruleReport;
|
|
50
|
+
if (ruleReport.message) {
|
|
51
|
+
return customErrorMessage(ruleReport.message, file, dependency, {
|
|
52
|
+
specifiers: ruleData.report && ruleData.report.join(", "),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
if (ruleReport.isDefault) {
|
|
56
|
+
return `No rule allows the usage of external module '${
|
|
57
|
+
dependency.baseModule
|
|
58
|
+
}' in elements ${elementMessage(file)}`;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const fileReport = `is not allowed in ${ruleElementMessage(
|
|
62
|
+
ruleReport.element,
|
|
63
|
+
file.capturedValues
|
|
64
|
+
)}. Disallowed in rule ${ruleReport.index + 1}`;
|
|
65
|
+
|
|
66
|
+
if (ruleData.report) {
|
|
67
|
+
return `Usage of '${ruleData.report.join(", ")}' from external module '${
|
|
68
|
+
dependency.baseModule
|
|
69
|
+
}' ${fileReport}`;
|
|
70
|
+
}
|
|
71
|
+
return `Usage of external module '${dependency.baseModule}' ${fileReport}`;
|
|
72
|
+
}
|
|
73
|
+
|
|
47
74
|
module.exports = dependencyRule(
|
|
48
75
|
{
|
|
49
76
|
ruleName: RULE_EXTERNAL,
|
|
@@ -65,27 +92,17 @@ module.exports = dependencyRule(
|
|
|
65
92
|
},
|
|
66
93
|
function ({ dependency, file, node, context, options }) {
|
|
67
94
|
if (dependency.isExternal) {
|
|
68
|
-
const
|
|
95
|
+
const ruleData = elementRulesAllowExternalDependency(
|
|
69
96
|
file,
|
|
70
97
|
{ ...dependency, specifiers: node.source.parent.specifiers },
|
|
71
98
|
options
|
|
72
99
|
);
|
|
73
|
-
if (!
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
node: node,
|
|
80
|
-
...dependencyLocation(node, context),
|
|
81
|
-
});
|
|
82
|
-
} else {
|
|
83
|
-
context.report({
|
|
84
|
-
message: `Usage of external module '${dependency.baseModule}' is not allowed in '${file.type}'`,
|
|
85
|
-
node: node,
|
|
86
|
-
...dependencyLocation(node, context),
|
|
87
|
-
});
|
|
88
|
-
}
|
|
100
|
+
if (!ruleData.result) {
|
|
101
|
+
context.report({
|
|
102
|
+
message: errorMessage(ruleData, file, dependency),
|
|
103
|
+
node: node,
|
|
104
|
+
...dependencyLocation(node, context),
|
|
105
|
+
});
|
|
89
106
|
}
|
|
90
107
|
}
|
|
91
108
|
},
|
package/src/rules/no-private.js
CHANGED
|
@@ -3,6 +3,14 @@ const { RULE_NO_PRIVATE } = require("../constants/settings");
|
|
|
3
3
|
const dependencyRule = require("../rules-factories/dependency-rule");
|
|
4
4
|
|
|
5
5
|
const { dependencyLocation } = require("../helpers/rules");
|
|
6
|
+
const { customErrorMessage, elementMessage } = require("../helpers/messages");
|
|
7
|
+
|
|
8
|
+
function errorMessage(file, dependency, options) {
|
|
9
|
+
if (options.message) {
|
|
10
|
+
return customErrorMessage(options.message, file, dependency);
|
|
11
|
+
}
|
|
12
|
+
return `Dependency is private of element ${elementMessage(dependency.parents[0])}`;
|
|
13
|
+
}
|
|
6
14
|
|
|
7
15
|
module.exports = dependencyRule(
|
|
8
16
|
{
|
|
@@ -15,12 +23,15 @@ module.exports = dependencyRule(
|
|
|
15
23
|
allowUncles: {
|
|
16
24
|
type: "boolean",
|
|
17
25
|
},
|
|
26
|
+
message: {
|
|
27
|
+
type: "string",
|
|
28
|
+
},
|
|
18
29
|
},
|
|
19
30
|
additionalProperties: false,
|
|
20
31
|
},
|
|
21
32
|
],
|
|
22
33
|
},
|
|
23
|
-
function ({ dependency, node, context, options }) {
|
|
34
|
+
function ({ file, dependency, node, context, options }) {
|
|
24
35
|
if (
|
|
25
36
|
!dependency.isIgnored &&
|
|
26
37
|
dependency.isLocal &&
|
|
@@ -32,7 +43,7 @@ module.exports = dependencyRule(
|
|
|
32
43
|
(!options || !options.allowUncles || dependency.relationship !== "uncle")
|
|
33
44
|
) {
|
|
34
45
|
context.report({
|
|
35
|
-
message:
|
|
46
|
+
message: errorMessage(file, dependency, options),
|
|
36
47
|
node: node,
|
|
37
48
|
...dependencyLocation(node, context),
|
|
38
49
|
});
|