@slip-stream-kit/eslint-plugin 0.17.3 → 0.20.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/dist/index.js +325 -11
- package/dist/index.js.map +4 -4
- package/dist/rules/feature-public-api/feature-public-api.d.ts +8 -0
- package/dist/rules/feature-public-api/index.d.ts +1 -0
- package/dist/rules/layer-imports/index.d.ts +1 -0
- package/dist/rules/layer-imports/layer-imports.d.ts +13 -0
- package/dist/rules/no-feature-self-import/index.d.ts +1 -0
- package/dist/rules/no-feature-self-import/no-feature-self-import.d.ts +8 -0
- package/dist/utils/import-target.d.ts +35 -0
- package/package.json +2 -2
- package/readme.md +91 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
export interface Options {
|
|
3
|
+
/** The alias that maps to the package's `src/`. */
|
|
4
|
+
rootAlias?: string;
|
|
5
|
+
/** Globs; the rule is skipped for matching files. */
|
|
6
|
+
ignore?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const featurePublicApi: Rule.RuleModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { featurePublicApi } from './feature-public-api';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { layerImports } from './layer-imports';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
export interface Options {
|
|
3
|
+
/** Folders under `src/`, lowest level first; folders in one inner array share a level. */
|
|
4
|
+
layers?: string[][];
|
|
5
|
+
/** Type-only imports carry no runtime edge, so they may point at any level. */
|
|
6
|
+
allowTypeImports?: boolean;
|
|
7
|
+
/** The alias that maps to the package's `src/`. */
|
|
8
|
+
rootAlias?: string;
|
|
9
|
+
/** Globs; the rule is skipped for matching files. */
|
|
10
|
+
ignore?: string[];
|
|
11
|
+
}
|
|
12
|
+
export declare const DEFAULT_LAYERS: string[][];
|
|
13
|
+
export declare const layerImports: Rule.RuleModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { noFeatureSelfImport } from './no-feature-self-import';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
export interface Options {
|
|
3
|
+
/** The alias that maps to the package's `src/`. */
|
|
4
|
+
rootAlias?: string;
|
|
5
|
+
/** Globs; the rule is skipped for matching files. */
|
|
6
|
+
ignore?: string[];
|
|
7
|
+
}
|
|
8
|
+
export declare const noFeatureSelfImport: Rule.RuleModule;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
export declare const DEFAULT_ROOT_ALIAS = "#root";
|
|
3
|
+
/** An import-like node whose module path is a string literal, with the literal node to report on. */
|
|
4
|
+
export interface ImportSource {
|
|
5
|
+
specifier: string;
|
|
6
|
+
node: Rule.Node;
|
|
7
|
+
typeOnly: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The module path an import-like node names, or null for a node without a static string path
|
|
11
|
+
* (`import(name)`, a template literal, a local `export { a }`). `typeof import('x')` counts and is
|
|
12
|
+
* type-only: hulyo reaches into feature internals that way, past every import-statement rule.
|
|
13
|
+
*/
|
|
14
|
+
export declare const readImportSource: (node: Rule.Node) => ImportSource | null;
|
|
15
|
+
/** The `src` directory of the package `filename` lives in, or null when it is not under one. */
|
|
16
|
+
export declare const srcDirOf: (filename: string) => string | null;
|
|
17
|
+
/**
|
|
18
|
+
* Where `specifier` points, as the path segments under `srcDir` — `['features', 'user']` for
|
|
19
|
+
* `#root/features/user` or `../../features/user`. Null for a package import, another alias, or a
|
|
20
|
+
* relative path that climbs out of `src/`: those have no layer.
|
|
21
|
+
*/
|
|
22
|
+
export declare const resolveUnderSrc: (filename: string, srcDir: string, specifier: string, rootAlias: string) => string[] | null;
|
|
23
|
+
/** Path segments of `filename`'s directory under `srcDir`. */
|
|
24
|
+
export declare const fileDirsUnderSrc: (filename: string, srcDir: string) => string[];
|
|
25
|
+
/** `features/<name>` or `features/<name>/index.*` — the only paths that form a feature's public API. */
|
|
26
|
+
export declare const isFeaturePublicApi: (target: readonly string[]) => boolean;
|
|
27
|
+
/** The feature name a src-relative path sits in (`features/<name>/…`), or null outside `features/`. */
|
|
28
|
+
export declare const featureNameOf: (segments: readonly string[]) => string | null;
|
|
29
|
+
/**
|
|
30
|
+
* Every node kind that names a module: static imports, re-exports, dynamic `import('x')` and the
|
|
31
|
+
* type-level `typeof import('x')`. A rule registers one handler for all of them.
|
|
32
|
+
*/
|
|
33
|
+
export declare const IMPORT_SELECTORS: readonly ["ImportDeclaration", "ExportNamedDeclaration", "ExportAllDeclaration", "ImportExpression", "TSImportType"];
|
|
34
|
+
/** Whether the src-layout import rules apply to `filename`: not ignored, not in a backend or e2e package. */
|
|
35
|
+
export declare const isLayeredFile: (filename: string, ignore: readonly string[]) => boolean;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slip-stream-kit/eslint-plugin",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.20.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ArthurSaenz/infra-kit.git",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"eslint": ">=9"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@slip-stream-kit/config": "0.
|
|
44
|
+
"@slip-stream-kit/config": "0.20.0",
|
|
45
45
|
"@types/node": "^26.5.1",
|
|
46
46
|
"@typescript-eslint/parser": "^8.70.0",
|
|
47
47
|
"@wl/eslint-config": "0.1.0",
|
package/readme.md
CHANGED
|
@@ -664,6 +664,97 @@ an `e2e` package (by the same package-type resolution `package-structure` uses)
|
|
|
664
664
|
The recommended preset enables it on test-file names only, with `ignore: ['**/*.e2e.test.*']` for
|
|
665
665
|
e2e suites that sit in a package of another type.
|
|
666
666
|
|
|
667
|
+
### `layer-imports`
|
|
668
|
+
|
|
669
|
+
Allow imports only to the **same or a lower layer** of a package's `src/`. The default levels,
|
|
670
|
+
lowest first (folders on one level may import each other):
|
|
671
|
+
|
|
672
|
+
```
|
|
673
|
+
lib, components, shared, assets, styles, data → core, entities → features → pages, routes → app, renderer, mobile
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
A file in `src/features/` may import `lib/`, `components/`, `entities/` and other features, but not
|
|
677
|
+
`pages/` or `app/`. The import is reported on its path, with the order spelled out:
|
|
678
|
+
|
|
679
|
+
```
|
|
680
|
+
`features` cannot import `#root/app/navigation` from the higher layer `app`. Layers import only their own level or below: lib, components, shared, assets, styles, data → core, entities → features → pages, routes → app, renderer, mobile. Move the shared code down into a lower layer, or pass it in from above.
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
The target is resolved from `#root/…` (the alias for `src/`) and from relative paths; package
|
|
684
|
+
imports and other aliases are never reported. Static imports, re-exports and `import('…')` are
|
|
685
|
+
checked. Type-only imports (`import type`, `typeof import()`) carry no runtime edge and are allowed
|
|
686
|
+
unless `allowTypeImports: false`. Files directly in `src/`, in an unlisted folder, in a `backend` / `e2e`
|
|
687
|
+
package, and test files (`__tests__/`, `*.test.*`, `*.spec.*`) are skipped: a test sets up its own
|
|
688
|
+
environment (global styles, providers) like an entry point and is not part of the runtime graph.
|
|
689
|
+
|
|
690
|
+
Imports between features are left to `eslint-plugin-boundaries`; this rule only judges direction.
|
|
691
|
+
|
|
692
|
+
```js
|
|
693
|
+
{
|
|
694
|
+
rules: {
|
|
695
|
+
'@wl/layer-imports': ['error', { layers: [['lib', 'components'], ['features'], ['pages'], ['app']] }],
|
|
696
|
+
},
|
|
697
|
+
}
|
|
698
|
+
```
|
|
699
|
+
|
|
700
|
+
#### Options
|
|
701
|
+
|
|
702
|
+
| Option | Type | Default | Description |
|
|
703
|
+
| ------------------ | ------------ | ---------------- | -------------------------------------------------------------------- |
|
|
704
|
+
| `layers` | `string[][]` | the levels above | Folders under `src/`, lowest level first; one inner array per level. |
|
|
705
|
+
| `allowTypeImports` | `boolean` | `true` | Whether type-only imports may point at a higher layer. |
|
|
706
|
+
| `rootAlias` | `string` | `'#root'` | The alias that maps to `src/`. |
|
|
707
|
+
| `ignore` | `string[]` | `[]` | Globs; the rule is skipped for matching files. |
|
|
708
|
+
|
|
709
|
+
### `feature-public-api`
|
|
710
|
+
|
|
711
|
+
Require code outside a feature to import it **only through its public API** — `features/<name>` or
|
|
712
|
+
`features/<name>/index` — whatever form the path takes. It closes the gaps of a
|
|
713
|
+
`no-restricted-imports` pattern on `**/features/*/**`: relative deep paths
|
|
714
|
+
(`../../other/components/x`) and type-level `typeof import('#root/features/x/services')` are
|
|
715
|
+
reported too, and so are assets and stories inside another feature.
|
|
716
|
+
|
|
717
|
+
```
|
|
718
|
+
`../../flights-result/components/picker-component` reaches into the internals of feature `flights-result`. Import from its public API `#root/features/flights-result` instead, exporting what you need from `features/flights-result/index.ts`.
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
Imports inside the same feature are never reported. Type-only imports are reported as well: a type
|
|
722
|
+
is part of the public API or it is internal.
|
|
723
|
+
|
|
724
|
+
#### Options
|
|
725
|
+
|
|
726
|
+
| Option | Type | Default | Description |
|
|
727
|
+
| ----------- | ---------- | --------- | ---------------------------------------------- |
|
|
728
|
+
| `rootAlias` | `string` | `'#root'` | The alias that maps to `src/`. |
|
|
729
|
+
| `ignore` | `string[]` | `[]` | Globs; the rule is skipped for matching files. |
|
|
730
|
+
|
|
731
|
+
### `no-feature-self-import`
|
|
732
|
+
|
|
733
|
+
Forbid a feature from importing **itself through its own public API** (`#root/features/<own>`,
|
|
734
|
+
`'..'`, `'../index'`). The feature's `index.ts` re-exports the importing file, so the import closes
|
|
735
|
+
a cycle and can hand back `undefined` during module evaluation.
|
|
736
|
+
|
|
737
|
+
```
|
|
738
|
+
`#root/features/coupons` imports feature `coupons` through its own public API from inside it, which makes an import cycle via `index.ts`. Import the module that defines it with a relative path instead.
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
A type-only self-import closes no runtime cycle, so it gets its own message that asks for the
|
|
742
|
+
relative path by convention instead:
|
|
743
|
+
|
|
744
|
+
```
|
|
745
|
+
`#root/features/coupons` reads types of feature `coupons` through its own public API from inside it. Import them from the module that defines them with a relative path; the public API is for code outside the feature.
|
|
746
|
+
```
|
|
747
|
+
|
|
748
|
+
Files in the feature's `__tests__/` and `__stories__/` are exempt: they consume the feature from
|
|
749
|
+
outside, so the public API is the right entry for them.
|
|
750
|
+
|
|
751
|
+
#### Options
|
|
752
|
+
|
|
753
|
+
| Option | Type | Default | Description |
|
|
754
|
+
| ----------- | ---------- | --------- | ---------------------------------------------- |
|
|
755
|
+
| `rootAlias` | `string` | `'#root'` | The alias that maps to `src/`. |
|
|
756
|
+
| `ignore` | `string[]` | `[]` | Globs; the rule is skipped for matching files. |
|
|
757
|
+
|
|
667
758
|
### `e2e-file-layout`
|
|
668
759
|
|
|
669
760
|
Hold an **e2e package** to its file layout: specs are `*.spec.ts` at the root of a domain folder
|