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 +3 -2
- package/dist/plugin.d.mts.map +1 -1
- package/dist/plugin.mjs +1 -0
- package/dist/plugin.mjs.map +1 -1
- package/dist/rule-types.d.mts +3 -3
- package/dist/rules/import-style.d.mts +2 -2
- package/dist/rules/import-utils.d.mts +1 -6
- package/dist/rules/import-utils.d.mts.map +1 -1
- package/dist/rules/import-utils.mjs +3 -59
- package/dist/rules/import-utils.mjs.map +1 -1
- package/dist/rules/rewrite-rule-utils.d.mts +5 -5
- package/package.json +6 -6
- package/src/plugin.mts +4 -3
- package/src/rule-types.mts +3 -3
- package/src/rules/ast-utils.mts +2 -2
- package/src/rules/import-style.mts +2 -2
- package/src/rules/import-utils.mts +5 -98
- package/src/rules/rewrite-rule-utils.mts +5 -5
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
|
|
255
|
-
import, and otherwise as a new
|
|
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.
|
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,KAAK,EAAoB,YAAY,EAAE,MAAM,aAAa,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
package/dist/plugin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","sourceRoot":"","sources":["../src/plugin.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD
|
|
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"}
|
package/dist/rule-types.d.mts
CHANGED
|
@@ -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
|
-
*
|
|
8
|
-
*
|
|
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
|
-
*
|
|
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
|
|
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;
|
|
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 {
|
|
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
|
|
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":"AAAA,OAAO,EACL,cAAc,GAGf,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,
|
|
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
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
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.
|
|
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.
|
|
40
|
-
"ts-data-forge": "~14.10.
|
|
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.
|
|
48
|
-
"eslint-plugin-ts-data-forge": "0.10.
|
|
49
|
-
"eslint-plugin-ts-fortress": "1.3.
|
|
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
|
|
8
|
-
* rule out, naming one that does not exist, or setting one to
|
|
9
|
-
* but `error` fails to type-check. That is the whole check; no test
|
|
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',
|
package/src/rule-types.mts
CHANGED
|
@@ -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
|
-
*
|
|
20
|
-
*
|
|
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<
|
package/src/rules/ast-utils.mts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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[]>,
|