eslint-plugin-import-boundaries 0.2.2 → 0.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 +210 -33
- package/eslint-plugin-import-boundaries.js +6 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/eslint-plugin-import-boundaries)
|
|
6
6
|
[](https://opensource.org/licenses/ISC)
|
|
7
7
|
|
|
8
|
-
**Note: This is
|
|
8
|
+
**Note: This is an alpha release, originally developed for a personal project. It is not yet stable and may have breaking changes.**
|
|
9
9
|
|
|
10
10
|
An opinionated ESLint rule that enforces architectural boundaries using deterministic import path rules. This rule determines when to use alias vs relative imports based on your architecture, rather than enforcing a single pattern for all imports.
|
|
11
11
|
|
|
@@ -20,6 +20,7 @@ An opinionated ESLint rule that enforces architectural boundaries using determin
|
|
|
20
20
|
- **Auto-fixable**: Legal import paths are auto-fixable and will always converge to the correct import string.
|
|
21
21
|
- **Zero I/O**: Pure path math and AST analysis - fast even on large codebases
|
|
22
22
|
- **Type-aware**: Different rules for type-only imports vs value imports
|
|
23
|
+
- **Test-ready**: Flexible configuration for test files (skip boundary rules while maintaining path format)
|
|
23
24
|
- **Circular Dependency Prevention**: Blocks ancestor barrel imports
|
|
24
25
|
- **Configurable**: Works with any architectural pattern
|
|
25
26
|
|
|
@@ -29,6 +30,8 @@ An opinionated ESLint rule that enforces architectural boundaries using determin
|
|
|
29
30
|
npm install --save-dev eslint-plugin-import-boundaries
|
|
30
31
|
```
|
|
31
32
|
|
|
33
|
+
### Using Alias Paths (Default)
|
|
34
|
+
|
|
32
35
|
```javascript
|
|
33
36
|
// eslint.config.js
|
|
34
37
|
import importBoundaries from "eslint-plugin-import-boundaries";
|
|
@@ -42,6 +45,7 @@ export default {
|
|
|
42
45
|
"error",
|
|
43
46
|
{
|
|
44
47
|
rootDir: "src",
|
|
48
|
+
crossBoundaryStyle: "alias", // Default: use alias paths
|
|
45
49
|
boundaries: [
|
|
46
50
|
{ dir: "domain", alias: "@domain" },
|
|
47
51
|
{ dir: "application", alias: "@application" },
|
|
@@ -53,6 +57,21 @@ export default {
|
|
|
53
57
|
};
|
|
54
58
|
```
|
|
55
59
|
|
|
60
|
+
**Import patterns with aliases:**
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// Cross-boundary imports → use alias
|
|
64
|
+
import { Entity } from "@domain"; // ✅
|
|
65
|
+
import { UseCase } from "@application"; // ✅
|
|
66
|
+
|
|
67
|
+
// Same-boundary, close imports → use relative
|
|
68
|
+
import { helper } from "./helper"; // ✅ Same directory
|
|
69
|
+
import { utils } from "../utils"; // ✅ Parent's sibling
|
|
70
|
+
|
|
71
|
+
// Same-boundary, distant imports → use alias
|
|
72
|
+
import { useCase } from "@application/use-cases"; // ✅ Distant in same boundary
|
|
73
|
+
```
|
|
74
|
+
|
|
56
75
|
## What Problem Does This Solve?
|
|
57
76
|
|
|
58
77
|
Most projects suffer from inconsistent import patterns that create ambiguity and technical debt:
|
|
@@ -104,8 +123,8 @@ import { helper } from "./helper"; // ✅
|
|
|
104
123
|
// Parent's sibling (cousin, max one ../)
|
|
105
124
|
import { utils } from "../utils"; // ✅
|
|
106
125
|
|
|
107
|
-
//
|
|
108
|
-
import { useCase } from "@application/
|
|
126
|
+
// Distant within same boundary → Use alias (with subpath allowed for same-boundary imports)
|
|
127
|
+
import { useCase } from "@application/use-cases"; // ✅ Same boundary, distant location
|
|
109
128
|
```
|
|
110
129
|
|
|
111
130
|
### 3. Architectural Boundary Enforcement
|
|
@@ -132,31 +151,70 @@ import { Database } from "@infrastructure";
|
|
|
132
151
|
|
|
133
152
|
#### Nested Boundaries
|
|
134
153
|
|
|
135
|
-
Boundaries can be nested, and each boundary must explicitly declare its import rules:
|
|
154
|
+
Boundaries can be nested, and each boundary must explicitly declare its import rules. Each nested boundary has its own independent rules (no inheritance from parent boundaries):
|
|
136
155
|
|
|
137
156
|
```javascript
|
|
138
157
|
{
|
|
139
158
|
boundaries: [
|
|
140
159
|
{
|
|
141
|
-
dir: '
|
|
142
|
-
alias: '@
|
|
143
|
-
allowImportsFrom: ['@domain'],
|
|
160
|
+
dir: 'interface',
|
|
161
|
+
alias: '@interface',
|
|
162
|
+
allowImportsFrom: ['@application', '@domain'], // @interface can import from @application and @domain
|
|
163
|
+
// Implicitly denies all other boundaries (including @infrastructure, @composition, etc.)
|
|
144
164
|
},
|
|
145
165
|
{
|
|
146
|
-
dir: '
|
|
147
|
-
alias: '@
|
|
148
|
-
allowImportsFrom: ['@
|
|
166
|
+
dir: 'interface/api',
|
|
167
|
+
alias: '@api',
|
|
168
|
+
allowImportsFrom: ['@domain', '@public-use-cases'],
|
|
169
|
+
// @api (public REST API) only allows public use cases, not all of @application
|
|
170
|
+
// This demonstrates selective access within an allowed parent boundary
|
|
171
|
+
// Note: @public-use-cases and @internal-use-cases would be separate boundaries
|
|
172
|
+
// defined elsewhere in your boundaries array
|
|
173
|
+
denyImportsFrom: ['@internal-use-cases'],
|
|
149
174
|
},
|
|
150
175
|
{
|
|
151
|
-
dir: 'interface',
|
|
152
|
-
alias: '@
|
|
153
|
-
allowImportsFrom: ['@application', '@
|
|
154
|
-
|
|
176
|
+
dir: 'interface/graphql',
|
|
177
|
+
alias: '@graphql',
|
|
178
|
+
allowImportsFrom: ['@application', '@domain'],
|
|
179
|
+
// @graphql can import from all of @application (different rules than @api sibling)
|
|
180
|
+
// This shows how sibling boundaries can have different rules
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
dir: 'composition',
|
|
184
|
+
alias: '@composition',
|
|
185
|
+
allowImportsFrom: ['@domain', '@application', '@infrastructure', '@interface'],
|
|
186
|
+
// @composition can import from all boundaries (wiring layer)
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
dir: 'composition/di',
|
|
190
|
+
alias: '@di',
|
|
191
|
+
allowImportsFrom: ['@domain', '@application', '@infrastructure'],
|
|
192
|
+
// @di (dependency injection setup) doesn't need @interface
|
|
193
|
+
// This shows how nested boundaries can be more restrictive than parent
|
|
155
194
|
},
|
|
156
195
|
],
|
|
157
196
|
}
|
|
158
197
|
```
|
|
159
198
|
|
|
199
|
+
**Example behavior:**
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
// File: interface/api/user-controller.ts
|
|
203
|
+
import { Entity } from "@domain"; // ✅ Allowed: @api can import from @domain
|
|
204
|
+
import { CreateUser } from "@public-use-cases"; // ✅ Allowed: @api can import from @public-use-cases
|
|
205
|
+
import { InternalAudit } from "@internal-use-cases"; // ❌ Violation: @api explicitly denies @internal-use-cases
|
|
206
|
+
|
|
207
|
+
// File: interface/graphql/user-resolver.ts
|
|
208
|
+
import { Entity } from "@domain"; // ✅ Allowed: @graphql can import from @domain
|
|
209
|
+
import { CreateUser } from "@public-use-cases"; // ✅ Allowed: @graphql can import from any @application code
|
|
210
|
+
import { InternalAudit } from "@internal-use-cases"; // ✅ Allowed: @graphql has different rules than @api sibling
|
|
211
|
+
|
|
212
|
+
// File: composition/di/container.ts
|
|
213
|
+
import { Repository } from "@infrastructure"; // ✅ Allowed: @di can import from @infrastructure for wiring
|
|
214
|
+
import { UseCase } from "@application"; // ✅ Allowed: @di can import from @application
|
|
215
|
+
import { Controller } from "@interface"; // ❌ Violation: @di cannot import from @interface (more restrictive than parent)
|
|
216
|
+
```
|
|
217
|
+
|
|
160
218
|
**Key behaviors:**
|
|
161
219
|
|
|
162
220
|
- Each boundary has rules: explicit (via `allowImportsFrom`/`denyImportsFrom`) or implicit "deny all" (if neither is specified)
|
|
@@ -167,11 +225,23 @@ Boundaries can be nested, and each boundary must explicitly declare its import r
|
|
|
167
225
|
|
|
168
226
|
**Rule semantics:**
|
|
169
227
|
|
|
170
|
-
- If both `allowImportsFrom` and `denyImportsFrom` exist: `allowImportsFrom` takes precedence (items in allow list are allowed even if also in deny list)
|
|
171
228
|
- If only `allowImportsFrom`: deny-all by default (only items in allow list are allowed)
|
|
172
229
|
- If only `denyImportsFrom`: allow-all by default (everything except deny list is allowed)
|
|
173
230
|
- If neither: deny-all by default (strictest)
|
|
174
|
-
-
|
|
231
|
+
- If both `allowImportsFrom` and `denyImportsFrom` exist: Both lists apply independently. Items in the allow list are allowed (unless also in deny list), items in the deny list are denied, and items in neither list are denied by default (whitelist behavior). This allows you to deny specific sub-boundaries within an allowed parent boundary. For example, you can allow `@application` but deny its sub-boundary `@units`:
|
|
232
|
+
|
|
233
|
+
```javascript
|
|
234
|
+
{
|
|
235
|
+
dir: 'interface',
|
|
236
|
+
alias: '@interface',
|
|
237
|
+
allowImportsFrom: ['@application'], // Allow all of @application
|
|
238
|
+
denyImportsFrom: ['@units'], // Deny this specific sub-boundary
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
Note: This works recursively - you can allow a boundary within a denied boundary within an allowed boundary, and so on.
|
|
243
|
+
|
|
244
|
+
**Conflict resolution:** If the same boundary identifier appears in both lists (which indicates a configuration error), `denyImportsFrom` takes precedence - the import will be denied. This ensures safety: explicit denials override allows.
|
|
175
245
|
|
|
176
246
|
### 4. Type-Only Imports
|
|
177
247
|
|
|
@@ -182,7 +252,7 @@ Different rules for types vs values (types don't create runtime dependencies):
|
|
|
182
252
|
dir: 'infrastructure',
|
|
183
253
|
alias: '@infrastructure',
|
|
184
254
|
allowImportsFrom: ['@domain'], // Value imports from domain
|
|
185
|
-
allowTypeImportsFrom: ['@
|
|
255
|
+
allowTypeImportsFrom: ['@ports'], // Type imports from ports (interfaces for dependency inversion)
|
|
186
256
|
}
|
|
187
257
|
```
|
|
188
258
|
|
|
@@ -209,27 +279,23 @@ import { something } from "@application"; // When inside @application boundary
|
|
|
209
279
|
|
|
210
280
|
### Basic Configuration
|
|
211
281
|
|
|
282
|
+
Here's a complete configuration example with all boundary rules:
|
|
283
|
+
|
|
212
284
|
```javascript
|
|
213
285
|
{
|
|
214
|
-
rootDir: 'src', // Root directory (default: 'src')
|
|
215
|
-
crossBoundaryStyle: 'alias', // 'alias' | 'absolute' (default: 'alias')
|
|
216
|
-
defaultSeverity: 'error', // 'error' | 'warn' (default: 'error')
|
|
217
|
-
allowUnknownBoundaries: false, // Allow imports outside boundaries (default: false)
|
|
218
|
-
skipBoundaryRulesForTestFiles: true, // Skip boundary rules for tests (default: true)
|
|
219
|
-
barrelFileName: 'index', // Barrel file name without extension (default: 'index')
|
|
220
|
-
fileExtensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'], // Extensions to recognize (default: all common JS/TS extensions)
|
|
286
|
+
rootDir: 'src', // Required: Root directory (default: 'src')
|
|
221
287
|
boundaries: [ // Required: Array of boundary definitions
|
|
222
288
|
{
|
|
223
289
|
dir: 'domain', // Required: Relative directory path
|
|
224
|
-
alias: '@domain', // Required
|
|
225
|
-
denyImportsFrom: ['@application', '@infrastructure', '@interface', '@composition'], // Domain is pure
|
|
290
|
+
alias: '@domain', // Required: Import alias (e.g., '@domain')
|
|
291
|
+
denyImportsFrom: ['@application', '@infrastructure', '@interface', '@composition'], // Domain is pure - denies all other boundaries
|
|
226
292
|
severity: 'error', // Optional: 'error' | 'warn' (overrides defaultSeverity for this boundary)
|
|
227
293
|
},
|
|
228
294
|
{
|
|
229
295
|
dir: 'application',
|
|
230
296
|
alias: '@application',
|
|
231
297
|
allowImportsFrom: ['@domain'], // Application uses domain (deny-all by default)
|
|
232
|
-
// Note: denyImportsFrom is redundant here -
|
|
298
|
+
// Note: denyImportsFrom is redundant here - anything not in allowImportsFrom is already denied
|
|
233
299
|
},
|
|
234
300
|
{
|
|
235
301
|
dir: 'infrastructure',
|
|
@@ -238,34 +304,92 @@ import { something } from "@application"; // When inside @application boundary
|
|
|
238
304
|
allowTypeImportsFrom: ['@application'], // Infrastructure implements application ports (types only)
|
|
239
305
|
},
|
|
240
306
|
],
|
|
307
|
+
// Optional configuration options (all have sensible defaults):
|
|
308
|
+
defaultSeverity: 'error', // 'error' | 'warn' (default: 'error')
|
|
309
|
+
enforceBoundaries: true, // Enforce boundary rules (default: true)
|
|
310
|
+
allowUnknownBoundaries: false, // Allow imports outside boundaries (default: false)
|
|
311
|
+
barrelFileName: 'index', // Barrel file name without extension (default: 'index')
|
|
312
|
+
fileExtensions: ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'], // Extensions to recognize (default: all common JS/TS extensions)
|
|
241
313
|
}
|
|
242
314
|
```
|
|
243
315
|
|
|
316
|
+
**Important:** The `enforceBoundaries` option applies globally to all files when set. To have different behavior for test files vs regular files, you must use ESLint's file matching (see Test Files Configuration below).
|
|
317
|
+
|
|
244
318
|
### Test Files Configuration
|
|
245
319
|
|
|
246
|
-
|
|
320
|
+
**How test exclusion works:** When `enforceBoundaries: false`, the rule skips boundary rule checking (allow/deny rules) but still enforces path format (alias vs relative). This allows test files to import from any boundary while maintaining consistent import path patterns. The default is `true` (boundary rules are enforced by default).
|
|
321
|
+
|
|
322
|
+
**Why skip boundary rules for tests?** Test files often need to:
|
|
323
|
+
|
|
324
|
+
- Import from multiple boundaries to set up test scenarios (e.g., mocking infrastructure while testing application logic)
|
|
325
|
+
- Use test helper libraries or mocks that don't fit clean architectural boundaries
|
|
326
|
+
- Access internal implementation details for thorough testing
|
|
327
|
+
- Create test fixtures that span multiple boundaries
|
|
328
|
+
|
|
329
|
+
By setting `enforceBoundaries: false` for test files, you maintain architectural boundaries in production code while giving tests the flexibility they need. Path format (alias vs relative) is still enforced, keeping import paths consistent and readable.
|
|
330
|
+
|
|
331
|
+
**Alternative approach:** You can also define separate boundaries for test directories (e.g., `test/domain`, `test/application`) with their own import rules, but this has two downsides: it discourages test collocation (tests must live in separate test directories rather than alongside source files), and it requires much more configuration overhead than most projects need. The `enforceBoundaries: false` approach is simpler and sufficient for most use cases.
|
|
332
|
+
|
|
333
|
+
**Configuration pattern:** Use ESLint's file matching to apply different configs to test files vs regular files. Define boundaries once and reuse them in both config blocks:
|
|
247
334
|
|
|
248
335
|
```javascript
|
|
336
|
+
import importBoundaries from "eslint-plugin-import-boundaries";
|
|
337
|
+
|
|
338
|
+
// Define boundaries once - shared between regular files and test files
|
|
339
|
+
const boundaries = [
|
|
340
|
+
{
|
|
341
|
+
dir: "domain",
|
|
342
|
+
alias: "@domain",
|
|
343
|
+
// No imports allowed by default
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
dir: "application",
|
|
347
|
+
alias: "@application",
|
|
348
|
+
allowImportsFrom: ["@domain"],
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
dir: "infrastructure",
|
|
352
|
+
alias: "@infrastructure",
|
|
353
|
+
allowImportsFrom: ["@domain"],
|
|
354
|
+
},
|
|
355
|
+
];
|
|
356
|
+
|
|
249
357
|
export default [
|
|
358
|
+
// Test files - skip boundary rules but keep path format enforcement
|
|
359
|
+
// Put test files first so they take precedence over regular file patterns
|
|
250
360
|
{
|
|
251
|
-
files: [
|
|
361
|
+
files: [
|
|
362
|
+
"**/*.test.{ts,js}",
|
|
363
|
+
"**/*.spec.{ts,js}",
|
|
364
|
+
"**/__tests__/**/*.{ts,js}",
|
|
365
|
+
],
|
|
252
366
|
rules: {
|
|
253
367
|
"import-boundaries/enforce": [
|
|
254
368
|
"error",
|
|
255
369
|
{
|
|
256
|
-
|
|
370
|
+
rootDir: "src",
|
|
371
|
+
enforceBoundaries: false, // Tests can import from any boundary
|
|
372
|
+
boundaries, // Same boundaries - needed for path format calculation
|
|
257
373
|
},
|
|
258
374
|
],
|
|
259
375
|
},
|
|
260
376
|
},
|
|
377
|
+
// Regular source files - enforce boundary rules
|
|
378
|
+
// Excludes test files via ignores to prevent overlap
|
|
261
379
|
{
|
|
262
|
-
files: ["
|
|
380
|
+
files: ["src/**/*.ts", "src/**/*.tsx"],
|
|
381
|
+
ignores: [
|
|
382
|
+
"**/*.test.{ts,js}",
|
|
383
|
+
"**/*.spec.{ts,js}",
|
|
384
|
+
"**/__tests__/**/*.{ts,js}",
|
|
385
|
+
],
|
|
263
386
|
rules: {
|
|
264
387
|
"import-boundaries/enforce": [
|
|
265
388
|
"error",
|
|
266
389
|
{
|
|
267
|
-
|
|
268
|
-
//
|
|
390
|
+
rootDir: "src",
|
|
391
|
+
enforceBoundaries: true, // Enforce boundary rules
|
|
392
|
+
boundaries,
|
|
269
393
|
},
|
|
270
394
|
],
|
|
271
395
|
},
|
|
@@ -273,6 +397,59 @@ export default [
|
|
|
273
397
|
];
|
|
274
398
|
```
|
|
275
399
|
|
|
400
|
+
**What gets checked in test files:**
|
|
401
|
+
|
|
402
|
+
- ✅ Path format (alias vs relative) - still enforced
|
|
403
|
+
- ✅ Barrel file imports - still enforced
|
|
404
|
+
- ✅ Ancestor barrel prevention - still enforced
|
|
405
|
+
- ❌ Boundary allow/deny rules - **skipped** (tests can import from any boundary)
|
|
406
|
+
|
|
407
|
+
**What gets checked in regular files:**
|
|
408
|
+
|
|
409
|
+
- ✅ Path format (alias vs relative)
|
|
410
|
+
- ✅ Barrel file imports
|
|
411
|
+
- ✅ Ancestor barrel prevention
|
|
412
|
+
- ✅ Boundary allow/deny rules - **enforced**
|
|
413
|
+
|
|
414
|
+
### Using Absolute Paths
|
|
415
|
+
|
|
416
|
+
By default, the rule uses alias paths (e.g., `@domain`). If your build configuration doesn't support path aliases, or you prefer absolute paths, you can use `crossBoundaryStyle: 'absolute'`:
|
|
417
|
+
|
|
418
|
+
```javascript
|
|
419
|
+
{
|
|
420
|
+
rootDir: 'src',
|
|
421
|
+
crossBoundaryStyle: 'absolute', // Use absolute paths instead of aliases
|
|
422
|
+
boundaries: [
|
|
423
|
+
{ dir: 'domain' }, // No alias required when using absolute paths
|
|
424
|
+
{ dir: 'application' },
|
|
425
|
+
{ dir: 'infrastructure' },
|
|
426
|
+
],
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
**Import patterns with absolute paths:**
|
|
431
|
+
|
|
432
|
+
```typescript
|
|
433
|
+
// Cross-boundary imports → use absolute path
|
|
434
|
+
import { Entity } from "src/domain"; // ✅
|
|
435
|
+
import { UseCase } from "src/application"; // ✅
|
|
436
|
+
|
|
437
|
+
// Same-boundary, close imports → use relative (same as alias style)
|
|
438
|
+
import { helper } from "./helper"; // ✅ Same directory
|
|
439
|
+
import { utils } from "../utils"; // ✅ Parent's sibling
|
|
440
|
+
|
|
441
|
+
// Same-boundary, distant imports → use absolute path
|
|
442
|
+
import { useCase } from "src/application/use-cases"; // ✅ Distant in same boundary
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
**When to use absolute paths:**
|
|
446
|
+
|
|
447
|
+
- Your build configuration doesn't support path aliases (e.g., some bundlers or older tooling)
|
|
448
|
+
- You prefer explicit paths over aliases for clarity
|
|
449
|
+
- You're working in a codebase that already uses absolute paths
|
|
450
|
+
|
|
451
|
+
**Note:** Alias paths are recommended for readability, especially for boundaries defined at deeper directory levels (e.g., `@entities/user` vs `src/hexagonal/domain/entities/user`). However, this rule does not require them since not all build configurations support path aliases. When using `crossBoundaryStyle: 'absolute'`, the `alias` property in boundary definitions becomes optional, and the rule will use paths like `src/domain` instead of `@domain`.
|
|
452
|
+
|
|
276
453
|
## How It Works
|
|
277
454
|
|
|
278
455
|
The rule uses pure path math - no file I/O, just deterministic algorithms:
|
|
@@ -139,8 +139,8 @@ function matchesBoundaryIdentifier(identifier, targetBoundary) {
|
|
|
139
139
|
*
|
|
140
140
|
* Semantics:
|
|
141
141
|
* - If both allowImportsFrom and denyImportsFrom are specified, they work as:
|
|
142
|
-
* -
|
|
143
|
-
* -
|
|
142
|
+
* - Both lists apply independently (allow applies to items in allow list, deny applies to items in deny list)
|
|
143
|
+
* - If the same identifier appears in both lists (configuration error), denyImportsFrom takes precedence for safety
|
|
144
144
|
* - If only allowImportsFrom: only those boundaries are allowed (deny-all by default)
|
|
145
145
|
* - If only denyImportsFrom: all boundaries allowed except those (allow-all by default)
|
|
146
146
|
* - If neither: deny-all by default (strictest)
|
|
@@ -153,8 +153,8 @@ function checkBoundaryRules(fileBoundary, targetBoundary, allBoundaries, isTypeO
|
|
|
153
153
|
if (isTypeOnly && fileBoundary.allowTypeImportsFrom?.some((id) => matchesBoundaryIdentifier(id, targetBoundary))) return null;
|
|
154
154
|
const hasAllowList = fileBoundary.allowImportsFrom && fileBoundary.allowImportsFrom.length > 0;
|
|
155
155
|
const hasDenyList = fileBoundary.denyImportsFrom && fileBoundary.denyImportsFrom.length > 0;
|
|
156
|
+
if (hasDenyList && fileBoundary.denyImportsFrom.some((id) => matchesBoundaryIdentifier(id, targetBoundary))) return { reason: hasAllowList && fileBoundary.allowImportsFrom.some((id) => matchesBoundaryIdentifier(id, targetBoundary)) ? `Boundary '${fileIdentifier}' explicitly denies imports from '${targetIdentifier}' (deny takes precedence over allow)` : `Boundary '${fileIdentifier}' explicitly denies imports from '${targetIdentifier}'` };
|
|
156
157
|
if (hasAllowList && fileBoundary.allowImportsFrom.some((id) => matchesBoundaryIdentifier(id, targetBoundary))) return null;
|
|
157
|
-
if (hasDenyList && fileBoundary.denyImportsFrom.some((id) => matchesBoundaryIdentifier(id, targetBoundary))) return { reason: `Boundary '${fileIdentifier}' explicitly denies imports from '${targetIdentifier}'` };
|
|
158
158
|
if (hasAllowList && !hasDenyList) return { reason: `Cross-boundary import from '${targetIdentifier}' to '${fileIdentifier}' is not allowed. Add '${targetIdentifier}' to 'allowImportsFrom' if this import is intentional.` };
|
|
159
159
|
if (hasDenyList && !hasAllowList) return null;
|
|
160
160
|
return { reason: `Cross-boundary import from '${targetIdentifier}' to '${fileIdentifier}' is not allowed. Add '${targetIdentifier}' to 'allowImportsFrom' if this import is intentional.` };
|
|
@@ -497,7 +497,7 @@ const rule = {
|
|
|
497
497
|
type: "boolean",
|
|
498
498
|
default: false
|
|
499
499
|
},
|
|
500
|
-
|
|
500
|
+
enforceBoundaries: {
|
|
501
501
|
type: "boolean",
|
|
502
502
|
default: true
|
|
503
503
|
},
|
|
@@ -529,7 +529,7 @@ const rule = {
|
|
|
529
529
|
},
|
|
530
530
|
create(context) {
|
|
531
531
|
if (!context.options || context.options.length === 0) throw new Error("boundary-alias-vs-relative requires boundaries configuration");
|
|
532
|
-
const { rootDir = "src", boundaries, crossBoundaryStyle = "alias", defaultSeverity, allowUnknownBoundaries = false,
|
|
532
|
+
const { rootDir = "src", boundaries, crossBoundaryStyle = "alias", defaultSeverity, allowUnknownBoundaries = false, enforceBoundaries = true, barrelFileName = "index", fileExtensions = [
|
|
533
533
|
".ts",
|
|
534
534
|
".tsx",
|
|
535
535
|
".js",
|
|
@@ -592,7 +592,7 @@ const rule = {
|
|
|
592
592
|
defaultSeverity,
|
|
593
593
|
allowUnknownBoundaries,
|
|
594
594
|
isTypeOnly,
|
|
595
|
-
skipBoundaryRules:
|
|
595
|
+
skipBoundaryRules: !enforceBoundaries,
|
|
596
596
|
barrelFileName,
|
|
597
597
|
fileExtensions
|
|
598
598
|
});
|
package/package.json
CHANGED