eslint-plugin-ts-type-forge 0.8.16 → 0.8.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -251,8 +251,9 @@ options.
251
251
  the type and never touches imports.
252
252
  - `'named'` — the fix additionally imports the name when the file does not
253
253
  import it yet: added to an existing `import type { … }` from `ts-type-forge`,
254
- else to an `import { … }` from it (as `type X`), else after a lone default
255
- import, and otherwise as a new `import { type … } from 'ts-type-forge';`.
254
+ else to an `import { … }` from it (as `type X`, empty braces included), else
255
+ after a lone default import, and otherwise as a new
256
+ `import { type … } from 'ts-type-forge';`.
256
257
 
257
258
  Either way, if the file already imports the target type from `ts-type-forge`
258
259
  (possibly under an alias), the fix reuses that binding.
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.mts","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAmClE,eAAO,MAAM,uBAAuB;;iBAEhC,IAAI,EAAE,6BAA6B;;;;;;;;;;;;;;;;;;;QAInC,oDAAoD;iBACpD,WAAW;2BAtBP,2BAA2B;;yBAQ3B,eAAe,EAAI,YAAY;;;gEAlBE,OAAO;oFACa,OAAO;0EACjB,OAAO;4EACL,OAAO;gFACH,OAAO;;;;CA8B/B,CAAC"}
1
+ {"version":3,"file":"plugin.d.mts","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,aAAa,CAAC;AAoClE,eAAO,MAAM,uBAAuB;;iBAEhC,IAAI,EAAE,6BAA6B;;;;;;;;;;;;;;;;;;;QAInC,oDAAoD;iBACpD,WAAW;2BAtBP,2BAA2B;;yBAQ3B,eAAe,EAAI,YAAY;;;gEAlBE,OAAO;oFACa,OAAO;0EACjB,OAAO;4EACL,OAAO;gFACH,OAAO;;;;CA8B/B,CAAC"}
package/dist/plugin.mjs CHANGED
@@ -6,6 +6,7 @@ import { tsTypeForgeRules } from './rules/index.mjs';
6
6
 
7
7
 
8
8
 
9
+
9
10
  const recommendedRules = {
10
11
  'ts-type-forge/no-side-effect-import': 'error',
11
12
  'ts-type-forge/prefer-canonical-length-constrained-tuple': 'error',
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD;;;;;;GAMG;AACH,MAAM,gBAAgB,GAAG;IACvB,qCAAqC,EAAE,OAAO;IAC9C,yDAAyD,EAAE,OAAO;IAClE,+CAA+C,EAAE,OAAO;IACxD,iDAAiD,EAAE,OAAO;IAC1D,qDAAqD,EAAE,OAAO;CAG/D,CAAC;AAEF,MAAM,iBAAiB,GAAG;IACxB,IAAI,EAAE,2BAA2B;IACjC,OAAO,EAAE;QACP,uEAAuE;QACvE,yEAAyE;QACzE,wEAAwE;QACxE,mDAAmD;QACnD,0EAA0E;QAC1E,wDAAwD;QACxD,IAAI,eAAe;YACjB,OAAO,uBAAuB,CAAC;QACjC,CAAC;KACF;IACD,KAAK,EAAE,gBAAgB;CACY,CAAC;AAEtC,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,IAAI,EAAE;QACJ,IAAI,EAAE,6BAA6B;KACpC;IACD,KAAK,EAAE,gBAAgB;IACvB,OAAO,EAAE;QACP,oDAAoD;QACpD,WAAW,EAAE,iBAAiB;KAC/B;CAC8B,CAAC"}
1
+ {"version":3,"file":"plugin.mjs","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD;;;;;;;GAOG;AACH,MAAM,gBAAgB,GAAG;IACvB,qCAAqC,EAAE,OAAO;IAC9C,yDAAyD,EAAE,OAAO;IAClE,+CAA+C,EAAE,OAAO;IACxD,iDAAiD,EAAE,OAAO;IAC1D,qDAAqD,EAAE,OAAO;CAG/D,CAAC;AAEF,MAAM,iBAAiB,GAAG;IACxB,IAAI,EAAE,2BAA2B;IACjC,OAAO,EAAE;QACP,uEAAuE;QACvE,yEAAyE;QACzE,wEAAwE;QACxE,mDAAmD;QACnD,0EAA0E;QAC1E,wDAAwD;QACxD,IAAI,eAAe;YACjB,OAAO,uBAAuB,CAAC;QACjC,CAAC;KACF;IACD,KAAK,EAAE,gBAAgB;CACY,CAAC;AAEtC,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,IAAI,EAAE;QACJ,IAAI,EAAE,6BAA6B;KACpC;IACD,KAAK,EAAE,gBAAgB;IACvB,OAAO,EAAE;QACP,oDAAoD;QACpD,WAAW,EAAE,iBAAiB;KAC/B;CAC8B,CAAC"}
@@ -3,9 +3,9 @@ import type { Linter } from 'eslint';
3
3
  import type { tsTypeForgeRules } from './rules/index.mjs';
4
4
  type SpreadOptionsIfIsArray<T extends readonly [Linter.StringSeverity, unknown]> = T[1] extends readonly unknown[] ? readonly [Linter.StringSeverity, ...T[1]] : T;
5
5
  /**
6
- * Extracts the options tuple type of a rule from its TypeScript
7
- * implementation, so that option types stay in sync with the rule source
8
- * without converting `meta.schema` (JSON Schema) back into TypeScript types.
6
+ * Extracts the options tuple type of a rule from its TypeScript implementation,
7
+ * so that option types stay in sync with the rule source without converting
8
+ * `meta.schema` (JSON Schema) back into TypeScript types.
9
9
  */
10
10
  type RuleOptionsOf<Name extends keyof typeof tsTypeForgeRules> = (typeof tsTypeForgeRules)[Name] extends TSESLint.RuleModule<infer _MessageIds extends string, infer Options extends readonly unknown[]> ? Options : never;
11
11
  /**
@@ -10,8 +10,8 @@ export declare const IMPORT_STYLE_SCHEMA_PROPERTY: JsonSchema;
10
10
  *
11
11
  * Returns `undefined` when the name is already bound to something else (a local
12
12
  * alias, an import from another module, or another ts-type-forge export
13
- * imported under the name) — rewriting to it would then silently
14
- * resolve to the wrong declaration, which is exactly what happens inside
13
+ * imported under the name) — rewriting to it would then silently resolve to the
14
+ * wrong declaration, which is exactly what happens inside
15
15
  * ts-type-forge's own sources.
16
16
  */
17
17
  export declare const resolveTypeName: (program: TSESTree.Program, canonicalName: string, importStyle: ImportStyle) => Readonly<{
@@ -1,12 +1,7 @@
1
1
  import { type TSESLint, type TSESTree } from '@typescript-eslint/utils';
2
2
  /**
3
3
  * The edits that import the ts-type-forge types `names` the file does not
4
- * import yet, as types: added to an existing `import type { … }` from the
5
- * module (`, X`), else to a named import from it (`, type X`), else after a
6
- * lone default import (`, { type X }`), and only failing all of those as a
7
- * new `import { type X } from 'ts-type-forge'` after the directive prologue.
8
- * Empty braces count as a host. Adding to an existing declaration keeps
9
- * `import-x/no-duplicates` satisfied.
4
+ * import yet, as `addTypeImports` places them.
10
5
  *
11
6
  * Fixes that add an import overlap, so `--fix` applies one per pass, and the
12
7
  * rest in later passes, by when the file imports their names (see
@@ -1 +1 @@
1
- {"version":3,"file":"import-utils.d.mts","sourceRoot":"","sources":["../../src/rules/import-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAOlC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,kBAAkB,UACtB,QAAQ,CAAC,SAAS,cACb,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,SAClC,SAAS,MAAM,EAAE,KACvB,SAAS,QAAQ,CAAC,OAAO,EAqD3B,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,YAC3B,QAAQ,CAAC,OAAO,QACnB,MAAM,KACX,OAuBC,CAAC"}
1
+ {"version":3,"file":"import-utils.d.mts","sourceRoot":"","sources":["../../src/rules/import-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAMlC;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,UACtB,QAAQ,CAAC,SAAS,cACb,QAAQ,CAAC,QAAQ,CAAC,UAAU,CAAC,SAClC,SAAS,MAAM,EAAE,KACvB,SAAS,QAAQ,CAAC,OAAO,EACoC,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,yBAAyB,YAC3B,QAAQ,CAAC,OAAO,QACnB,MAAM,KACX,OAqBC,CAAC"}
@@ -1,6 +1,5 @@
1
1
  import { AST_NODE_TYPES, } from '@typescript-eslint/utils';
2
- import { buildNamedImportFix, getImportDeclarations } from 'eslint-rule-forge';
3
- import { Arr } from 'ts-data-forge';
2
+ import { addTypeImports, importedNameOf } from 'eslint-rule-forge';
4
3
  import { TS_TYPE_FORGE_MODULE } from './constants.mjs';
5
4
 
6
5
 
@@ -11,40 +10,7 @@ import { TS_TYPE_FORGE_MODULE } from './constants.mjs';
11
10
 
12
11
 
13
12
 
14
-
15
-
16
-
17
-
18
-
19
- export const buildTypeImportFix = (fixer, sourceCode, names) => {
20
- if (!Arr.isNonEmpty(names)) {
21
- return [];
22
- }
23
- const declarations = getImportDeclarations(sourceCode.ast, TS_TYPE_FORGE_MODULE);
24
- const typeOnlyFix = declarations
25
- .filter((declaration) => declaration.importKind === 'type')
26
- .map((declaration) => addToBraces(fixer, sourceCode, declaration, names))
27
- .find((fix) => fix !== undefined);
28
- if (typeOnlyFix !== undefined) {
29
- return [typeOnlyFix];
30
- }
31
- const typeSpecifiers = names.map((name) => `type ${name}`);
32
- const valueDeclarations = declarations.filter((declaration) => declaration.importKind !== 'type');
33
- const valueFix = valueDeclarations
34
- .map((declaration) => addToBraces(fixer, sourceCode, declaration, typeSpecifiers))
35
- .find((fix) => fix !== undefined);
36
- if (valueFix !== undefined) {
37
- return [valueFix];
38
- }
39
- const defaultHost = valueDeclarations.find((declaration) => Arr.isFixedLengthArray(1, declaration.specifiers) &&
40
- declaration.specifiers[0].type === AST_NODE_TYPES.ImportDefaultSpecifier)?.specifiers[0];
41
- if (defaultHost !== undefined) {
42
- return [
43
- fixer.insertTextAfter(defaultHost, `, { ${typeSpecifiers.join(', ')} }`),
44
- ];
45
- }
46
- return buildNamedImportFix(fixer, sourceCode.ast, TS_TYPE_FORGE_MODULE, typeSpecifiers);
47
- };
13
+ export const buildTypeImportFix = (fixer, sourceCode, names) => addTypeImports(fixer, sourceCode, TS_TYPE_FORGE_MODULE, names);
48
14
 
49
15
 
50
16
 
@@ -62,35 +28,13 @@ export const hasConflictingDeclaration = (program, name) => program.body.some((n
62
28
  return node.specifiers.some((specifier) => specifier.local.name === name &&
63
29
  !(node.source.value === TS_TYPE_FORGE_MODULE &&
64
30
  specifier.type === AST_NODE_TYPES.ImportSpecifier &&
65
- (specifier.imported.type === AST_NODE_TYPES.Identifier
66
- ? specifier.imported.name
67
- : specifier.imported.value) === name));
31
+ importedNameOf(specifier) === name));
68
32
  }
69
33
  return declaresName(node.type === AST_NODE_TYPES.ExportNamedDeclaration ||
70
34
  node.type === AST_NODE_TYPES.ExportDefaultDeclaration
71
35
  ? node.declaration
72
36
  : node, name);
73
37
  });
74
-
75
-
76
-
77
-
78
-
79
- const addToBraces = (fixer, sourceCode, declaration, specifiers) => {
80
- const lastNamed = declaration.specifiers
81
- .filter((specifier) => specifier.type === AST_NODE_TYPES.ImportSpecifier)
82
- .at(-1);
83
- if (lastNamed !== undefined) {
84
- return fixer.insertTextAfter(lastNamed, `, ${specifiers.join(', ')}`);
85
- }
86
-
87
- const openingBrace = sourceCode.getFirstToken(declaration, {
88
- filter: (token) => token.value === '{' && token.range[1] <= declaration.source.range[0],
89
- });
90
- return openingBrace === null
91
- ? undefined
92
- : fixer.insertTextAfter(openingBrace, ` ${specifiers.join(', ')} `);
93
- };
94
38
  const declaresName = (node, name) => {
95
39
  if (node === null || node === undefined) {
96
40
  return false;
@@ -1 +1 @@
1
- {"version":3,"file":"import-utils.mjs","sourceRoot":"","sources":["../../src/rules/import-utils.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"import-utils.mjs","sourceRoot":"","sources":["../../src/rules/import-utils.mts"],"names":[],"mappings":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD,uEAAuE;AAEvE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,KAAyB,EACzB,UAAyC,EACzC,KAAwB,EACK,EAAE,CAC/B,cAAc,CAAC,KAAK,EAAE,UAAU,EAAE,oBAAoB,EAAE,KAAK,CAAC,CAAC;AAEjE;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CACvC,OAAyB,EACzB,IAAY,EACH,EAAE,CACX,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;IACzB,IAAI,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,iBAAiB,EAAE,CAAC;QACnD,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CACzB,CAAC,SAAS,EAAE,EAAE,CACZ,SAAS,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI;YAC7B,CAAC,CACC,IAAI,CAAC,MAAM,CAAC,KAAK,KAAK,oBAAoB;gBAC1C,SAAS,CAAC,IAAI,KAAK,cAAc,CAAC,eAAe;gBACjD,cAAc,CAAC,SAAS,CAAC,KAAK,IAAI,CACnC,CACJ,CAAC;IACJ,CAAC;IAED,OAAO,YAAY,CACjB,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,sBAAsB;QACjD,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,wBAAwB;QACrD,CAAC,CAAC,IAAI,CAAC,WAAW;QAClB,CAAC,CAAC,IAAI,EACR,IAAI,CACL,CAAC;AACJ,CAAC,CAAC,CAAC;AAEL,MAAM,YAAY,GAAG,CACnB,IAAsC,EACtC,IAAY,EACH,EAAE;IACX,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACxC,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IACE,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,sBAAsB;QACnD,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,sBAAsB;QACnD,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,iBAAiB,EAC9C,CAAC;QACD,OAAO,IAAI,CAAC,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;IAC/B,CAAC;IAED,OAAO,CACL,IAAI,CAAC,IAAI,KAAK,cAAc,CAAC,gBAAgB,IAAI,IAAI,CAAC,EAAE,EAAE,IAAI,KAAK,IAAI,CACxE,CAAC;AACJ,CAAC,CAAC"}
@@ -11,11 +11,11 @@ export type TypeRewrite = Readonly<{
11
11
  * Emits one report per rewrite, resolving each target name. Every fix imports
12
12
  * the name it writes when the file does not import it yet
13
13
  * ({@link buildTypeImportFix}), so that one applied alone is complete, and the
14
- * first of those imports every name the reports need: the import edits
15
- * overlap, so `--fix` applies one fix per pass, and when that is the batching
16
- * fix the second pass applies the rest. (With nested rewrites an inner fix,
17
- * whose range ends first, can win the pass instead; that costs a pass, not a
18
- * fix.) A report that needs no import has no import edit.
14
+ * first of those imports every name the reports need: the import edits overlap,
15
+ * so `--fix` applies one fix per pass, and when that is the batching fix the
16
+ * second pass applies the rest. (With nested rewrites an inner fix, whose range
17
+ * ends first, can win the pass instead; that costs a pass, not a fix.) A report
18
+ * that needs no import has no import edit.
19
19
  */
20
20
  export declare const reportTypeRewrites: <MessageIds extends string>(context: TSESLint.RuleContext<MessageIds, readonly unknown[]>, rewrites: readonly TypeRewrite[], messageId: MessageIds, importStyle: ImportStyle) => void;
21
21
  //# sourceMappingURL=rewrite-rule-utils.d.mts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-ts-type-forge",
3
- "version": "0.8.16",
3
+ "version": "0.8.17",
4
4
  "private": false,
5
5
  "description": "ESLint rules that steer type declarations toward ts-type-forge idioms.",
6
6
  "keywords": [
@@ -36,17 +36,17 @@
36
36
  ],
37
37
  "dependencies": {
38
38
  "@typescript-eslint/utils": "~8.72.0",
39
- "eslint-rule-forge": "^0.4.0",
40
- "ts-data-forge": "~14.10.0",
39
+ "eslint-rule-forge": "^0.5.0",
40
+ "ts-data-forge": "~14.10.1",
41
41
  "ts-type-forge": "9.2.2"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@typescript-eslint/parser": "8.72.0",
45
45
  "@typescript-eslint/rule-tester": "8.72.0",
46
46
  "dedent": "1.7.2",
47
- "eslint-config-typed": "7.2.1",
48
- "eslint-plugin-ts-data-forge": "0.10.6",
49
- "eslint-plugin-ts-fortress": "1.3.11",
47
+ "eslint-config-typed": "7.3.0",
48
+ "eslint-plugin-ts-data-forge": "0.10.7",
49
+ "eslint-plugin-ts-fortress": "1.3.12",
50
50
  "prettier": "3.9.10",
51
51
  "prettier-plugin-organize-imports": "4.3.0",
52
52
  "prettier-plugin-packagejson": "3.0.2",
package/src/plugin.mts CHANGED
@@ -4,9 +4,10 @@ import type { ESLintFlatConfig, ESLintPlugin } from './types.mjs';
4
4
  /**
5
5
  * Every rule this plugin ships, at `error`.
6
6
  *
7
- * The `satisfies` clause below is keyed off {@link tsTypeForgeRules}, so leaving a
8
- * rule out, naming one that does not exist, or setting one to anything
9
- * but `error` fails to type-check. That is the whole check; no test repeats it.
7
+ * The `satisfies` clause below is keyed off {@link tsTypeForgeRules}, so
8
+ * leaving a rule out, naming one that does not exist, or setting one to
9
+ * anything but `error` fails to type-check. That is the whole check; no test
10
+ * repeats it.
10
11
  */
11
12
  const recommendedRules = {
12
13
  'ts-type-forge/no-side-effect-import': 'error',
@@ -15,9 +15,9 @@ type SpreadOptionsIfIsArray<
15
15
  : T;
16
16
 
17
17
  /**
18
- * Extracts the options tuple type of a rule from its TypeScript
19
- * implementation, so that option types stay in sync with the rule source
20
- * without converting `meta.schema` (JSON Schema) back into TypeScript types.
18
+ * Extracts the options tuple type of a rule from its TypeScript implementation,
19
+ * so that option types stay in sync with the rule source without converting
20
+ * `meta.schema` (JSON Schema) back into TypeScript types.
21
21
  */
22
22
  type RuleOptionsOf<Name extends keyof typeof tsTypeForgeRules> =
23
23
  (typeof tsTypeForgeRules)[Name] extends TSESLint.RuleModule<
@@ -163,8 +163,8 @@ const isNode = (value: unknown): value is TSESTree.Node =>
163
163
  * Children are found through the parser's visitor keys, so only the properties
164
164
  * that actually hold one are read. Reading every own property instead works
165
165
  * too, and is the fallback for a parser that declares no keys for a node type,
166
- * but it allocates an entry pair per property and walks `loc` / `range` as
167
- * well — on the generated rule-type files that alone was most of the cost.
166
+ * but it allocates an entry pair per property and walks `loc` / `range` as well
167
+ * — on the generated rule-type files that alone was most of the cost.
168
168
  */
169
169
  const walk = (
170
170
  node: TSESTree.Node,
@@ -34,8 +34,8 @@ export const IMPORT_STYLE_SCHEMA_PROPERTY: JsonSchema = {
34
34
  *
35
35
  * Returns `undefined` when the name is already bound to something else (a local
36
36
  * alias, an import from another module, or another ts-type-forge export
37
- * imported under the name) — rewriting to it would then silently
38
- * resolve to the wrong declaration, which is exactly what happens inside
37
+ * imported under the name) — rewriting to it would then silently resolve to the
38
+ * wrong declaration, which is exactly what happens inside
39
39
  * ts-type-forge's own sources.
40
40
  */
41
41
  export const resolveTypeName = (
@@ -3,20 +3,14 @@ import {
3
3
  type TSESLint,
4
4
  type TSESTree,
5
5
  } from '@typescript-eslint/utils';
6
- import { buildNamedImportFix, getImportDeclarations } from 'eslint-rule-forge';
7
- import { Arr } from 'ts-data-forge';
6
+ import { addTypeImports, importedNameOf } from 'eslint-rule-forge';
8
7
  import { TS_TYPE_FORGE_MODULE } from './constants.mjs';
9
8
 
10
9
  /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */
11
10
 
12
11
  /**
13
12
  * The edits that import the ts-type-forge types `names` the file does not
14
- * import yet, as types: added to an existing `import type { … }` from the
15
- * module (`, X`), else to a named import from it (`, type X`), else after a
16
- * lone default import (`, { type X }`), and only failing all of those as a
17
- * new `import { type X } from 'ts-type-forge'` after the directive prologue.
18
- * Empty braces count as a host. Adding to an existing declaration keeps
19
- * `import-x/no-duplicates` satisfied.
13
+ * import yet, as `addTypeImports` places them.
20
14
  *
21
15
  * Fixes that add an import overlap, so `--fix` applies one per pass, and the
22
16
  * rest in later passes, by when the file imports their names (see
@@ -26,60 +20,8 @@ export const buildTypeImportFix = (
26
20
  fixer: TSESLint.RuleFixer,
27
21
  sourceCode: Readonly<TSESLint.SourceCode>,
28
22
  names: readonly string[],
29
- ): readonly TSESLint.RuleFix[] => {
30
- if (!Arr.isNonEmpty(names)) {
31
- return [];
32
- }
33
-
34
- const declarations = getImportDeclarations(
35
- sourceCode.ast,
36
- TS_TYPE_FORGE_MODULE,
37
- );
38
-
39
- const typeOnlyFix = declarations
40
- .filter((declaration) => declaration.importKind === 'type')
41
- .map((declaration) => addToBraces(fixer, sourceCode, declaration, names))
42
- .find((fix) => fix !== undefined);
43
-
44
- if (typeOnlyFix !== undefined) {
45
- return [typeOnlyFix];
46
- }
47
-
48
- const typeSpecifiers = names.map((name) => `type ${name}`);
49
-
50
- const valueDeclarations = declarations.filter(
51
- (declaration) => declaration.importKind !== 'type',
52
- );
53
-
54
- const valueFix = valueDeclarations
55
- .map((declaration) =>
56
- addToBraces(fixer, sourceCode, declaration, typeSpecifiers),
57
- )
58
- .find((fix) => fix !== undefined);
59
-
60
- if (valueFix !== undefined) {
61
- return [valueFix];
62
- }
63
-
64
- const defaultHost = valueDeclarations.find(
65
- (declaration) =>
66
- Arr.isFixedLengthArray(1, declaration.specifiers) &&
67
- declaration.specifiers[0].type === AST_NODE_TYPES.ImportDefaultSpecifier,
68
- )?.specifiers[0];
69
-
70
- if (defaultHost !== undefined) {
71
- return [
72
- fixer.insertTextAfter(defaultHost, `, { ${typeSpecifiers.join(', ')} }`),
73
- ];
74
- }
75
-
76
- return buildNamedImportFix(
77
- fixer,
78
- sourceCode.ast,
79
- TS_TYPE_FORGE_MODULE,
80
- typeSpecifiers,
81
- );
82
- };
23
+ ): readonly TSESLint.RuleFix[] =>
24
+ addTypeImports(fixer, sourceCode, TS_TYPE_FORGE_MODULE, names);
83
25
 
84
26
  /**
85
27
  * Whether the file already binds `name` to something that is not the
@@ -105,9 +47,7 @@ export const hasConflictingDeclaration = (
105
47
  !(
106
48
  node.source.value === TS_TYPE_FORGE_MODULE &&
107
49
  specifier.type === AST_NODE_TYPES.ImportSpecifier &&
108
- (specifier.imported.type === AST_NODE_TYPES.Identifier
109
- ? specifier.imported.name
110
- : specifier.imported.value) === name
50
+ importedNameOf(specifier) === name
111
51
  ),
112
52
  );
113
53
  }
@@ -121,39 +61,6 @@ export const hasConflictingDeclaration = (
121
61
  );
122
62
  });
123
63
 
124
- /**
125
- * Adds `specifiers` to the `{ … }` of `declaration`: after its last named
126
- * specifier, or inside empty braces (`import d, {} from …`). `undefined` when
127
- * the declaration has no braces (a default or namespace import alone).
128
- */
129
- const addToBraces = (
130
- fixer: TSESLint.RuleFixer,
131
- sourceCode: Readonly<TSESLint.SourceCode>,
132
- declaration: TSESTree.ImportDeclaration,
133
- specifiers: readonly string[],
134
- ): TSESLint.RuleFix | undefined => {
135
- const lastNamed = declaration.specifiers
136
- .filter(
137
- (specifier): specifier is TSESTree.ImportSpecifier =>
138
- specifier.type === AST_NODE_TYPES.ImportSpecifier,
139
- )
140
- .at(-1);
141
-
142
- if (lastNamed !== undefined) {
143
- return fixer.insertTextAfter(lastNamed, `, ${specifiers.join(', ')}`);
144
- }
145
-
146
- // Before the source, or the brace found is that of `with { … }`.
147
- const openingBrace = sourceCode.getFirstToken(declaration, {
148
- filter: (token) =>
149
- token.value === '{' && token.range[1] <= declaration.source.range[0],
150
- });
151
-
152
- return openingBrace === null
153
- ? undefined
154
- : fixer.insertTextAfter(openingBrace, ` ${specifiers.join(', ')} `);
155
- };
156
-
157
64
  const declaresName = (
158
65
  node: TSESTree.Node | null | undefined,
159
66
  name: string,
@@ -17,11 +17,11 @@ export type TypeRewrite = Readonly<{
17
17
  * Emits one report per rewrite, resolving each target name. Every fix imports
18
18
  * the name it writes when the file does not import it yet
19
19
  * ({@link buildTypeImportFix}), so that one applied alone is complete, and the
20
- * first of those imports every name the reports need: the import edits
21
- * overlap, so `--fix` applies one fix per pass, and when that is the batching
22
- * fix the second pass applies the rest. (With nested rewrites an inner fix,
23
- * whose range ends first, can win the pass instead; that costs a pass, not a
24
- * fix.) A report that needs no import has no import edit.
20
+ * first of those imports every name the reports need: the import edits overlap,
21
+ * so `--fix` applies one fix per pass, and when that is the batching fix the
22
+ * second pass applies the rest. (With nested rewrites an inner fix, whose range
23
+ * ends first, can win the pass instead; that costs a pass, not a fix.) A report
24
+ * that needs no import has no import edit.
25
25
  */
26
26
  export const reportTypeRewrites = <MessageIds extends string>(
27
27
  context: TSESLint.RuleContext<MessageIds, readonly unknown[]>,