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 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.<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:
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 ${dependency.family} from a module with name ${file.elementName}"
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": "2.10.2",
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.10.0",
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.23.0",
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.1",
45
+ "husky": "8.0.2",
46
46
  "is-ci": "3.0.1",
47
- "jest": "29.0.1",
48
- "lint-staged": "13.0.3",
49
- "prettier": "2.7.1"
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": ">=12.0.0"
57
+ "node": ">=14.0.0"
58
58
  }
59
59
  }
@@ -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 resultCache = importsCache.load(path, context.settings);
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;
@@ -1,5 +1,5 @@
1
1
  const { isString, isArray, replaceObjectValuesInTemplates } = require("./utils");
2
- const { micromatchPatternReplacingObjectValues } = require("./rules");
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 = micromatchPatternReplacingObjectValues(
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
  }
@@ -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 micromatchPatternReplacingObjectValues(pattern, object) {
48
- return replaceObjectValuesInTemplates(pattern, object);
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, objectWithValuesToReplace) {
62
+ function isObjectMatch(objectWithMatchers, object, objectsWithValuesToReplace) {
52
63
  return Object.keys(objectWithMatchers).reduce((isMatch, key) => {
53
- if (isMatch || isMatch === null) {
54
- const micromatchPattern = objectWithValuesToReplace
55
- ? micromatchPatternReplacingObjectValues(
56
- objectWithMatchers[key],
57
- objectWithValuesToReplace
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
- }, null);
72
+ }, true);
64
73
  }
65
74
 
66
75
  function rulesMainKey(key) {
67
- return key || "from";
76
+ return key || FROM;
68
77
  }
69
78
 
70
- function ruleMatch(ruleMatchers, elementInfo, isMatch, elementToCompare = {}) {
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(elementInfo, value, captures, elementToCompare.capturedValues);
86
+ match = isMatch(targetElement, value, captures, {
87
+ from: fromElement.capturedValues,
88
+ target: targetElement.capturedValues,
89
+ });
78
90
  } else {
79
- match = isMatch(elementInfo, matcher);
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
- elementToCompareCapturedValues
111
+ elementsToCompareCapturedValues
92
112
  ) {
93
- const isMatch = micromatch.isMatch(elementInfo[elementKey], matcher);
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, elementToCompareCapturedValues),
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, elementToCompareCapturedValues) {
105
- return isMatchElementKey(elementInfo, matcher, options, "type", elementToCompareCapturedValues);
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(element, options, mainKey).reduce(
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
- micromatchPatternReplacingObjectValues,
220
+ micromatchPatternReplacingObjectsValues,
183
221
  };
@@ -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(_element, dependency, options) {
17
+ function elementRulesAllowEntryPoint(element, dependency, options) {
18
18
  return elementRulesAllowDependency({
19
- element: dependency,
19
+ element,
20
20
  dependency,
21
21
  options,
22
22
  isMatch: isMatchElementInternalPath,
@@ -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 { dependencyLocation, elementRulesAllowDependency } = require("../helpers/rules");
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
- if (micromatch.some(importedSpecifiersNames, option)) {
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 isMatch = micromatch.isMatch(dependency.baseModule, matcher);
27
- if (isMatch && options) {
28
- const specifiersResult = specifiersMatch(dependency.specifiers, options.specifiers);
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,