eslint-plugin-fsd-imports-check 1.1.0

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/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2026 ALTaww
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to persons to whom the
8
+ Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO: THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTIONB OF ACTION, ACTION, WHETHER IN AN CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,466 @@
1
+ # eslint-plugin-fsd-imports-check
2
+
3
+ ESLint plugin for enforcing import rules in projects that use [Feature-Sliced Design (FSD)](https://feature-sliced.design/).
4
+
5
+ GitLab: https://gitlab.com/ALTaww/eslint-plugin-fsd-imports-check
6
+
7
+ ## What does this plugin do?
8
+
9
+ The plugin provides three rules for controlling imports between FSD modules:
10
+
11
+ 1. **`path-checker`** prevents absolute imports between files inside the same FSD slice. Such imports must use relative paths.
12
+
13
+ 2. **`layer-imports`** enforces the allowed dependency direction between FSD layers. For example, `features` can import from `entities` and `shared`, but cannot import from `widgets`, `pages`, or `app`.
14
+
15
+ 3. **`public-api-imports`** requires imports from another FSD module to use its public API instead of accessing internal files directly. The rule can optionally replace deep imports with public API imports.
16
+
17
+ The rules work with both JavaScript and TypeScript source files.
18
+
19
+ ## Installation
20
+
21
+ Install ESLint:
22
+
23
+ ```sh
24
+ npm install eslint --save-dev
25
+ ```
26
+
27
+ Then install the plugin:
28
+
29
+ ```sh
30
+ npm install eslint-plugin-fsd-imports-check --save-dev
31
+ ```
32
+
33
+ ## Usage
34
+
35
+ ### ESLint 9 and 10
36
+
37
+ For modern ESLint versions using flat config, import the plugin and register it under the `plugins` key.
38
+
39
+ ```js
40
+ import { defineConfig } from "eslint/config";
41
+ import fsdImportsCheck from "eslint-plugin-fsd-imports-check";
42
+
43
+ export default defineConfig([
44
+ {
45
+ plugins: {
46
+ "fsd-imports-check": fsdImportsCheck,
47
+ },
48
+
49
+ rules: {
50
+ "fsd-imports-check/path-checker": "error",
51
+ "fsd-imports-check/layer-imports": "error",
52
+ "fsd-imports-check/public-api-imports": "error",
53
+ },
54
+ },
55
+ ]);
56
+ ```
57
+
58
+ ### ESLint 6–8
59
+
60
+ For legacy ESLint configuration, add the plugin to the `plugins` array:
61
+
62
+ ```js
63
+ module.exports = {
64
+ plugins: ["fsd-imports-check"],
65
+
66
+ rules: {
67
+ "fsd-imports-check/path-checker": "error",
68
+ "fsd-imports-check/layer-imports": "error",
69
+ "fsd-imports-check/public-api-imports": "error",
70
+ },
71
+ };
72
+ ```
73
+
74
+ The plugin is distributed as a CommonJS package, so the same package can be used with legacy and flat ESLint configurations.
75
+
76
+ ## Configuration example
77
+
78
+ A typical FSD project can use the following configuration:
79
+
80
+ ```js
81
+ import { defineConfig } from "eslint/config";
82
+ import fsdImportsCheck from "eslint-plugin-fsd-imports-check";
83
+
84
+ export default defineConfig([
85
+ {
86
+ files: ["**/*.{js,mjs,cjs,ts,tsx}"],
87
+
88
+ plugins: {
89
+ "fsd-imports-check": fsdImportsCheck,
90
+ },
91
+
92
+ rules: {
93
+ "fsd-imports-check/path-checker": [
94
+ "error",
95
+ {
96
+ alias: "@",
97
+ },
98
+ ],
99
+
100
+ "fsd-imports-check/layer-imports": [
101
+ "error",
102
+ {
103
+ alias: "@",
104
+ ignoreImports: ["**/testing"],
105
+ },
106
+ ],
107
+
108
+ "fsd-imports-check/public-api-imports": [
109
+ "error",
110
+ {
111
+ alias: "@",
112
+ autofix: true,
113
+ testFilesPatterns: ["**/*.test.ts", "**/*.stories.*"],
114
+ },
115
+ ],
116
+ },
117
+ },
118
+ ]);
119
+ ```
120
+
121
+ ## Rules
122
+
123
+ <!-- begin auto-generated rules list -->
124
+
125
+ 🔧 Automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/use/command-line-interface#fixing-problems).
126
+
127
+ | Name | Description | 🔧 |
128
+ | :----------------------------------------------------- | :----------------------------------------------------------- | :-- |
129
+ | [layer-imports](docs/rules/layer-imports.md) | Checking right layer imports (shared, widgets, pages, etc.). | |
130
+ | [path-checker](docs/rules/path-checker.md) | Feature sliced relative path checker. | 🔧 |
131
+ | [public-api-imports](docs/rules/public-api-imports.md) | Enforces importing from public APIs of FSD modules. | 🔧 |
132
+
133
+ <!-- end auto-generated rules list -->
134
+
135
+ ## Rule Examples
136
+
137
+ ### `path-checker`
138
+
139
+ Files belonging to the same slice should use relative imports.
140
+
141
+ Invalid:
142
+
143
+ ```ts
144
+ // src/entities/Article/ui/ArticleDetails.ts
145
+
146
+ import { addCommentFormSlice } from "@/entities/Article/model/addCommentFormSlice";
147
+ ```
148
+
149
+ Valid:
150
+
151
+ ```ts
152
+ import { addCommentFormSlice } from "../../model/addCommentFormSlice";
153
+ ```
154
+
155
+ The rule does not restrict imports between different slices:
156
+
157
+ ```ts
158
+ // src/entities/Article/file.ts
159
+
160
+ import { Profile } from "entities/Profile";
161
+ ```
162
+
163
+ The rule can optionally fix invalid imports automatically.
164
+
165
+ By default, `autofix` is disabled:
166
+
167
+ ```js
168
+ {
169
+ alias: "@",
170
+ autofix: false,
171
+ }
172
+ ```
173
+
174
+ To enable automatic fixes:
175
+
176
+ ```js
177
+ {
178
+ alias: "@",
179
+ autofix: true,
180
+ }
181
+ ```
182
+
183
+ ### `layer-imports`
184
+
185
+ The rule enforces the following dependency direction:
186
+
187
+ ```text
188
+ app
189
+ ├── pages
190
+ ├── widgets
191
+ ├── features
192
+ ├── shared
193
+ └── entities
194
+
195
+ pages
196
+ ├── widgets
197
+ ├── features
198
+ ├── shared
199
+ └── entities
200
+
201
+ widgets
202
+ ├── features
203
+ ├── shared
204
+ └── entities
205
+
206
+ features
207
+ ├── shared
208
+ └── entities
209
+
210
+ entities
211
+ └── shared
212
+
213
+ shared
214
+ └── shared
215
+ ```
216
+
217
+ Invalid:
218
+
219
+ ```ts
220
+ // src/features/Auth/ui/LoginForm.ts
221
+
222
+ import { Header } from "@/widgets/Header";
223
+ ```
224
+
225
+ Valid:
226
+
227
+ ```ts
228
+ import { User } from "@/entities/User";
229
+ import { Button } from "@/shared/Button";
230
+ ```
231
+
232
+ Relative imports are also checked:
233
+
234
+ ```ts
235
+ // src/shared/index.ts
236
+
237
+ import { App } from "../app/App";
238
+ ```
239
+
240
+ The import above is invalid because `shared` must not depend on `app`.
241
+
242
+ Imports from external packages are ignored:
243
+
244
+ ```ts
245
+ import React from "react";
246
+ import { useLocation } from "react-router-dom";
247
+ ```
248
+
249
+ Specific source files and imports can also be excluded with `ignoreFiles` and `ignoreImports`.
250
+
251
+ ### `public-api-imports`
252
+
253
+ Other FSD modules should normally be imported through their public API.
254
+
255
+ Invalid:
256
+
257
+ ```ts
258
+ import { ArticleDetails } from "@/entities/Article/ui/ArticleDetails/ArticleDetails";
259
+ ```
260
+
261
+ Valid:
262
+
263
+ ```ts
264
+ import { ArticleDetails } from "@/entities/Article";
265
+ ```
266
+
267
+ By default, `autofix` is disabled. When enabled, ESLint can automatically replace deep imports with public API imports:
268
+
269
+ ```sh
270
+ npx eslint src --fix
271
+ ```
272
+
273
+ The rule also supports a testing public API:
274
+
275
+ ```ts
276
+ import { testHelper } from "@/entities/Article/testing";
277
+ ```
278
+
279
+ This import is allowed only in files matching `testFilesPatterns`.
280
+
281
+ For example:
282
+
283
+ ```js
284
+ {
285
+ alias: "@",
286
+ testFilesPatterns: [
287
+ "**/*.test.ts",
288
+ "**/*.stories.*",
289
+ ],
290
+ }
291
+ ```
292
+
293
+ A custom testing Public API name can be configured with `testingFilename`:
294
+
295
+ ```js
296
+ {
297
+ alias: "@",
298
+ testingFilename: "test-utils",
299
+ }
300
+ ```
301
+
302
+ Then the testing Public API becomes:
303
+
304
+ ```ts
305
+ import { testHelper } from "@/entities/Article/test-utils";
306
+ ```
307
+
308
+ ## Options
309
+
310
+ ### `alias`
311
+
312
+ Specifies the alias used for absolute imports.
313
+
314
+ ```js
315
+ {
316
+ alias: "@",
317
+ }
318
+ ```
319
+
320
+ For example:
321
+
322
+ ```ts
323
+ import { Article } from "@/entities/Article";
324
+ ```
325
+
326
+ Without an alias, imports such as this are also supported:
327
+
328
+ ```ts
329
+ import { Article } from "entities/Article";
330
+ ```
331
+
332
+ ### `ignoreFiles`
333
+
334
+ Used by all three rules to exclude source files from validation.
335
+
336
+ ```js
337
+ {
338
+ ignoreFiles: [
339
+ "**/*.test.ts",
340
+ "**/*.stories.*",
341
+ ],
342
+ }
343
+ ```
344
+
345
+ A matching source file is completely ignored by the rule.
346
+
347
+ ### `ignoreImports`
348
+
349
+ Used by `path-checker`, `layer-imports`, and `public-api-imports` to exclude specific import paths from validation.
350
+
351
+ ```js
352
+ {
353
+ alias: "@",
354
+ ignoreImports: [
355
+ "@/entities/Legacy/**",
356
+ "**/testing",
357
+ ],
358
+ }
359
+ ```
360
+
361
+ Matching imports are ignored.
362
+
363
+ ### `autofix`
364
+
365
+ Used by `path-checker` and `public-api-imports` to enable automatic source code fixes.
366
+
367
+ The default value is `false`.
368
+
369
+ For example:
370
+
371
+ ```js
372
+ {
373
+ alias: "@",
374
+ autofix: true,
375
+ }
376
+ ```
377
+
378
+ For `path-checker`, absolute imports inside the same slice are replaced with relative imports.
379
+
380
+ For `public-api-imports`, deep imports are replaced with imports from the slice public API.
381
+
382
+ ### `testFilesPatterns`
383
+
384
+ Used by `public-api-imports` to define files that are allowed to import a testing public API.
385
+
386
+ ```js
387
+ {
388
+ alias: "@",
389
+ testFilesPatterns: [
390
+ "**/*.test.ts",
391
+ "**/*.stories.*",
392
+ ],
393
+ }
394
+ ```
395
+
396
+ Only matching files can import:
397
+
398
+ ```ts
399
+ import { testHelper } from "@/entities/Article/testing";
400
+ ```
401
+
402
+ ### `testingFilename`
403
+
404
+ Used by `public-api-imports` to change the name of the testing Public API path segment.
405
+
406
+ The default value is `testing`.
407
+
408
+ For example:
409
+
410
+ ```js
411
+ {
412
+ alias: "@",
413
+ testingFilename: "test-utils",
414
+ }
415
+ ```
416
+
417
+ allows:
418
+
419
+ ```ts
420
+ import { testHelper } from "@/entities/Article/test-utils";
421
+ ```
422
+
423
+ ## Auto-fix
424
+
425
+ The following rules provide automatic fixes when `autofix: true` is configured:
426
+
427
+ - `path-checker`
428
+ - `public-api-imports`
429
+
430
+ Run ESLint with:
431
+
432
+ ```sh
433
+ npx eslint . --fix
434
+ ```
435
+
436
+ `layer-imports` only reports architecture violations and does not modify source code.
437
+
438
+ ## Requirements
439
+
440
+ The plugin declares ESLint as a peer dependency and can be used with supported ESLint versions that satisfy the package configuration.
441
+
442
+ Modern ESLint versions use flat configuration:
443
+
444
+ ```text
445
+ eslint.config.js
446
+ eslint.config.mjs
447
+ eslint.config.cjs
448
+ ```
449
+
450
+ Older ESLint versions can use:
451
+
452
+ ```text
453
+ .eslintrc.js
454
+ .eslintrc.json
455
+ ```
456
+
457
+ The plugin itself is implemented in TypeScript and distributed as compiled JavaScript.
458
+
459
+ ## Documentation
460
+
461
+ Detailed documentation for each rule:
462
+
463
+ - [layer-imports](docs/rules/layer-imports.md)
464
+ - [path-checker](docs/rules/path-checker.md)
465
+ - [public-api-imports](docs/rules/public-api-imports.md)
466
+
@@ -0,0 +1,188 @@
1
+ # fsd-imports-check/layer-imports
2
+
3
+ 📝 Checking right layer imports (shared, widgets, pages, etc.).
4
+
5
+ <!-- end auto-generated rule header -->
6
+
7
+ The rule enforces the dependency direction between layers in a Feature-Sliced Design (FSD) project.
8
+
9
+ A layer can import only from the layers located below it in the project architecture. Imports that violate this dependency direction are reported as errors.
10
+
11
+ The rule supports both absolute imports, such as `@/shared/Button`, and relative imports, such as `../../shared/Button`. Relative imports are resolved against the current file before the target layer is determined.
12
+
13
+ Imports from external packages such as `react` or `react-router-dom` are ignored because they do not belong to the FSD layer structure.
14
+
15
+ ## Rule Details
16
+
17
+ The following layer dependencies are allowed:
18
+
19
+ | Current layer | Allowed imports |
20
+ | :------------ | :--------------------------------------------------- |
21
+ | `app` | `pages`, `widgets`, `features`, `shared`, `entities` |
22
+ | `pages` | `widgets`, `features`, `shared`, `entities` |
23
+ | `widgets` | `features`, `shared`, `entities` |
24
+ | `features` | `shared`, `entities` |
25
+ | `entities` | `shared`, `entities` |
26
+ | `shared` | `shared` |
27
+
28
+ For example, `features` can import from `entities` and `shared`, but cannot import from `widgets`, `pages`, or `app`.
29
+
30
+ Examples of **incorrect** code for this rule:
31
+
32
+ ```ts
33
+ // src/features/Auth/ui/LoginForm.ts
34
+
35
+ import { Header } from "@/widgets/Header";
36
+ ```
37
+
38
+ ```ts
39
+ // src/shared/index.ts
40
+
41
+ import { App } from "../app/App";
42
+ ```
43
+
44
+ ```ts
45
+ // src/entities/Article/ui/ArticleDetails.ts
46
+
47
+ import { Auth } from "@/features/Auth";
48
+ ```
49
+
50
+ In these examples, the current layer imports from a layer that is not allowed by the FSD dependency direction.
51
+
52
+ Examples of **correct** code for this rule:
53
+
54
+ ```ts
55
+ // src/features/Auth/ui/LoginForm.ts
56
+
57
+ import { User } from "@/entities/User";
58
+ ```
59
+
60
+ ```ts
61
+ // src/features/Auth/ui/LoginForm.ts
62
+
63
+ import { Button } from "@/shared/Button";
64
+ ```
65
+
66
+ ```ts
67
+ // src/entities/Article/ui/ArticleDetails.ts
68
+
69
+ import { Button } from "../../shared/Button";
70
+ ```
71
+
72
+ Relative imports are also checked when they point to another FSD layer:
73
+
74
+ ```ts
75
+ // src/entities/Article/file.ts
76
+
77
+ import { Button } from "../../shared/Button";
78
+ ```
79
+
80
+ Here the relative path resolves to the `shared` layer, which is allowed for `entities`.
81
+
82
+ External dependencies are ignored:
83
+
84
+ ```ts
85
+ import React from "react";
86
+ import { useLocation } from "react-router-dom";
87
+ ```
88
+
89
+ ### Options
90
+
91
+ <!-- begin auto-generated rule options list -->
92
+
93
+ | Name | Description | Type | Default |
94
+ | :-------------- | :----------------------------------------------------- | :------- | :------ |
95
+ | `alias` | Alias used for absolute imports, for example '@'. | String | `` |
96
+ | `ignoreFiles` | Glob patterns for source files that should be ignored. | String[] | `[]` |
97
+ | `ignoreImports` | Glob patterns for imports that should be ignored. | String[] | `[]` |
98
+
99
+ <!-- end auto-generated rule options list -->
100
+
101
+ ### `alias`
102
+
103
+ The `alias` option specifies the alias used for absolute project imports.
104
+
105
+ For example:
106
+
107
+ ```js
108
+ {
109
+ alias: "@";
110
+ }
111
+ ```
112
+
113
+ the following import is treated as an import from the `shared` layer:
114
+
115
+ ```ts
116
+ import { Button } from "@/shared/Button";
117
+ ```
118
+
119
+ Without an alias, imports such as the following are also supported:
120
+
121
+ ```ts
122
+ import { Button } from "shared/Button";
123
+ ```
124
+
125
+ ### `ignoreFiles`
126
+
127
+ The `ignoreFiles` option allows entire source files to be excluded from layer checking.
128
+
129
+ Patterns use glob syntax.
130
+
131
+ For example:
132
+
133
+ ```js
134
+ {
135
+ alias: "@",
136
+ ignoreFiles: [
137
+ "**/*.stories.tsx",
138
+ "**/*.test.ts"
139
+ ]
140
+ }
141
+ ```
142
+
143
+ With this configuration, imports inside matching files are not checked by `layer-imports`.
144
+
145
+ This can be useful for Storybook files, tests, generated files, or other parts of the project that intentionally do not follow the regular layer dependency rules.
146
+
147
+ ### `ignoreImports`
148
+
149
+ The `ignoreImports` option allows individual imports to be excluded from layer checking.
150
+
151
+ Patterns use glob syntax.
152
+
153
+ For example:
154
+
155
+ ```js
156
+ {
157
+ alias: "@",
158
+ ignoreImports: [
159
+ "**/testing",
160
+ "**/testing/**",
161
+ "**/*.module.scss"
162
+ ]
163
+ }
164
+ ```
165
+
166
+ The following imports will then be ignored by the rule:
167
+
168
+ ```ts
169
+ import { testHelper } from "@/features/Auth/testing";
170
+ ```
171
+
172
+ ```ts
173
+ import styles from "@/features/Auth/ui/LoginForm.module.scss";
174
+ ```
175
+
176
+ Unlike `ignoreFiles`, this option does not disable checking for the entire file. Other imports in the same file are still checked.
177
+
178
+ ## When Not To Use It
179
+
180
+ Do not use this rule if the project does not follow Feature-Sliced Design or intentionally allows dependencies between layers that violate the standard FSD dependency direction.
181
+
182
+ It may also be inappropriate for projects with a different architectural model or for parts of the codebase where layer boundaries are intentionally relaxed.
183
+
184
+ ## Further Reading
185
+
186
+ - [Feature-Sliced Design](https://feature-sliced.design/)
187
+ - [Feature-Sliced Design: Layers](https://feature-sliced.design/docs/reference/layers)
188
+