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.
- package/README.md +82 -29
- package/dist/index.js +1 -1
- package/package.json +12 -2
package/README.md
CHANGED
|
@@ -1,39 +1,90 @@
|
|
|
1
1
|
# eslint-plugin-import-structure
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+

|
|
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
|
-
//
|
|
18
|
-
import
|
|
19
|
-
import {
|
|
20
|
-
|
|
21
|
-
|
|
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`:
|
|
27
|
-
- `all`: every rule, including
|
|
76
|
+
- `modules`: every rule below, all at `error`.
|
|
77
|
+
- `all`: every rule, including rules added later. Today it equals `modules`.
|
|
28
78
|
|
|
29
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
|
76
|
-
|
|
|
77
|
-
| [enforce-
|
|
78
|
-
| [
|
|
79
|
-
| [
|
|
80
|
-
| [no-
|
|
81
|
-
| [no-
|
|
82
|
-
| [no-
|
|
83
|
-
| [no-
|
|
84
|
-
| [no-
|
|
85
|
-
| [
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-import-structure",
|
|
3
|
-
"version": "0.10.
|
|
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",
|