@slip-stream-kit/eslint-plugin 0.17.3 → 0.18.1

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.
@@ -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.17.3",
4
+ "version": "0.18.1",
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.17.3",
44
+ "@slip-stream-kit/config": "0.18.1",
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