eslint-plugin-unslop 0.5.3 → 0.6.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.
package/README.md CHANGED
@@ -26,10 +26,10 @@ export default [
26
26
  architecture: {
27
27
  utils: { shared: true },
28
28
  'repository/*': {
29
- imports: ['utils', 'models/*'],
29
+ imports: ['utils', 'models/+'],
30
30
  exports: ['^create\\w+Repo$', '^Repository[A-Z]\\w+$'],
31
31
  },
32
- 'models/*': {
32
+ models: {
33
33
  imports: ['utils'],
34
34
  },
35
35
  app: {
@@ -66,18 +66,45 @@ export default [unslop.configs.minimal]
66
66
 
67
67
  ## Architecture Settings
68
68
 
69
- All architecture rules read from `settings.unslop.architecture`. Each key is a module matcher (path segments, `*` per segment), and each value is a policy object:
69
+ All architecture rules read from `settings.unslop.architecture`. Each file first resolves to a canonical module path equal to its containing directory relative to the source root from `tsconfig.json`.
70
+
71
+ - `src/index.ts` -> `.`
72
+ - `src/models/index.ts` -> `models`
73
+ - `src/models/user/index.ts` -> `models/user`
74
+ - `src/models/user/internal.ts` -> `models/user`
75
+
76
+ Architecture keys are directory-shaped subtree selectors:
77
+
78
+ - `.` matches the source-root module
79
+ - `models` owns `models` and everything below it
80
+ - `models/*` owns each direct child subtree under `models`, such as `models/user` and `models/user/internal`
81
+
82
+ File-shaped keys like `index.ts` or `rules/public.ts` are not supported.
83
+
84
+ Each value is a policy object:
70
85
 
71
86
  ```ts
72
87
  {
73
- imports?: string[] // module matchers this module may import from; '*' allows all
74
- exports?: string[] // regex patterns symbols exported from index.ts/types.ts must match
75
- entrypoints?: string[] // public files allowed for external and test imports
76
- shared?: boolean // marks module as shared; enables no-false-sharing
88
+ imports?: string[] // exact module, direct child via /*, self-or-child via /+, or '*' for all
89
+ typeImports?: string[] // same patterns as imports, but only for type-only imports
90
+ exports?: string[] // regex patterns symbols exported from entrypoints must match
91
+ entrypoints?: string[] // public files allowed for external and test imports
92
+ shared?: boolean // marks module as shared; enables no-false-sharing
77
93
  }
78
94
  ```
79
95
 
80
- Best match wins by fewest wildcards, then longest matcher, then declaration order. All architecture rules take no options - policy comes entirely from this shared settings block.
96
+ `typeImports` defaults to `[]` when omitted. Type-only imports are also allowed when the target matches `imports`, so `typeImports` is only needed for modules you want to allow type access to without allowing value imports.
97
+
98
+ Architecture keys and import allowlists use different matching rules:
99
+
100
+ - keys assign ownership to subtrees
101
+ - `imports: ['models']` allows only the exact `models` module
102
+ - `imports: ['models/*']` allows only direct children like `models/user`
103
+ - `imports: ['models/+']` allows `models` and direct children like `models/user`
104
+
105
+ When multiple keys cover the same canonical module path, the winner is chosen by nearest owner first, then exact named path over wildcard path at the same depth, then longer selector path, then declaration order. Unmatched canonical module paths become anonymous modules with empty `imports`, empty `typeImports`, empty `exports`, `shared: false`, and default `entrypoints: ['index.ts']`.
106
+
107
+ All architecture rules take no options. Policy comes entirely from this shared settings block.
81
108
 
82
109
  ## Rules
83
110
 
@@ -87,10 +114,11 @@ Customs control for your modules: you declare which modules are allowed to impor
87
114
 
88
115
  Deny-by-default for cross-module imports, so forgetting to declare a dependency is a loud error rather than a silent free-for-all. It also enforces:
89
116
 
90
- - cross-module imports must arrive through the public gate (`index.ts` or `types.ts`)
117
+ - cross-module imports must arrive through the public gate (configured entrypoints)
118
+ - type-only imports can be separately allowed via `typeImports` (value imports from those modules remain forbidden)
91
119
  - local cross-module namespace imports are forbidden (`import * as X from '<local-module>'`)
92
120
  - same-module relative imports can only go one level deeper - no tunnelling into internals
93
- - files that don't match any declared module are denied (fail-closed, not fail-silently)
121
+ - files that don't match any declared module become anonymous modules and are denied by default
94
122
 
95
123
  Alias imports are resolved via `compilerOptions.paths` from `tsconfig.json`.
96
124
 
@@ -104,7 +132,7 @@ This rule only checks recognized test filenames (`*.test.*`, `*.spec.*`, `*.*-te
104
132
 
105
133
  The customs declaration form for the other direction: what are you actually exporting from your module's public entrypoints?
106
134
 
107
- When a module defines `exports` regex patterns, every symbol exported from its `index.ts` or `types.ts` must match at least one pattern - otherwise it's stopped at the gate. Modules without `exports` are waved through by default, so you can adopt this gradually. Regardless of module policy, `export * from ...` is rejected in public entrypoints so symbol provenance stays explicit.
135
+ When a module defines `exports` regex patterns, every symbol exported from its entrypoints must match at least one pattern - otherwise it's stopped at the gate. Modules without `exports` are waved through by default, so you can adopt this gradually. Regardless of module policy, `export * from ...` is rejected in public entrypoints so symbol provenance stays explicit.
108
136
 
109
137
  ### `unslop/no-false-sharing`
110
138
 
@@ -117,7 +145,7 @@ settings: {
117
145
  unslop: {
118
146
  architecture: {
119
147
  utils: { shared: true },
120
- 'shared/*': { shared: true },
148
+ shared: { shared: true },
121
149
  },
122
150
  },
123
151
  }
@@ -131,7 +159,7 @@ src/shared/index.ts
131
159
  -> imported only by src/features/calendar/view.ts
132
160
  -> error: symbol "formatDate" has 1 consumer group(s) (group: features/calendar)
133
161
 
134
- src/shared/types.ts
162
+ src/shared/index.ts
135
163
  export type LegacyOptions = ...
136
164
  -> not imported by anyone
137
165
  -> error: symbol "LegacyOptions" has 0 consumer group(s) (no consumers found)