eslint-plugin-ts-type-forge 0.5.0 → 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.
Files changed (46) hide show
  1. package/README.md +80 -13
  2. package/dist/plugin.d.mts +8 -0
  3. package/dist/plugin.d.mts.map +1 -1
  4. package/dist/plugin.mjs +3 -1
  5. package/dist/plugin.mjs.map +1 -1
  6. package/dist/rule-types.d.mts +78 -0
  7. package/dist/rule-types.d.mts.map +1 -1
  8. package/dist/rules/import-style.d.mts +21 -0
  9. package/dist/rules/import-style.d.mts.map +1 -0
  10. package/dist/rules/import-style.mjs +29 -0
  11. package/dist/rules/import-style.mjs.map +1 -0
  12. package/dist/rules/prefer-canonical-length-constrained-tuple.d.mts.map +1 -1
  13. package/dist/rules/prefer-canonical-length-constrained-tuple.mjs +2 -1
  14. package/dist/rules/prefer-canonical-length-constrained-tuple.mjs.map +1 -1
  15. package/dist/rules/prefer-readonly-or-mutable-record.d.mts +10 -0
  16. package/dist/rules/prefer-readonly-or-mutable-record.d.mts.map +1 -0
  17. package/dist/rules/prefer-readonly-or-mutable-record.mjs +49 -0
  18. package/dist/rules/prefer-readonly-or-mutable-record.mjs.map +1 -0
  19. package/dist/rules/prefer-strict-or-relaxed-utility-type.d.mts +10 -0
  20. package/dist/rules/prefer-strict-or-relaxed-utility-type.d.mts.map +1 -0
  21. package/dist/rules/prefer-strict-or-relaxed-utility-type.mjs +49 -0
  22. package/dist/rules/prefer-strict-or-relaxed-utility-type.mjs.map +1 -0
  23. package/dist/rules/rules.d.mts +6 -0
  24. package/dist/rules/rules.d.mts.map +1 -1
  25. package/dist/rules/rules.mjs +5 -1
  26. package/dist/rules/rules.mjs.map +1 -1
  27. package/dist/rules/std-type-rule-utils.d.mts +22 -0
  28. package/dist/rules/std-type-rule-utils.d.mts.map +1 -0
  29. package/dist/rules/std-type-rule-utils.mjs +40 -0
  30. package/dist/rules/std-type-rule-utils.mjs.map +1 -0
  31. package/dist/rules/tuple-rule-utils.d.mts +1 -19
  32. package/dist/rules/tuple-rule-utils.d.mts.map +1 -1
  33. package/dist/rules/tuple-rule-utils.mjs +3 -26
  34. package/dist/rules/tuple-rule-utils.mjs.map +1 -1
  35. package/package.json +2 -2
  36. package/src/plugin.mts +2 -0
  37. package/src/rule-types.mts +92 -0
  38. package/src/rules/import-style.mts +60 -0
  39. package/src/rules/prefer-canonical-length-constrained-tuple.mts +2 -3
  40. package/src/rules/prefer-readonly-or-mutable-record.mts +74 -0
  41. package/src/rules/prefer-readonly-or-mutable-record.test.mts +160 -0
  42. package/src/rules/prefer-strict-or-relaxed-utility-type.mts +80 -0
  43. package/src/rules/prefer-strict-or-relaxed-utility-type.test.mts +279 -0
  44. package/src/rules/rules.mts +4 -0
  45. package/src/rules/std-type-rule-utils.mts +103 -0
  46. package/src/rules/tuple-rule-utils.mts +2 -58
package/README.md CHANGED
@@ -3,7 +3,9 @@
3
3
  ESLint rules that steer TypeScript **type declarations** toward
4
4
  [`ts-type-forge`](https://www.npmjs.com/package/ts-type-forge) idioms — replacing
5
5
  hand-rolled uniform tuple spellings with the library's named length-constrained
6
- tuple types. Every rule is auto-fixable, and every rewrite is **type-preserving**.
6
+ tuple types, and the standard library's under-specified `Exclude` / `Extract` /
7
+ `Omit` / `Pick` / `Record` with counterparts that state what was left implicit.
8
+ Every rewrite is **type-preserving**.
7
9
 
8
10
  ## Installation
9
11
 
@@ -70,9 +72,13 @@ export default [
70
72
 
71
73
  ## Rules
72
74
 
73
- | Rule | Description |
74
- | :------------------------------------------ | :--------------------------------------------------------------------------------------- |
75
- | `prefer-canonical-length-constrained-tuple` | Replace hand-rolled uniform tuple spellings with the canonical ts-type-forge tuple type. |
75
+ | Rule | Fix | Description |
76
+ | :------------------------------------------ | :--------- | :---------------------------------------------------------------------------------------- |
77
+ | `prefer-canonical-length-constrained-tuple` | autofix | Replace hand-rolled uniform tuple spellings with the canonical ts-type-forge tuple type. |
78
+ | `prefer-strict-or-relaxed-utility-type` | suggestion | Replace `Exclude` / `Extract` / `Omit` / `Pick` with the `Strict*` or `Relaxed*` variant. |
79
+ | `prefer-readonly-or-mutable-record` | suggestion | Replace `Record` with `ReadonlyRecord` or `MutableRecord`. |
80
+
81
+ ### `prefer-canonical-length-constrained-tuple`
76
82
 
77
83
  It covers the whole uniform-tuple family:
78
84
 
@@ -112,7 +118,7 @@ type AtLeastTwo = MutableMinLengthTuple<2, string>;
112
118
  Single-element tuples without a rest (`readonly [V]`) are left alone — they read
113
119
  better as-is than `FixedLengthTuple<1, V>`.
114
120
 
115
- ### The rule deliberately leaves alone
121
+ #### The rule deliberately leaves alone
116
122
 
117
123
  - heterogeneous tuples (`readonly [string, number]`);
118
124
  - labelled members (`readonly [head: V, ...tail: V[]]`), because rewriting them
@@ -121,21 +127,82 @@ better as-is than `FixedLengthTuple<1, V>`.
121
127
  - files that already bind the target name to something else (a local alias, or
122
128
  an import from another module).
123
129
 
130
+ ### `prefer-strict-or-relaxed-utility-type`
131
+
132
+ `Exclude<T, U>`, `Extract<T, U>`, `Omit<T, K>` and `Pick<T, K>` do not constrain
133
+ their second argument, so a member or key that is not part of `T` is silently
134
+ accepted — typically producing an empty result instead of the compile error that
135
+ would have pointed at the leftover of a rename.
136
+
137
+ ts-type-forge splits each one in two, and this rule makes the choice explicit:
138
+
139
+ | built-in | checks the second argument | keeps the built-in behavior |
140
+ | :-------------- | :------------------------- | :-------------------------- |
141
+ | `Exclude<T, U>` | `StrictExclude<T, U>` | `RelaxedExclude<T, U>` |
142
+ | `Extract<T, U>` | `StrictExtract<T, U>` | `RelaxedExtract<T, U>` |
143
+ | `Omit<T, K>` | `StrictOmit<T, K>` | `RelaxedOmit<T, K>` |
144
+ | `Pick<T, K>` | `StrictPick<T, K>` | `RelaxedPick<T, K>` |
145
+
146
+ ```ts
147
+ // ❌
148
+ type Remaining = Exclude<'a' | 'b' | 'c', 'a'>;
149
+ type PublicInfo = Omit<Person, 'email'>;
150
+
151
+ // ✅ — the key is checked against the union / `keyof T`
152
+ type Remaining = StrictExclude<'a' | 'b' | 'c', 'a'>;
153
+ type PublicInfo = StrictOmit<Person, 'email'>;
154
+
155
+ // ✅ — deliberately unchecked (the subtrahend need not be part of `T`)
156
+ type NonStrings = RelaxedExclude<string | number | boolean, string>;
157
+ ```
158
+
159
+ Reach for `Relaxed*` when the second argument genuinely need not be part of the
160
+ first — a key set computed from a still deferred type parameter, or a subtraction
161
+ whose subtrahend is only partly present.
162
+
163
+ ### `prefer-readonly-or-mutable-record`
164
+
165
+ `Record<K, V>` says nothing about whether the properties may be reassigned, so
166
+ the mutability of every record spelled with it is decided by whoever reads it.
167
+
168
+ ```ts
169
+ // ❌
170
+ type Config = Record<string, string | number>;
171
+
172
+ // ✅
173
+ type Config = ReadonlyRecord<string, string | number>;
174
+ type Counters = MutableRecord<string, number>;
175
+ ```
176
+
177
+ ### Both rules deliberately leave alone
178
+
179
+ - qualified names (`Utils.Pick<…>`) — they never denote the standard-library
180
+ type;
181
+ - files that declare or import their own `Pick` / `Record` / …, for the same
182
+ reason;
183
+ - the choice itself: replacing `Exclude` with `StrictExclude` can turn working
184
+ code into a compile error, and `Record` maps onto two different types, so
185
+ these rules report **suggestions** rather than an autofix. Editors offer both
186
+ replacements; `--fix` changes nothing.
187
+
124
188
  ## Options
125
189
 
126
- | Option | Type | Default | Description |
127
- | :------------ | :-------------------- | :--------- | :-------------------------------------------------------- |
128
- | `importStyle` | `'global' \| 'named'` | `'global'` | How the ts-type-forge type is brought into scope. |
129
- | `maxLength` | `integer` | `10` | Longest tuple `prefer-length-constrained-tuple` rewrites. |
190
+ `importStyle` is accepted by every rule; `maxLength` only by
191
+ `prefer-canonical-length-constrained-tuple`.
192
+
193
+ | Option | Type | Default | Description |
194
+ | :------------ | :-------------------- | :-------- | :------------------------------------------------------------------ |
195
+ | `importStyle` | `'global' \| 'named'` | `'named'` | How the ts-type-forge type is brought into scope. |
196
+ | `maxLength` | `integer` | `10` | Longest tuple `prefer-canonical-length-constrained-tuple` rewrites. |
130
197
 
131
198
  - `'global'` — the ambient globals of `ts-type-forge/global` are in use
132
- (`/// <reference types="ts-type-forge/global" />`), so the autofix only
133
- rewrites the type and never touches imports.
134
- - `'named'` — the autofix additionally inserts
199
+ (`/// <reference types="ts-type-forge/global" />`), so the fix only rewrites
200
+ the type and never touches imports.
201
+ - `'named'` — the fix additionally inserts
135
202
  `import { type … } from 'ts-type-forge';` when the name is not imported yet.
136
203
 
137
204
  Either way, if the file already imports the target type from `ts-type-forge`
138
- (possibly under an alias), the autofix reuses that binding.
205
+ (possibly under an alias), the fix reuses that binding.
139
206
 
140
207
  ```ts
141
208
  {
package/dist/plugin.d.mts CHANGED
@@ -8,6 +8,12 @@ export declare const eslintPluginTsTypeForge: {
8
8
  importStyle?: 'global' | 'named';
9
9
  maxLength?: number;
10
10
  }> | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
11
+ readonly 'prefer-readonly-or-mutable-record': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWith" | "useReadonlyOrMutableRecord", readonly [(Readonly<{
12
+ importStyle?: 'global' | 'named';
13
+ }> | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
14
+ readonly 'prefer-strict-or-relaxed-utility-type': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWith" | "useStrictOrRelaxed", readonly [(Readonly<{
15
+ importStyle?: 'global' | 'named';
16
+ }> | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
11
17
  };
12
18
  readonly configs: {
13
19
  /** Enables every rule of this plugin at `error`. */
@@ -18,6 +24,8 @@ export declare const eslintPluginTsTypeForge: {
18
24
  };
19
25
  readonly rules: {
20
26
  readonly 'ts-type-forge/prefer-canonical-length-constrained-tuple': 'error';
27
+ readonly 'ts-type-forge/prefer-readonly-or-mutable-record': 'error';
28
+ readonly 'ts-type-forge/prefer-strict-or-relaxed-utility-type': 'error';
21
29
  };
22
30
  };
23
31
  };
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.mts","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AA8BvE,eAAO,MAAM,uBAAuB;;iBAEhC,IAAI,EAAE,6BAA6B;;;;;;;;;QAInC,oDAAoD;iBACpD,WAAW;2BAtBP,2BAA2B;;yBAQ3B,eAAe,EAAI,YAAY;;;oFAdsB,OAAO;;;;CA8BnC,CAAC"}
1
+ {"version":3,"file":"plugin.d.mts","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AACA,OAAO,EAAyB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAgCvE,eAAO,MAAM,uBAAuB;;iBAEhC,IAAI,EAAE,6BAA6B;;;;;;;;;;;;;;;QAInC,oDAAoD;iBACpD,WAAW;2BAtBP,2BAA2B;;yBAQ3B,eAAe,EAAI,YAAY;;;oFAhBsB,OAAO;4EACf,OAAO;gFACH,OAAO;;;;CA8B/B,CAAC"}
package/dist/plugin.mjs CHANGED
@@ -1,7 +1,9 @@
1
1
  import { tsTypeForgeRules } from './rules/rules.mjs';
2
2
 
3
3
  const recommendedRules = {
4
- "ts-type-forge/prefer-canonical-length-constrained-tuple": "error"
4
+ "ts-type-forge/prefer-canonical-length-constrained-tuple": "error",
5
+ "ts-type-forge/prefer-readonly-or-mutable-record": "error",
6
+ "ts-type-forge/prefer-strict-or-relaxed-utility-type": "error"
5
7
  };
6
8
  const recommendedConfig = {
7
9
  name: "ts-type-forge/recommended",
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","sources":["../src/plugin.mts"],"sourcesContent":["import { tsTypeForgeRules } from './rules/index.mjs';\nimport { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';\n\n/**\n * Every rule this plugin ships, at `error`.\n *\n * The `satisfies` clause below is keyed off {@link tsTypeForgeRules}, so adding\n * a rule without listing it here fails to type-check.\n */\nconst recommendedRules = {\n 'ts-type-forge/prefer-canonical-length-constrained-tuple': 'error',\n} as const satisfies Readonly<\n Record<`ts-type-forge/${keyof typeof tsTypeForgeRules}`, 'error'>\n>;\n\nconst recommendedConfig = {\n name: 'ts-type-forge/recommended',\n plugins: {\n // Resolved lazily so that this config registers the *same* object that\n // `eslintPluginTsTypeForge` refers to. ESLint rejects a plugin name that\n // maps to two different objects with `Cannot redefine plugin`, which is\n // what a user combining this preset with their own\n // `plugins: { 'ts-type-forge': eslintPluginTsTypeForge }` entry would hit\n // if the preset embedded a separate copy of the plugin.\n get 'ts-type-forge'(): ESLintPlugin {\n return eslintPluginTsTypeForge;\n },\n },\n rules: recommendedRules,\n} as const satisfies ESLintFlatConfig;\n\nexport const eslintPluginTsTypeForge = {\n meta: {\n name: 'eslint-plugin-ts-type-forge',\n },\n rules: tsTypeForgeRules,\n configs: {\n /** Enables every rule of this plugin at `error`. */\n recommended: recommendedConfig,\n },\n} as const satisfies ESLintPlugin;\n"],"names":[],"mappings":";;AASA,MAAM,gBAAA,GAAmB;AAAA,EACvB,yDAAA,EAA2D;AAC7D,CAAA;AAIA,MAAM,iBAAA,GAAoB;AAAA,EACxB,IAAA,EAAM,2BAAA;AAAA,EACN,OAAA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOP,IAAI,eAAA,GAAgC;AAClC,MAAA,OAAO,uBAAA;AAAA,IACT;AAAA,GACF;AAAA,EACA,KAAA,EAAO;AACT,CAAA;AAEO,MAAM,uBAAA,GAA0B;AAAA,EACrC,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM;AAAA,GACR;AAAA,EACA,KAAA,EAAO,gBAAA;AAAA,EACP,OAAA,EAAS;AAAA;AAAA,IAEP,WAAA,EAAa;AAAA;AAEjB;;;;"}
1
+ {"version":3,"file":"plugin.mjs","sources":["../src/plugin.mts"],"sourcesContent":["import { tsTypeForgeRules } from './rules/index.mjs';\nimport { type ESLintFlatConfig, type ESLintPlugin } from './types.mjs';\n\n/**\n * Every rule this plugin ships, at `error`.\n *\n * The `satisfies` clause below is keyed off {@link tsTypeForgeRules}, so adding\n * a rule without listing it here fails to type-check.\n */\nconst recommendedRules = {\n 'ts-type-forge/prefer-canonical-length-constrained-tuple': 'error',\n 'ts-type-forge/prefer-readonly-or-mutable-record': 'error',\n 'ts-type-forge/prefer-strict-or-relaxed-utility-type': 'error',\n} as const satisfies Readonly<\n Record<`ts-type-forge/${keyof typeof tsTypeForgeRules}`, 'error'>\n>;\n\nconst recommendedConfig = {\n name: 'ts-type-forge/recommended',\n plugins: {\n // Resolved lazily so that this config registers the *same* object that\n // `eslintPluginTsTypeForge` refers to. ESLint rejects a plugin name that\n // maps to two different objects with `Cannot redefine plugin`, which is\n // what a user combining this preset with their own\n // `plugins: { 'ts-type-forge': eslintPluginTsTypeForge }` entry would hit\n // if the preset embedded a separate copy of the plugin.\n get 'ts-type-forge'(): ESLintPlugin {\n return eslintPluginTsTypeForge;\n },\n },\n rules: recommendedRules,\n} as const satisfies ESLintFlatConfig;\n\nexport const eslintPluginTsTypeForge = {\n meta: {\n name: 'eslint-plugin-ts-type-forge',\n },\n rules: tsTypeForgeRules,\n configs: {\n /** Enables every rule of this plugin at `error`. */\n recommended: recommendedConfig,\n },\n} as const satisfies ESLintPlugin;\n"],"names":[],"mappings":";;AASA,MAAM,gBAAA,GAAmB;AAAA,EACvB,yDAAA,EAA2D,OAAA;AAAA,EAC3D,iDAAA,EAAmD,OAAA;AAAA,EACnD,qDAAA,EAAuD;AACzD,CAAA;AAIA,MAAM,iBAAA,GAAoB;AAAA,EACxB,IAAA,EAAM,2BAAA;AAAA,EACN,OAAA,EAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOP,IAAI,eAAA,GAAgC;AAClC,MAAA,OAAO,uBAAA;AAAA,IACT;AAAA,GACF;AAAA,EACA,KAAA,EAAO;AACT,CAAA;AAEO,MAAM,uBAAA,GAA0B;AAAA,EACrC,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM;AAAA,GACR;AAAA,EACA,KAAA,EAAO,gBAAA;AAAA,EACP,OAAA,EAAS;AAAA;AAAA,IAEP,WAAA,EAAa;AAAA;AAEjB;;;;"}
@@ -51,11 +51,89 @@ declare namespace PreferCanonicalLengthConstrainedTuple {
51
51
  type Options = NonNullable<RuleOptionsOf<'prefer-canonical-length-constrained-tuple'>[0]>;
52
52
  type RuleEntry = 'off' | Linter.Severity | SpreadOptionsIfIsArray<readonly [Linter.StringSeverity, Options]>;
53
53
  }
54
+ /**
55
+ * @description Replace the built-in `Record` with the ts-type-forge `ReadonlyRecord` or `MutableRecord`, so that the mutability of the record is stated rather than implied.
56
+ *
57
+ * ```md
58
+ * | key | value |
59
+ * | :--------- | :--------- |
60
+ * | type | suggestion |
61
+ * | deprecated | false |
62
+ * | fixable | false |
63
+ * ```
64
+ */
65
+ declare namespace PreferReadonlyOrMutableRecord {
66
+ /**
67
+ * ### schema
68
+ *
69
+ * ```json
70
+ * [
71
+ * {
72
+ * "type": "object",
73
+ * "properties": {
74
+ * "importStyle": {
75
+ * "type": "string",
76
+ * "enum": [
77
+ * "global",
78
+ * "named"
79
+ * ],
80
+ * "description": "How the ts-type-forge type is brought into scope. 'named' (default) makes the autofix add the corresponding `import { type … } from 'ts-type-forge';` when the name is not imported yet; 'global' assumes the ambient globals of `ts-type-forge/global` and never touches imports."
81
+ * }
82
+ * },
83
+ * "additionalProperties": false
84
+ * }
85
+ * ]
86
+ * ```
87
+ */
88
+ type Options = NonNullable<RuleOptionsOf<'prefer-readonly-or-mutable-record'>[0]>;
89
+ type RuleEntry = 'off' | Linter.Severity | SpreadOptionsIfIsArray<readonly [Linter.StringSeverity, Options]>;
90
+ }
91
+ /**
92
+ * @description Replace the built-in `Exclude` / `Extract` / `Omit` / `Pick` with the ts-type-forge `Strict*` or `Relaxed*` counterpart, so that the unchecked second argument becomes an explicit choice.
93
+ *
94
+ * ```md
95
+ * | key | value |
96
+ * | :--------- | :--------- |
97
+ * | type | suggestion |
98
+ * | deprecated | false |
99
+ * | fixable | false |
100
+ * ```
101
+ */
102
+ declare namespace PreferStrictOrRelaxedUtilityType {
103
+ /**
104
+ * ### schema
105
+ *
106
+ * ```json
107
+ * [
108
+ * {
109
+ * "type": "object",
110
+ * "properties": {
111
+ * "importStyle": {
112
+ * "type": "string",
113
+ * "enum": [
114
+ * "global",
115
+ * "named"
116
+ * ],
117
+ * "description": "How the ts-type-forge type is brought into scope. 'named' (default) makes the autofix add the corresponding `import { type … } from 'ts-type-forge';` when the name is not imported yet; 'global' assumes the ambient globals of `ts-type-forge/global` and never touches imports."
118
+ * }
119
+ * },
120
+ * "additionalProperties": false
121
+ * }
122
+ * ]
123
+ * ```
124
+ */
125
+ type Options = NonNullable<RuleOptionsOf<'prefer-strict-or-relaxed-utility-type'>[0]>;
126
+ type RuleEntry = 'off' | Linter.Severity | SpreadOptionsIfIsArray<readonly [Linter.StringSeverity, Options]>;
127
+ }
54
128
  export type EslintTsTypeForgeRules = Readonly<{
55
129
  'ts-type-forge/prefer-canonical-length-constrained-tuple': PreferCanonicalLengthConstrainedTuple.RuleEntry;
130
+ 'ts-type-forge/prefer-readonly-or-mutable-record': PreferReadonlyOrMutableRecord.RuleEntry;
131
+ 'ts-type-forge/prefer-strict-or-relaxed-utility-type': PreferStrictOrRelaxedUtilityType.RuleEntry;
56
132
  }>;
57
133
  export type EslintTsTypeForgeRulesOption = Readonly<{
58
134
  'ts-type-forge/prefer-canonical-length-constrained-tuple': PreferCanonicalLengthConstrainedTuple.Options;
135
+ 'ts-type-forge/prefer-readonly-or-mutable-record': PreferReadonlyOrMutableRecord.Options;
136
+ 'ts-type-forge/prefer-strict-or-relaxed-utility-type': PreferStrictOrRelaxedUtilityType.Options;
59
137
  }>;
60
138
  type AssertTrue<B extends true> = B;
61
139
  export type RuleNamesAreInSync = AssertTrue<TypeEq<keyof EslintTsTypeForgeRules, `ts-type-forge/${keyof typeof tsTypeForgeRules}`>>;
@@ -1 +1 @@
1
- {"version":3,"file":"rule-types.d.mts","sourceRoot":"","sources":["../src/rule-types.mts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,KAAK,sBAAsB,CACzB,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,IACjD,CAAC,CAAC,CAAC,CAAC,SAAS,SAAS,OAAO,EAAE,GAC/B,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GACzC,CAAC,CAAC;AAEN;;;;GAIG;AACH,KAAK,aAAa,CAAC,IAAI,SAAS,MAAM,OAAO,gBAAgB,IAC3D,CAAC,OAAO,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,UAAU,CACzD,MAAM,WAAW,SAAS,MAAM,EAChC,MAAM,OAAO,SAAS,SAAS,OAAO,EAAE,CACzC,GACG,OAAO,GACP,KAAK,CAAC;AAEZ;;;;;;;;;;GAUG;AACH,kBAAU,qCAAqC,CAAC;IAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,KAAY,OAAO,GAAG,WAAW,CAC/B,aAAa,CAAC,2CAA2C,CAAC,CAAC,CAAC,CAAC,CAC9D,CAAC;IAEF,KAAY,SAAS,GACjB,KAAK,GACL,MAAM,CAAC,QAAQ,GACf,sBAAsB,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;CACvE;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,yDAAyD,EAAE,qCAAqC,CAAC,SAAS,CAAC;CAC5G,CAAC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,yDAAyD,EAAE,qCAAqC,CAAC,OAAO,CAAC;CAC1G,CAAC,CAAC;AAIH,KAAK,UAAU,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG,UAAU,CACzC,MAAM,CACJ,MAAM,sBAAsB,EAC5B,iBAAiB,MAAM,OAAO,gBAAgB,EAAE,CACjD,CACF,CAAC"}
1
+ {"version":3,"file":"rule-types.d.mts","sourceRoot":"","sources":["../src/rule-types.mts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAE1D,KAAK,sBAAsB,CACzB,CAAC,SAAS,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,IACjD,CAAC,CAAC,CAAC,CAAC,SAAS,SAAS,OAAO,EAAE,GAC/B,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GACzC,CAAC,CAAC;AAEN;;;;GAIG;AACH,KAAK,aAAa,CAAC,IAAI,SAAS,MAAM,OAAO,gBAAgB,IAC3D,CAAC,OAAO,gBAAgB,CAAC,CAAC,IAAI,CAAC,SAAS,QAAQ,CAAC,UAAU,CACzD,MAAM,WAAW,SAAS,MAAM,EAChC,MAAM,OAAO,SAAS,SAAS,OAAO,EAAE,CACzC,GACG,OAAO,GACP,KAAK,CAAC;AAEZ;;;;;;;;;;GAUG;AACH,kBAAU,qCAAqC,CAAC;IAC9C;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,KAAY,OAAO,GAAG,WAAW,CAC/B,aAAa,CAAC,2CAA2C,CAAC,CAAC,CAAC,CAAC,CAC9D,CAAC;IAEF,KAAY,SAAS,GACjB,KAAK,GACL,MAAM,CAAC,QAAQ,GACf,sBAAsB,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;CACvE;AAED;;;;;;;;;;GAUG;AACH,kBAAU,6BAA6B,CAAC;IACtC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAY,OAAO,GAAG,WAAW,CAC/B,aAAa,CAAC,mCAAmC,CAAC,CAAC,CAAC,CAAC,CACtD,CAAC;IAEF,KAAY,SAAS,GACjB,KAAK,GACL,MAAM,CAAC,QAAQ,GACf,sBAAsB,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;CACvE;AAED;;;;;;;;;;GAUG;AACH,kBAAU,gCAAgC,CAAC;IACzC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,KAAY,OAAO,GAAG,WAAW,CAC/B,aAAa,CAAC,uCAAuC,CAAC,CAAC,CAAC,CAAC,CAC1D,CAAC;IAEF,KAAY,SAAS,GACjB,KAAK,GACL,MAAM,CAAC,QAAQ,GACf,sBAAsB,CAAC,SAAS,CAAC,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,CAAC,CAAC;CACvE;AAED,MAAM,MAAM,sBAAsB,GAAG,QAAQ,CAAC;IAC5C,yDAAyD,EAAE,qCAAqC,CAAC,SAAS,CAAC;IAC3G,iDAAiD,EAAE,6BAA6B,CAAC,SAAS,CAAC;IAC3F,qDAAqD,EAAE,gCAAgC,CAAC,SAAS,CAAC;CACnG,CAAC,CAAC;AAEH,MAAM,MAAM,4BAA4B,GAAG,QAAQ,CAAC;IAClD,yDAAyD,EAAE,qCAAqC,CAAC,OAAO,CAAC;IACzG,iDAAiD,EAAE,6BAA6B,CAAC,OAAO,CAAC;IACzF,qDAAqD,EAAE,gCAAgC,CAAC,OAAO,CAAC;CACjG,CAAC,CAAC;AAIH,KAAK,UAAU,CAAC,CAAC,SAAS,IAAI,IAAI,CAAC,CAAC;AAEpC,MAAM,MAAM,kBAAkB,GAAG,UAAU,CACzC,MAAM,CACJ,MAAM,sBAAsB,EAC5B,iBAAiB,MAAM,OAAO,gBAAgB,EAAE,CACjD,CACF,CAAC"}
@@ -0,0 +1,21 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ /** `JSONSchema4`, reached through the public rule-module types. */
3
+ type JsonSchema = Exclude<TSESLint.RuleModule<string, readonly unknown[]>['meta']['schema'], readonly unknown[]>;
4
+ export type ImportStyle = 'global' | 'named';
5
+ export declare const DEFAULT_IMPORT_STYLE: ImportStyle;
6
+ export declare const IMPORT_STYLE_SCHEMA_PROPERTY: JsonSchema;
7
+ /**
8
+ * Resolves how a ts-type-forge type name should be written in this file, and
9
+ * whether the fix has to add an import for it.
10
+ *
11
+ * Returns `undefined` when the name is already bound to something else (a local
12
+ * alias, or an import from another module) — rewriting to it would then silently
13
+ * resolve to the wrong declaration, which is exactly what happens inside
14
+ * ts-type-forge's own sources.
15
+ */
16
+ export declare const resolveTypeName: (program: TSESTree.Program, canonicalName: string, importStyle: ImportStyle) => Readonly<{
17
+ localName: string;
18
+ needsImport: boolean;
19
+ }> | undefined;
20
+ export {};
21
+ //# sourceMappingURL=import-style.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-style.d.mts","sourceRoot":"","sources":["../../src/rules/import-style.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAUxE,mEAAmE;AACnE,KAAK,UAAU,GAAG,OAAO,CACvB,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,EACjE,SAAS,OAAO,EAAE,CACnB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE7C,eAAO,MAAM,oBAAoB,EAAE,WAAqB,CAAC;AAEzD,eAAO,MAAM,4BAA4B,EAAE,UAW1C,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,YACjB,QAAQ,CAAC,OAAO,iBACV,MAAM,eACR,WAAW,KACvB,QAAQ,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,CAAC,GAAG,SAa1D,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { TS_TYPE_FORGE_MODULE } from './constants.mjs';
2
+ import { getImportedLocalName, getTsTypeForgeImports, hasConflictingDeclaration } from './import-utils.mjs';
3
+
4
+ const DEFAULT_IMPORT_STYLE = "named";
5
+ const IMPORT_STYLE_SCHEMA_PROPERTY = {
6
+ type: "string",
7
+ enum: ["global", "named"],
8
+ description: [
9
+ "How the ts-type-forge type is brought into scope.",
10
+ "'named' (default) makes the autofix add the corresponding",
11
+ `\`import { type \u2026 } from '${TS_TYPE_FORGE_MODULE}';\``,
12
+ "when the name is not imported yet; 'global' assumes the",
13
+ "ambient globals of `ts-type-forge/global` and never touches",
14
+ "imports."
15
+ ].join(" ")
16
+ };
17
+ const resolveTypeName = (program, canonicalName, importStyle) => {
18
+ const importedLocalName = getImportedLocalName(
19
+ getTsTypeForgeImports(program),
20
+ canonicalName
21
+ );
22
+ if (importedLocalName !== void 0) {
23
+ return { localName: importedLocalName, needsImport: false };
24
+ }
25
+ return hasConflictingDeclaration(program, canonicalName) ? void 0 : { localName: canonicalName, needsImport: importStyle === "named" };
26
+ };
27
+
28
+ export { DEFAULT_IMPORT_STYLE, IMPORT_STYLE_SCHEMA_PROPERTY, resolveTypeName };
29
+ //# sourceMappingURL=import-style.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"import-style.mjs","sources":["../../src/rules/import-style.mts"],"sourcesContent":["import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';\nimport { TS_TYPE_FORGE_MODULE } from './constants.mjs';\nimport {\n getImportedLocalName,\n getTsTypeForgeImports,\n hasConflictingDeclaration,\n} from './import-utils.mjs';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\n/** `JSONSchema4`, reached through the public rule-module types. */\ntype JsonSchema = Exclude<\n TSESLint.RuleModule<string, readonly unknown[]>['meta']['schema'],\n readonly unknown[]\n>;\n\nexport type ImportStyle = 'global' | 'named';\n\nexport const DEFAULT_IMPORT_STYLE: ImportStyle = 'named';\n\nexport const IMPORT_STYLE_SCHEMA_PROPERTY: JsonSchema = {\n type: 'string',\n enum: ['global', 'named'],\n description: [\n 'How the ts-type-forge type is brought into scope.',\n \"'named' (default) makes the autofix add the corresponding\",\n `\\`import { type … } from '${TS_TYPE_FORGE_MODULE}';\\``,\n \"when the name is not imported yet; 'global' assumes the\",\n 'ambient globals of `ts-type-forge/global` and never touches',\n 'imports.',\n ].join(' '),\n};\n\n/**\n * Resolves how a ts-type-forge type name should be written in this file, and\n * whether the fix has to add an import for it.\n *\n * Returns `undefined` when the name is already bound to something else (a local\n * alias, or an import from another module) — rewriting to it would then silently\n * resolve to the wrong declaration, which is exactly what happens inside\n * ts-type-forge's own sources.\n */\nexport const resolveTypeName = (\n program: TSESTree.Program,\n canonicalName: string,\n importStyle: ImportStyle,\n): Readonly<{ localName: string; needsImport: boolean }> | undefined => {\n const importedLocalName = getImportedLocalName(\n getTsTypeForgeImports(program),\n canonicalName,\n );\n\n if (importedLocalName !== undefined) {\n return { localName: importedLocalName, needsImport: false };\n }\n\n return hasConflictingDeclaration(program, canonicalName)\n ? undefined\n : { localName: canonicalName, needsImport: importStyle === 'named' };\n};\n"],"names":[],"mappings":";;;AAkBO,MAAM,oBAAA,GAAoC;AAE1C,MAAM,4BAAA,GAA2C;AAAA,EACtD,IAAA,EAAM,QAAA;AAAA,EACN,IAAA,EAAM,CAAC,QAAA,EAAU,OAAO,CAAA;AAAA,EACxB,WAAA,EAAa;AAAA,IACX,mDAAA;AAAA,IACA,2DAAA;AAAA,IACA,kCAA6B,oBAAoB,CAAA,IAAA,CAAA;AAAA,IACjD,yDAAA;AAAA,IACA,6DAAA;AAAA,IACA;AAAA,GACF,CAAE,KAAK,GAAG;AACZ;AAWO,MAAM,eAAA,GAAkB,CAC7B,OAAA,EACA,aAAA,EACA,WAAA,KACsE;AACtE,EAAA,MAAM,iBAAA,GAAoB,oBAAA;AAAA,IACxB,sBAAsB,OAAO,CAAA;AAAA,IAC7B;AAAA,GACF;AAEA,EAAA,IAAI,sBAAsB,MAAA,EAAW;AACnC,IAAA,OAAO,EAAE,SAAA,EAAW,iBAAA,EAAmB,WAAA,EAAa,KAAA,EAAM;AAAA,EAC5D;AAEA,EAAA,OAAO,yBAAA,CAA0B,OAAA,EAAS,aAAa,CAAA,GACnD,MAAA,GACA,EAAE,SAAA,EAAW,aAAA,EAAe,WAAA,EAAa,WAAA,KAAgB,OAAA,EAAQ;AACvE;;;;"}
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-canonical-length-constrained-tuple.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-constrained-tuple.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAkBzD,KAAK,OAAO,GAAG,SAAS;IACtB,QAAQ,CAAC;QACP,WAAW,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;QACjC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ,CAAC;AAEF,KAAK,UAAU,GAAG,mBAAmB,CAAC;AAQtC,eAAO,MAAM,qCAAqC,EAAE,QAAQ,CAAC,UAAU,CACrE,UAAU,EACV,OAAO,CA6EC,CAAC"}
1
+ {"version":3,"file":"prefer-canonical-length-constrained-tuple.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-canonical-length-constrained-tuple.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAiBzD,KAAK,OAAO,GAAG,SAAS;IACtB,QAAQ,CAAC;QACP,WAAW,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;QACjC,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC,CAAC;CACJ,CAAC;AAEF,KAAK,UAAU,GAAG,mBAAmB,CAAC;AAQtC,eAAO,MAAM,qCAAqC,EAAE,QAAQ,CAAC,UAAU,CACrE,UAAU,EACV,OAAO,CA6EC,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { analyzeUniformTuple } from './ast-utils.mjs';
2
2
  import { DEFAULT_MAX_TUPLE_LENGTH, NON_EMPTY_TUPLE_TYPE_NAME, MUTABLE_NON_EMPTY_TUPLE_TYPE_NAME, MIN_LENGTH_TUPLE_TYPE_NAME, MUTABLE_MIN_LENGTH_TUPLE_TYPE_NAME, FIXED_LENGTH_TUPLE_TYPE_NAME, MUTABLE_FIXED_LENGTH_TUPLE_TYPE_NAME } from './constants.mjs';
3
- import { DEFAULT_IMPORT_STYLE, IMPORT_STYLE_SCHEMA_PROPERTY, reportTupleRewrites } from './tuple-rule-utils.mjs';
3
+ import { DEFAULT_IMPORT_STYLE, IMPORT_STYLE_SCHEMA_PROPERTY } from './import-style.mjs';
4
+ import { reportTupleRewrites } from './tuple-rule-utils.mjs';
4
5
 
5
6
  const MIN_FIXED_LENGTH = 2;
6
7
  const preferCanonicalLengthConstrainedTuple = {
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-canonical-length-constrained-tuple.mjs","sources":["../../src/rules/prefer-canonical-length-constrained-tuple.mts"],"sourcesContent":["import { type TSESLint } from '@typescript-eslint/utils';\nimport { analyzeUniformTuple, type UniformTupleShape } from './ast-utils.mjs';\nimport {\n DEFAULT_MAX_TUPLE_LENGTH,\n FIXED_LENGTH_TUPLE_TYPE_NAME,\n MIN_LENGTH_TUPLE_TYPE_NAME,\n MUTABLE_FIXED_LENGTH_TUPLE_TYPE_NAME,\n MUTABLE_MIN_LENGTH_TUPLE_TYPE_NAME,\n MUTABLE_NON_EMPTY_TUPLE_TYPE_NAME,\n NON_EMPTY_TUPLE_TYPE_NAME,\n} from './constants.mjs';\nimport {\n DEFAULT_IMPORT_STYLE,\n IMPORT_STYLE_SCHEMA_PROPERTY,\n reportTupleRewrites,\n type TupleRewrite,\n} from './tuple-rule-utils.mjs';\n\ntype Options = readonly [\n Readonly<{\n importStyle?: 'global' | 'named';\n maxLength?: number;\n }>?,\n];\n\ntype MessageIds = 'useCanonicalTuple';\n\n/**\n * A single fixed element without a rest (`[V]`) is left alone: it reads better\n * than `FixedLengthTuple<1, V>`.\n */\nconst MIN_FIXED_LENGTH = 2;\n\nexport const preferCanonicalLengthConstrainedTuple: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace uniform tuple spellings such as `[V, ...V[]]` / `[V, V, ...V[]]` / `[V, V, V]` with the canonical ts-type-forge tuple type.',\n },\n fixable: 'code',\n schema: [\n {\n type: 'object',\n properties: {\n importStyle: IMPORT_STYLE_SCHEMA_PROPERTY,\n maxLength: {\n type: 'integer',\n minimum: MIN_FIXED_LENGTH,\n description: [\n 'Longest tuple the rule rewrites (default',\n `${DEFAULT_MAX_TUPLE_LENGTH}).`,\n 'Longer tuples are usually spelled out on purpose, and the named',\n 'form stops being more readable.',\n ].join(' '),\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n useCanonicalTuple:\n 'Replace `{{original}}` with `{{replacement}}` from ts-type-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const options = context.options[0];\n\n const importStyle = options?.importStyle ?? DEFAULT_IMPORT_STYLE;\n\n const maxLength = options?.maxLength ?? DEFAULT_MAX_TUPLE_LENGTH;\n\n const mut_rewrites: TupleRewrite[] = [];\n\n return {\n TSTupleType: (node) => {\n const shape = analyzeUniformTuple(node, sourceCode);\n\n if (shape === undefined || shape.fixedCount > maxLength) return;\n\n const canonicalName = canonicalNameFor(shape);\n\n if (canonicalName === undefined) return;\n\n mut_rewrites.push({\n node: shape.node,\n canonicalName,\n typeArgs:\n shape.fixedCount === 1\n ? shape.elementText\n : `${shape.fixedCount}, ${shape.elementText}`,\n });\n },\n 'Program:exit': () => {\n reportTupleRewrites(\n context,\n mut_rewrites,\n 'useCanonicalTuple',\n importStyle,\n );\n },\n };\n },\n defaultOptions: [\n { importStyle: DEFAULT_IMPORT_STYLE, maxLength: DEFAULT_MAX_TUPLE_LENGTH },\n ],\n} as const;\n\n/**\n * The ts-type-forge type a given tuple shape spells out, or `undefined` when the\n * shape is deliberately left alone.\n *\n * | shape | readonly | mutable |\n * | :-------------------- | :------------------ | :------------------------- |\n * | `[V, ...V[]]` | `NonEmptyTuple` | `MutableNonEmptyTuple` |\n * | `[V, …×N, ...V[]]` | `MinLengthTuple` | `MutableMinLengthTuple` |\n * | `[V, …×N]` (N >= 2) | `FixedLengthTuple` | `MutableFixedLengthTuple` |\n */\nconst canonicalNameFor = (\n // `UniformTupleShape` carries a `TSESTree.Node`, which is not deeply readonly.\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n shape: UniformTupleShape,\n): string | undefined => {\n const { isReadonly, hasRest, fixedCount } = shape;\n\n if (hasRest) {\n if (fixedCount === 1) {\n return isReadonly\n ? NON_EMPTY_TUPLE_TYPE_NAME\n : MUTABLE_NON_EMPTY_TUPLE_TYPE_NAME;\n }\n\n return isReadonly\n ? MIN_LENGTH_TUPLE_TYPE_NAME\n : MUTABLE_MIN_LENGTH_TUPLE_TYPE_NAME;\n }\n\n if (fixedCount < MIN_FIXED_LENGTH) return undefined;\n\n return isReadonly\n ? FIXED_LENGTH_TUPLE_TYPE_NAME\n : MUTABLE_FIXED_LENGTH_TUPLE_TYPE_NAME;\n};\n"],"names":[],"mappings":";;;;AA+BA,MAAM,gBAAA,GAAmB,CAAA;AAElB,MAAM,qCAAA,GAGT;AAAA,EACF,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,OAAA,EAAS,MAAA;AAAA,IACT,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,IAAA,EAAM,QAAA;AAAA,QACN,UAAA,EAAY;AAAA,UACV,WAAA,EAAa,4BAAA;AAAA,UACb,SAAA,EAAW;AAAA,YACT,IAAA,EAAM,SAAA;AAAA,YACN,OAAA,EAAS,gBAAA;AAAA,YACT,WAAA,EAAa;AAAA,cACX,0CAAA;AAAA,cACA,GAAG,wBAAwB,CAAA,EAAA,CAAA;AAAA,cAC3B,iEAAA;AAAA,cACA;AAAA,aACF,CAAE,KAAK,GAAG;AAAA;AACZ,SACF;AAAA,QACA,oBAAA,EAAsB;AAAA;AACxB,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR,iBAAA,EACE;AAAA;AACJ,GACF;AAAA,EAEA,MAAA,EAAQ,CAAC,OAAA,KAAY;AACnB,IAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAE3B,IAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAA;AAEjC,IAAA,MAAM,WAAA,GAAc,SAAS,WAAA,IAAe,oBAAA;AAE5C,IAAA,MAAM,SAAA,GAAY,SAAS,SAAA,IAAa,wBAAA;AAExC,IAAA,MAAM,eAA+B,EAAC;AAEtC,IAAA,OAAO;AAAA,MACL,WAAA,EAAa,CAAC,IAAA,KAAS;AACrB,QAAA,MAAM,KAAA,GAAQ,mBAAA,CAAoB,IAAA,EAAM,UAAU,CAAA;AAElD,QAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,CAAM,UAAA,GAAa,SAAA,EAAW;AAEzD,QAAA,MAAM,aAAA,GAAgB,iBAAiB,KAAK,CAAA;AAE5C,QAAA,IAAI,kBAAkB,MAAA,EAAW;AAEjC,QAAA,YAAA,CAAa,IAAA,CAAK;AAAA,UAChB,MAAM,KAAA,CAAM,IAAA;AAAA,UACZ,aAAA;AAAA,UACA,QAAA,EACE,KAAA,CAAM,UAAA,KAAe,CAAA,GACjB,KAAA,CAAM,WAAA,GACN,CAAA,EAAG,KAAA,CAAM,UAAU,CAAA,EAAA,EAAK,KAAA,CAAM,WAAW,CAAA;AAAA,SAChD,CAAA;AAAA,MACH,CAAA;AAAA,MACA,gBAAgB,MAAM;AACpB,QAAA,mBAAA;AAAA,UACE,OAAA;AAAA,UACA,YAAA;AAAA,UACA,mBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EACA,cAAA,EAAgB;AAAA,IACd,EAAE,WAAA,EAAa,oBAAA,EAAsB,SAAA,EAAW,wBAAA;AAAyB;AAE7E;AAYA,MAAM,gBAAA,GAAmB,CAGvB,KAAA,KACuB;AACvB,EAAA,MAAM,EAAE,UAAA,EAAY,OAAA,EAAS,UAAA,EAAW,GAAI,KAAA;AAE5C,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,IAAI,eAAe,CAAA,EAAG;AACpB,MAAA,OAAO,aACH,yBAAA,GACA,iCAAA;AAAA,IACN;AAEA,IAAA,OAAO,aACH,0BAAA,GACA,kCAAA;AAAA,EACN;AAEA,EAAA,IAAI,UAAA,GAAa,kBAAkB,OAAO,MAAA;AAE1C,EAAA,OAAO,aACH,4BAAA,GACA,oCAAA;AACN,CAAA;;;;"}
1
+ {"version":3,"file":"prefer-canonical-length-constrained-tuple.mjs","sources":["../../src/rules/prefer-canonical-length-constrained-tuple.mts"],"sourcesContent":["import { type TSESLint } from '@typescript-eslint/utils';\nimport { analyzeUniformTuple, type UniformTupleShape } from './ast-utils.mjs';\nimport {\n DEFAULT_MAX_TUPLE_LENGTH,\n FIXED_LENGTH_TUPLE_TYPE_NAME,\n MIN_LENGTH_TUPLE_TYPE_NAME,\n MUTABLE_FIXED_LENGTH_TUPLE_TYPE_NAME,\n MUTABLE_MIN_LENGTH_TUPLE_TYPE_NAME,\n MUTABLE_NON_EMPTY_TUPLE_TYPE_NAME,\n NON_EMPTY_TUPLE_TYPE_NAME,\n} from './constants.mjs';\nimport {\n DEFAULT_IMPORT_STYLE,\n IMPORT_STYLE_SCHEMA_PROPERTY,\n} from './import-style.mjs';\nimport { reportTupleRewrites, type TupleRewrite } from './tuple-rule-utils.mjs';\n\ntype Options = readonly [\n Readonly<{\n importStyle?: 'global' | 'named';\n maxLength?: number;\n }>?,\n];\n\ntype MessageIds = 'useCanonicalTuple';\n\n/**\n * A single fixed element without a rest (`[V]`) is left alone: it reads better\n * than `FixedLengthTuple<1, V>`.\n */\nconst MIN_FIXED_LENGTH = 2;\n\nexport const preferCanonicalLengthConstrainedTuple: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace uniform tuple spellings such as `[V, ...V[]]` / `[V, V, ...V[]]` / `[V, V, V]` with the canonical ts-type-forge tuple type.',\n },\n fixable: 'code',\n schema: [\n {\n type: 'object',\n properties: {\n importStyle: IMPORT_STYLE_SCHEMA_PROPERTY,\n maxLength: {\n type: 'integer',\n minimum: MIN_FIXED_LENGTH,\n description: [\n 'Longest tuple the rule rewrites (default',\n `${DEFAULT_MAX_TUPLE_LENGTH}).`,\n 'Longer tuples are usually spelled out on purpose, and the named',\n 'form stops being more readable.',\n ].join(' '),\n },\n },\n additionalProperties: false,\n },\n ],\n messages: {\n useCanonicalTuple:\n 'Replace `{{original}}` with `{{replacement}}` from ts-type-forge.',\n },\n },\n\n create: (context) => {\n const sourceCode = context.sourceCode;\n\n const options = context.options[0];\n\n const importStyle = options?.importStyle ?? DEFAULT_IMPORT_STYLE;\n\n const maxLength = options?.maxLength ?? DEFAULT_MAX_TUPLE_LENGTH;\n\n const mut_rewrites: TupleRewrite[] = [];\n\n return {\n TSTupleType: (node) => {\n const shape = analyzeUniformTuple(node, sourceCode);\n\n if (shape === undefined || shape.fixedCount > maxLength) return;\n\n const canonicalName = canonicalNameFor(shape);\n\n if (canonicalName === undefined) return;\n\n mut_rewrites.push({\n node: shape.node,\n canonicalName,\n typeArgs:\n shape.fixedCount === 1\n ? shape.elementText\n : `${shape.fixedCount}, ${shape.elementText}`,\n });\n },\n 'Program:exit': () => {\n reportTupleRewrites(\n context,\n mut_rewrites,\n 'useCanonicalTuple',\n importStyle,\n );\n },\n };\n },\n defaultOptions: [\n { importStyle: DEFAULT_IMPORT_STYLE, maxLength: DEFAULT_MAX_TUPLE_LENGTH },\n ],\n} as const;\n\n/**\n * The ts-type-forge type a given tuple shape spells out, or `undefined` when the\n * shape is deliberately left alone.\n *\n * | shape | readonly | mutable |\n * | :-------------------- | :------------------ | :------------------------- |\n * | `[V, ...V[]]` | `NonEmptyTuple` | `MutableNonEmptyTuple` |\n * | `[V, …×N, ...V[]]` | `MinLengthTuple` | `MutableMinLengthTuple` |\n * | `[V, …×N]` (N >= 2) | `FixedLengthTuple` | `MutableFixedLengthTuple` |\n */\nconst canonicalNameFor = (\n // `UniformTupleShape` carries a `TSESTree.Node`, which is not deeply readonly.\n // eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types\n shape: UniformTupleShape,\n): string | undefined => {\n const { isReadonly, hasRest, fixedCount } = shape;\n\n if (hasRest) {\n if (fixedCount === 1) {\n return isReadonly\n ? NON_EMPTY_TUPLE_TYPE_NAME\n : MUTABLE_NON_EMPTY_TUPLE_TYPE_NAME;\n }\n\n return isReadonly\n ? MIN_LENGTH_TUPLE_TYPE_NAME\n : MUTABLE_MIN_LENGTH_TUPLE_TYPE_NAME;\n }\n\n if (fixedCount < MIN_FIXED_LENGTH) return undefined;\n\n return isReadonly\n ? FIXED_LENGTH_TUPLE_TYPE_NAME\n : MUTABLE_FIXED_LENGTH_TUPLE_TYPE_NAME;\n};\n"],"names":[],"mappings":";;;;;AA8BA,MAAM,gBAAA,GAAmB,CAAA;AAElB,MAAM,qCAAA,GAGT;AAAA,EACF,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,OAAA,EAAS,MAAA;AAAA,IACT,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,IAAA,EAAM,QAAA;AAAA,QACN,UAAA,EAAY;AAAA,UACV,WAAA,EAAa,4BAAA;AAAA,UACb,SAAA,EAAW;AAAA,YACT,IAAA,EAAM,SAAA;AAAA,YACN,OAAA,EAAS,gBAAA;AAAA,YACT,WAAA,EAAa;AAAA,cACX,0CAAA;AAAA,cACA,GAAG,wBAAwB,CAAA,EAAA,CAAA;AAAA,cAC3B,iEAAA;AAAA,cACA;AAAA,aACF,CAAE,KAAK,GAAG;AAAA;AACZ,SACF;AAAA,QACA,oBAAA,EAAsB;AAAA;AACxB,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR,iBAAA,EACE;AAAA;AACJ,GACF;AAAA,EAEA,MAAA,EAAQ,CAAC,OAAA,KAAY;AACnB,IAAA,MAAM,aAAa,OAAA,CAAQ,UAAA;AAE3B,IAAA,MAAM,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAA;AAEjC,IAAA,MAAM,WAAA,GAAc,SAAS,WAAA,IAAe,oBAAA;AAE5C,IAAA,MAAM,SAAA,GAAY,SAAS,SAAA,IAAa,wBAAA;AAExC,IAAA,MAAM,eAA+B,EAAC;AAEtC,IAAA,OAAO;AAAA,MACL,WAAA,EAAa,CAAC,IAAA,KAAS;AACrB,QAAA,MAAM,KAAA,GAAQ,mBAAA,CAAoB,IAAA,EAAM,UAAU,CAAA;AAElD,QAAA,IAAI,KAAA,KAAU,MAAA,IAAa,KAAA,CAAM,UAAA,GAAa,SAAA,EAAW;AAEzD,QAAA,MAAM,aAAA,GAAgB,iBAAiB,KAAK,CAAA;AAE5C,QAAA,IAAI,kBAAkB,MAAA,EAAW;AAEjC,QAAA,YAAA,CAAa,IAAA,CAAK;AAAA,UAChB,MAAM,KAAA,CAAM,IAAA;AAAA,UACZ,aAAA;AAAA,UACA,QAAA,EACE,KAAA,CAAM,UAAA,KAAe,CAAA,GACjB,KAAA,CAAM,WAAA,GACN,CAAA,EAAG,KAAA,CAAM,UAAU,CAAA,EAAA,EAAK,KAAA,CAAM,WAAW,CAAA;AAAA,SAChD,CAAA;AAAA,MACH,CAAA;AAAA,MACA,gBAAgB,MAAM;AACpB,QAAA,mBAAA;AAAA,UACE,OAAA;AAAA,UACA,YAAA;AAAA,UACA,mBAAA;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EACA,cAAA,EAAgB;AAAA,IACd,EAAE,WAAA,EAAa,oBAAA,EAAsB,SAAA,EAAW,wBAAA;AAAyB;AAE7E;AAYA,MAAM,gBAAA,GAAmB,CAGvB,KAAA,KACuB;AACvB,EAAA,MAAM,EAAE,UAAA,EAAY,OAAA,EAAS,UAAA,EAAW,GAAI,KAAA;AAE5C,EAAA,IAAI,OAAA,EAAS;AACX,IAAA,IAAI,eAAe,CAAA,EAAG;AACpB,MAAA,OAAO,aACH,yBAAA,GACA,iCAAA;AAAA,IACN;AAEA,IAAA,OAAO,aACH,0BAAA,GACA,kCAAA;AAAA,EACN;AAEA,EAAA,IAAI,UAAA,GAAa,kBAAkB,OAAO,MAAA;AAE1C,EAAA,OAAO,aACH,4BAAA,GACA,oCAAA;AACN,CAAA;;;;"}
@@ -0,0 +1,10 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Options = readonly [
3
+ Readonly<{
4
+ importStyle?: 'global' | 'named';
5
+ }>?
6
+ ];
7
+ type MessageIds = 'useReadonlyOrMutableRecord' | 'replaceWith';
8
+ export declare const preferReadonlyOrMutableRecord: TSESLint.RuleModule<MessageIds, Options>;
9
+ export {};
10
+ //# sourceMappingURL=prefer-readonly-or-mutable-record.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-readonly-or-mutable-record.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-readonly-or-mutable-record.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAUzD,KAAK,OAAO,GAAG,SAAS;IACtB,QAAQ,CAAC;QACP,WAAW,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;KAClC,CAAC,CAAC;CACJ,CAAC;AAEF,KAAK,UAAU,GAAG,4BAA4B,GAAG,aAAa,CAAC;AAW/D,eAAO,MAAM,6BAA6B,EAAE,QAAQ,CAAC,UAAU,CAC7D,UAAU,EACV,OAAO,CA4CC,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { DEFAULT_IMPORT_STYLE, IMPORT_STYLE_SCHEMA_PROPERTY } from './import-style.mjs';
2
+ import { reportStdTypeReference } from './std-type-rule-utils.mjs';
3
+
4
+ const ALTERNATIVES = /* @__PURE__ */ new Map([
5
+ ["Record", ["ReadonlyRecord", "MutableRecord"]]
6
+ ]);
7
+ const preferReadonlyOrMutableRecord = {
8
+ meta: {
9
+ type: "suggestion",
10
+ docs: {
11
+ description: "Replace the built-in `Record` with the ts-type-forge `ReadonlyRecord` or `MutableRecord`, so that the mutability of the record is stated rather than implied."
12
+ },
13
+ hasSuggestions: true,
14
+ schema: [
15
+ {
16
+ type: "object",
17
+ properties: {
18
+ importStyle: IMPORT_STYLE_SCHEMA_PROPERTY
19
+ },
20
+ additionalProperties: false
21
+ }
22
+ ],
23
+ messages: {
24
+ useReadonlyOrMutableRecord: "Use {{alternatives}} from ts-type-forge instead of the built-in `{{name}}`.",
25
+ replaceWith: "Replace `{{name}}` with `{{replacement}}`."
26
+ }
27
+ },
28
+ create: (context) => {
29
+ const importStyle = context.options[0]?.importStyle ?? DEFAULT_IMPORT_STYLE;
30
+ return {
31
+ TSTypeReference: (node) => {
32
+ reportStdTypeReference(
33
+ context,
34
+ node,
35
+ ALTERNATIVES,
36
+ {
37
+ report: "useReadonlyOrMutableRecord",
38
+ suggestion: "replaceWith"
39
+ },
40
+ importStyle
41
+ );
42
+ }
43
+ };
44
+ },
45
+ defaultOptions: [{ importStyle: DEFAULT_IMPORT_STYLE }]
46
+ };
47
+
48
+ export { preferReadonlyOrMutableRecord };
49
+ //# sourceMappingURL=prefer-readonly-or-mutable-record.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-readonly-or-mutable-record.mjs","sources":["../../src/rules/prefer-readonly-or-mutable-record.mts"],"sourcesContent":["import { type TSESLint } from '@typescript-eslint/utils';\nimport {\n DEFAULT_IMPORT_STYLE,\n IMPORT_STYLE_SCHEMA_PROPERTY,\n} from './import-style.mjs';\nimport {\n reportStdTypeReference,\n type StdTypeAlternatives,\n} from './std-type-rule-utils.mjs';\n\ntype Options = readonly [\n Readonly<{\n importStyle?: 'global' | 'named';\n }>?,\n];\n\ntype MessageIds = 'useReadonlyOrMutableRecord' | 'replaceWith';\n\n/**\n * `Record` says nothing about whether the properties may be reassigned, so the\n * mutability of every record type spelled with it is decided by whoever reads\n * it. The ts-type-forge pair spells it out.\n */\nconst ALTERNATIVES: StdTypeAlternatives = new Map([\n ['Record', ['ReadonlyRecord', 'MutableRecord']],\n]);\n\nexport const preferReadonlyOrMutableRecord: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace the built-in `Record` with the ts-type-forge `ReadonlyRecord` or `MutableRecord`, so that the mutability of the record is stated rather than implied.',\n },\n hasSuggestions: true,\n schema: [\n {\n type: 'object',\n properties: {\n importStyle: IMPORT_STYLE_SCHEMA_PROPERTY,\n },\n additionalProperties: false,\n },\n ],\n messages: {\n useReadonlyOrMutableRecord:\n 'Use {{alternatives}} from ts-type-forge instead of the built-in `{{name}}`.',\n replaceWith: 'Replace `{{name}}` with `{{replacement}}`.',\n },\n },\n\n create: (context) => {\n const importStyle = context.options[0]?.importStyle ?? DEFAULT_IMPORT_STYLE;\n\n return {\n TSTypeReference: (node) => {\n reportStdTypeReference(\n context,\n node,\n ALTERNATIVES,\n {\n report: 'useReadonlyOrMutableRecord',\n suggestion: 'replaceWith',\n },\n importStyle,\n );\n },\n };\n },\n defaultOptions: [{ importStyle: DEFAULT_IMPORT_STYLE }],\n} as const;\n"],"names":[],"mappings":";;;AAuBA,MAAM,YAAA,uBAAwC,GAAA,CAAI;AAAA,EAChD,CAAC,QAAA,EAAU,CAAC,gBAAA,EAAkB,eAAe,CAAC;AAChD,CAAC,CAAA;AAEM,MAAM,6BAAA,GAGT;AAAA,EACF,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,cAAA,EAAgB,IAAA;AAAA,IAChB,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,IAAA,EAAM,QAAA;AAAA,QACN,UAAA,EAAY;AAAA,UACV,WAAA,EAAa;AAAA,SACf;AAAA,QACA,oBAAA,EAAsB;AAAA;AACxB,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR,0BAAA,EACE,6EAAA;AAAA,MACF,WAAA,EAAa;AAAA;AACf,GACF;AAAA,EAEA,MAAA,EAAQ,CAAC,OAAA,KAAY;AACnB,IAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,OAAA,CAAQ,CAAC,GAAG,WAAA,IAAe,oBAAA;AAEvD,IAAA,OAAO;AAAA,MACL,eAAA,EAAiB,CAAC,IAAA,KAAS;AACzB,QAAA,sBAAA;AAAA,UACE,OAAA;AAAA,UACA,IAAA;AAAA,UACA,YAAA;AAAA,UACA;AAAA,YACE,MAAA,EAAQ,4BAAA;AAAA,YACR,UAAA,EAAY;AAAA,WACd;AAAA,UACA;AAAA,SACF;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EACA,cAAA,EAAgB,CAAC,EAAE,WAAA,EAAa,sBAAsB;AACxD;;;;"}
@@ -0,0 +1,10 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ type Options = readonly [
3
+ Readonly<{
4
+ importStyle?: 'global' | 'named';
5
+ }>?
6
+ ];
7
+ type MessageIds = 'useStrictOrRelaxed' | 'replaceWith';
8
+ export declare const preferStrictOrRelaxedUtilityType: TSESLint.RuleModule<MessageIds, Options>;
9
+ export {};
10
+ //# sourceMappingURL=prefer-strict-or-relaxed-utility-type.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-strict-or-relaxed-utility-type.d.mts","sourceRoot":"","sources":["../../src/rules/prefer-strict-or-relaxed-utility-type.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAUzD,KAAK,OAAO,GAAG,SAAS;IACtB,QAAQ,CAAC;QACP,WAAW,CAAC,EAAE,QAAQ,GAAG,OAAO,CAAC;KAClC,CAAC,CAAC;CACJ,CAAC;AAEF,KAAK,UAAU,GAAG,oBAAoB,GAAG,aAAa,CAAC;AAoBvD,eAAO,MAAM,gCAAgC,EAAE,QAAQ,CAAC,UAAU,CAChE,UAAU,EACV,OAAO,CAyCC,CAAC"}
@@ -0,0 +1,49 @@
1
+ import { DEFAULT_IMPORT_STYLE, IMPORT_STYLE_SCHEMA_PROPERTY } from './import-style.mjs';
2
+ import { reportStdTypeReference } from './std-type-rule-utils.mjs';
3
+
4
+ const ALTERNATIVES = /* @__PURE__ */ new Map([
5
+ ["Exclude", ["StrictExclude", "RelaxedExclude"]],
6
+ ["Extract", ["StrictExtract", "RelaxedExtract"]],
7
+ ["Omit", ["StrictOmit", "RelaxedOmit"]],
8
+ ["Pick", ["StrictPick", "RelaxedPick"]]
9
+ ]);
10
+ const preferStrictOrRelaxedUtilityType = {
11
+ meta: {
12
+ type: "suggestion",
13
+ docs: {
14
+ description: "Replace the built-in `Exclude` / `Extract` / `Omit` / `Pick` with the ts-type-forge `Strict*` or `Relaxed*` counterpart, so that the unchecked second argument becomes an explicit choice."
15
+ },
16
+ hasSuggestions: true,
17
+ schema: [
18
+ {
19
+ type: "object",
20
+ properties: {
21
+ importStyle: IMPORT_STYLE_SCHEMA_PROPERTY
22
+ },
23
+ additionalProperties: false
24
+ }
25
+ ],
26
+ messages: {
27
+ useStrictOrRelaxed: "Use {{alternatives}} from ts-type-forge instead of the built-in `{{name}}`.",
28
+ replaceWith: "Replace `{{name}}` with `{{replacement}}`."
29
+ }
30
+ },
31
+ create: (context) => {
32
+ const importStyle = context.options[0]?.importStyle ?? DEFAULT_IMPORT_STYLE;
33
+ return {
34
+ TSTypeReference: (node) => {
35
+ reportStdTypeReference(
36
+ context,
37
+ node,
38
+ ALTERNATIVES,
39
+ { report: "useStrictOrRelaxed", suggestion: "replaceWith" },
40
+ importStyle
41
+ );
42
+ }
43
+ };
44
+ },
45
+ defaultOptions: [{ importStyle: DEFAULT_IMPORT_STYLE }]
46
+ };
47
+
48
+ export { preferStrictOrRelaxedUtilityType };
49
+ //# sourceMappingURL=prefer-strict-or-relaxed-utility-type.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-strict-or-relaxed-utility-type.mjs","sources":["../../src/rules/prefer-strict-or-relaxed-utility-type.mts"],"sourcesContent":["import { type TSESLint } from '@typescript-eslint/utils';\nimport {\n DEFAULT_IMPORT_STYLE,\n IMPORT_STYLE_SCHEMA_PROPERTY,\n} from './import-style.mjs';\nimport {\n reportStdTypeReference,\n type StdTypeAlternatives,\n} from './std-type-rule-utils.mjs';\n\ntype Options = readonly [\n Readonly<{\n importStyle?: 'global' | 'named';\n }>?,\n];\n\ntype MessageIds = 'useStrictOrRelaxed' | 'replaceWith';\n\n/**\n * The standard-library types whose second argument is unchecked, and the\n * ts-type-forge pair that makes the choice explicit.\n *\n * `Strict*` constrains the second argument (`U extends T` / `K extends keyof T`),\n * so a key that does not exist — typically a leftover after a rename — is a\n * compile error instead of a silently empty result. `Relaxed*` keeps the\n * standard-library behavior for the cases that genuinely need it (a subtrahend\n * that is not provably part of the union, a key set computed from a still\n * deferred type parameter).\n */\nconst ALTERNATIVES: StdTypeAlternatives = new Map([\n ['Exclude', ['StrictExclude', 'RelaxedExclude']],\n ['Extract', ['StrictExtract', 'RelaxedExtract']],\n ['Omit', ['StrictOmit', 'RelaxedOmit']],\n ['Pick', ['StrictPick', 'RelaxedPick']],\n]);\n\nexport const preferStrictOrRelaxedUtilityType: TSESLint.RuleModule<\n MessageIds,\n Options\n> = {\n meta: {\n type: 'suggestion',\n docs: {\n description:\n 'Replace the built-in `Exclude` / `Extract` / `Omit` / `Pick` with the ts-type-forge `Strict*` or `Relaxed*` counterpart, so that the unchecked second argument becomes an explicit choice.',\n },\n hasSuggestions: true,\n schema: [\n {\n type: 'object',\n properties: {\n importStyle: IMPORT_STYLE_SCHEMA_PROPERTY,\n },\n additionalProperties: false,\n },\n ],\n messages: {\n useStrictOrRelaxed:\n 'Use {{alternatives}} from ts-type-forge instead of the built-in `{{name}}`.',\n replaceWith: 'Replace `{{name}}` with `{{replacement}}`.',\n },\n },\n\n create: (context) => {\n const importStyle = context.options[0]?.importStyle ?? DEFAULT_IMPORT_STYLE;\n\n return {\n TSTypeReference: (node) => {\n reportStdTypeReference(\n context,\n node,\n ALTERNATIVES,\n { report: 'useStrictOrRelaxed', suggestion: 'replaceWith' },\n importStyle,\n );\n },\n };\n },\n defaultOptions: [{ importStyle: DEFAULT_IMPORT_STYLE }],\n} as const;\n"],"names":[],"mappings":";;;AA6BA,MAAM,YAAA,uBAAwC,GAAA,CAAI;AAAA,EAChD,CAAC,SAAA,EAAW,CAAC,eAAA,EAAiB,gBAAgB,CAAC,CAAA;AAAA,EAC/C,CAAC,SAAA,EAAW,CAAC,eAAA,EAAiB,gBAAgB,CAAC,CAAA;AAAA,EAC/C,CAAC,MAAA,EAAQ,CAAC,YAAA,EAAc,aAAa,CAAC,CAAA;AAAA,EACtC,CAAC,MAAA,EAAQ,CAAC,YAAA,EAAc,aAAa,CAAC;AACxC,CAAC,CAAA;AAEM,MAAM,gCAAA,GAGT;AAAA,EACF,IAAA,EAAM;AAAA,IACJ,IAAA,EAAM,YAAA;AAAA,IACN,IAAA,EAAM;AAAA,MACJ,WAAA,EACE;AAAA,KACJ;AAAA,IACA,cAAA,EAAgB,IAAA;AAAA,IAChB,MAAA,EAAQ;AAAA,MACN;AAAA,QACE,IAAA,EAAM,QAAA;AAAA,QACN,UAAA,EAAY;AAAA,UACV,WAAA,EAAa;AAAA,SACf;AAAA,QACA,oBAAA,EAAsB;AAAA;AACxB,KACF;AAAA,IACA,QAAA,EAAU;AAAA,MACR,kBAAA,EACE,6EAAA;AAAA,MACF,WAAA,EAAa;AAAA;AACf,GACF;AAAA,EAEA,MAAA,EAAQ,CAAC,OAAA,KAAY;AACnB,IAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,OAAA,CAAQ,CAAC,GAAG,WAAA,IAAe,oBAAA;AAEvD,IAAA,OAAO;AAAA,MACL,eAAA,EAAiB,CAAC,IAAA,KAAS;AACzB,QAAA,sBAAA;AAAA,UACE,OAAA;AAAA,UACA,IAAA;AAAA,UACA,YAAA;AAAA,UACA,EAAE,MAAA,EAAQ,oBAAA,EAAsB,UAAA,EAAY,aAAA,EAAc;AAAA,UAC1D;AAAA,SACF;AAAA,MACF;AAAA,KACF;AAAA,EACF,CAAA;AAAA,EACA,cAAA,EAAgB,CAAC,EAAE,WAAA,EAAa,sBAAsB;AACxD;;;;"}
@@ -3,5 +3,11 @@ export declare const tsTypeForgeRules: {
3
3
  importStyle?: 'global' | 'named';
4
4
  maxLength?: number;
5
5
  }> | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
+ readonly 'prefer-readonly-or-mutable-record': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWith" | "useReadonlyOrMutableRecord", readonly [(Readonly<{
7
+ importStyle?: 'global' | 'named';
8
+ }> | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
9
+ readonly 'prefer-strict-or-relaxed-utility-type': import("@typescript-eslint/utils/ts-eslint").RuleModule<"replaceWith" | "useStrictOrRelaxed", readonly [(Readonly<{
10
+ importStyle?: 'global' | 'named';
11
+ }> | undefined)?], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
6
12
  };
7
13
  //# sourceMappingURL=rules.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../src/rules/rules.mts"],"names":[],"mappings":"AAGA,eAAO,MAAM,gBAAgB;;;;;CAGa,CAAC"}
1
+ {"version":3,"file":"rules.d.mts","sourceRoot":"","sources":["../../src/rules/rules.mts"],"names":[],"mappings":"AAKA,eAAO,MAAM,gBAAgB;;;;;;;;;;;CAKa,CAAC"}
@@ -1,7 +1,11 @@
1
1
  import { preferCanonicalLengthConstrainedTuple } from './prefer-canonical-length-constrained-tuple.mjs';
2
+ import { preferReadonlyOrMutableRecord } from './prefer-readonly-or-mutable-record.mjs';
3
+ import { preferStrictOrRelaxedUtilityType } from './prefer-strict-or-relaxed-utility-type.mjs';
2
4
 
3
5
  const tsTypeForgeRules = {
4
- "prefer-canonical-length-constrained-tuple": preferCanonicalLengthConstrainedTuple
6
+ "prefer-canonical-length-constrained-tuple": preferCanonicalLengthConstrainedTuple,
7
+ "prefer-readonly-or-mutable-record": preferReadonlyOrMutableRecord,
8
+ "prefer-strict-or-relaxed-utility-type": preferStrictOrRelaxedUtilityType
5
9
  };
6
10
 
7
11
  export { tsTypeForgeRules };
@@ -1 +1 @@
1
- {"version":3,"file":"rules.mjs","sources":["../../src/rules/rules.mts"],"sourcesContent":["import { type ESLintPlugin } from '../types.mjs';\nimport { preferCanonicalLengthConstrainedTuple } from './prefer-canonical-length-constrained-tuple.mjs';\n\nexport const tsTypeForgeRules = {\n 'prefer-canonical-length-constrained-tuple':\n preferCanonicalLengthConstrainedTuple,\n} as const satisfies ESLintPlugin['rules'];\n"],"names":[],"mappings":";;AAGO,MAAM,gBAAA,GAAmB;AAAA,EAC9B,2CAAA,EACE;AACJ;;;;"}
1
+ {"version":3,"file":"rules.mjs","sources":["../../src/rules/rules.mts"],"sourcesContent":["import { type ESLintPlugin } from '../types.mjs';\nimport { preferCanonicalLengthConstrainedTuple } from './prefer-canonical-length-constrained-tuple.mjs';\nimport { preferReadonlyOrMutableRecord } from './prefer-readonly-or-mutable-record.mjs';\nimport { preferStrictOrRelaxedUtilityType } from './prefer-strict-or-relaxed-utility-type.mjs';\n\nexport const tsTypeForgeRules = {\n 'prefer-canonical-length-constrained-tuple':\n preferCanonicalLengthConstrainedTuple,\n 'prefer-readonly-or-mutable-record': preferReadonlyOrMutableRecord,\n 'prefer-strict-or-relaxed-utility-type': preferStrictOrRelaxedUtilityType,\n} as const satisfies ESLintPlugin['rules'];\n"],"names":[],"mappings":";;;;AAKO,MAAM,gBAAA,GAAmB;AAAA,EAC9B,2CAAA,EACE,qCAAA;AAAA,EACF,mCAAA,EAAqC,6BAAA;AAAA,EACrC,uCAAA,EAAyC;AAC3C;;;;"}
@@ -0,0 +1,22 @@
1
+ import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
+ import { type ImportStyle } from './import-style.mjs';
3
+ /**
4
+ * The ts-type-forge replacements offered for one standard-library type, in the
5
+ * order they are suggested.
6
+ */
7
+ export type StdTypeAlternatives = ReadonlyMap<string, readonly string[]>;
8
+ /**
9
+ * Reports a reference to a banned standard-library type and offers one
10
+ * suggestion per ts-type-forge alternative.
11
+ *
12
+ * Choosing between the alternatives is a semantic decision — `StrictExclude`
13
+ * additionally checks that the subtrahend is part of the union, `ReadonlyRecord`
14
+ * and `MutableRecord` describe different types — so this is deliberately *not*
15
+ * an autofix. Each suggestion carries its own import fix, which is safe because
16
+ * ESLint applies suggestions one at a time.
17
+ */
18
+ export declare const reportStdTypeReference: <MessageIds extends string>(context: TSESLint.RuleContext<MessageIds, readonly unknown[]>, node: TSESTree.TSTypeReference, alternatives: StdTypeAlternatives, messageIds: Readonly<{
19
+ report: MessageIds;
20
+ suggestion: MessageIds;
21
+ }>, importStyle: ImportStyle) => void;
22
+ //# sourceMappingURL=std-type-rule-utils.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"std-type-rule-utils.d.mts","sourceRoot":"","sources":["../../src/rules/std-type-rule-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAKvE;;;GAGG;AACH,MAAM,MAAM,mBAAmB,GAAG,WAAW,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC;AAEzE;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,GAAI,UAAU,SAAS,MAAM,WACrD,QAAQ,CAAC,WAAW,CAAC,UAAU,EAAE,SAAS,OAAO,EAAE,CAAC,QACvD,QAAQ,CAAC,eAAe,gBAChB,mBAAmB,cACrB,QAAQ,CAAC;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,CAAC,eACvD,WAAW,KACvB,IA4CF,CAAC"}