eslint-plugin-boundaries 3.4.0 → 4.0.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 +67 -15
- package/package.json +10 -10
- package/src/constants/settings.js +26 -0
- package/src/helpers/rules.js +0 -18
- package/src/helpers/utils.js +10 -0
- package/src/helpers/validations.js +65 -2
- package/src/rules/element-types.js +1 -6
- package/src/rules/entry-point.js +0 -2
- package/src/rules/external.js +18 -9
- package/src/rules/no-ignored.js +0 -3
- package/src/rules/no-private.js +0 -2
- package/src/rules/no-unknown.js +0 -3
- package/src/rules-factories/dependency-rule.js +26 -5
package/README.md
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
In words of Robert C. Martin, _"Software architecture is the art of drawing lines that I call boundaries. Those boundaries separate software elements from one another, and restrict those on one side from knowing about those on the other."_ _([\*acknowledgements](#acknowledgements))_
|
|
10
10
|
|
|
11
|
-
__This plugin ensures that your architecture boundaries are respected by the elements in your project__ checking the folders and files structure and the
|
|
11
|
+
__This plugin ensures that your architecture boundaries are respected by the elements in your project__ checking the folders and files structure and the dependencies between them. __It is not a replacement for [eslint-plugin-import](https://www.npmjs.com/package/eslint-plugin-import), on the contrary, the combination of both plugins is recommended.__
|
|
12
|
+
|
|
13
|
+
By default, __the plugin works by checking `import` statements, but it is also able to analyze exports, dynamic imports, and can be configured to check any other [AST nodes](https://eslint.org/docs/latest/extend/selectors)__. (_Read the [main rules overview](#main-rules-overview) and [configuration](#configuration) chapters for better comprehension_)
|
|
12
14
|
|
|
13
15
|
## Table of Contents
|
|
14
16
|
|
|
@@ -33,6 +35,7 @@ __This plugin ensures that your architecture boundaries are respected by the ele
|
|
|
33
35
|
* [Advanced example](#advanced-example)
|
|
34
36
|
- [Resolvers](#resolvers)
|
|
35
37
|
- [Usage with TypeScript](#usage-with-typescript)
|
|
38
|
+
- [Migration guides](#migration-guides)
|
|
36
39
|
- [Debug mode](#debug-mode)
|
|
37
40
|
- [Acknowledgements](#acknowledgements)
|
|
38
41
|
- [Contributing](#contributing)
|
|
@@ -59,15 +62,11 @@ Activate the plugin and one of the canned configs in your `.eslintrc.(yml|json|j
|
|
|
59
62
|
}
|
|
60
63
|
```
|
|
61
64
|
|
|
62
|
-
## Migrating from v1.x
|
|
63
|
-
|
|
64
|
-
New v2.0.0 release has introduced many breaking changes. If you were using v1.x, you should [read the "how to migrate from v1 to v2" guide](./docs/guides/how-to-migrate-from-v1-to-v2.md).
|
|
65
|
-
|
|
66
65
|
## Overview
|
|
67
66
|
|
|
68
|
-
All of the plugin rules need to be able to identify the elements in the project, so, first of all you have to define your project
|
|
67
|
+
All of the plugin rules need to be able to identify the elements in the project, so, first of all you have to define your project element types by using the `boundaries/elements` setting.
|
|
69
68
|
|
|
70
|
-
The plugin will use the provided patterns to identify each file
|
|
69
|
+
The plugin will use the provided patterns to identify each file as one of the element types. It will also assign a type to each dependency detected in the [dependency nodes (`import` or other statements)](#boundariesdependency-nodes), and it will check if the relationship between the dependent element and the dependency is allowed or not.
|
|
71
70
|
|
|
72
71
|
```json
|
|
73
72
|
{
|
|
@@ -92,7 +91,7 @@ The plugin will use the provided patterns to identify each file or local `import
|
|
|
92
91
|
|
|
93
92
|
This is only a basic example of configuration. The plugin can be configured to identify elements being a file, or elements being a folder containing files. It also supports capturing path fragments to be used afterwards on each rule options, etc. __Read the [configuration chapter](#configuration) for further info, as configuring it properly is crucial__ to take advantage of all of the plugin features.
|
|
94
93
|
|
|
95
|
-
Once your project
|
|
94
|
+
Once your project element types are defined, you can use them to configure each rule using its own options. For example, you could define which elements can be dependencies of other ones by configuring the `element-types` rule as in:
|
|
96
95
|
|
|
97
96
|
```json
|
|
98
97
|
{
|
|
@@ -114,7 +113,7 @@ Once your project elements are defined, you can use them to configure each rule
|
|
|
114
113
|
}
|
|
115
114
|
```
|
|
116
115
|
|
|
117
|
-
> The plugin won't apply rules to a file or
|
|
116
|
+
> The plugin won't apply rules to a file or dependency when it does not recognize its element type, but you can force all files in your project to belong to an element type by enabling the [boundaries/no-unknown-files](docs/rules/no-unknown-files.md) rule.
|
|
118
117
|
|
|
119
118
|
## Main rules overview
|
|
120
119
|
|
|
@@ -202,6 +201,52 @@ Define patterns to recognize each file in the project as one of this element typ
|
|
|
202
201
|
|
|
203
202
|
> Tip: You can enable the [debug mode](#debug-mode) when configuring the plugin, and you will get information about the type assigned to each file in the project, as well as captured properties and values.
|
|
204
203
|
|
|
204
|
+
#### __`boundaries/dependency-nodes`__
|
|
205
|
+
|
|
206
|
+
This setting allows to modify built-in default dependency nodes. By default, the plugin will analyze only the `import` statements. All the rules defined for the plugin will be applicable to the nodes defined in this setting.
|
|
207
|
+
|
|
208
|
+
The setting should be an array of the following strings:
|
|
209
|
+
|
|
210
|
+
* `'import'`: analyze `import` statements.
|
|
211
|
+
* `'export'`: analyze `export` statements.
|
|
212
|
+
* `'dynamic-import'`: analyze [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) statements.
|
|
213
|
+
|
|
214
|
+
If you want to define custom dependency nodes, such as `jest.mock(...)`, use [additional-dependency-nodes](#boundariesadditional-dependency-nodes) setting.
|
|
215
|
+
|
|
216
|
+
For example, if you want to analyze the `import` and `dynamic-import` statements, you should use the following value:
|
|
217
|
+
|
|
218
|
+
```jsonc
|
|
219
|
+
"boundaries/dependency-nodes": ["import", "dynamic-import"],
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
#### __`boundaries/additional-dependency-nodes`__
|
|
223
|
+
|
|
224
|
+
This setting allows to define custom dependency nodes to analyze. All the rules defined for the plugin will be applicable to the nodes defined in this setting.
|
|
225
|
+
|
|
226
|
+
The setting should be an array of objects with the following structure:
|
|
227
|
+
|
|
228
|
+
* __`selector`__: The [esquery selector](https://github.com/estools/esquery) for the `Literal` node in which dependency source are defined. For example, to analyze `jest.mock(...)` calls you could use this [AST selector](https://eslint.org/docs/latest/extend/selectors): `CallExpression[callee.object.name=jest][callee.property.name=mock] > Literal:first-child`.
|
|
229
|
+
* __`kind`__: The kind of dependency, possible values are: `"value"` or `"type"`. It is available only when using TypeScript.
|
|
230
|
+
|
|
231
|
+
Example of usage:
|
|
232
|
+
|
|
233
|
+
```jsonc
|
|
234
|
+
{
|
|
235
|
+
"boundaries/additional-dependency-nodes": [
|
|
236
|
+
// jest.requireActual('source')
|
|
237
|
+
{
|
|
238
|
+
"selector": "CallExpression[callee.object.name=jest][callee.property.name=requireActual] > Literal",
|
|
239
|
+
"kind": "value",
|
|
240
|
+
},
|
|
241
|
+
// jest.mock('source', ...)
|
|
242
|
+
{
|
|
243
|
+
"selector": "CallExpression[callee.object.name=jest][callee.property.name=mock] > Literal:first-child",
|
|
244
|
+
"kind": "value",
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
205
250
|
#### __`boundaries/include`__
|
|
206
251
|
|
|
207
252
|
Files or dependencies not matching these [`micromatch` patterns](https://github.com/micromatch/micromatch) will be ignored by the plugin. If this option is not provided, all files will be included.
|
|
@@ -235,7 +280,7 @@ Use this setting only if you are facing issues with the plugin when executing th
|
|
|
235
280
|
<details>
|
|
236
281
|
<summary>How to define the root path of the project</summary>
|
|
237
282
|
|
|
238
|
-
By default, the plugin uses the current working directory (`process.cwd()`) as root path of the project. This path is used as the base path when resolving file matchers from rules and `boundaries/elements` settings. This is specially important when using the `basePattern` option or the `full` mode in the `boundaries/elements` setting. This may produce unexpected results [when the lint command is executed from a different path than the project root](https://github.com/javierbrea/eslint-plugin-boundaries/issues/296). To fix this, you can define a different root path using this option.
|
|
283
|
+
By default, the plugin uses the current working directory (`process.cwd()`) as root path of the project. This path is used as the base path when resolving file matchers from rules and `boundaries/elements` settings. This is specially important when using the `basePattern` option or the `full` mode in the `boundaries/elements` setting. This may produce unexpected results [when the lint command is executed from a different path than the project root](https://github.com/javierbrea/eslint-plugin-boundaries/issues/296). To fix this, you can define a different root path by using this option.
|
|
239
284
|
|
|
240
285
|
For example, supposing that the `.eslintrc.js` file is located in the project root, you could define the root path as in:
|
|
241
286
|
|
|
@@ -257,12 +302,9 @@ You can also provide an absolute path in the environment variable, but it may be
|
|
|
257
302
|
|
|
258
303
|
</details>
|
|
259
304
|
|
|
260
|
-
|
|
261
|
-
|
|
262
305
|
### Predefined configurations
|
|
263
306
|
|
|
264
|
-
|
|
265
|
-
|
|
307
|
+
The plugin is distributed with two different predefined configurations: "recommended" and "strict".
|
|
266
308
|
|
|
267
309
|
#### Recommended
|
|
268
310
|
|
|
@@ -333,7 +375,7 @@ Remember that:
|
|
|
333
375
|
|
|
334
376
|
* __`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`).
|
|
335
377
|
* __`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.
|
|
336
|
-
* __`importKind`__: `<string>` _Optional_. It is useful only when using TypeScript,
|
|
378
|
+
* __`importKind`__: `<string>` _Optional_. It is useful only when using TypeScript, because 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.
|
|
337
379
|
* __`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.
|
|
338
380
|
|
|
339
381
|
> Tip: Properties `from/target` and `disallow/allow` can receive a single matcher, or an array of matchers.
|
|
@@ -511,6 +553,16 @@ module.exports = {
|
|
|
511
553
|
|
|
512
554
|
In case you face any issue configuring it, you can also [use this repository as a guide](https://github.com/javierbrea/epb-ts-example). It contains a fully working and tested example.
|
|
513
555
|
|
|
556
|
+
## Migration guides
|
|
557
|
+
|
|
558
|
+
### Migrating from v3.x
|
|
559
|
+
|
|
560
|
+
New v4.0.0 release has introduced breaking changes. If you were using v3.x, you should [read the "how to migrate from v3 to v4" guide](./docs/guides/how-to-migrate-from-v3-to-v4.md).
|
|
561
|
+
|
|
562
|
+
### Migrating from v1.x
|
|
563
|
+
|
|
564
|
+
New v2.0.0 release has introduced many breaking changes. If you were using v1.x, you should [read the "how to migrate from v1 to v2" guide](./docs/guides/how-to-migrate-from-v1-to-v2.md).
|
|
565
|
+
|
|
514
566
|
## Debug mode
|
|
515
567
|
|
|
516
568
|
In order to help during the configuration process, the plugin can trace information about the files and imports being analyzed. The information includes the file path, the assigned element type, the captured values, etc. So, it can help you to check that your `elements` setting works as expected. You can enable it using the `ESLINT_PLUGIN_BOUNDARIES_DEBUG` environment variable.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-boundaries",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Eslint plugin checking architecture boundaries between elements",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -34,22 +34,22 @@
|
|
|
34
34
|
"chalk": "4.1.2",
|
|
35
35
|
"eslint-import-resolver-node": "0.3.9",
|
|
36
36
|
"eslint-module-utils": "2.8.0",
|
|
37
|
-
"is-core-module": "2.13.
|
|
37
|
+
"is-core-module": "2.13.1",
|
|
38
38
|
"micromatch": "4.0.5"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@typescript-eslint/eslint-plugin": "6.
|
|
42
|
-
"@typescript-eslint/parser": "6.
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "6.9.1",
|
|
42
|
+
"@typescript-eslint/parser": "6.9.1",
|
|
43
43
|
"cross-env": "7.0.3",
|
|
44
|
-
"eslint": "8.
|
|
44
|
+
"eslint": "8.52.0",
|
|
45
45
|
"eslint-config-prettier": "9.0.0",
|
|
46
|
-
"eslint-import-resolver-typescript": "3.6.
|
|
47
|
-
"eslint-plugin-prettier": "5.0.
|
|
46
|
+
"eslint-import-resolver-typescript": "3.6.1",
|
|
47
|
+
"eslint-plugin-prettier": "5.0.1",
|
|
48
48
|
"husky": "8.0.3",
|
|
49
49
|
"is-ci": "3.0.1",
|
|
50
|
-
"jest": "29.
|
|
51
|
-
"lint-staged": "
|
|
52
|
-
"prettier": "3.0.
|
|
50
|
+
"jest": "29.7.0",
|
|
51
|
+
"lint-staged": "15.0.2",
|
|
52
|
+
"prettier": "3.0.3"
|
|
53
53
|
},
|
|
54
54
|
"lint-staged": {
|
|
55
55
|
"test/**/*.js": "eslint",
|
|
@@ -16,6 +16,8 @@ module.exports = {
|
|
|
16
16
|
IGNORE: `${PLUGIN_NAME}/ignore`,
|
|
17
17
|
INCLUDE: `${PLUGIN_NAME}/include`,
|
|
18
18
|
ROOT_PATH: `${PLUGIN_NAME}/root-path`,
|
|
19
|
+
DEPENDENCY_NODES: `${PLUGIN_NAME}/dependency-nodes`,
|
|
20
|
+
ADDITIONAL_DEPENDENCY_NODES: `${PLUGIN_NAME}/additional-dependency-nodes`,
|
|
19
21
|
|
|
20
22
|
// env vars
|
|
21
23
|
DEBUG: `${PLUGIN_ENV_VARS_PREFIX}_DEBUG`,
|
|
@@ -36,4 +38,28 @@ module.exports = {
|
|
|
36
38
|
|
|
37
39
|
// elements settings properties,
|
|
38
40
|
VALID_MODES: ["folder", "file", "full"],
|
|
41
|
+
|
|
42
|
+
VALID_DEPENDENCY_NODE_KINDS: ["value", "type"],
|
|
43
|
+
DEFAULT_DEPENDENCY_NODES: {
|
|
44
|
+
import: [
|
|
45
|
+
// Note: detects "import x from 'source'"
|
|
46
|
+
{ selector: "ImportDeclaration:not([importKind=type]) > Literal", kind: "value" },
|
|
47
|
+
// Note: detects "import type x from 'source'"
|
|
48
|
+
{ selector: "ImportDeclaration[importKind=type] > Literal", kind: "type" },
|
|
49
|
+
],
|
|
50
|
+
"dynamic-import": [
|
|
51
|
+
// Note: detects "import('source')"
|
|
52
|
+
{ selector: "ImportExpression > Literal", kind: "value" },
|
|
53
|
+
],
|
|
54
|
+
export: [
|
|
55
|
+
// Note: detects "export * from 'source'";
|
|
56
|
+
{ selector: "ExportAllDeclaration:not([exportKind=type]) > Literal", kind: "value" },
|
|
57
|
+
// Note: detects "export type * from 'source'";
|
|
58
|
+
{ selector: "ExportAllDeclaration[exportKind=type] > Literal", kind: "type" },
|
|
59
|
+
// Note: detects "export { x } from 'source'";
|
|
60
|
+
{ selector: "ExportNamedDeclaration:not([exportKind=type]) > Literal", kind: "value" },
|
|
61
|
+
// Note: detects "export type { x } from 'source'";
|
|
62
|
+
{ selector: "ExportNamedDeclaration[exportKind=type] > Literal", kind: "type" },
|
|
63
|
+
],
|
|
64
|
+
},
|
|
39
65
|
};
|
package/src/helpers/rules.js
CHANGED
|
@@ -28,23 +28,6 @@ function meta({ description, schema = [], ruleName }) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
function dependencyLocation(node, context) {
|
|
32
|
-
const columnStart = context.getSourceCode().getText(node).indexOf(node.source.value) - 1;
|
|
33
|
-
const columnEnd = columnStart + node.source.value.length + 2;
|
|
34
|
-
return {
|
|
35
|
-
loc: {
|
|
36
|
-
start: {
|
|
37
|
-
line: node.loc.start.line,
|
|
38
|
-
column: columnStart,
|
|
39
|
-
},
|
|
40
|
-
end: {
|
|
41
|
-
line: node.loc.end.line,
|
|
42
|
-
column: columnEnd,
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
31
|
function micromatchPatternReplacingObjectsValues(pattern, object) {
|
|
49
32
|
let patternToReplace = pattern;
|
|
50
33
|
// Backward compatibility
|
|
@@ -237,7 +220,6 @@ function elementRulesAllowDependency({
|
|
|
237
220
|
|
|
238
221
|
module.exports = {
|
|
239
222
|
meta,
|
|
240
|
-
dependencyLocation,
|
|
241
223
|
isObjectMatch,
|
|
242
224
|
isMatchElementKey,
|
|
243
225
|
isMatchElementType,
|
package/src/helpers/utils.js
CHANGED
|
@@ -6,6 +6,14 @@ function isArray(object) {
|
|
|
6
6
|
return Array.isArray(object);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function isObject(object) {
|
|
10
|
+
return typeof object === "object" && object !== null && !isArray(object);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function getArrayOrNull(value) {
|
|
14
|
+
return isArray(value) ? value : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
function replaceObjectValueInTemplate(template, key, value, namespace) {
|
|
10
18
|
const keyToReplace = namespace ? `${namespace}.${key}` : key;
|
|
11
19
|
const regexp = new RegExp(`\\$\\{${keyToReplace}\\}`, "g");
|
|
@@ -27,5 +35,7 @@ function replaceObjectValuesInTemplates(strings, object, namespace) {
|
|
|
27
35
|
module.exports = {
|
|
28
36
|
isString,
|
|
29
37
|
isArray,
|
|
38
|
+
isObject,
|
|
39
|
+
getArrayOrNull,
|
|
30
40
|
replaceObjectValuesInTemplates,
|
|
31
41
|
};
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
const micromatch = require("micromatch");
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const {
|
|
4
|
+
TYPES,
|
|
5
|
+
ALIAS,
|
|
6
|
+
ELEMENTS,
|
|
7
|
+
VALID_MODES,
|
|
8
|
+
DEPENDENCY_NODES,
|
|
9
|
+
ADDITIONAL_DEPENDENCY_NODES,
|
|
10
|
+
VALID_DEPENDENCY_NODE_KINDS,
|
|
11
|
+
DEFAULT_DEPENDENCY_NODES,
|
|
12
|
+
} = require("../constants/settings");
|
|
4
13
|
|
|
5
14
|
const { getElementsTypeNames, isLegacyType } = require("./settings");
|
|
6
15
|
const { rulesMainKey } = require("./rules");
|
|
7
16
|
const { warnOnce } = require("./debug");
|
|
8
|
-
const { isArray, isString } = require("./utils");
|
|
17
|
+
const { isArray, isString, isObject } = require("./utils");
|
|
9
18
|
|
|
10
19
|
const invalidMatchers = [];
|
|
11
20
|
|
|
@@ -147,6 +156,58 @@ function validateElements(elements) {
|
|
|
147
156
|
});
|
|
148
157
|
}
|
|
149
158
|
|
|
159
|
+
function validateDependencyNodes(dependencyNodes) {
|
|
160
|
+
if (!dependencyNodes) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const defaultNodesNames = Object.keys(DEFAULT_DEPENDENCY_NODES);
|
|
165
|
+
const invalidFormatMessage = [
|
|
166
|
+
`Please provide a valid value in ${DEPENDENCY_NODES} setting.`,
|
|
167
|
+
`The value should be an array of the following strings:`,
|
|
168
|
+
` "${defaultNodesNames.join('", "')}".`,
|
|
169
|
+
].join(" ");
|
|
170
|
+
|
|
171
|
+
if (!isArray(dependencyNodes)) {
|
|
172
|
+
warnOnce(invalidFormatMessage);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
dependencyNodes.forEach((dependencyNode) => {
|
|
177
|
+
if (!isString(dependencyNode) || !defaultNodesNames.includes(dependencyNode)) {
|
|
178
|
+
warnOnce(invalidFormatMessage);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function validateAdditionalDependencyNodes(additionalDependencyNodes) {
|
|
184
|
+
if (!additionalDependencyNodes) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const invalidFormatMessage = [
|
|
189
|
+
`Please provide a valid value in ${ADDITIONAL_DEPENDENCY_NODES} setting.`,
|
|
190
|
+
"The value should be an array composed of the following objects:",
|
|
191
|
+
'{ selector: "<esquery selector>", kind: "value" | "type" }.',
|
|
192
|
+
].join(" ");
|
|
193
|
+
|
|
194
|
+
if (!isArray(additionalDependencyNodes)) {
|
|
195
|
+
warnOnce(invalidFormatMessage);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
additionalDependencyNodes.forEach((dependencyNode) => {
|
|
200
|
+
const isValidObject =
|
|
201
|
+
isObject(dependencyNode) &&
|
|
202
|
+
isString(dependencyNode.selector) &&
|
|
203
|
+
(!dependencyNode.kind || VALID_DEPENDENCY_NODE_KINDS.includes(dependencyNode.kind));
|
|
204
|
+
|
|
205
|
+
if (!isValidObject) {
|
|
206
|
+
warnOnce(invalidFormatMessage);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
150
211
|
function deprecateAlias(aliases) {
|
|
151
212
|
if (aliases) {
|
|
152
213
|
warnOnce(
|
|
@@ -165,6 +226,8 @@ function validateSettings(settings) {
|
|
|
165
226
|
deprecateTypes(settings[TYPES]);
|
|
166
227
|
deprecateAlias(settings[ALIAS]);
|
|
167
228
|
validateElements(settings[ELEMENTS] || settings[TYPES]);
|
|
229
|
+
validateDependencyNodes(settings[DEPENDENCY_NODES]);
|
|
230
|
+
validateAdditionalDependencyNodes(settings[ADDITIONAL_DEPENDENCY_NODES]);
|
|
168
231
|
}
|
|
169
232
|
|
|
170
233
|
function validateRules(settings, rules = [], options = {}) {
|
|
@@ -3,11 +3,7 @@ const { RULE_ELEMENT_TYPES } = require("../constants/settings");
|
|
|
3
3
|
const dependencyRule = require("../rules-factories/dependency-rule");
|
|
4
4
|
|
|
5
5
|
const { rulesOptionsSchema } = require("../helpers/validations");
|
|
6
|
-
const {
|
|
7
|
-
dependencyLocation,
|
|
8
|
-
isMatchElementType,
|
|
9
|
-
elementRulesAllowDependency,
|
|
10
|
-
} = require("../helpers/rules");
|
|
6
|
+
const { isMatchElementType, elementRulesAllowDependency } = require("../helpers/rules");
|
|
11
7
|
const {
|
|
12
8
|
customErrorMessage,
|
|
13
9
|
ruleElementMessage,
|
|
@@ -59,7 +55,6 @@ module.exports = dependencyRule(
|
|
|
59
55
|
context.report({
|
|
60
56
|
message: errorMessage(ruleData, file, dependency),
|
|
61
57
|
node: node,
|
|
62
|
-
...dependencyLocation(node, context),
|
|
63
58
|
});
|
|
64
59
|
}
|
|
65
60
|
}
|
package/src/rules/entry-point.js
CHANGED
|
@@ -4,7 +4,6 @@ const dependencyRule = require("../rules-factories/dependency-rule");
|
|
|
4
4
|
|
|
5
5
|
const { rulesOptionsSchema } = require("../helpers/validations");
|
|
6
6
|
const {
|
|
7
|
-
dependencyLocation,
|
|
8
7
|
isMatchElementKey,
|
|
9
8
|
elementRulesAllowDependency,
|
|
10
9
|
isMatchImportKind,
|
|
@@ -73,7 +72,6 @@ module.exports = dependencyRule(
|
|
|
73
72
|
context.report({
|
|
74
73
|
message: errorMessage(ruleData, file, dependency),
|
|
75
74
|
node: node,
|
|
76
|
-
...dependencyLocation(node, context),
|
|
77
75
|
});
|
|
78
76
|
}
|
|
79
77
|
}
|
package/src/rules/external.js
CHANGED
|
@@ -6,7 +6,6 @@ const dependencyRule = require("../rules-factories/dependency-rule");
|
|
|
6
6
|
|
|
7
7
|
const { rulesOptionsSchema } = require("../helpers/validations");
|
|
8
8
|
const {
|
|
9
|
-
dependencyLocation,
|
|
10
9
|
elementRulesAllowDependency,
|
|
11
10
|
micromatchPatternReplacingObjectsValues,
|
|
12
11
|
isMatchImportKind,
|
|
@@ -19,18 +18,29 @@ const {
|
|
|
19
18
|
} = require("../helpers/messages");
|
|
20
19
|
const { isArray } = require("../helpers/utils");
|
|
21
20
|
|
|
21
|
+
function getSpecifiers(node) {
|
|
22
|
+
if (node.parent.type === "ImportDeclaration") {
|
|
23
|
+
return node.parent.specifiers
|
|
24
|
+
.filter((specifier) => specifier.type === "ImportSpecifier" && specifier.imported.name)
|
|
25
|
+
.map((specifier) => specifier.imported.name);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (node.parent.type === "ExportNamedDeclaration") {
|
|
29
|
+
return node.parent.specifiers
|
|
30
|
+
.filter((specifier) => specifier.type === "ExportSpecifier" && specifier.exported.name)
|
|
31
|
+
.map((specifier) => specifier.exported.name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
|
|
22
37
|
function specifiersMatch(specifiers, specifierOptions, elementsCapturedValues) {
|
|
23
|
-
const importedSpecifiersNames = specifiers
|
|
24
|
-
.filter((specifier) => {
|
|
25
|
-
return specifier.type === "ImportSpecifier" && specifier.imported.name;
|
|
26
|
-
})
|
|
27
|
-
.map((specifier) => specifier.imported.name);
|
|
28
38
|
return specifierOptions.reduce((found, option) => {
|
|
29
39
|
const matcherWithTemplateReplaced = micromatchPatternReplacingObjectsValues(
|
|
30
40
|
option,
|
|
31
41
|
elementsCapturedValues,
|
|
32
42
|
);
|
|
33
|
-
if (micromatch.some(
|
|
43
|
+
if (micromatch.some(specifiers, matcherWithTemplateReplaced)) {
|
|
34
44
|
found.push(option);
|
|
35
45
|
}
|
|
36
46
|
return found;
|
|
@@ -182,14 +192,13 @@ module.exports = dependencyRule(
|
|
|
182
192
|
if (dependency.isExternal) {
|
|
183
193
|
const ruleData = elementRulesAllowExternalDependency(
|
|
184
194
|
file,
|
|
185
|
-
{ ...dependency, specifiers: node
|
|
195
|
+
{ ...dependency, specifiers: getSpecifiers(node) },
|
|
186
196
|
options,
|
|
187
197
|
);
|
|
188
198
|
if (!ruleData.result) {
|
|
189
199
|
context.report({
|
|
190
200
|
message: errorMessage(ruleData, file, dependency),
|
|
191
201
|
node: node,
|
|
192
|
-
...dependencyLocation(node, context),
|
|
193
202
|
});
|
|
194
203
|
}
|
|
195
204
|
}
|
package/src/rules/no-ignored.js
CHANGED
|
@@ -2,8 +2,6 @@ const { RULE_NO_IGNORED } = require("../constants/settings");
|
|
|
2
2
|
|
|
3
3
|
const dependencyRule = require("../rules-factories/dependency-rule");
|
|
4
4
|
|
|
5
|
-
const { dependencyLocation } = require("../helpers/rules");
|
|
6
|
-
|
|
7
5
|
module.exports = dependencyRule(
|
|
8
6
|
{
|
|
9
7
|
ruleName: RULE_NO_IGNORED,
|
|
@@ -14,7 +12,6 @@ module.exports = dependencyRule(
|
|
|
14
12
|
context.report({
|
|
15
13
|
message: `Importing ignored files is not allowed`,
|
|
16
14
|
node: node,
|
|
17
|
-
...dependencyLocation(node, context),
|
|
18
15
|
});
|
|
19
16
|
}
|
|
20
17
|
},
|
package/src/rules/no-private.js
CHANGED
|
@@ -2,7 +2,6 @@ const { RULE_NO_PRIVATE } = require("../constants/settings");
|
|
|
2
2
|
|
|
3
3
|
const dependencyRule = require("../rules-factories/dependency-rule");
|
|
4
4
|
|
|
5
|
-
const { dependencyLocation } = require("../helpers/rules");
|
|
6
5
|
const { customErrorMessage, elementMessage } = require("../helpers/messages");
|
|
7
6
|
|
|
8
7
|
function errorMessage(file, dependency, options) {
|
|
@@ -45,7 +44,6 @@ module.exports = dependencyRule(
|
|
|
45
44
|
context.report({
|
|
46
45
|
message: errorMessage(file, dependency, options),
|
|
47
46
|
node: node,
|
|
48
|
-
...dependencyLocation(node, context),
|
|
49
47
|
});
|
|
50
48
|
}
|
|
51
49
|
},
|
package/src/rules/no-unknown.js
CHANGED
|
@@ -2,8 +2,6 @@ const { RULE_NO_UNKNOWN } = require("../constants/settings");
|
|
|
2
2
|
|
|
3
3
|
const dependencyRule = require("../rules-factories/dependency-rule");
|
|
4
4
|
|
|
5
|
-
const { dependencyLocation } = require("../helpers/rules");
|
|
6
|
-
|
|
7
5
|
module.exports = dependencyRule(
|
|
8
6
|
{
|
|
9
7
|
ruleName: RULE_NO_UNKNOWN,
|
|
@@ -14,7 +12,6 @@ module.exports = dependencyRule(
|
|
|
14
12
|
context.report({
|
|
15
13
|
message: `Importing unknown elements is not allowed`,
|
|
16
14
|
node: node,
|
|
17
|
-
...dependencyLocation(node, context),
|
|
18
15
|
});
|
|
19
16
|
}
|
|
20
17
|
},
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
const {
|
|
2
|
+
DEPENDENCY_NODES,
|
|
3
|
+
DEFAULT_DEPENDENCY_NODES,
|
|
4
|
+
ADDITIONAL_DEPENDENCY_NODES,
|
|
5
|
+
} = require("../constants/settings");
|
|
6
|
+
const { getArrayOrNull } = require("../helpers/utils");
|
|
1
7
|
const { fileInfo } = require("../core/elementsInfo");
|
|
2
8
|
const { dependencyInfo } = require("../core/dependencyInfo");
|
|
3
9
|
|
|
@@ -19,13 +25,28 @@ module.exports = function (ruleMeta, rule, ruleOptions = {}) {
|
|
|
19
25
|
validateRules(context.settings, options.rules, ruleOptions.validateRules);
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
const dependencyNodesSetting = getArrayOrNull(context.settings[DEPENDENCY_NODES]);
|
|
29
|
+
const additionalDependencyNodesSetting = getArrayOrNull(
|
|
30
|
+
context.settings[ADDITIONAL_DEPENDENCY_NODES],
|
|
31
|
+
);
|
|
32
|
+
const dependencyNodes = (dependencyNodesSetting || ["import"])
|
|
33
|
+
.map((dependencyNode) => DEFAULT_DEPENDENCY_NODES[dependencyNode])
|
|
34
|
+
.flat()
|
|
35
|
+
.filter(Boolean);
|
|
36
|
+
const additionalDependencyNodes = additionalDependencyNodesSetting || [];
|
|
25
37
|
|
|
26
|
-
|
|
38
|
+
return [...dependencyNodes, ...additionalDependencyNodes].reduce(
|
|
39
|
+
(visitors, { selector, kind }) => {
|
|
40
|
+
visitors[selector] = (node) => {
|
|
41
|
+
const dependency = dependencyInfo(node.value, kind, context);
|
|
42
|
+
|
|
43
|
+
rule({ file, dependency, options, node, context });
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
return visitors;
|
|
27
47
|
},
|
|
28
|
-
|
|
48
|
+
{},
|
|
49
|
+
);
|
|
29
50
|
},
|
|
30
51
|
};
|
|
31
52
|
};
|