eslint-plugin-ts-type-forge 0.5.0 → 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.
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
@@ -0,0 +1,80 @@
1
+ import { type TSESLint } from '@typescript-eslint/utils';
2
+ import {
3
+ DEFAULT_IMPORT_STYLE,
4
+ IMPORT_STYLE_SCHEMA_PROPERTY,
5
+ } from './import-style.mjs';
6
+ import {
7
+ reportStdTypeReference,
8
+ type StdTypeAlternatives,
9
+ } from './std-type-rule-utils.mjs';
10
+
11
+ type Options = readonly [
12
+ Readonly<{
13
+ importStyle?: 'global' | 'named';
14
+ }>?,
15
+ ];
16
+
17
+ type MessageIds = 'useStrictOrRelaxed' | 'replaceWith';
18
+
19
+ /**
20
+ * The standard-library types whose second argument is unchecked, and the
21
+ * ts-type-forge pair that makes the choice explicit.
22
+ *
23
+ * `Strict*` constrains the second argument (`U extends T` / `K extends keyof T`),
24
+ * so a key that does not exist — typically a leftover after a rename — is a
25
+ * compile error instead of a silently empty result. `Relaxed*` keeps the
26
+ * standard-library behavior for the cases that genuinely need it (a subtrahend
27
+ * that is not provably part of the union, a key set computed from a still
28
+ * deferred type parameter).
29
+ */
30
+ const ALTERNATIVES: StdTypeAlternatives = new Map([
31
+ ['Exclude', ['StrictExclude', 'RelaxedExclude']],
32
+ ['Extract', ['StrictExtract', 'RelaxedExtract']],
33
+ ['Omit', ['StrictOmit', 'RelaxedOmit']],
34
+ ['Pick', ['StrictPick', 'RelaxedPick']],
35
+ ]);
36
+
37
+ export const preferStrictOrRelaxedUtilityType: TSESLint.RuleModule<
38
+ MessageIds,
39
+ Options
40
+ > = {
41
+ meta: {
42
+ type: 'suggestion',
43
+ docs: {
44
+ description:
45
+ '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.',
46
+ },
47
+ hasSuggestions: true,
48
+ schema: [
49
+ {
50
+ type: 'object',
51
+ properties: {
52
+ importStyle: IMPORT_STYLE_SCHEMA_PROPERTY,
53
+ },
54
+ additionalProperties: false,
55
+ },
56
+ ],
57
+ messages: {
58
+ useStrictOrRelaxed:
59
+ 'Use {{alternatives}} from ts-type-forge instead of the built-in `{{name}}`.',
60
+ replaceWith: 'Replace `{{name}}` with `{{replacement}}`.',
61
+ },
62
+ },
63
+
64
+ create: (context) => {
65
+ const importStyle = context.options[0]?.importStyle ?? DEFAULT_IMPORT_STYLE;
66
+
67
+ return {
68
+ TSTypeReference: (node) => {
69
+ reportStdTypeReference(
70
+ context,
71
+ node,
72
+ ALTERNATIVES,
73
+ { report: 'useStrictOrRelaxed', suggestion: 'replaceWith' },
74
+ importStyle,
75
+ );
76
+ },
77
+ };
78
+ },
79
+ defaultOptions: [{ importStyle: DEFAULT_IMPORT_STYLE }],
80
+ } as const;
@@ -0,0 +1,279 @@
1
+ import parser from '@typescript-eslint/parser';
2
+ import { RuleTester } from '@typescript-eslint/rule-tester';
3
+ import dedent from 'dedent';
4
+ import { preferStrictOrRelaxedUtilityType } from './prefer-strict-or-relaxed-utility-type.mjs';
5
+
6
+ const tester = new RuleTester({
7
+ languageOptions: {
8
+ parser,
9
+ parserOptions: { ecmaVersion: 2020, sourceType: 'module' },
10
+ },
11
+ });
12
+
13
+ describe('prefer-strict-or-relaxed-utility-type', () => {
14
+ tester.run(
15
+ 'prefer-strict-or-relaxed-utility-type',
16
+ preferStrictOrRelaxedUtilityType,
17
+ {
18
+ valid: [
19
+ {
20
+ name: 'accepts the ts-type-forge counterparts',
21
+ code: dedent`
22
+ type A = StrictExclude<'a' | 'b', 'a'>;
23
+ type B = RelaxedExtract<string | number, number>;
24
+ type C = StrictOmit<{ a: 1; b: 2 }, 'a'>;
25
+ type D = RelaxedPick<{ a: 1; b: 2 }, 'a' | 'z'>;
26
+ `,
27
+ },
28
+ {
29
+ name: 'ignores unrelated standard-library types',
30
+ code: 'type A = Partial<Required<{ a: 1 }>>;',
31
+ },
32
+ {
33
+ name: 'ignores a qualified name that merely ends in a banned name',
34
+ code: "type A = Utils.Pick<{ a: 1 }, 'a'>;",
35
+ },
36
+ {
37
+ name: 'ignores a locally declared type of the same name',
38
+ code: dedent`
39
+ type Pick<T, K extends keyof T> = { readonly [P in K]: T[P] };
40
+ type A = Pick<{ a: 1 }, 'a'>;
41
+ `,
42
+ },
43
+ {
44
+ name: 'ignores a name imported from another module',
45
+ code: dedent`
46
+ import { type Omit } from './my-types.mjs';
47
+ type A = Omit<{ a: 1; b: 2 }, 'a'>;
48
+ `,
49
+ },
50
+ ],
51
+ invalid: [
52
+ {
53
+ name: 'reports `Exclude` and suggests both counterparts',
54
+ code: "type A = Exclude<'a' | 'b', 'a'>;",
55
+ errors: [
56
+ {
57
+ messageId: 'useStrictOrRelaxed',
58
+ data: {
59
+ name: 'Exclude',
60
+ alternatives: '`StrictExclude` or `RelaxedExclude`',
61
+ },
62
+ suggestions: [
63
+ {
64
+ messageId: 'replaceWith',
65
+ data: { name: 'Exclude', replacement: 'StrictExclude' },
66
+ output: dedent`
67
+ import { type StrictExclude } from 'ts-type-forge';
68
+ type A = StrictExclude<'a' | 'b', 'a'>;
69
+ `,
70
+ },
71
+ {
72
+ messageId: 'replaceWith',
73
+ data: { name: 'Exclude', replacement: 'RelaxedExclude' },
74
+ output: dedent`
75
+ import { type RelaxedExclude } from 'ts-type-forge';
76
+ type A = RelaxedExclude<'a' | 'b', 'a'>;
77
+ `,
78
+ },
79
+ ],
80
+ },
81
+ ],
82
+ },
83
+ {
84
+ name: 'reports `Extract`',
85
+ code: 'type A = Extract<string | number, number>;',
86
+ errors: [
87
+ {
88
+ messageId: 'useStrictOrRelaxed',
89
+ suggestions: [
90
+ {
91
+ messageId: 'replaceWith',
92
+ output: dedent`
93
+ import { type StrictExtract } from 'ts-type-forge';
94
+ type A = StrictExtract<string | number, number>;
95
+ `,
96
+ },
97
+ {
98
+ messageId: 'replaceWith',
99
+ output: dedent`
100
+ import { type RelaxedExtract } from 'ts-type-forge';
101
+ type A = RelaxedExtract<string | number, number>;
102
+ `,
103
+ },
104
+ ],
105
+ },
106
+ ],
107
+ },
108
+ {
109
+ name: 'reports `Omit`',
110
+ code: "type A = Omit<{ a: 1; b: 2 }, 'a'>;",
111
+ errors: [
112
+ {
113
+ messageId: 'useStrictOrRelaxed',
114
+ suggestions: [
115
+ {
116
+ messageId: 'replaceWith',
117
+ output: dedent`
118
+ import { type StrictOmit } from 'ts-type-forge';
119
+ type A = StrictOmit<{ a: 1; b: 2 }, 'a'>;
120
+ `,
121
+ },
122
+ {
123
+ messageId: 'replaceWith',
124
+ output: dedent`
125
+ import { type RelaxedOmit } from 'ts-type-forge';
126
+ type A = RelaxedOmit<{ a: 1; b: 2 }, 'a'>;
127
+ `,
128
+ },
129
+ ],
130
+ },
131
+ ],
132
+ },
133
+ {
134
+ name: 'reports `Pick`',
135
+ code: "type A = Pick<{ a: 1; b: 2 }, 'a'>;",
136
+ errors: [
137
+ {
138
+ messageId: 'useStrictOrRelaxed',
139
+ suggestions: [
140
+ {
141
+ messageId: 'replaceWith',
142
+ output: dedent`
143
+ import { type StrictPick } from 'ts-type-forge';
144
+ type A = StrictPick<{ a: 1; b: 2 }, 'a'>;
145
+ `,
146
+ },
147
+ {
148
+ messageId: 'replaceWith',
149
+ output: dedent`
150
+ import { type RelaxedPick } from 'ts-type-forge';
151
+ type A = RelaxedPick<{ a: 1; b: 2 }, 'a'>;
152
+ `,
153
+ },
154
+ ],
155
+ },
156
+ ],
157
+ },
158
+ {
159
+ name: 'touches no import under the `global` import style',
160
+ options: [{ importStyle: 'global' }],
161
+ code: "type A = Pick<{ a: 1 }, 'a'>;",
162
+ errors: [
163
+ {
164
+ messageId: 'useStrictOrRelaxed',
165
+ suggestions: [
166
+ {
167
+ messageId: 'replaceWith',
168
+ output: "type A = StrictPick<{ a: 1 }, 'a'>;",
169
+ },
170
+ {
171
+ messageId: 'replaceWith',
172
+ output: "type A = RelaxedPick<{ a: 1 }, 'a'>;",
173
+ },
174
+ ],
175
+ },
176
+ ],
177
+ },
178
+ {
179
+ name: 'reuses an existing ts-type-forge import, alias included',
180
+ code: dedent`
181
+ import { type StrictPick as SPick } from 'ts-type-forge';
182
+ type A = Pick<{ a: 1 }, 'a'>;
183
+ `,
184
+ errors: [
185
+ {
186
+ messageId: 'useStrictOrRelaxed',
187
+ suggestions: [
188
+ {
189
+ messageId: 'replaceWith',
190
+ data: { name: 'Pick', replacement: 'SPick' },
191
+ output: dedent`
192
+ import { type StrictPick as SPick } from 'ts-type-forge';
193
+ type A = SPick<{ a: 1 }, 'a'>;
194
+ `,
195
+ },
196
+ {
197
+ messageId: 'replaceWith',
198
+ data: { name: 'Pick', replacement: 'RelaxedPick' },
199
+ output: dedent`
200
+ import { type RelaxedPick } from 'ts-type-forge';
201
+ import { type StrictPick as SPick } from 'ts-type-forge';
202
+ type A = RelaxedPick<{ a: 1 }, 'a'>;
203
+ `,
204
+ },
205
+ ],
206
+ },
207
+ ],
208
+ },
209
+ {
210
+ name: 'drops the suggestion whose target name is bound locally',
211
+ code: dedent`
212
+ type StrictOmit<T, K extends keyof T> = { readonly [P in K]: T[P] };
213
+ type A = Omit<{ a: 1; b: 2 }, 'a'>;
214
+ `,
215
+ errors: [
216
+ {
217
+ messageId: 'useStrictOrRelaxed',
218
+ suggestions: [
219
+ {
220
+ messageId: 'replaceWith',
221
+ data: { name: 'Omit', replacement: 'RelaxedOmit' },
222
+ output: dedent`
223
+ import { type RelaxedOmit } from 'ts-type-forge';
224
+ type StrictOmit<T, K extends keyof T> = { readonly [P in K]: T[P] };
225
+ type A = RelaxedOmit<{ a: 1; b: 2 }, 'a'>;
226
+ `,
227
+ },
228
+ ],
229
+ },
230
+ ],
231
+ },
232
+ {
233
+ name: 'reports every occurrence, nesting included',
234
+ code: "type A = Pick<{ a: 1; b: 2 }, Exclude<'a' | 'b', 'b'>>;",
235
+ errors: [
236
+ {
237
+ messageId: 'useStrictOrRelaxed',
238
+ suggestions: [
239
+ {
240
+ messageId: 'replaceWith',
241
+ output: dedent`
242
+ import { type StrictPick } from 'ts-type-forge';
243
+ type A = StrictPick<{ a: 1; b: 2 }, Exclude<'a' | 'b', 'b'>>;
244
+ `,
245
+ },
246
+ {
247
+ messageId: 'replaceWith',
248
+ output: dedent`
249
+ import { type RelaxedPick } from 'ts-type-forge';
250
+ type A = RelaxedPick<{ a: 1; b: 2 }, Exclude<'a' | 'b', 'b'>>;
251
+ `,
252
+ },
253
+ ],
254
+ },
255
+ {
256
+ messageId: 'useStrictOrRelaxed',
257
+ suggestions: [
258
+ {
259
+ messageId: 'replaceWith',
260
+ output: dedent`
261
+ import { type StrictExclude } from 'ts-type-forge';
262
+ type A = Pick<{ a: 1; b: 2 }, StrictExclude<'a' | 'b', 'b'>>;
263
+ `,
264
+ },
265
+ {
266
+ messageId: 'replaceWith',
267
+ output: dedent`
268
+ import { type RelaxedExclude } from 'ts-type-forge';
269
+ type A = Pick<{ a: 1; b: 2 }, RelaxedExclude<'a' | 'b', 'b'>>;
270
+ `,
271
+ },
272
+ ],
273
+ },
274
+ ],
275
+ },
276
+ ],
277
+ },
278
+ );
279
+ });
@@ -1,7 +1,11 @@
1
1
  import { type ESLintPlugin } from '../types.mjs';
2
2
  import { preferCanonicalLengthConstrainedTuple } from './prefer-canonical-length-constrained-tuple.mjs';
3
+ import { preferReadonlyOrMutableRecord } from './prefer-readonly-or-mutable-record.mjs';
4
+ import { preferStrictOrRelaxedUtilityType } from './prefer-strict-or-relaxed-utility-type.mjs';
3
5
 
4
6
  export const tsTypeForgeRules = {
5
7
  'prefer-canonical-length-constrained-tuple':
6
8
  preferCanonicalLengthConstrainedTuple,
9
+ 'prefer-readonly-or-mutable-record': preferReadonlyOrMutableRecord,
10
+ 'prefer-strict-or-relaxed-utility-type': preferStrictOrRelaxedUtilityType,
7
11
  } as const satisfies ESLintPlugin['rules'];
@@ -0,0 +1,103 @@
1
+ import {
2
+ AST_NODE_TYPES,
3
+ ASTUtils,
4
+ type TSESLint,
5
+ type TSESTree,
6
+ } from '@typescript-eslint/utils';
7
+ import { Arr } from 'ts-data-forge';
8
+ import { resolveTypeName, type ImportStyle } from './import-style.mjs';
9
+ import { buildTypeImportFix } from './import-utils.mjs';
10
+
11
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
12
+
13
+ /**
14
+ * The ts-type-forge replacements offered for one standard-library type, in the
15
+ * order they are suggested.
16
+ */
17
+ export type StdTypeAlternatives = ReadonlyMap<string, readonly string[]>;
18
+
19
+ /**
20
+ * Reports a reference to a banned standard-library type and offers one
21
+ * suggestion per ts-type-forge alternative.
22
+ *
23
+ * Choosing between the alternatives is a semantic decision — `StrictExclude`
24
+ * additionally checks that the subtrahend is part of the union, `ReadonlyRecord`
25
+ * and `MutableRecord` describe different types — so this is deliberately *not*
26
+ * an autofix. Each suggestion carries its own import fix, which is safe because
27
+ * ESLint applies suggestions one at a time.
28
+ */
29
+ export const reportStdTypeReference = <MessageIds extends string>(
30
+ context: TSESLint.RuleContext<MessageIds, readonly unknown[]>,
31
+ node: TSESTree.TSTypeReference,
32
+ alternatives: StdTypeAlternatives,
33
+ messageIds: Readonly<{ report: MessageIds; suggestion: MessageIds }>,
34
+ importStyle: ImportStyle,
35
+ ): void => {
36
+ const typeName = node.typeName;
37
+
38
+ // `ns.Pick` / `import('…').Pick` never denote the standard-library type.
39
+ if (typeName.type !== AST_NODE_TYPES.Identifier) return;
40
+
41
+ const stdName = typeName.name;
42
+
43
+ const candidates = alternatives.get(stdName);
44
+
45
+ if (candidates === undefined) return;
46
+
47
+ // A file that declares — or imports — its own `Pick` does not mean the
48
+ // standard-library one, so there is nothing to steer.
49
+ if (isLocallyBound(context.sourceCode, node, stdName)) return;
50
+
51
+ const program = context.sourceCode.ast;
52
+
53
+ context.report({
54
+ node: typeName,
55
+ messageId: messageIds.report,
56
+ data: { name: stdName, alternatives: formatAlternatives(candidates) },
57
+ suggest: candidates.flatMap((candidate) => {
58
+ const target = resolveTypeName(program, candidate, importStyle);
59
+
60
+ // The replacement name is already bound to something else in this file,
61
+ // so rewriting to it would resolve to the wrong declaration.
62
+ return target === undefined
63
+ ? []
64
+ : [
65
+ {
66
+ messageId: messageIds.suggestion,
67
+ data: { name: stdName, replacement: target.localName },
68
+ fix: (fixer: TSESLint.RuleFixer): readonly TSESLint.RuleFix[] =>
69
+ Arr.toPushed(
70
+ target.needsImport
71
+ ? buildTypeImportFix(fixer, program, candidate)
72
+ : [],
73
+ fixer.replaceText(typeName, target.localName),
74
+ ),
75
+ },
76
+ ];
77
+ }),
78
+ });
79
+ };
80
+
81
+ /** `['StrictPick', 'RelaxedPick']` → `` '`StrictPick` or `RelaxedPick`' ``. */
82
+ const formatAlternatives = (candidates: readonly string[]): string =>
83
+ candidates.map((candidate) => `\`${candidate}\``).join(' or ');
84
+
85
+ /**
86
+ * Whether `name` resolves to a declaration in this file (a type alias, an
87
+ * interface, a class, an import, …) rather than to the ambient standard-library
88
+ * type of the same name.
89
+ *
90
+ * The `defs` check is what separates the two: ESLint materializes an *implicit*
91
+ * global variable for every unresolved reference, so `findVariable` also
92
+ * succeeds for `Exclude` in a file that never declares it. Only a variable with
93
+ * at least one definition was actually written down somewhere in this file.
94
+ */
95
+ const isLocallyBound = (
96
+ sourceCode: TSESLint.SourceCode,
97
+ node: TSESTree.Node,
98
+ name: string,
99
+ ): boolean => {
100
+ const variable = ASTUtils.findVariable(sourceCode.getScope(node), name);
101
+
102
+ return variable !== null && Arr.isNonEmpty(variable.defs);
103
+ };
@@ -1,66 +1,10 @@
1
1
  import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
2
  import { Arr } from 'ts-data-forge';
3
- import { TS_TYPE_FORGE_MODULE } from './constants.mjs';
4
- import {
5
- buildTypeImportFix,
6
- getImportedLocalName,
7
- getTsTypeForgeImports,
8
- hasConflictingDeclaration,
9
- } from './import-utils.mjs';
3
+ import { resolveTypeName, type ImportStyle } from './import-style.mjs';
4
+ import { buildTypeImportFix } from './import-utils.mjs';
10
5
 
11
6
  /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
12
7
 
13
- /** `JSONSchema4`, reached through the public rule-module types. */
14
- type JsonSchema = Exclude<
15
- TSESLint.RuleModule<string, readonly unknown[]>['meta']['schema'],
16
- readonly unknown[]
17
- >;
18
-
19
- export type ImportStyle = 'global' | 'named';
20
-
21
- export const DEFAULT_IMPORT_STYLE: ImportStyle = 'named';
22
-
23
- export const IMPORT_STYLE_SCHEMA_PROPERTY: JsonSchema = {
24
- type: 'string',
25
- enum: ['global', 'named'],
26
- description: [
27
- 'How the ts-type-forge type is brought into scope.',
28
- "'named' (default) makes the autofix add the corresponding",
29
- `\`import { type … } from '${TS_TYPE_FORGE_MODULE}';\``,
30
- "when the name is not imported yet; 'global' assumes the",
31
- 'ambient globals of `ts-type-forge/global` and never touches',
32
- 'imports.',
33
- ].join(' '),
34
- };
35
-
36
- /**
37
- * Resolves how a ts-type-forge type name should be written in this file, and
38
- * whether the autofix has to add an import for it.
39
- *
40
- * Returns `undefined` when the name is already bound to something else (a local
41
- * alias, or an import from another module) — rewriting to it would then silently
42
- * resolve to the wrong declaration, which is exactly what happens inside
43
- * ts-type-forge's own sources.
44
- */
45
- export const resolveTypeName = (
46
- program: TSESTree.Program,
47
- canonicalName: string,
48
- importStyle: ImportStyle,
49
- ): Readonly<{ localName: string; needsImport: boolean }> | undefined => {
50
- const importedLocalName = getImportedLocalName(
51
- getTsTypeForgeImports(program),
52
- canonicalName,
53
- );
54
-
55
- if (importedLocalName !== undefined) {
56
- return { localName: importedLocalName, needsImport: false };
57
- }
58
-
59
- return hasConflictingDeclaration(program, canonicalName)
60
- ? undefined
61
- : { localName: canonicalName, needsImport: importStyle === 'named' };
62
- };
63
-
64
8
  export type TupleRewrite = Readonly<{
65
9
  node: TSESTree.Node;
66
10
  canonicalName: string;