eslint-plugin-boundaries 2.8.0 → 2.10.1

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 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, __except the `message` property, which for the moment is only supported in the [`element-types`](docs/rules/element-types.md), [`entry-point`](docs/rules/entry-point.md) and [`no-private`](docs/rules/no-private.md) rules settings.
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/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.
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
  {
@@ -331,12 +331,20 @@ Available properties in error templates both from `file` or `dependency` are:
331
331
 
332
332
  * `type`: Element's type.
333
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.
334
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.
335
336
  * ...All captured properties are also available
336
337
 
337
-
338
338
  > Tip: Read ["Global settings"](#global-settings) for further info about how to capture values from elements.
339
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
+
340
348
  ##### Advanced example of a rule configuration
341
349
 
342
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.8.0",
3
+ "version": "2.10.1",
4
4
  "description": "Eslint plugin checking architecture boundaries between elements",
5
5
  "keywords": [
6
6
  "eslint",
@@ -31,22 +31,22 @@
31
31
  "eslint": ">=6.0.0"
32
32
  },
33
33
  "dependencies": {
34
- "eslint-import-resolver-node": "0.3.6",
35
- "eslint-module-utils": "2.7.1",
36
34
  "chalk": "4.1.2",
37
- "is-core-module": "2.8.0",
38
- "micromatch": "4.0.4"
35
+ "eslint-import-resolver-node": "0.3.6",
36
+ "eslint-module-utils": "2.7.3",
37
+ "is-core-module": "2.9.0",
38
+ "micromatch": "4.0.5"
39
39
  },
40
40
  "devDependencies": {
41
41
  "cross-env": "7.0.3",
42
- "eslint": "8.3.0",
43
- "eslint-config-prettier": "8.3.0",
42
+ "eslint": "8.16.0",
43
+ "eslint-config-prettier": "8.5.0",
44
44
  "eslint-plugin-prettier": "4.0.0",
45
- "husky": "7.0.4",
45
+ "husky": "8.0.1",
46
46
  "is-ci": "3.0.1",
47
- "jest": "27.3.1",
48
- "lint-staged": "12.1.2",
49
- "prettier": "2.4.1"
47
+ "jest": "28.1.0",
48
+ "lint-staged": "12.4.3",
49
+ "prettier": "2.6.2"
50
50
  },
51
51
  "lint-staged": {
52
52
  "test/**/*.js": "eslint",
@@ -14,7 +14,7 @@ const replaceAliases = (filePath, config) => {
14
14
 
15
15
  module.exports = {
16
16
  interfaceVersion: 2,
17
- resolve: function (source, file, config) {
17
+ resolve: function (source, _file, config) {
18
18
  if (resolve.isCore(source)) return { found: true, path: null };
19
19
  try {
20
20
  return {
@@ -80,10 +80,15 @@ function ruleElementMessage(elementPatterns, elementCapturedValues) {
80
80
  }
81
81
 
82
82
  function elementPropertiesToReplaceInTemplate(element) {
83
- return { ...element.capturedValues, type: element.type, internalPath: element.internalPath };
83
+ return {
84
+ ...element.capturedValues,
85
+ type: element.type,
86
+ internalPath: element.internalPath,
87
+ source: element.source,
88
+ };
84
89
  }
85
90
 
86
- function customErrorMessage(message, file, dependency) {
91
+ function customErrorMessage(message, file, dependency, report = {}) {
87
92
  let replacedMessage = replaceObjectValuesInTemplates(
88
93
  replaceObjectValuesInTemplates(message, elementPropertiesToReplaceInTemplate(file), "file"),
89
94
  elementPropertiesToReplaceInTemplate(dependency),
@@ -103,7 +108,7 @@ function customErrorMessage(message, file, dependency) {
103
108
  "dependency.parent"
104
109
  );
105
110
  }
106
- return replacedMessage;
111
+ return replaceObjectValuesInTemplates(replacedMessage, report, "report");
107
112
  }
108
113
 
109
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
- const schema = [
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) {
@@ -14,7 +14,7 @@ function isMatchElementInternalPath(elementInfo, matcher, options) {
14
14
  return isMatchElementKey(elementInfo, matcher, options, "internalPath");
15
15
  }
16
16
 
17
- function elementRulesAllowEntryPoint(element, dependency, options) {
17
+ function elementRulesAllowEntryPoint(_element, dependency, options) {
18
18
  return elementRulesAllowDependency({
19
19
  element: dependency,
20
20
  dependency,
@@ -46,8 +46,6 @@ module.exports = dependencyRule(
46
46
  description: `Check entry point used for each element type`,
47
47
  schema: rulesOptionsSchema({
48
48
  rulesMainKey: "target",
49
- customMessage: true,
50
- customRuleMessage: true,
51
49
  }),
52
50
  },
53
51
  function ({ dependency, file, node, context, options }) {
@@ -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 isAllowed = elementRulesAllowExternalDependency(
95
+ const ruleData = elementRulesAllowExternalDependency(
69
96
  file,
70
97
  { ...dependency, specifiers: node.source.parent.specifiers },
71
98
  options
72
99
  );
73
- if (!isAllowed.result) {
74
- if (isAllowed.report) {
75
- context.report({
76
- message: `Usage of '${isAllowed.report.join(", ")}' from external module '${
77
- dependency.baseModule
78
- }' is not allowed in '${file.type}'`,
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
  },