eslint-plugin-import-structure 0.10.4 → 0.10.5

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.
Files changed (3) hide show
  1. package/README.md +82 -29
  2. package/dist/index.js +1 -1
  3. package/package.json +12 -2
package/README.md CHANGED
@@ -1,39 +1,90 @@
1
1
  # eslint-plugin-import-structure
2
2
 
3
- Import-structure rules for both [ESLint](https://eslint.org) and [Oxlint](https://oxc.rs/docs/guide/usage/linter): a module graph that stays layered and free of cycles.\
4
- Install with `npm install --save-dev eslint-plugin-import-structure`. Requires Node.js 22.18+.
3
+ Can the `#billing` module import `#auth` without creating a cycle? With layers you can see the answer, and ESLint enforces it.
4
+ This plugin turns the order of your `package.json` `imports` into those layers, and reports upward imports and deep imports into another module's internals.
5
+ Works in both [ESLint](https://eslint.org) and [Oxlint](https://oxc.rs/docs/guide/usage/linter).
6
+
7
+ ![package.json imports listing #app, #billing, #auth, #utils in layer order, next to each module drawn as a tree of files behind its index.ts, importing only modules to its right](https://raw.githubusercontent.com/wiki/cunarist/eslint-plugin-import-structure/images/module-layers.svg)
8
+
9
+ ## Why
10
+
11
+ A cycle compiles fine, then bites at runtime: `ReferenceError: Cannot access 'x' before initialization` in native ESM, or a silent `undefined` from CommonJS output and some bundlers.
12
+ Worse, it hides until something shuffles the import order.
13
+
14
+ Here, each `#` alias is a module with one front door, and the alias order is a one-way layer order.
15
+ A module imports only the modules listed below it, so cycles between modules can't form in the first place.
16
+ Unlike cycle detectors that walk the whole import graph, each rule checks one file against the declared layers, so it stays fast and the error names the alias to fix.
17
+
18
+ `package.json` becomes your architecture map.
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ npm install --save-dev eslint-plugin-import-structure
24
+ # for ESLint, also:
25
+ npm install --save-dev eslint typescript-eslint jiti
26
+ ```
27
+
28
+ Requires Node.js 22.18+.
29
+
30
+ ## Usage
31
+
32
+ ```ts
33
+ // ESLint 9+: eslint.config.ts (needs eslint, typescript-eslint, and jiti)
34
+ import plugin from "eslint-plugin-import-structure";
35
+ import { parser } from "typescript-eslint";
36
+
37
+ const ts = { files: ["**/*.ts"], languageOptions: { parser } };
38
+ export default [ts, plugin.configs.modules];
39
+ ```
5
40
 
6
41
  ```ts
7
42
  // Oxlint, through Vite+: vite.config.ts
8
43
  import plugin from "eslint-plugin-import-structure";
9
44
  import { defineConfig } from "vite-plus";
45
+
10
46
  const rules = { ...plugin.configs.modules.rules };
11
47
  export default defineConfig({
12
48
  lint: { jsPlugins: ["eslint-plugin-import-structure"], rules },
13
49
  });
14
50
  ```
15
51
 
52
+ Then declare your modules in `package.json`, top layer first:
53
+
54
+ ```json
55
+ {
56
+ "imports": {
57
+ "#billing": "./src/billing/index.ts",
58
+ "#auth": "./src/auth/index.ts",
59
+ "#db": "./src/db/index.ts"
60
+ }
61
+ }
62
+ ```
63
+
16
64
  ```ts
17
- // ESLint 9: eslint.config.ts (loading a .ts config needs jiti)
18
- import plugin from "eslint-plugin-import-structure";
19
- import { parser } from "typescript-eslint";
20
- const ts = { files: ["**/*.ts"], languageOptions: { parser } };
21
- export default [ts, plugin.configs.modules];
65
+ // in src/auth/session.ts
66
+ import { charge } from "#billing"; // ❌ upward import (enforce-layer-order)
67
+ import { query } from "#db"; // ✅ a lower layer, through its alias
68
+ import { pool } from "#db/pool.ts"; // ❌ deep import (no-barrel-bypass)
69
+
70
+ import { x } from "../x.ts"; // ❌ parent path (no-parent-import)
71
+ import { hash } from "./hash.ts"; // ✅ a sibling in the same module
22
72
  ```
23
73
 
24
74
  ## Configs and rules
25
75
 
26
- - `modules`: areas reached only through their `package.json` `imports` aliases, layered by their order.
27
- - `all`: every rule, including ones added later. Rules take no options.
76
+ - `modules`: every rule below, all at `error`.
77
+ - `all`: every rule, including rules added later. Today it equals `modules`.
28
78
 
29
- The order of the `#` aliases in `package.json` is the layer order, top first; an area may only import aliases declared below its own.
79
+ No rule takes options.
30
80
 
31
81
  ### Modules
32
82
 
83
+ The order of the `#` aliases in `package.json` is the layer order, top first; a module may only import aliases declared below its own.
33
84
  An alias owns its target, and code outside the owned area reaches it only through the alias.
34
- The rule never depends on folder or file names; an alias may point anywhere.
85
+ The rules never depend on folder or file names; an alias may point anywhere.
35
86
 
36
- - An alias to a folder's `index.ts` owns that folder and everything below it. An alias to any other file owns only that file.
87
+ - An alias to a folder's `index` file (`index.ts`, or any other extension) owns that folder and everything below it. An alias to any other file owns only that file.
37
88
  - No file may be owned by 2 aliases: no alias inside another alias's area, and no 2 aliases with the same target.
38
89
  `enforce-layer-order` reports an overlap once at the top of every file 2 aliases claim, since lint cannot report on `package.json`.
39
90
  - Outside an area, any other route to an owned file is a bypass: a deeper alias specifier such as `#a/one.ts` (`no-barrel-bypass`), or a relative path (`no-relative-bypass`, and `prefer-alias-import`, which rewrites a path to the alias's exact target).
@@ -52,37 +103,39 @@ The rule never depends on folder or file names; an alias may point anywhere.
52
103
  ```ts
53
104
  // in main.ts, which no alias owns
54
105
  import { a } from "#a"; // ✅
55
- import { one } from "#a/one.ts"; // ❌ deeper alias specifier
56
106
  import { two } from "./a/two.ts"; // ❌ relative path into #a
57
107
  import { f } from "./b/f.ts"; // ❌ relative path to a file alias; use "#f"
58
108
  import { g } from "./b/g.ts"; // ✅ unowned
59
109
  // in a/one.ts, owned by #a
60
110
  import { two } from "./two.ts"; // ✅ sibling
61
111
  import { deep } from "./sub/deep.ts"; // ✅ subfolder of the same area
62
- import { x } from "../x.ts"; // ❌ no ../ anywhere
63
112
  ```
64
113
 
65
114
  The other rules (import order, duplicate sources, wildcards, absolute paths, self-imports) are not about aliases and apply to every file.
66
115
 
116
+ ### Rules
117
+
67
118
  <!-- prettier-ignore-start -->
68
119
  <!-- begin auto-generated rules list -->
69
120
 
70
121
  💼 [Configurations](https://github.com/cunarist/eslint-plugin-import-structure#configs-and-rules) enabled in.\
71
- 🧭 Set in the `modules` [configuration](https://github.com/cunarist/eslint-plugin-import-structure#configs-and-rules).
72
-
73
- | Name                       | Description | 💼 |
74
- | :--------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :- |
75
- | [enforce-import-order](docs/rules/enforce-import-order.md) | Enforce grouped and sorted import declarations and import members | 🧭 |
76
- | [enforce-layer-order](docs/rules/enforce-layer-order.md) | Enforce the order of `package.json` `imports` as the layer order | 🧭 |
77
- | [enforce-mod-file](docs/rules/enforce-mod-file.md) | Enforce `index.ts` as the name of module entry points | 🧭 |
78
- | [no-absolute-import](docs/rules/no-absolute-import.md) | Disallow absolute paths and `file:` URLs as module specifiers | 🧭 |
79
- | [no-barrel-bypass](docs/rules/no-barrel-bypass.md) | Disallow `#` specifiers deeper than an alias | 🧭 |
80
- | [no-duplicate-import-source](docs/rules/no-duplicate-import-source.md) | Disallow separate import declarations from the same source | 🧭 |
81
- | [no-parent-import](docs/rules/no-parent-import.md) | Disallow parent-relative module specifiers | 🧭 |
82
- | [no-relative-bypass](docs/rules/no-relative-bypass.md) | Disallow relative specifiers that reach into an alias's area from outside it | 🧭 |
83
- | [no-self-import](docs/rules/no-self-import.md) | Disallow a module from importing itself | 🧭 |
84
- | [no-wildcard-import](docs/rules/no-wildcard-import.md) | Disallow namespace imports | 🧭 |
85
- | [prefer-alias-import](docs/rules/prefer-alias-import.md) | Require the `#` alias for a module that `package.json` declares | 🧭 |
122
+ 🧭 Set in the `modules` [configuration](https://github.com/cunarist/eslint-plugin-import-structure#configs-and-rules).\
123
+ 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/user-guide/command-line-interface#--fix).\
124
+ 💡 Manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
125
+
126
+ | Name                       | Description | 💼 | 🔧 | 💡 |
127
+ | :--------------------------------------------------------------------- | :--------------------------------------------------------------------------- | :- | :- | :- |
128
+ | [enforce-import-order](docs/rules/enforce-import-order.md) | Enforce grouped and sorted import declarations and import members | 🧭 | 🔧 | |
129
+ | [enforce-layer-order](docs/rules/enforce-layer-order.md) | Enforce the order of `package.json` `imports` as the layer order | 🧭 | | |
130
+ | [enforce-mod-file](docs/rules/enforce-mod-file.md) | Enforce `index.ts` as the name of module entry points | 🧭 | | |
131
+ | [no-absolute-import](docs/rules/no-absolute-import.md) | Disallow absolute paths and `file:` URLs as module specifiers | 🧭 | | |
132
+ | [no-barrel-bypass](docs/rules/no-barrel-bypass.md) | Disallow `#` specifiers deeper than an alias | 🧭 | | 💡 |
133
+ | [no-duplicate-import-source](docs/rules/no-duplicate-import-source.md) | Disallow separate import declarations from the same source | 🧭 | | |
134
+ | [no-parent-import](docs/rules/no-parent-import.md) | Disallow parent-relative module specifiers | 🧭 | 🔧 | |
135
+ | [no-relative-bypass](docs/rules/no-relative-bypass.md) | Disallow relative specifiers that reach into an alias's area from outside it | 🧭 | | |
136
+ | [no-self-import](docs/rules/no-self-import.md) | Disallow a module from importing itself | 🧭 | | |
137
+ | [no-wildcard-import](docs/rules/no-wildcard-import.md) | Disallow namespace imports | 🧭 | | |
138
+ | [prefer-alias-import](docs/rules/prefer-alias-import.md) | Require the `#` alias for a module that `package.json` declares | 🧭 | 🔧 | |
86
139
 
87
140
  <!-- end auto-generated rules list -->
88
141
  <!-- prettier-ignore-end -->
package/dist/index.js CHANGED
@@ -1138,7 +1138,7 @@ const MODULES = [
1138
1138
  const basePlugin = {
1139
1139
  meta: {
1140
1140
  name: "eslint-plugin-import-structure",
1141
- version: "0.10.4",
1141
+ version: "0.10.5",
1142
1142
  namespace: NAMESPACE
1143
1143
  },
1144
1144
  rules: Object.fromEntries(rules)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-import-structure",
3
- "version": "0.10.4",
3
+ "version": "0.10.5",
4
4
  "description": "ESLint and Oxlint rules that keep a module graph layered and free of cycles, with package.json \"#\" imports as the layer order.",
5
5
  "keywords": [
6
6
  "eslint",
@@ -48,6 +48,14 @@
48
48
  "eslint-plugin-explicit-types": "^0.7.0",
49
49
  "vite-plus": "^1.1.0"
50
50
  },
51
+ "peerDependencies": {
52
+ "eslint": ">=9"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "eslint": {
56
+ "optional": true
57
+ }
58
+ },
51
59
  "engines": {
52
60
  "node": ">=22.18"
53
61
  },
@@ -64,7 +72,9 @@
64
72
  "ruleListColumns": [
65
73
  "name",
66
74
  "description",
67
- "configsError"
75
+ "configsError",
76
+ "fixable",
77
+ "hasSuggestions"
68
78
  ],
69
79
  "ruleDocSectionInclude": [
70
80
  "Rule Details",