eslint-plugin-ts-type-forge 0.4.3 → 0.6.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/README.md +80 -13
- package/dist/plugin.d.mts +8 -0
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +3 -1
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +79 -1
- package/dist/rule-types.d.mts.map +1 -1
- package/dist/rules/ast-utils.d.mts +2 -0
- package/dist/rules/ast-utils.d.mts.map +1 -1
- package/dist/rules/ast-utils.mjs +10 -3
- package/dist/rules/ast-utils.mjs.map +1 -1
- package/dist/rules/import-style.d.mts +21 -0
- package/dist/rules/import-style.d.mts.map +1 -0
- package/dist/rules/import-style.mjs +29 -0
- package/dist/rules/import-style.mjs.map +1 -0
- package/dist/rules/prefer-canonical-length-constrained-tuple.d.mts.map +1 -1
- package/dist/rules/prefer-canonical-length-constrained-tuple.mjs +2 -1
- package/dist/rules/prefer-canonical-length-constrained-tuple.mjs.map +1 -1
- package/dist/rules/prefer-readonly-or-mutable-record.d.mts +10 -0
- package/dist/rules/prefer-readonly-or-mutable-record.d.mts.map +1 -0
- package/dist/rules/prefer-readonly-or-mutable-record.mjs +49 -0
- package/dist/rules/prefer-readonly-or-mutable-record.mjs.map +1 -0
- package/dist/rules/prefer-strict-or-relaxed-utility-type.d.mts +10 -0
- package/dist/rules/prefer-strict-or-relaxed-utility-type.d.mts.map +1 -0
- package/dist/rules/prefer-strict-or-relaxed-utility-type.mjs +49 -0
- package/dist/rules/prefer-strict-or-relaxed-utility-type.mjs.map +1 -0
- package/dist/rules/rules.d.mts +6 -0
- package/dist/rules/rules.d.mts.map +1 -1
- package/dist/rules/rules.mjs +5 -1
- package/dist/rules/rules.mjs.map +1 -1
- package/dist/rules/std-type-rule-utils.d.mts +22 -0
- package/dist/rules/std-type-rule-utils.d.mts.map +1 -0
- package/dist/rules/std-type-rule-utils.mjs +40 -0
- package/dist/rules/std-type-rule-utils.mjs.map +1 -0
- package/dist/rules/tuple-rule-utils.d.mts +1 -19
- package/dist/rules/tuple-rule-utils.d.mts.map +1 -1
- package/dist/rules/tuple-rule-utils.mjs +7 -29
- package/dist/rules/tuple-rule-utils.mjs.map +1 -1
- package/package.json +4 -3
- package/src/plugin.mts +2 -0
- package/src/rule-types.mts +93 -1
- package/src/rules/ast-utils.mts +32 -3
- package/src/rules/import-style.mts +60 -0
- package/src/rules/prefer-canonical-length-constrained-tuple.mts +2 -3
- package/src/rules/prefer-canonical-length-constrained-tuple.test.mts +84 -15
- package/src/rules/prefer-readonly-or-mutable-record.mts +74 -0
- package/src/rules/prefer-readonly-or-mutable-record.test.mts +160 -0
- package/src/rules/prefer-strict-or-relaxed-utility-type.mts +80 -0
- package/src/rules/prefer-strict-or-relaxed-utility-type.test.mts +279 -0
- package/src/rules/rules.mts +4 -0
- package/src/rules/std-type-rule-utils.mts +103 -0
- package/src/rules/tuple-rule-utils.mts +12 -66
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
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
|
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
|
|
133
|
-
|
|
134
|
-
- `'named'` — the
|
|
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
|
|
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
|
};
|
package/dist/plugin.d.mts.map
CHANGED
|
@@ -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;
|
|
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",
|
package/dist/plugin.mjs.map
CHANGED
|
@@ -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;
|
|
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;;;;"}
|
package/dist/rule-types.d.mts
CHANGED
|
@@ -35,7 +35,7 @@ declare namespace PreferCanonicalLengthConstrainedTuple {
|
|
|
35
35
|
* "global",
|
|
36
36
|
* "named"
|
|
37
37
|
* ],
|
|
38
|
-
* "description": "How the ts-type-forge type is brought into scope. '
|
|
38
|
+
* "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."
|
|
39
39
|
* },
|
|
40
40
|
* "maxLength": {
|
|
41
41
|
* "type": "integer",
|
|
@@ -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;
|
|
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"}
|
|
@@ -27,6 +27,8 @@ export type UniformTupleShape = Readonly<{
|
|
|
27
27
|
* rewriting them would drop information.
|
|
28
28
|
* - A tuple that is only a rest element (`[...V[]]`) is not a length
|
|
29
29
|
* constraint at all.
|
|
30
|
+
* - A tuple inside the `extends` clause of a conditional type is a match
|
|
31
|
+
* pattern, not a type (see {@link isInConditionalTypeExtendsClause}).
|
|
30
32
|
*/
|
|
31
33
|
export declare const analyzeUniformTuple: (tuple: TSESTree.TSTupleType, sourceCode: TSESLint.SourceCode) => UniformTupleShape | undefined;
|
|
32
34
|
//# sourceMappingURL=ast-utils.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-utils.d.mts","sourceRoot":"","sources":["../../src/rules/ast-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"ast-utils.d.mts","sourceRoot":"","sources":["../../src/rules/ast-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAKlC,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACvC,+EAA+E;IAC/E,IAAI,EAAE,QAAQ,CAAC,IAAI,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,mBAAmB,UACvB,QAAQ,CAAC,WAAW,cACf,QAAQ,CAAC,UAAU,KAC9B,iBAAiB,GAAG,SA4DtB,CAAC"}
|
package/dist/rules/ast-utils.mjs
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
+
import { Arr } from 'ts-data-forge';
|
|
2
3
|
|
|
3
4
|
const analyzeUniformTuple = (tuple, sourceCode) => {
|
|
5
|
+
if (isInConditionalTypeExtendsClause(tuple)) return void 0;
|
|
4
6
|
const { parent } = tuple;
|
|
5
7
|
const isReadonly = parent.type === AST_NODE_TYPES.TSTypeOperator && parent.operator === "readonly";
|
|
6
8
|
const elements = tuple.elementTypes;
|
|
7
9
|
const last = elements.at(-1);
|
|
8
10
|
const hasRest = last?.type === AST_NODE_TYPES.TSRestType;
|
|
9
|
-
const leading = hasRest ?
|
|
10
|
-
if (leading
|
|
11
|
+
const leading = hasRest ? Arr.butLast(elements) : elements;
|
|
12
|
+
if (Arr.isEmpty(leading) || leading.some(
|
|
11
13
|
(element) => element.type === AST_NODE_TYPES.TSRestType || element.type === AST_NODE_TYPES.TSOptionalType || element.type === AST_NODE_TYPES.TSNamedTupleMember
|
|
12
14
|
)) {
|
|
13
15
|
return void 0;
|
|
@@ -19,7 +21,7 @@ const analyzeUniformTuple = (tuple, sourceCode) => {
|
|
|
19
21
|
const elementText = sourceCode.getText(head);
|
|
20
22
|
const expected = normalizeWhitespace(elementText);
|
|
21
23
|
const others = [
|
|
22
|
-
...
|
|
24
|
+
...Arr.tail(leading),
|
|
23
25
|
...restElementType === void 0 ? [] : [restElementType]
|
|
24
26
|
];
|
|
25
27
|
return others.every(
|
|
@@ -32,6 +34,11 @@ const analyzeUniformTuple = (tuple, sourceCode) => {
|
|
|
32
34
|
hasRest
|
|
33
35
|
} : void 0;
|
|
34
36
|
};
|
|
37
|
+
const isInConditionalTypeExtendsClause = (node) => {
|
|
38
|
+
if (node.type === AST_NODE_TYPES.Program) return false;
|
|
39
|
+
const { parent } = node;
|
|
40
|
+
return parent.type === AST_NODE_TYPES.TSConditionalType && parent.extendsType === node || isInConditionalTypeExtendsClause(parent);
|
|
41
|
+
};
|
|
35
42
|
const getArrayElementType = (node) => {
|
|
36
43
|
if (node.type === AST_NODE_TYPES.TSArrayType) return node.elementType;
|
|
37
44
|
return node.type === AST_NODE_TYPES.TSTypeOperator && node.operator === "readonly" && node.typeAnnotation?.type === AST_NODE_TYPES.TSArrayType ? node.typeAnnotation.elementType : void 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-utils.mjs","sources":["../../src/rules/ast-utils.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\nexport type UniformTupleShape = Readonly<{\n /** The node an autofix must replace: the tuple, or the `readonly` operator. */\n node: TSESTree.Node;\n isReadonly: boolean;\n /** Source text of the (single, shared) element type. */\n elementText: string;\n /** Number of leading fixed-position elements. */\n fixedCount: number;\n /** Whether a trailing `...V[]` rest element is present. */\n hasRest: boolean;\n}>;\n\n/**\n * Recognizes a tuple whose every position has the *same* element type — the\n * hand-rolled spelling of ts-type-forge's length-constrained tuple family:\n *\n * | shape | meaning |\n * | :------------------- | :------------------------- |\n * | `[V, ..., V]` | `FixedLengthTuple<N, V>` |\n * | `[V, ..., V, ...V[]]`| `MinLengthTuple<N, V>` |\n *\n * Returns `undefined` for anything else. Deliberately narrow:\n *\n * - Element types are compared by normalized source text, so heterogeneous\n * tuples are never matched.\n * - Optional (`[V?]`) and labelled (`[head: V]`) members are skipped, since\n * rewriting them would drop information.\n * - A tuple that is only a rest element (`[...V[]]`) is not a length\n * constraint at all.\n */\nexport const analyzeUniformTuple = (\n tuple: TSESTree.TSTupleType,\n sourceCode: TSESLint.SourceCode,\n): UniformTupleShape | undefined => {\n const { parent } = tuple;\n\n const isReadonly =\n parent.type === AST_NODE_TYPES.TSTypeOperator &&\n parent.operator === 'readonly';\n\n const elements = tuple.elementTypes;\n\n const last = elements.at(-1);\n\n const hasRest = last?.type === AST_NODE_TYPES.TSRestType;\n\n const leading = hasRest ?
|
|
1
|
+
{"version":3,"file":"ast-utils.mjs","sources":["../../src/rules/ast-utils.mts"],"sourcesContent":["import {\n AST_NODE_TYPES,\n type TSESLint,\n type TSESTree,\n} from '@typescript-eslint/utils';\nimport { Arr } from 'ts-data-forge';\n\n/* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */\n\nexport type UniformTupleShape = Readonly<{\n /** The node an autofix must replace: the tuple, or the `readonly` operator. */\n node: TSESTree.Node;\n isReadonly: boolean;\n /** Source text of the (single, shared) element type. */\n elementText: string;\n /** Number of leading fixed-position elements. */\n fixedCount: number;\n /** Whether a trailing `...V[]` rest element is present. */\n hasRest: boolean;\n}>;\n\n/**\n * Recognizes a tuple whose every position has the *same* element type — the\n * hand-rolled spelling of ts-type-forge's length-constrained tuple family:\n *\n * | shape | meaning |\n * | :------------------- | :------------------------- |\n * | `[V, ..., V]` | `FixedLengthTuple<N, V>` |\n * | `[V, ..., V, ...V[]]`| `MinLengthTuple<N, V>` |\n *\n * Returns `undefined` for anything else. Deliberately narrow:\n *\n * - Element types are compared by normalized source text, so heterogeneous\n * tuples are never matched.\n * - Optional (`[V?]`) and labelled (`[head: V]`) members are skipped, since\n * rewriting them would drop information.\n * - A tuple that is only a rest element (`[...V[]]`) is not a length\n * constraint at all.\n * - A tuple inside the `extends` clause of a conditional type is a match\n * pattern, not a type (see {@link isInConditionalTypeExtendsClause}).\n */\nexport const analyzeUniformTuple = (\n tuple: TSESTree.TSTupleType,\n sourceCode: TSESLint.SourceCode,\n): UniformTupleShape | undefined => {\n if (isInConditionalTypeExtendsClause(tuple)) return undefined;\n\n const { parent } = tuple;\n\n const isReadonly =\n parent.type === AST_NODE_TYPES.TSTypeOperator &&\n parent.operator === 'readonly';\n\n const elements = tuple.elementTypes;\n\n const last = elements.at(-1);\n\n const hasRest = last?.type === AST_NODE_TYPES.TSRestType;\n\n const leading = hasRest ? Arr.butLast(elements) : elements;\n\n if (\n Arr.isEmpty(leading) ||\n leading.some(\n (element) =>\n element.type === AST_NODE_TYPES.TSRestType ||\n element.type === AST_NODE_TYPES.TSOptionalType ||\n element.type === AST_NODE_TYPES.TSNamedTupleMember,\n )\n ) {\n return undefined;\n }\n\n const restElementType =\n last !== undefined && hasRest\n ? getArrayElementType(last.typeAnnotation)\n : undefined;\n\n if (hasRest && restElementType === undefined) return undefined;\n\n const [head] = leading;\n\n if (head === undefined) return undefined;\n\n const elementText = sourceCode.getText(head);\n\n const expected = normalizeWhitespace(elementText);\n\n const others = [\n ...Arr.tail(leading),\n ...(restElementType === undefined ? [] : [restElementType]),\n ];\n\n return others.every(\n (element) => normalizeWhitespace(sourceCode.getText(element)) === expected,\n )\n ? {\n node: isReadonly ? parent : tuple,\n isReadonly,\n elementText,\n fixedCount: leading.length,\n hasRest,\n }\n : undefined;\n};\n\n/**\n * Whether `node` sits inside the `extends` clause of a conditional type, where\n * a tuple is a *match pattern* rather than a type.\n *\n * `[A, B] extends [true, true]` matches element-wise even while `A` and `B` are\n * still generic. The canonical spellings resolve through a mapped type\n * (`MutableFixedLengthTuple<N, V>` is `Mutable<FixedLengthTuple<N, V>>`), and\n * against one of those the checker defers the whole conditional instead —\n * yielding `boolean` where the tuple pattern yielded `true` or `false`, which\n * silently widens everything downstream. So those positions are left alone.\n */\nconst isInConditionalTypeExtendsClause = (node: TSESTree.Node): boolean => {\n // The root has no parent, so it terminates the walk.\n if (node.type === AST_NODE_TYPES.Program) return false;\n\n const { parent } = node;\n\n return (\n (parent.type === AST_NODE_TYPES.TSConditionalType &&\n parent.extendsType === node) ||\n isInConditionalTypeExtendsClause(parent)\n );\n};\n\n/** Unwraps `V[]` and `readonly V[]` to `V`. */\nconst getArrayElementType = (\n node: TSESTree.TypeNode,\n): TSESTree.TypeNode | undefined => {\n if (node.type === AST_NODE_TYPES.TSArrayType) return node.elementType;\n\n return node.type === AST_NODE_TYPES.TSTypeOperator &&\n node.operator === 'readonly' &&\n node.typeAnnotation?.type === AST_NODE_TYPES.TSArrayType\n ? node.typeAnnotation.elementType\n : undefined;\n};\n\nconst normalizeWhitespace = (text: string): string =>\n text.replaceAll(/\\s+/gu, ' ').trim();\n"],"names":[],"mappings":";;;AAyCO,MAAM,mBAAA,GAAsB,CACjC,KAAA,EACA,UAAA,KACkC;AAClC,EAAA,IAAI,gCAAA,CAAiC,KAAK,CAAA,EAAG,OAAO,MAAA;AAEpD,EAAA,MAAM,EAAE,QAAO,GAAI,KAAA;AAEnB,EAAA,MAAM,aACJ,MAAA,CAAO,IAAA,KAAS,cAAA,CAAe,cAAA,IAC/B,OAAO,QAAA,KAAa,UAAA;AAEtB,EAAA,MAAM,WAAW,KAAA,CAAM,YAAA;AAEvB,EAAA,MAAM,IAAA,GAAO,QAAA,CAAS,EAAA,CAAG,EAAE,CAAA;AAE3B,EAAA,MAAM,OAAA,GAAU,IAAA,EAAM,IAAA,KAAS,cAAA,CAAe,UAAA;AAE9C,EAAA,MAAM,OAAA,GAAU,OAAA,GAAU,GAAA,CAAI,OAAA,CAAQ,QAAQ,CAAA,GAAI,QAAA;AAElD,EAAA,IACE,GAAA,CAAI,OAAA,CAAQ,OAAO,CAAA,IACnB,OAAA,CAAQ,IAAA;AAAA,IACN,CAAC,OAAA,KACC,OAAA,CAAQ,IAAA,KAAS,cAAA,CAAe,UAAA,IAChC,OAAA,CAAQ,IAAA,KAAS,cAAA,CAAe,cAAA,IAChC,OAAA,CAAQ,IAAA,KAAS,cAAA,CAAe;AAAA,GACpC,EACA;AACA,IAAA,OAAO,MAAA;AAAA,EACT;AAEA,EAAA,MAAM,kBACJ,IAAA,KAAS,MAAA,IAAa,UAClB,mBAAA,CAAoB,IAAA,CAAK,cAAc,CAAA,GACvC,MAAA;AAEN,EAAA,IAAI,OAAA,IAAW,eAAA,KAAoB,MAAA,EAAW,OAAO,MAAA;AAErD,EAAA,MAAM,CAAC,IAAI,CAAA,GAAI,OAAA;AAEf,EAAA,IAAI,IAAA,KAAS,QAAW,OAAO,MAAA;AAE/B,EAAA,MAAM,WAAA,GAAc,UAAA,CAAW,OAAA,CAAQ,IAAI,CAAA;AAE3C,EAAA,MAAM,QAAA,GAAW,oBAAoB,WAAW,CAAA;AAEhD,EAAA,MAAM,MAAA,GAAS;AAAA,IACb,GAAG,GAAA,CAAI,IAAA,CAAK,OAAO,CAAA;AAAA,IACnB,GAAI,eAAA,KAAoB,MAAA,GAAY,EAAC,GAAI,CAAC,eAAe;AAAA,GAC3D;AAEA,EAAA,OAAO,MAAA,CAAO,KAAA;AAAA,IACZ,CAAC,OAAA,KAAY,mBAAA,CAAoB,WAAW,OAAA,CAAQ,OAAO,CAAC,CAAA,KAAM;AAAA,GACpE,GACI;AAAA,IACE,IAAA,EAAM,aAAa,MAAA,GAAS,KAAA;AAAA,IAC5B,UAAA;AAAA,IACA,WAAA;AAAA,IACA,YAAY,OAAA,CAAQ,MAAA;AAAA,IACpB;AAAA,GACF,GACA,MAAA;AACN;AAaA,MAAM,gCAAA,GAAmC,CAAC,IAAA,KAAiC;AAEzE,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,OAAA,EAAS,OAAO,KAAA;AAEjD,EAAA,MAAM,EAAE,QAAO,GAAI,IAAA;AAEnB,EAAA,OACG,MAAA,CAAO,SAAS,cAAA,CAAe,iBAAA,IAC9B,OAAO,WAAA,KAAgB,IAAA,IACzB,iCAAiC,MAAM,CAAA;AAE3C,CAAA;AAGA,MAAM,mBAAA,GAAsB,CAC1B,IAAA,KACkC;AAClC,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,WAAA,SAAoB,IAAA,CAAK,WAAA;AAE1D,EAAA,OAAO,IAAA,CAAK,IAAA,KAAS,cAAA,CAAe,cAAA,IAClC,KAAK,QAAA,KAAa,UAAA,IAClB,IAAA,CAAK,cAAA,EAAgB,IAAA,KAAS,cAAA,CAAe,WAAA,GAC3C,IAAA,CAAK,eAAe,WAAA,GACpB,MAAA;AACN,CAAA;AAEA,MAAM,mBAAA,GAAsB,CAAC,IAAA,KAC3B,IAAA,CAAK,WAAW,OAAA,EAAS,GAAG,EAAE,IAAA,EAAK;;;;"}
|
|
@@ -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;
|
|
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
|
|
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
|
|
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"}
|