eslint-plugin-boundaries 5.2.0 → 5.3.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
@@ -4,807 +4,110 @@
4
4
 
5
5
  [![NPM downloads][npm-downloads-image]][npm-downloads-url] [![License][license-image]][license-url]
6
6
 
7
- # eslint-plugin-boundaries
7
+ [coveralls-image]: https://coveralls.io/repos/github/javierbrea/eslint-plugin-boundaries/badge.svg
8
+ [coveralls-url]: https://coveralls.io/github/javierbrea/eslint-plugin-boundaries
9
+ [build-image]: https://github.com/javierbrea/eslint-plugin-boundaries/workflows/build/badge.svg
10
+ [build-url]: https://github.com/javierbrea/eslint-plugin-boundaries/actions?query=workflow%3Abuild+branch%3Amaster
11
+ [last-commit-image]: https://img.shields.io/github/last-commit/javierbrea/eslint-plugin-boundaries.svg
12
+ [last-commit-url]: https://github.com/javierbrea/eslint-plugin-boundaries/commits
13
+ [license-image]: https://img.shields.io/npm/l/eslint-plugin-boundaries.svg
14
+ [license-url]: https://github.com/javierbrea/eslint-plugin-boundaries/blob/master/LICENSE
15
+ [npm-downloads-image]: https://img.shields.io/npm/dm/eslint-plugin-boundaries.svg
16
+ [npm-downloads-url]: https://www.npmjs.com/package/eslint-plugin-boundaries
17
+ [quality-gate-image]: https://sonarcloud.io/api/project_badges/measure?project=javierbrea_eslint-plugin-boundaries&metric=alert_status
18
+ [quality-gate-url]: https://sonarcloud.io/dashboard?id=javierbrea_eslint-plugin-boundaries
19
+ [release-image]: https://img.shields.io/github/release-date/javierbrea/eslint-plugin-boundaries.svg
20
+ [release-url]: https://github.com/javierbrea/eslint-plugin-boundaries/releases
8
21
 
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))_
22
+ # ESLint Plugin Boundaries
10
23
 
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.__
24
+ Enforce architectural boundaries in your JavaScript and TypeScript projects.
12
25
 
13
- By default, __the plugin works by checking `import` statements, but it is also able to analyze "require", "exports" and 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_)
26
+ **ESLint Plugin Boundaries** is an ESLint plugin that helps you maintain clean architecture by enforcing boundaries between different parts of your codebase. Define your architectural layers, specify how they can interact, and get instant feedback when boundaries are violated.
14
27
 
15
- ## Table of Contents
28
+ - **Architectural Enforcement**: Define element types and dependency rules that match your project's architecture
29
+ - **Flexible Configuration**: Adapt the plugin to any project structure. It works with monorepos, modular architectures, layered patterns, and more
30
+ - **Real-time Feedback**: Get immediate ESLint errors when imports violate your architectural rules
16
31
 
17
- <details>
18
- <summary><strong>Details</strong></summary>
32
+ ## Documentation
19
33
 
20
- - [Installation](#installation)
21
- - [Overview](#overview)
22
- - [Main rules overview](#main-rules-overview)
23
- * [Allowed element types](#allowed-element-types)
24
- * [Allowed external modules](#allowed-external-modules)
25
- * [Private elements](#private-elements)
26
- * [Entry point](#entry-point)
27
- - [Rules](#rules)
28
- - [Configuration](#configuration)
29
- * [Global settings](#global-settings)
30
- * [Predefined configurations](#predefined-configurations)
31
- * [createConfig helper](#createconfig-helper)
32
- * [Rules configuration](#rules-configuration)
33
- * [Main format of rules options](#main-format-of-rules-options)
34
- * [Elements selectors](#elements-selectors)
35
- * [Error messages](#error-messages)
36
- * [Advanced example](#advanced-example)
37
- - [Resolvers](#resolvers)
38
- - [TypeScript Support](#typescript-support)
39
- * [Exported Types](#exported-types)
40
- - [Migration guides](#migration-guides)
41
- - [Debug mode](#debug-mode)
42
- - [Acknowledgements](#acknowledgements)
43
- - [Contributing](#contributing)
44
- - [License](#license)
34
+ The full documentation is available on the [JS Boundaries website](https://www.jsboundaries.dev/docs/overview/).
45
35
 
46
- </details>
36
+ ### Key Sections
47
37
 
48
- ## Installation
38
+ - **[Overview](https://www.jsboundaries.dev/docs/overview/)** - Introduction to the plugin and its core concepts
39
+ - **[Quick Start](https://www.jsboundaries.dev/docs/quick-start/)** - Set up the plugin in 5 minutes
40
+ - **[Setup Guide](https://www.jsboundaries.dev/docs/setup/)** - In-depth configuration guide
41
+ - [Define Elements](https://www.jsboundaries.dev/docs/setup/elements/)
42
+ - [Use Element Selectors](https://www.jsboundaries.dev/docs/setup/selectors/)
43
+ - [Configure Rules](https://www.jsboundaries.dev/docs/setup/rules/)
44
+ - [Global Settings](https://www.jsboundaries.dev/docs/setup/settings/)
45
+ - **[Rules Reference](https://www.jsboundaries.dev/docs/rules/)** - Complete documentation for all available rules
46
+ - **[TypeScript Support](https://www.jsboundaries.dev/docs/guides/typescript-support/)** - Use with TypeScript projects
49
47
 
50
- This module is distributed via npm which is bundled with node and should be installed as one of your project's devDependencies:
48
+ ## Quick Example
49
+
50
+ Install the plugin using npm:
51
51
 
52
52
  ```bash
53
- npm install --save-dev eslint eslint-plugin-boundaries
53
+ npm install eslint eslint-plugin-boundaries --save-dev
54
54
  ```
55
55
 
56
- `eslint-plugin-boundaries` does not install `eslint` for you. You must install it yourself.
57
-
58
- Activate the plugin and one of the canned configs in your `eslint.config.js` file:
56
+ Define your architectural elements:
59
57
 
60
- ```js
58
+ ```javascript
61
59
  import boundaries from "eslint-plugin-boundaries";
62
60
 
63
61
  export default [
64
62
  {
65
- plugins: {
66
- boundaries,
67
- },
68
- rules: {
69
- ...boundaries.configs.recommended.rules,
63
+ plugins: { boundaries },
64
+ settings: {
65
+ "boundaries/elements": [
66
+ { type: "controllers", pattern: "controllers/*" },
67
+ { type: "models", pattern: "models/*" },
68
+ { type: "views", pattern: "views/*" }
69
+ ]
70
70
  }
71
71
  }
72
72
  ];
73
73
  ```
74
74
 
75
- > [!NOTE]
76
- > From version `5.0.0`, this plugin is compatible with eslint v9 and above. It may be also compatible with previous eslint versions, but you might read the [documentation of the `4.2.2` version](https://github.com/javierbrea/eslint-plugin-boundaries/tree/v4.2.2) to know how to configure it properly using the legacy configuration format.
77
-
78
- ## Overview
79
-
80
- 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.
81
-
82
- 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.
83
-
84
- ```js
85
- export default [{
86
- settings: {
87
- "boundaries/elements": [
88
- {
89
- type: "helpers",
90
- pattern: "helpers/*"
91
- },
92
- {
93
- type: "components",
94
- pattern: "components/*"
95
- },
96
- {
97
- type: "modules",
98
- pattern: "modules/*"
99
- }
100
- ]
101
- }
102
- }]
103
- ```
104
-
105
- 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.
106
-
107
- 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:
108
-
109
- ```js
110
- export default [{
111
- rules: {
112
- "boundaries/element-types": [2, {
113
- default: "disallow",
114
- rules: [
115
- {
116
- from: "components",
117
- allow: ["helpers", "components"]
118
- },
119
- {
120
- from: "modules",
121
- allow: ["helpers", "components", "modules"]
122
- }
123
- ]
124
- }]
125
- }
126
- }]
127
- ```
128
-
129
- > 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.
130
-
131
- ## Main rules overview
132
-
133
- ### Allowed element types
134
-
135
- This rule ensures that dependencies between your project element types are allowed.
136
-
137
- Examples of usage:
138
-
139
- * Define types in your project as "models", "views" and "controllers". Then ensure that "views" and "models" can be imported only by "controllers", and "controllers" will never be used by "views" or "models".
140
- * Define types in your project as "components", "views", "layouts", "pages", "helpers". Then ensure that "components" can only import "helpers", that "views" can only import "components" or "helpers", that "layouts" can only import "views", "components" or "helpers", and that "pages" can import any other element type.
141
-
142
- Read the [docs of the `boundaries/element-types` rule](docs/rules/element-types.md) for further info.
143
-
144
- ### Allowed external modules
145
-
146
- External dependencies used by each type of element in your project can be checked using this rule. For example, you can define that "helpers" can't import `react`, or "components" can't import `react-router-dom`, or modules can't import `{ Link } from react-router-dom`.
147
-
148
- Read the [docs of the `boundaries/external` rule](docs/rules/external.md) for further info.
149
-
150
- ### Private elements
151
-
152
- This rule ensures that elements can't require other element's children. So, when an element B is children of A, B becomes a "private" element of A, and only A can use it.
153
-
154
- Read the [docs of the `boundaries/no-private` rule](docs/rules/no-private.md) for further info.
155
-
156
- ### Entry point
157
-
158
- This rule ensures that elements can't import another file from other element than the defined entry point for that type (`index.js` by default)
159
-
160
- Read the [docs of the `boundaries/entry-point` rule](docs/rules/entry-point.md) for further info.
161
-
162
- ## Rules
163
-
164
- * __[boundaries/element-types](docs/rules/element-types.md)__: Check allowed dependencies between element types
165
- * __[boundaries/external](docs/rules/external.md)__: Check allowed external dependencies by element type
166
- * __[boundaries/entry-point](docs/rules/entry-point.md)__: Check entry point used for each element type
167
- * [boundaries/no-private](docs/rules/no-private.md): Prevent importing private elements of another element
168
- * [boundaries/no-unknown](docs/rules/no-unknown.md): Prevent importing unknown elements from the known ones
169
- * [boundaries/no-ignored](docs/rules/no-ignored.md): Prevent importing ignored files from recognized elements
170
- * [boundaries/no-unknown-files](docs/rules/no-unknown-files.md): Prevent creating files not recognized as any of the element types
171
-
172
- ## Configuration
173
-
174
- ### Global settings
175
-
176
- #### __`boundaries/elements`__
177
-
178
- Define element descriptors to recognize each file in the project as one of this element types. All rules need this setting to be configured properly to work. The plugin tries to identify each file being analyzed or `import` statement in rules as one of the defined element types. The assigned element type will be that with the first matching pattern, in the same order that elements are defined in the array, so you __should sort them from the most accurate patterns to the less ones__. Properties of each `element`:
179
-
180
- * __`type`__: `<string>` Element type to be assigned to files or imports matching the `pattern`. This type will be used afterwards in the rules configuration.
181
- * __`pattern`__: `<string>|<array>` [`micromatch` pattern](https://github.com/micromatch/micromatch). __By default the plugin will try to match this pattern progressively starting from the right side of each file path.__ This means that you don't have to define patterns matching from the base project path, but only the last part of the path that you want to be matched. This is made because the plugin supports elements being children of other elements, and otherwise it could wrongly recognize children elements as a part of the parent one. <br/>For example, given a path `src/helpers/awesome-helper/index.js`, it will try to assign the element to a pattern matching `index.js`, then `awesome-helper/index.js`, then `helpers/awesome-helper/index.js`, etc. Once a pattern matches, it assign the correspondent element type, and continues searching for parents elements with the same logic until the full path has been analyzed. __This behavior can be disabled setting the `mode` option to `full`__, then the provided pattern will try to match the full path.
182
- * __`basePattern`__: `<string>` Optional [`micromatch` pattern](https://github.com/micromatch/micromatch). If provided, the left side of the element path must match also with this pattern from the root of the project (like if pattern is `[basePattern]/**/[pattern]`). This option is useful when using the option `mode` with `file` or `folder` values, but capturing fragments from the rest of the full path is also needed (see `baseCapture` option below).
183
- * __`mode`__: `<string> file|folder|full` Optional.
184
- * When it is set to `folder` (default value), the element type will be assigned to the first file's parent folder matching the pattern. In the practice, it is like adding `**/*` to the given pattern, but the plugin makes it by itself because it needs to know exactly which parent folder has to be considered the element.
185
- * If it is set to `file`, the given pattern will not be modified, but the plugin will still try to match the last part of the path. So, a pattern like `*.model.js` would match with paths `src/foo.model.js`, `src/modules/foo/foo.model.js`, `src/modules/foo/models/foo.model.js`, etc.
186
- * If it is set to `full`, the given pattern will only match with patterns matching the full path. This means that you will have to provide patterns matching from the base project path. So, in order to match `src/modules/foo/foo.model.js` you'll have to provide patterns like `**/*.model.js`, `**/*/*.model.js`, `src/*/*/*.model.js`, etc. _(the chosen pattern will depend on what do you want to capture from the path)_
187
- * __`capture`__: `<array>` Optional. This is a very powerful feature of the plugin. It allows to capture values of some fragments in the matching path to use them later in the rules configuration. It uses [`micromatch` capture feature](https://github.com/micromatch/micromatch#capture) under the hood, and stores each value in an object with the given `capture` key being in the same index of the captured array.<br/>For example, given `pattern: "helpers/*/*.js"`, `capture: ["category", "elementName"]`, and a path `helpers/data/parsers.js`, it will result in `{ category: "data", elementName: "parsers" }`.
188
- * __`baseCapture`__: `<array>` Optional. [`micromatch` pattern](https://github.com/micromatch/micromatch). It allows capturing values from `basePattern` as `capture` does with `pattern`. All keys from `capture` and `baseCapture` can be used in the rules configuration.
189
-
190
- ```js
191
- export default [{
192
- settings: {
193
- "boundaries/elements": [
194
- {
195
- type: "helpers",
196
- pattern: "helpers/*/*.js",
197
- mode: "file",
198
- capture: ["category", "elementName"]
199
- },
200
- {
201
- type: "components",
202
- pattern: "components/*/*",
203
- capture: ["family", "elementName"]
204
- },
205
- {
206
- type: "modules",
207
- pattern: "module/*",
208
- capture: ["elementName"]
209
- }
210
- ]
211
- }
212
- }]
213
- ```
214
-
215
- > 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.
216
-
217
- #### __`boundaries/dependency-nodes`__
218
-
219
- 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.
220
-
221
- The setting should be an array of the following strings:
222
-
223
- * `'require'`: analyze `require` statements.
224
- * `'import'`: analyze `import` statements.
225
- * `'export'`: analyze `export` statements.
226
- * `'dynamic-import'`: analyze [dynamic import](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import) statements.
227
-
228
- If you want to define custom dependency nodes, such as `jest.mock(...)`, use [additional-dependency-nodes](#boundariesadditional-dependency-nodes) setting.
229
-
230
- For example, if you want to analyze the `import` and `dynamic-import` statements, you should use the following value:
231
-
232
- ```jsonc
233
- "boundaries/dependency-nodes": ["import", "dynamic-import"],
234
- ```
235
-
236
- #### __`boundaries/additional-dependency-nodes`__
237
-
238
- 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.
239
-
240
- The setting should be an array of objects with the following structure:
241
-
242
- * __`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`.
243
- * __`kind`__: The kind of dependency, possible values are: `"value"` or `"type"`. It is available only when using TypeScript.
244
-
245
- Example of usage:
246
-
247
- ```js
248
- export default [{
249
- settings: {
250
- "boundaries/additional-dependency-nodes": [
251
- // jest.requireActual('source')
252
- {
253
- selector: "CallExpression[callee.object.name=jest][callee.property.name=requireActual] > Literal",
254
- kind: "value",
255
- },
256
- // jest.mock('source', ...)
257
- {
258
- selector: "CallExpression[callee.object.name=jest][callee.property.name=mock] > Literal:first-child",
259
- kind: "value",
260
- },
261
- ],
262
- }
263
- }]
264
- ```
265
-
266
- #### __`boundaries/include`__
267
-
268
- 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.
269
-
270
- ```js
271
- export default [{
272
- settings: {
273
- "boundaries/include": ["src/**/*.js"]
274
- }
275
- }]
276
- ```
277
-
278
- #### __`boundaries/ignore`__
279
-
280
- Files or dependencies matching these [`micromatch` patterns](https://github.com/micromatch/micromatch) will be ignored by the plugin.
281
-
282
- ```js
283
- export default [{
284
- settings: {
285
- "boundaries/ignore": ["**/*.spec.js", "src/legacy-code/**/*"]
286
- }
287
- }]
288
- ```
289
-
290
- > Note: The `boundaries/ignore` option has precedence over `boundaries/include`. If you define `boundaries/include`, use `boundaries/ignore` to ignore subsets of included files.
291
-
292
- #### __`boundaries/root-path`__
293
-
294
- Use this setting only if you are facing issues with the plugin when executing the lint command from a different path than the project root.
295
-
296
- <details>
297
- <summary>How to define the root path of the project</summary>
298
-
299
- 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.
300
-
301
- For example, supposing that the `eslint.config.js` file is located in the project root, you could define the root path as in:
302
-
303
- ```js
304
- import { resolve } from "node:path";
305
-
306
- export default [{
307
- settings: {
308
- "boundaries/root-path": resolve(import.meta.dirname)
309
- }
310
- }]
311
- ```
312
-
313
- Note that the path should be absolute and resolved before passing it to the plugin. Otherwise, it will be resolved using the current working directory, and the problem will persist. You can also use the next environment variable to define the root path when executing the lint command:
314
-
315
- ```bash
316
- ESLINT_PLUGIN_BOUNDARIES_ROOT_PATH=../../project-root npm run lint
317
- ```
318
-
319
- You can also provide an absolute path in the environment variable, but it may be more useful to use a relative path to the project root. Remember that it will be resolved from the path where the lint command is executed.
320
-
321
- </details>
322
-
323
- #### __`boundaries/cache`__
324
-
325
- Enable or disable the cache mechanism used to boost performance. By default, it is enabled. We recommend to keep it enabled unless you experience issues. In such case, please, open an issue describing the problem.
326
-
327
- ```js
328
- export default [{
329
- settings: {
330
- "boundaries/cache": true // or false to disable the cache
331
- }
332
- }]
333
- ```
334
-
335
- ### Predefined configurations
336
-
337
- The plugin is distributed with two different predefined configurations: "recommended" and "strict".
338
-
339
- #### Recommended
340
-
341
- We recommend to use this setting if you are applying the plugin to an already existing project. Rules `boundaries/no-unknown`, `boundaries/no-unknown-files` and `boundaries/no-ignored` are disabled, so it allows to have parts of the project non-compliant with your element types, allowing to refactor the code progressively.
342
-
343
- ```js
344
- import boundaries from "eslint-plugin-boundaries";
345
- import { recommended } from "eslint-plugin-boundaries/config";
346
-
347
- export default [{
348
- plugins: {
349
- boundaries,
350
- },
351
- settings: {
352
- ...recommended.settings,
353
- // Define your own element descriptors here
354
- "boundaries/elements": [
355
- {
356
- type: "helpers",
357
- pattern: "helpers/*"
358
- },
359
- ]
360
- },
361
- rules: {
362
- ...recommended.rules,
363
- "boundaries/element-types": [2, {
364
- // Define your own options here
365
- }],
366
- }
367
- }]
368
- ```
369
-
370
- #### Strict
371
-
372
- All rules are enabled by default, so all elements in the project will be compliant with your architecture boundaries. 😃
373
-
374
- Follow the same example as in "recommended" configuration, but importing `strict` instead of `recommended`.
375
-
376
- ```js
377
- import { strict } from "eslint-plugin-boundaries/config";
378
- ```
379
-
380
- ### createConfig helper
381
-
382
- A `createConfig` helper is also available for making configurations easier.
383
-
384
- It enforces valid types for settings and rules and automatically:
385
-
386
- * Adds the plugin to the plugins property
387
- * Includes JavaScript and TypeScript file patterns in the files property
388
- * Validates that all provided settings and rules belong to the plugin
389
-
390
- ```js
391
- import { createConfig, recommended } from "eslint-plugin-boundaries/config";
392
-
393
- const config = createConfig({
394
- settings: {
395
- ...recommended.settings,
396
- "boundaries/elements": [],
397
- "boundaries/ignore": ["**/ignored/**/*.js"],
398
- },
399
- rules: {
400
- ...recommended.rules,
401
- "boundaries/element-types": ["error", { default: "disallow" }],
402
- }
403
- });
404
-
405
- export default [config];
406
- ```
407
-
408
- You can also rename the plugin by passing a second argument. The helper will rename all rules from `boundaries/` prefix to the provided one, so recommended or strict configs can be used as a base without issues.
409
-
410
- ```js
411
- import { createConfig, recommended } from "eslint-plugin-boundaries/config";
412
-
413
- const config = createConfig({
414
- settings: {
415
- ...recommended.settings,
416
- "boundaries/elements": [], // Note the original prefix here
417
- },
418
- rules: {
419
- ...recommended.rules,
420
- "custom-boundaries/element-types": ["error", { default: "disallow" }], // Note the renamed prefix here
421
- "boundaries/entry-point": 0, // The original prefix still works too
422
- // @ts-expect-error Any other prefix raises an error
423
- "foo/entry-point": 2, // This rule does not match the new plugin name nor the original one
424
- }
425
- }, "custom-boundaries");
426
-
427
- export default [config];
428
- ```
429
-
430
- > [!WARNING]
431
- > Note that the settings still must use the `boundaries/` prefix — ESLint doesn’t namespace settings by plugin name.
432
-
433
- The plugin also exports constants and type guard methods for settings keys, rule names, and other configuration-related values. You can use them when defining your configuration. For example:
434
-
435
- ```ts
436
- import {
437
- RULE_NAMES_MAP,
438
- isRuleName,
439
- SETTINGS_KEYS_MAP,
440
- isSettingsKey,
441
- ELEMENT_DESCRIPTOR_MODES_MAP,
442
- isElementDescriptorMode,
443
- RULE_POLICIES_MAP,
444
- isRulePolicy,
445
- } from "eslint-plugin-boundaries/config";
446
- ```
447
-
448
- ### Rules configuration
449
-
450
- Some rules require extra configuration, and it has to be defined in each specific `rule` property of the `eslint.config.js` file. For example, allowed element types relationships has to be provided as an option to the [`boundaries/element-types` rule](docs/rules/element-types.md). Rules requiring extra configuration will print a warning in case they are enabled without the needed options.
451
-
452
- #### Main format of rules options
453
-
454
- 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.
455
-
456
- 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 correspondent way, making it to produce an eslint error or not.
457
-
458
- ```js
459
- export default [{
460
- rules: {
461
- "boundaries/element-types": [2, {
462
- // Allow or disallow any dependency by default
463
- default: "allow",
464
- // Define a custom message for this rule
465
- message: "${file.type} is not allowed to import ${dependency.type}",
466
- rules: [
467
- {
468
- // In this type of files...
469
- from: ["helpers"],
470
- // ...disallow importing this type of elements
471
- disallow: ["modules", "components"],
472
- // ..for this kind of imports (applies only when using TypeScript)
473
- importKind: "value",
474
- // ...and return this custom error message
475
- message: "Helpers must not import other thing than helpers"
476
- },
477
- {
478
- from: ["components"],
479
- disallow: ["modules"]
480
- // As this rule has not "message" property, it will use the message defined at first level
481
- }
482
- ]
483
- }]
484
- }
485
- }]
486
- ```
487
-
488
- Remember that:
75
+ Define your dependency rules:
489
76
 
490
- * All rules are executed, and the resultant value will be the one returned by the last matching one.
491
- * If one rule contains both `allow` and `disallow` properties, the `disallow` one has priority. It will not try to match the `allow` one if `disallow` matches. The result for that rule will be `disallow` in that case.
492
-
493
- ##### Rules options properties
494
-
495
- * __`from/target`__: `<element selectors>` 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 selector (`from`), or the dependency being imported matches with this element selector (`target`).
496
- * __`disallow/allow`__: `<selectors>` 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 selector>` has to be provided in order to check the type of the local dependency.
497
- * __`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.
498
- * __`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.
499
-
500
- > Tip: Properties `from/target` and `disallow/allow` can receive a single selector, or an array of selectors.
501
-
502
- ##### Elements selectors
503
-
504
- Elements selectors used in the rules options can have the next formats:
505
-
506
- * __`<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.
507
- * __`[<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 selectors as:
508
- * `["helpers", { category: "data", elementName: "parsers"}]`: Will only match with helpers with category "data" and elementName "parsers" (`helpers/data/parsers.js`).
509
- * `["helpers", { category: "data" }]`: Will match with all helpers with category "data" (`helpers/data/*.js`)
510
- * `["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.
511
-
512
- ##### Templating
513
-
514
- When defining [__Element selectors__](#elements-selectors), 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>`.
515
-
516
- Templates must be defined with the format `${from.CAPTURED_PROPERTY}` or `${target.CAPTURED_PROPERTY}`.
517
-
518
- ##### Error messages
519
-
520
- 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).
521
-
522
- 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:
523
-
524
- ```jsonc
525
- {
526
- "message": "${file.type}s of category ${file.category} are not allowed to import ${dependency.category}s"
527
- // If the error was produced by a file with type "component" and captured value "category" being "atom", trying to import a dependency with category "molecule", the message would be:
528
- // "components of category atom are not allowed to import molecules"
529
- }
530
- ```
531
-
532
- Available properties in error templates both from `file` or `dependency` are:
533
-
534
- * `type`: Element's type.
535
- * `internalPath`: File path being analyzed or imported. Relative to the element's root path.
536
- * `source`: Available only for `dependency`. The source of the `import` statement as it is in the code.
537
- * `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.
538
- * `importKind`: Available only for `dependency` when using TypeScript. It contains the kind of import being analyzed. Possible values are `"value"`, `"type"` or `"typeof"`.
539
- * ...All captured properties are also available
540
-
541
- > Tip: Read ["Global settings"](#global-settings) for further info about how to capture values from elements.
542
-
543
- 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:
544
-
545
- ```jsonc
77
+ ```javascript
546
78
  {
547
- "message": "Do not import ${report.specifiers} from ${dependency.source} in helpers"
548
- }
549
- ```
550
-
551
- ##### Advanced example of a rule configuration
552
-
553
- 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:
554
-
555
- ```js
556
- export default [{
557
79
  rules: {
558
80
  "boundaries/element-types": [2, {
559
- // disallow importing any element by default
560
81
  default: "disallow",
561
82
  rules: [
562
- {
563
- // allow importing helpers files from helpers files
564
- from: ["helpers"],
565
- allow: ["helpers"]
566
- },
567
- {
568
- // when file is inside an element of type "components"
569
- from: ["components"],
570
- allow: [
571
- // allow importing components of the same family
572
- ["components", { family: "${from.family}" }],
573
- // allow importing helpers with captured category "data"
574
- ["helpers", { category: "data" }],
575
- ]
576
- },
577
- {
578
- // when component has captured family "molecule"
579
- from: [["components", { family: "molecule" }]],
580
- allow: [
581
- // allow importing components with captured family "atom"
582
- ["components", { family: "atom" }],
583
- ],
584
- },
585
- {
586
- // when component has captured family "atom"
587
- from: [["components", { family: "atom" }]],
588
- disallow: [
589
- // disallow importing helpers with captured category "data"
590
- ["helpers", { category: "data" }]
591
- ],
592
- // Custom message only for this specific error
593
- message: "Atom components can't import data helpers"
594
- },
595
- {
596
- // when file is inside a module
597
- from: ["modules"],
598
- allow: [
599
- // allow importing any type of component or helper
600
- "helpers",
601
- "components"
602
- ]
603
- },
604
- {
605
- // when module name starts by "page-"
606
- from: [["modules", { elementName: "page-*" }]],
607
- disallow: [
608
- // disallow importing any type of component not being of family layout
609
- ["components", { family: "!layout" }],
610
- ],
611
- // Custom message only for this specific error
612
- 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}"
613
- }
83
+ { from: "controllers", allow: ["models", "views"] },
84
+ { from: "views", allow: ["models"] },
85
+ { from: "models", disallow: ["*"] }
614
86
  ]
615
87
  }]
616
88
  }
617
- }]
618
- ```
619
-
620
- ## Resolvers
621
-
622
- _"With the advent of module bundlers and the current state of modules and module syntax specs, it's not always obvious where import x from 'module' should look to find the file behind module."_ ([\**Quote from the `eslint-plugin-import` docs](#acknowledgements))
623
-
624
- This plugin uses `eslint-module-utils/resolve` module under the hood, which is a part of the `eslint-plugin-import` plugin. So __the `import/resolver` setting can be used to use custom resolvers for this plugin too__.
625
-
626
- [Read the `resolvers` chapter of the `eslint-plugin-import` plugin for further info](https://github.com/benmosher/eslint-plugin-import#resolvers).
627
-
628
- ```js
629
- export default [{
630
- settings: {
631
- "import/resolver": {
632
- "eslint-import-resolver-node": {},
633
- "some-other-custom-resolver": { someConfig: "value" }
634
- }
635
- }
636
- }]
637
- ```
638
-
639
- ## TypeScript Support
640
-
641
- This plugin can be used also in [TypeScript](https://www.typescriptlang.org/) projects using `@typescript-eslint/eslint-plugin`. Follow next steps to configure it:
642
-
643
- Install dependencies:
644
-
645
- ```bash
646
- npm i --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-import-resolver-typescript
647
- ```
648
-
649
- Configure [`@typescript-eslint/parser`](https://github.com/typescript-eslint/typescript-eslint) as parser, load the [`@typescript-eslint`](https://github.com/typescript-eslint/typescript-eslint) plugin, and setup the [`eslint-import-resolver-typescript`](https://github.com/alexgorbatchev/eslint-import-resolver-typescript) resolver in the `eslint.config.js` config file:
650
-
651
- ```js
652
- import boundaries from "eslint-plugin-boundaries";
653
- import typescriptParser from "@typescript-eslint/parser";
654
- import typescriptEslintPlugin from "@typescript-eslint/eslint-plugin";
655
-
656
- export default [{
657
- languageOptions: {
658
- parser: typescriptParser,
659
- },
660
- plugins: {
661
- "@typescript-eslint": typescriptEslintPlugin,
662
- boundaries,
663
- },
664
- settings: {
665
- "import/resolver": {
666
- typescript: {
667
- alwaysTryTypes: true,
668
- },
669
- },
670
- },
671
- }];
672
- ```
673
-
674
- > Note that `eslint-import-resolver-typescript` detects even custom paths defined in the `tsconfig.json` file, so its usage is also compatible with this plugin.
675
-
676
- 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.
677
-
678
- ### Exported Types
679
-
680
- The main type exported by the plugin is `Config`, which represents a fully typed [Flat Config](https://eslint.org/docs/latest/use/core-concepts/glossary#flat-config).
681
-
682
- ```ts
683
- import type { Config } from "eslint-plugin-boundaries";
684
-
685
- const config: Config = {
686
- plugins: {
687
- boundaries,
688
- },
689
- settings: {
690
- "boundaries/elements": [],
691
- "boundaries/ignore": ["**/ignored/**/*.js"],
692
- },
693
- rules: {
694
- "boundaries/element-types": [
695
- "error",
696
- { default: "disallow", rules: [] },
697
- ],
698
- },
699
- };
700
- ```
701
-
702
- Types also support renaming the plugin when loading it in the `plugins` property, so rules and must be defined using the new name:
703
-
704
- ```ts
705
- import type { Config } from "eslint-plugin-boundaries";
706
-
707
- const config: Config<"custom-boundaries"> = {
708
- plugins: {
709
- "custom-boundaries": boundaries, // NOTE the renamed prefix here
710
- },
711
- settings: {
712
- "boundaries/elements": [],
713
- "boundaries/ignore": ["**/ignored/**/*.js"],
714
- },
715
- rules: {
716
- "custom-boundaries/element-types": 2, // NOTE the renamed prefix here
717
- },
718
- };
719
- ```
720
-
721
- > [!WARNING]
722
- > Note that the settings still use the `boundaries/` prefix — ESLint doesn’t namespace settings by plugin name.
723
-
724
- In addition, individual subtypes are available for each part of the configuration, such as:
725
-
726
- * `Settings`, `Rules`, `ElementDescriptor`, `ElementTypesRule`, `ElementTypesRuleOptions`, `ElementSelector`, `IgnoreSetting`, etc.
727
-
728
- This allows you to import only what you need and get full autocompletion and type safety.
729
-
730
- ```ts
731
- import type {
732
- Config,
733
- Settings,
734
- Rules,
735
- ElementDescriptor,
736
- ElementTypesRuleOptions,
737
- } from "eslint-plugin-boundaries";
738
-
739
- const moduleDescriptor: ElementDescriptor = {
740
- type: "module",
741
- pattern: "src/modules/*",
742
- capture: ["module"],
743
- };
744
-
745
- const settings: Settings = {
746
- "boundaries/elements": [moduleDescriptor],
747
- };
748
-
749
- const rules: Rules = {
750
- "boundaries/element-types": ["error", { default: "disallow", rules: [] }],
751
- };
752
-
753
- const config: Config = {
754
- files: ["**/*.js", "**/*.ts"],
755
- settings,
756
- rules,
757
- };
89
+ }
758
90
  ```
759
91
 
760
- ## Migration guides
761
-
762
- ### Migrating from v4.x
763
-
764
- v5.0.0 release is compatible with eslint v9 and above. It may be also compatible with previous eslint versions, but you might read the [documentation of the `4.2.2` version](https://github.com/javierbrea/eslint-plugin-boundaries/tree/v4.2.2) to know how to configure it properly using the legacy configuration format. You may also be interested on reading the [eslint guide to migrate to v9](https://eslint.org/docs/latest/use/migrate-to-9.0.0).
765
-
766
- ### Migrating from v3.x
92
+ Now ESLint will catch violations:
767
93
 
768
- v4.0.0 release 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).
769
-
770
- ### Migrating from v1.x
771
-
772
- v2.0.0 release 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).
773
-
774
- ## Debug mode
775
-
776
- 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.
777
-
778
- ```bash
779
- ESLINT_PLUGIN_BOUNDARIES_DEBUG=1 npm run lint
94
+ ```javascript
95
+ // In src/models/model.js
96
+ import View from "../views/view"; // ❌ Error: Architectural boundary violated
780
97
  ```
781
98
 
782
- ## Acknowledgements
99
+ ## Contributing
783
100
 
784
- \* Quote from Robert C. Martin's book ["Clean Architecture: A Craftsman's Guide to Software Structure and Design"](https://www.oreilly.com/library/view/clean-architecture-a/9780134494272/).
101
+ To everyone who has opened an issue, suggested improvements, fixed bugs, added features, or improved documentation: **Thank you**. Your contributions, no matter how small, make a real difference. Every bug report helps us improve, every feature request guides our roadmap, and every pull request strengthens the project.
785
102
 
786
- \** This plugin uses internally the `eslint-module-utils/resolve` module, which is a part of the [`eslint-plugin-import` plugin](https://github.com/benmosher/eslint-plugin-import). Thanks to the maintainers of that plugin for their awesome work.
103
+ Special recognition goes to [those who have contributed code to the project](https://github.com/javierbrea/eslint-plugin-boundaries/graphs/contributors). Your technical contributions are the foundation of what makes this plugin valuable to the community.
787
104
 
788
- ## Contributing
105
+ Want to contribute? We'd love to have you! Here are some ways to get involved:
789
106
 
790
- Contributors are welcome.
791
- Please read the [contributing guidelines](.github/CONTRIBUTING.md) and [code of conduct](.github/CODE_OF_CONDUCT.md).
107
+ - **Report Issues**: [Open an issue](https://github.com/javierbrea/eslint-plugin-boundaries/issues) if you find a bug or have a suggestion
108
+ - **Participate in Discussions**: Join the conversation on our [GitHub Discussions](https://github.com/javierbrea/eslint-plugin-boundaries/discussions). Review RFCs, share ideas, and help shape the future of the project.
109
+ - **Contribute Code**: Check out our [Contributing Guidelines](https://github.com/javierbrea/eslint-plugin-boundaries/blob/main/.github/CONTRIBUTING.md)
792
110
 
793
111
  ## License
794
112
 
795
- MIT, see [LICENSE](./LICENSE) for details.
796
-
797
- [coveralls-image]: https://coveralls.io/repos/github/javierbrea/eslint-plugin-boundaries/badge.svg
798
- [coveralls-url]: https://coveralls.io/github/javierbrea/eslint-plugin-boundaries
799
- [build-image]: https://github.com/javierbrea/eslint-plugin-boundaries/workflows/build/badge.svg
800
- [build-url]: https://github.com/javierbrea/eslint-plugin-boundaries/actions?query=workflow%3Abuild+branch%3Amaster
801
- [last-commit-image]: https://img.shields.io/github/last-commit/javierbrea/eslint-plugin-boundaries.svg
802
- [last-commit-url]: https://github.com/javierbrea/eslint-plugin-boundaries/commits
803
- [license-image]: https://img.shields.io/npm/l/eslint-plugin-boundaries.svg
804
- [license-url]: https://github.com/javierbrea/eslint-plugin-boundaries/blob/master/LICENSE
805
- [npm-downloads-image]: https://img.shields.io/npm/dm/eslint-plugin-boundaries.svg
806
- [npm-downloads-url]: https://www.npmjs.com/package/eslint-plugin-boundaries
807
- [quality-gate-image]: https://sonarcloud.io/api/project_badges/measure?project=javierbrea_eslint-plugin-boundaries&metric=alert_status
808
- [quality-gate-url]: https://sonarcloud.io/dashboard?id=javierbrea_eslint-plugin-boundaries
809
- [release-image]: https://img.shields.io/github/release-date/javierbrea/eslint-plugin-boundaries.svg
810
- [release-url]: https://github.com/javierbrea/eslint-plugin-boundaries/releases
113
+ MIT © [javierbrea](https://github.com/javierbrea)
@@ -9,5 +9,6 @@ export type { Settings, IgnoreSetting, IncludeSetting, RootPathSetting, Settings
9
9
  export declare const IMPORT_KINDS_MAP: {
10
10
  readonly TYPE: "type";
11
11
  readonly VALUE: "value";
12
+ readonly TYPE_OF: "typeof";
12
13
  };
13
14
  export { isImportKind, DEPENDENCY_NODE_KEYS_MAP, isDependencyNodeKey, SETTINGS_KEYS_MAP, isSettingsKey, } from "../Settings";
@@ -10,13 +10,25 @@ const Settings_1 = require("../../Settings");
10
10
  function removePluginNamespace(ruleName) {
11
11
  return ruleName.replace(`${Settings_1.PLUGIN_NAME}/`, "");
12
12
  }
13
+ /**
14
+ * Adapts the rule name to be used in a URL.
15
+ * @param ruleName The name of the rule.
16
+ * @returns The adapted rule name for URL usage.
17
+ */
18
+ function adaptRuleNameToUrl(ruleName) {
19
+ // NOTE: Urls are already prepared for the next major release where "element-types" rule will be renamed to "dependencies", so no 301 redirect will be needed then.
20
+ if (ruleName === "element-types") {
21
+ return "dependencies";
22
+ }
23
+ return ruleName;
24
+ }
13
25
  /**
14
26
  * Returns the documentation URL for an ESLint rule.
15
27
  * @param ruleName The name of the rule.
16
28
  * @returns The documentation URL for the ESLint rule.
17
29
  */
18
30
  function docsUrl(ruleName) {
19
- return `${Settings_1.REPO_URL}/blob/master/docs/rules/${removePluginNamespace(ruleName)}.md`;
31
+ return `${Settings_1.WEBSITE_URL}/docs/rules/${adaptRuleNameToUrl(removePluginNamespace(ruleName))}/`;
20
32
  }
21
33
  /**
22
34
  * Returns the meta object for an ESLint rule.
@@ -3,6 +3,7 @@ import type { ESLint, Linter, Rule } from "eslint";
3
3
  export declare const PLUGIN_NAME: "boundaries";
4
4
  export declare const PLUGIN_ENV_VARS_PREFIX: "ESLINT_PLUGIN_BOUNDARIES";
5
5
  export declare const REPO_URL: "https://github.com/javierbrea/eslint-plugin-boundaries";
6
+ export declare const WEBSITE_URL: "https://www.jsboundaries.dev";
6
7
  export declare const PLUGIN_ISSUES_URL: "https://github.com/javierbrea/eslint-plugin-boundaries/issues";
7
8
  export declare const DEPENDENCY_NODE_REQUIRE: "require";
8
9
  export declare const DEPENDENCY_NODE_IMPORT: "import";
@@ -127,7 +128,7 @@ export declare const SETTINGS: {
127
128
  readonly TYPES: "boundaries/types";
128
129
  readonly ALIAS: "boundaries/alias";
129
130
  readonly VALID_MODES: readonly ["folder", "file", "full"];
130
- readonly VALID_DEPENDENCY_NODE_KINDS: readonly ["value", "type"];
131
+ readonly VALID_DEPENDENCY_NODE_KINDS: readonly ["value", "type", "typeof"];
131
132
  readonly DEFAULT_DEPENDENCY_NODES: {
132
133
  readonly require: readonly [{
133
134
  readonly selector: "CallExpression[callee.name=require] > Literal";
@@ -1,10 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RULE_POLICIES_MAP = exports.RULE_POLICY_DISALLOW = exports.RULE_POLICY_ALLOW = exports.CACHE_DEFAULT = exports.LEGACY_TEMPLATES_DEFAULT = exports.SETTINGS_KEYS_MAP = exports.SETTINGS = exports.DEPENDENCY_NODE_KEYS_MAP = exports.FROM = exports.RULE_SHORT_NAMES = exports.RULE_NAMES = exports.RULE_NAMES_MAP = exports.RULE_SHORT_NAMES_MAP = exports.NO_UNKNOWN = exports.NO_UNKNOWN_FILES = exports.NO_PRIVATE = exports.NO_IGNORED = exports.EXTERNAL = exports.ENTRY_POINT = exports.ELEMENT_TYPES = exports.DEPENDENCY_NODE_EXPORT = exports.DEPENDENCY_NODE_DYNAMIC_IMPORT = exports.DEPENDENCY_NODE_IMPORT = exports.DEPENDENCY_NODE_REQUIRE = exports.PLUGIN_ISSUES_URL = exports.REPO_URL = exports.PLUGIN_ENV_VARS_PREFIX = exports.PLUGIN_NAME = void 0;
3
+ exports.RULE_POLICIES_MAP = exports.RULE_POLICY_DISALLOW = exports.RULE_POLICY_ALLOW = exports.CACHE_DEFAULT = exports.LEGACY_TEMPLATES_DEFAULT = exports.SETTINGS_KEYS_MAP = exports.SETTINGS = exports.DEPENDENCY_NODE_KEYS_MAP = exports.FROM = exports.RULE_SHORT_NAMES = exports.RULE_NAMES = exports.RULE_NAMES_MAP = exports.RULE_SHORT_NAMES_MAP = exports.NO_UNKNOWN = exports.NO_UNKNOWN_FILES = exports.NO_PRIVATE = exports.NO_IGNORED = exports.EXTERNAL = exports.ENTRY_POINT = exports.ELEMENT_TYPES = exports.DEPENDENCY_NODE_EXPORT = exports.DEPENDENCY_NODE_DYNAMIC_IMPORT = exports.DEPENDENCY_NODE_IMPORT = exports.DEPENDENCY_NODE_REQUIRE = exports.PLUGIN_ISSUES_URL = exports.WEBSITE_URL = exports.REPO_URL = exports.PLUGIN_ENV_VARS_PREFIX = exports.PLUGIN_NAME = void 0;
4
4
  // Plugin constants
5
5
  exports.PLUGIN_NAME = "boundaries";
6
6
  exports.PLUGIN_ENV_VARS_PREFIX = "ESLINT_PLUGIN_BOUNDARIES";
7
7
  exports.REPO_URL = "https://github.com/javierbrea/eslint-plugin-boundaries";
8
+ exports.WEBSITE_URL = "https://www.jsboundaries.dev";
8
9
  exports.PLUGIN_ISSUES_URL = `${exports.REPO_URL}/issues`;
9
10
  exports.DEPENDENCY_NODE_REQUIRE = "require";
10
11
  exports.DEPENDENCY_NODE_IMPORT = "import";
@@ -111,7 +112,7 @@ exports.SETTINGS = {
111
112
  ALIAS: `${exports.PLUGIN_NAME}/alias`,
112
113
  // elements settings properties,
113
114
  VALID_MODES: ["folder", "file", "full"],
114
- VALID_DEPENDENCY_NODE_KINDS: ["value", "type"],
115
+ VALID_DEPENDENCY_NODE_KINDS: ["value", "type", "typeof"],
115
116
  DEFAULT_DEPENDENCY_NODES: {
116
117
  [exports.DEPENDENCY_NODE_KEYS_MAP.REQUIRE]: [
117
118
  // Note: detects "require('source')"
@@ -18,7 +18,6 @@ const Helpers_1 = require("./Helpers");
18
18
  const Settings_1 = require("./Settings");
19
19
  const Settings_types_1 = require("./Settings.types");
20
20
  const { TYPES, ALIAS, ELEMENTS, DEPENDENCY_NODES, ADDITIONAL_DEPENDENCY_NODES, VALID_DEPENDENCY_NODE_KINDS, DEFAULT_DEPENDENCY_NODES, VALID_MODES, } = Settings_types_1.SETTINGS;
21
- const invalidMatchers = [];
22
21
  const DEFAULT_MATCHER_OPTIONS = {
23
22
  type: "object",
24
23
  };
@@ -108,23 +107,50 @@ function rulesOptionsSchema(options = {}) {
108
107
  ];
109
108
  }
110
109
  function isValidElementTypesMatcher(matcher, settings) {
111
- const matcherToCheck = (0, Common_1.isArray)(matcher) ? matcher[0] : matcher;
112
- const typeMatcherToCheck = (0, Common_1.isString)(matcherToCheck)
113
- ? matcherToCheck
114
- : matcherToCheck.type;
115
- return (!typeMatcherToCheck ||
116
- (typeMatcherToCheck &&
117
- micromatch_1.default.some(settings.elementTypeNames, typeMatcherToCheck)));
110
+ const matcherToCheck = (0, Common_1.isArray)(matcher)
111
+ ? matcher[0]
112
+ : matcher;
113
+ return (!matcherToCheck ||
114
+ (matcherToCheck &&
115
+ micromatch_1.default.some(settings.elementTypeNames, matcherToCheck)));
116
+ }
117
+ /**
118
+ * Checks if the value is a single matcher with options (tuple of [string, object])
119
+ */
120
+ function isSingleMatcherWithOptions(value) {
121
+ return ((0, Common_1.isArray)(value) &&
122
+ value.length === 2 &&
123
+ (0, Common_1.isString)(value[0]) &&
124
+ (0, Common_1.isObject)(value[1]));
118
125
  }
119
126
  // TODO: Remove this validation. Selectors should not be limited to element types defined in settings when using selector objects
120
127
  function validateElementTypesMatcher(elementsMatcher, settings) {
121
- const [matcher] = (0, Common_1.isArray)(elementsMatcher)
122
- ? elementsMatcher
123
- : [elementsMatcher];
124
- if (!invalidMatchers.includes(matcher) &&
125
- !isValidElementTypesMatcher(matcher, settings)) {
126
- invalidMatchers.push(matcher);
127
- (0, Debug_1.warnOnce)(`Option '${matcher}' does not match any element type from '${ELEMENTS}' setting`);
128
+ // Handle empty array case
129
+ if ((0, Common_1.isArray)(elementsMatcher) && elementsMatcher.length === 0) {
130
+ return;
131
+ }
132
+ // Determine if it's a single matcher or an array of matchers
133
+ let matcher;
134
+ if ((0, Common_1.isString)(elementsMatcher)) {
135
+ matcher = elementsMatcher;
136
+ }
137
+ else if (isSingleMatcherWithOptions(elementsMatcher)) {
138
+ // It's a single matcher with options: ["type", { option: value }]
139
+ matcher = elementsMatcher;
140
+ }
141
+ else if ((0, Common_1.isArray)(elementsMatcher)) {
142
+ // It's an array of matchers: ["helpers", "components"] or [["helpers", {...}], "components"]
143
+ // NOTE: Validate only the first matcher. It is wrong, but we don't want to impact performance, and anyway it was already validating only the first one.
144
+ // In next major version, validation will be removed, because schema validation will handle it.
145
+ matcher = elementsMatcher[0];
146
+ }
147
+ else {
148
+ (0, Debug_1.warnOnce)(`Option is not a valid elements selector: '${JSON.stringify(elementsMatcher)}'`);
149
+ return;
150
+ }
151
+ // Validate the matcher
152
+ if (!isValidElementTypesMatcher(matcher, settings)) {
153
+ (0, Debug_1.warnOnce)(`Option '${JSON.stringify(matcher)}' does not match any element type from '${ELEMENTS}' setting`);
128
154
  }
129
155
  }
130
156
  function isValidElementAssigner(element) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-boundaries",
3
- "version": "5.2.0",
3
+ "version": "5.3.1",
4
4
  "description": "Eslint plugin checking architecture boundaries between elements",
5
5
  "keywords": [
6
6
  "eslint",
@@ -53,14 +53,16 @@
53
53
  "eslint-import-resolver-node": "0.3.9",
54
54
  "eslint-module-utils": "2.12.1",
55
55
  "micromatch": "4.0.8",
56
- "@boundaries/elements": "1.1.0"
56
+ "@boundaries/elements": "1.1.2"
57
57
  },
58
58
  "engines": {
59
59
  "node": ">=18.18"
60
60
  },
61
61
  "devDependencies": {
62
62
  "@types/estree": "1.0.8",
63
- "@types/micromatch": "4.0.9"
63
+ "@types/micromatch": "4.0.9",
64
+ "eslint-plugin-eslint-plugin": "7.2.0",
65
+ "eslint-plugin-local-rules": "3.0.2"
64
66
  },
65
67
  "scripts": {
66
68
  "eslint": "eslint",