eslint-plugin-import-boundaries 0.2.0 → 0.2.2

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
@@ -116,8 +116,8 @@ Prevent violations of your architecture:
116
116
  {
117
117
  dir: 'application',
118
118
  alias: '@application',
119
- allowImportsFrom: ['@domain'], // Only allow imports from @domain
120
- denyImportsFrom: ['@infrastructure'], // Deny imports from @infrastructure (dependency inversion)
119
+ allowImportsFrom: ['@domain'], // Only allow imports from @domain (deny-all by default)
120
+ // Note: denyImportsFrom is redundant here - anything not in allowImportsFrom is already denied
121
121
  }
122
122
  ```
123
123
 
@@ -151,7 +151,7 @@ Boundaries can be nested, and each boundary must explicitly declare its import r
151
151
  dir: 'interface',
152
152
  alias: '@interface',
153
153
  allowImportsFrom: ['@application', '@public-use-cases'],
154
- denyImportsFrom: ['@use-cases'], // Can allow parent and specific child, but deny intermediate boundary
154
+ denyImportsFrom: ['@use-cases'], // Deny specific sub-boundary even though parent @application is allowed
155
155
  },
156
156
  ],
157
157
  }
@@ -159,11 +159,19 @@ Boundaries can be nested, and each boundary must explicitly declare its import r
159
159
 
160
160
  **Key behaviors:**
161
161
 
162
- - Each boundary must explicitly declare its rules (no inheritance)
163
- - Files in boundaries without rules resolve to their nearest ancestor with rules (or are rejected if no ancestor has rules)
162
+ - Each boundary has rules: explicit (via `allowImportsFrom`/`denyImportsFrom`) or implicit "deny all" (if neither is specified)
163
+ - Each boundary uses its own rules directly (no inheritance from parent boundaries)
164
164
  - Rules work the same regardless of nesting depth (flat rule checking)
165
165
  - You can selectively allow/deny specific nested boundaries
166
- - A boundary is considered to have rules if it defines `allowImportsFrom`, `denyImportsFrom`, or `allowTypeImportsFrom` (even if empty arrays)
166
+ - Files resolve to their most specific boundary (longest matching path), which determines the rules to apply
167
+
168
+ **Rule semantics:**
169
+
170
+ - If both `allowImportsFrom` and `denyImportsFrom` exist: `allowImportsFrom` takes precedence (items in allow list are allowed even if also in deny list)
171
+ - If only `allowImportsFrom`: deny-all by default (only items in allow list are allowed)
172
+ - If only `denyImportsFrom`: allow-all by default (everything except deny list is allowed)
173
+ - If neither: deny-all by default (strictest)
174
+ - **Important**: When `allowImportsFrom` is specified, `denyImportsFrom` can deny specific sub-boundaries (e.g. deny `@utils` within allowed `@application`), but is otherwise redundant since anything not in the allow list is already denied by default. Note that this works recursively: It is possible to allow a boundary within a denied boundary within an allowed boundary, and so on.
167
175
 
168
176
  ### 4. Type-Only Imports
169
177
 
@@ -220,8 +228,8 @@ import { something } from "@application"; // When inside @application boundary
220
228
  {
221
229
  dir: 'application',
222
230
  alias: '@application',
223
- allowImportsFrom: ['@domain'], // Application uses domain
224
- denyImportsFrom: ['@infrastructure', '@interface', '@composition'], // Dependency inversion
231
+ allowImportsFrom: ['@domain'], // Application uses domain (deny-all by default)
232
+ // Note: denyImportsFrom is redundant here - those boundaries are already denied
225
233
  },
226
234
  {
227
235
  dir: 'infrastructure',
@@ -87,18 +87,16 @@ function checkAliasSubpath(spec, boundaries) {
87
87
  return { isSubpath: false };
88
88
  }
89
89
  /**
90
- * Resolve a file to the nearest boundary that has rules specified.
91
- * If no boundaries with rules are found, returns null.
90
+ * Resolve a file/path to the nearest boundary (regardless of rules).
91
+ * Used for target boundaries - returns the boundary if it exists, even without rules.
92
92
  *
93
93
  * @param filename - Absolute filename
94
94
  * @param boundaries - Array of all boundaries
95
- * @returns The nearest boundary with rules, or null if none found
95
+ * @returns The nearest boundary, or null if none found
96
96
  */
97
- function resolveToSpecifiedBoundary(filename, boundaries) {
98
- const specifiedBoundaries = boundaries.filter((b) => isInsideDir(b.absDir, filename)).filter((b) => b.allowImportsFrom !== void 0 || b.denyImportsFrom !== void 0 || b.allowTypeImportsFrom !== void 0);
99
- if (specifiedBoundaries.length > 0) return specifiedBoundaries.sort((a, b) => b.absDir.length - a.absDir.length)[0];
100
- const ancestors = boundaries.filter((b) => b.allowImportsFrom !== void 0 || b.denyImportsFrom !== void 0 || b.allowTypeImportsFrom !== void 0).filter((b) => isInsideDir(b.absDir, filename));
101
- if (ancestors.length > 0) return ancestors.sort((a, b) => b.absDir.length - a.absDir.length)[0];
97
+ function resolveToBoundary(filename, boundaries) {
98
+ const matchingBoundaries = boundaries.filter((b) => isInsideDir(b.absDir, filename));
99
+ if (matchingBoundaries.length > 0) return matchingBoundaries.sort((a, b) => b.absDir.length - a.absDir.length)[0];
102
100
  return null;
103
101
  }
104
102
  /**
@@ -114,7 +112,7 @@ function getFileData(filename, boundaries) {
114
112
  return {
115
113
  isValid: true,
116
114
  fileDir: path.dirname(filename),
117
- fileBoundary: resolveToSpecifiedBoundary(filename, boundaries)
115
+ fileBoundary: resolveToBoundary(filename, boundaries)
118
116
  };
119
117
  }
120
118
 
@@ -286,7 +284,7 @@ function calculateCorrectImportPath(rawSpec, fileDir, fileBoundary, boundaries,
286
284
  ".cjs"
287
285
  ]) {
288
286
  const { targetAbs, targetDir } = resolveTargetPath(rawSpec, fileDir, boundaries, rootDir, cwd, barrelFileName, fileExtensions);
289
- const targetBoundary = resolveToSpecifiedBoundary(targetAbs, boundaries);
287
+ const targetBoundary = resolveToBoundary(targetAbs, boundaries);
290
288
  if (!fileBoundary || targetBoundary !== fileBoundary) {
291
289
  if (targetBoundary) {
292
290
  if (crossBoundaryStyle === "absolute") return path.join(rootDir, targetBoundary.dir).replace(/\\/g, "/");
@@ -371,7 +369,7 @@ function handleImport(options) {
371
369
  }
372
370
  }
373
371
  }
374
- const targetBoundary = resolveToSpecifiedBoundary(targetAbs, boundaries);
372
+ const targetBoundary = resolveToBoundary(targetAbs, boundaries);
375
373
  if (!skipBoundaryRules && fileBoundary && targetBoundary && fileBoundary !== targetBoundary) {
376
374
  const violation = checkBoundaryRules(fileBoundary, targetBoundary, boundaries, isTypeOnly);
377
375
  if (violation) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-import-boundaries",
3
3
  "type": "module",
4
- "version": "0.2.0",
4
+ "version": "0.2.2",
5
5
  "description": "Enforce architectural boundaries with deterministic import paths",
6
6
  "author": "ClassicalMoser",
7
7
  "license": "ISC",
@@ -55,7 +55,7 @@
55
55
  "format:check": "prettier --check .",
56
56
  "validate:fix": "pnpm run lint:fix && pnpm run format && pnpm run typecheck",
57
57
  "validate:check": "pnpm run lint && pnpm run format:check && pnpm run typecheck",
58
- "prepublishOnly": "npm run build && npm run test && npm run typecheck"
58
+ "prepublishOnly": "npm run build && npm run test && npm run validate:check"
59
59
  },
60
60
  "peerDependencies": {
61
61
  "eslint": ">=9.0.0"