Skip to content

Commit e46a12c

Browse files
authored
Merge pull request #319 from javierbrea/release
Release v4.0.0
2 parents 8725895 + 9cb9743 commit e46a12c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1224
-249
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ yarn-error.log*
2323
# ides
2424
.idea
2525
.vs
26+
.vscode
2627

2728
# stryker temp files
2829
/reports

CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
99
### Changed
1010
### Fixed
1111
### Removed
12+
### BREAKING CHANGES
13+
14+
## [4.0.0] - 2023-12-01
15+
16+
### Added
17+
18+
- feat(#213): Add `dependency-nodes` setting to allow analyzing dependencies from additional nodes, such as exports or dynamic imports.
19+
- feat: Add `additional-dependency-nodes` setting to add custom dependency nodes to the default ones. For example, you could enable to analyze dependencies in `jest.mock(...)`, etc.
20+
21+
### BREAKING CHANGES
22+
23+
- fix: Fixed the error position in multiline imports. See ["how to migrate from v3 to v4" guide](./docs/guides/how-to-migrate-from-v3-to-v4.md).
1224

1325
## [3.4.1] - 2023-11-01
1426

README.md

Lines changed: 67 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88

99
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))_
1010

11-
__This plugin ensures that your architecture boundaries are respected by the elements in your project__ checking the folders and files structure and the `import` statements (_Read the [main rules overview chapter](#main-rules-overview) for better comprehension._). __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.__
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_)
1214

1315
## Table of Contents
1416

@@ -33,6 +35,7 @@ __This plugin ensures that your architecture boundaries are respected by the ele
3335
* [Advanced example](#advanced-example)
3436
- [Resolvers](#resolvers)
3537
- [Usage with TypeScript](#usage-with-typescript)
38+
- [Migration guides](#migration-guides)
3639
- [Debug mode](#debug-mode)
3740
- [Acknowledgements](#acknowledgements)
3841
- [Contributing](#contributing)
@@ -59,15 +62,11 @@ Activate the plugin and one of the canned configs in your `.eslintrc.(yml|json|j
5962
}
6063
```
6164

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-
6665
## Overview
6766

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 elements using the `boundaries/elements` setting.
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.
6968

70-
The plugin will use the provided patterns to identify each file or local `import` statement as one of the element types.
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.
7170

7271
```json
7372
{
@@ -92,7 +91,7 @@ The plugin will use the provided patterns to identify each file or local `import
9291

9392
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.
9493

95-
Once your project elements 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 configuring the `element-types` rule as in:
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:
9695

9796
```json
9897
{
@@ -114,7 +113,7 @@ Once your project elements are defined, you can use them to configure each rule
114113
}
115114
```
116115

117-
> The plugin won't apply rules to a file or `import` when it does not recognize its element type, but you can force all files in your project to belong to an element type enabling the [boundaries/no-unknown-files](docs/rules/no-unknown-files.md) rule.
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.
118117
119118
## Main rules overview
120119

@@ -202,6 +201,52 @@ Define patterns to recognize each file in the project as one of this element typ
202201

203202
> 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.
204203
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+
205250
#### __`boundaries/include`__
206251

207252
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
235280
<details>
236281
<summary>How to define the root path of the project</summary>
237282

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.
239284

240285
For example, supposing that the `.eslintrc.js` file is located in the project root, you could define the root path as in:
241286

@@ -257,12 +302,9 @@ You can also provide an absolute path in the environment variable, but it may be
257302

258303
</details>
259304

260-
261-
262305
### Predefined configurations
263306

264-
This plugin is distributed with two different predefined configurations: "recommended" and "strict".
265-
307+
The plugin is distributed with two different predefined configurations: "recommended" and "strict".
266308

267309
#### Recommended
268310

@@ -333,7 +375,7 @@ Remember that:
333375

334376
* __`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`).
335377
* __`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, as 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.
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.
337379
* __`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.
338380

339381
> Tip: Properties `from/target` and `disallow/allow` can receive a single matcher, or an array of matchers.
@@ -511,6 +553,16 @@ module.exports = {
511553
512554
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.
513555

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+
514566
## Debug mode
515567

516568
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.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# How to migrate from v3.x to v4.x
2+
3+
## Table of Contents
4+
5+
- [Breaking changes](#breaking-changes)
6+
- [How to migrate](#how-to-migrate)
7+
8+
## Breaking changes
9+
10+
There is only one breaking change in the v4.0.0 release. We've fixed the bug that caused ESLint to incorrectly mark the error position for multiline imports.
11+
12+
Previous behavior:
13+
14+
```js
15+
import {
16+
// ----^ (start of the error)
17+
ComponentA
18+
} from './components/component-a';
19+
// -----------------------------^ (end of the error)
20+
```
21+
22+
Fixed behavior:
23+
24+
```js
25+
import {
26+
ComponentA
27+
} from './components/component-a';
28+
// ----^ (start) ---------------^ (end)
29+
```
30+
31+
## How to migrate
32+
33+
You need to adjust your `eslint-disable-next-line` directives to match the new position.
34+
35+
For example, this directive:
36+
37+
```js
38+
// eslint-disable-next-line
39+
import {
40+
ComponentA
41+
} from './components/component-a';
42+
```
43+
44+
Should be moved here:
45+
46+
```js
47+
import {
48+
ComponentA
49+
// eslint-disable-next-line
50+
} from './components/component-a';
51+
```

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "eslint-plugin-boundaries",
3-
"version": "3.4.1",
3+
"version": "4.0.0",
44
"description": "Eslint plugin checking architecture boundaries between elements",
55
"keywords": [
66
"eslint",

sonar-project.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
sonar.organization=javierbrea
22
sonar.projectKey=javierbrea_eslint-plugin-boundaries
3-
sonar.projectVersion=3.4.1
3+
sonar.projectVersion=4.0.0
44

55
sonar.javascript.file.suffixes=.js
66
sonar.sourceEncoding=UTF-8

src/constants/settings.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ module.exports = {
1616
IGNORE: `${PLUGIN_NAME}/ignore`,
1717
INCLUDE: `${PLUGIN_NAME}/include`,
1818
ROOT_PATH: `${PLUGIN_NAME}/root-path`,
19+
DEPENDENCY_NODES: `${PLUGIN_NAME}/dependency-nodes`,
20+
ADDITIONAL_DEPENDENCY_NODES: `${PLUGIN_NAME}/additional-dependency-nodes`,
1921

2022
// env vars
2123
DEBUG: `${PLUGIN_ENV_VARS_PREFIX}_DEBUG`,
@@ -36,4 +38,28 @@ module.exports = {
3638

3739
// elements settings properties,
3840
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+
},
3965
};

src/helpers/rules.js

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,6 @@ function meta({ description, schema = [], ruleName }) {
2828
};
2929
}
3030

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-
4831
function micromatchPatternReplacingObjectsValues(pattern, object) {
4932
let patternToReplace = pattern;
5033
// Backward compatibility
@@ -237,7 +220,6 @@ function elementRulesAllowDependency({
237220

238221
module.exports = {
239222
meta,
240-
dependencyLocation,
241223
isObjectMatch,
242224
isMatchElementKey,
243225
isMatchElementType,

src/helpers/utils.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,14 @@ function isArray(object) {
66
return Array.isArray(object);
77
}
88

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+
917
function replaceObjectValueInTemplate(template, key, value, namespace) {
1018
const keyToReplace = namespace ? `${namespace}.${key}` : key;
1119
const regexp = new RegExp(`\\$\\{${keyToReplace}\\}`, "g");
@@ -27,5 +35,7 @@ function replaceObjectValuesInTemplates(strings, object, namespace) {
2735
module.exports = {
2836
isString,
2937
isArray,
38+
isObject,
39+
getArrayOrNull,
3040
replaceObjectValuesInTemplates,
3141
};

0 commit comments

Comments
 (0)