@typescript-eslint/eslint-plugin 8.61.1-alpha.0 → 8.61.1-alpha.2
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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
-
export type MessageIds = 'preferIndexSignature' | 'preferIndexSignatureSuggestion' | 'preferRecord';
|
|
2
|
+
export type MessageIds = 'preferIndexSignature' | 'preferIndexSignatureSuggestion' | 'preferRecord' | 'preferRecordSuggestion';
|
|
3
3
|
export type Options = ['index-signature' | 'record'];
|
|
4
4
|
declare const _default: TSESLint.RuleModule<MessageIds, Options, import("../../rules").ESLintPluginDocs, TSESLint.RuleListener> & {
|
|
5
5
|
name: string;
|
|
@@ -17,6 +17,7 @@ exports.default = (0, util_1.createRule)({
|
|
|
17
17
|
preferIndexSignature: 'An index signature is preferred over a record.',
|
|
18
18
|
preferIndexSignatureSuggestion: 'Change into an index signature instead of a record.',
|
|
19
19
|
preferRecord: 'A record is preferred over an index signature.',
|
|
20
|
+
preferRecordSuggestion: 'Change into a record instead of an index signature.',
|
|
20
21
|
},
|
|
21
22
|
schema: [
|
|
22
23
|
{
|
|
@@ -28,6 +29,16 @@ exports.default = (0, util_1.createRule)({
|
|
|
28
29
|
},
|
|
29
30
|
defaultOptions: ['record'],
|
|
30
31
|
create(context, [mode]) {
|
|
32
|
+
// The fixers rebuild the type from the text of a few sub-nodes, so a
|
|
33
|
+
// comment inside `node` but outside all of those preserved sub-nodes would
|
|
34
|
+
// be dropped by the fix. Returns true when at least one such comment exists.
|
|
35
|
+
function hasUnpreservedComments(node, ...preserved) {
|
|
36
|
+
return context.sourceCode
|
|
37
|
+
.getCommentsInside(node)
|
|
38
|
+
.some(comment => preserved.every(target => target == null ||
|
|
39
|
+
comment.range[0] < target.range[0] ||
|
|
40
|
+
comment.range[1] > target.range[1]));
|
|
41
|
+
}
|
|
31
42
|
function checkMembers(members, node, parentId, prefix, postfix, safeFix = true) {
|
|
32
43
|
if (members.length !== 1) {
|
|
33
44
|
return;
|
|
@@ -59,16 +70,24 @@ exports.default = (0, util_1.createRule)({
|
|
|
59
70
|
context.report({
|
|
60
71
|
node,
|
|
61
72
|
messageId: 'preferRecord',
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
...(0, util_1.getFixOrSuggest)({
|
|
74
|
+
fixOrSuggest: !safeFix
|
|
75
|
+
? 'none'
|
|
76
|
+
: hasUnpreservedComments(node, keyType.typeAnnotation, valueType.typeAnnotation)
|
|
77
|
+
? 'suggest'
|
|
78
|
+
: 'fix',
|
|
79
|
+
suggestion: {
|
|
80
|
+
messageId: 'preferRecordSuggestion',
|
|
81
|
+
fix: (fixer) => {
|
|
82
|
+
const key = context.sourceCode.getText(keyType.typeAnnotation);
|
|
83
|
+
const value = context.sourceCode.getText(valueType.typeAnnotation);
|
|
84
|
+
const record = member.readonly
|
|
85
|
+
? `Readonly<Record<${key}, ${value}>>`
|
|
86
|
+
: `Record<${key}, ${value}>`;
|
|
87
|
+
return fixer.replaceText(node, `${prefix}${record}${postfix}`);
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
}),
|
|
72
91
|
});
|
|
73
92
|
}
|
|
74
93
|
return {
|
|
@@ -93,7 +112,9 @@ exports.default = (0, util_1.createRule)({
|
|
|
93
112
|
node,
|
|
94
113
|
messageId: 'preferIndexSignature',
|
|
95
114
|
...(0, util_1.getFixOrSuggest)({
|
|
96
|
-
fixOrSuggest: shouldFix
|
|
115
|
+
fixOrSuggest: shouldFix && !hasUnpreservedComments(node, params[0], params[1])
|
|
116
|
+
? 'fix'
|
|
117
|
+
: 'suggest',
|
|
97
118
|
suggestion: {
|
|
98
119
|
messageId: 'preferIndexSignatureSuggestion',
|
|
99
120
|
fix: fixer => {
|
|
@@ -146,28 +167,36 @@ exports.default = (0, util_1.createRule)({
|
|
|
146
167
|
}
|
|
147
168
|
}
|
|
148
169
|
}
|
|
149
|
-
// There's no builtin Mutable<T> type, so we can't autofix it really.
|
|
150
|
-
const canFix = node.readonly !== '-';
|
|
151
170
|
context.report({
|
|
152
171
|
node,
|
|
153
172
|
messageId: 'preferRecord',
|
|
154
|
-
...(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
173
|
+
...(0, util_1.getFixOrSuggest)({
|
|
174
|
+
// There's no builtin Mutable<T> type, so a `-readonly` mapped
|
|
175
|
+
// type can't be represented as a Record and is left untouched.
|
|
176
|
+
fixOrSuggest: node.readonly === '-'
|
|
177
|
+
? 'none'
|
|
178
|
+
: hasUnpreservedComments(node, constraint, node.typeAnnotation)
|
|
179
|
+
? 'suggest'
|
|
180
|
+
: 'fix',
|
|
181
|
+
suggestion: {
|
|
182
|
+
messageId: 'preferRecordSuggestion',
|
|
183
|
+
fix: (fixer) => {
|
|
184
|
+
const keyType = context.sourceCode.getText(constraint);
|
|
185
|
+
const valueType = node.typeAnnotation
|
|
186
|
+
? context.sourceCode.getText(node.typeAnnotation)
|
|
187
|
+
: 'any';
|
|
188
|
+
let recordText = `Record<${keyType}, ${valueType}>`;
|
|
189
|
+
if (node.optional === '+' || node.optional === true) {
|
|
190
|
+
recordText = `Partial<${recordText}>`;
|
|
191
|
+
}
|
|
192
|
+
else if (node.optional === '-') {
|
|
193
|
+
recordText = `Required<${recordText}>`;
|
|
194
|
+
}
|
|
195
|
+
if (node.readonly === '+' || node.readonly === true) {
|
|
196
|
+
recordText = `Readonly<${recordText}>`;
|
|
197
|
+
}
|
|
198
|
+
return fixer.replaceText(node, recordText);
|
|
199
|
+
},
|
|
171
200
|
},
|
|
172
201
|
}),
|
|
173
202
|
});
|
|
@@ -515,6 +515,14 @@ exports.default = (0, util_1.createRule)({
|
|
|
515
515
|
return (node.parent.type === utils_1.AST_NODE_TYPES.LogicalExpression ||
|
|
516
516
|
isInGenericContext(node));
|
|
517
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* Interpolated template literals can be widened to `string` while contextual
|
|
520
|
+
* typing still accepts them, so the assertion may be required.
|
|
521
|
+
* @see https://github.com/typescript-eslint/typescript-eslint/issues/12276
|
|
522
|
+
*/
|
|
523
|
+
if (isTemplateLiteralWithExpressions(node.expression)) {
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
518
526
|
if (SKIP_PARENT_TYPES.has(node.parent.type) ||
|
|
519
527
|
node.expression.type === utils_1.AST_NODE_TYPES.ArrayExpression ||
|
|
520
528
|
isInDestructuringDeclaration(node) ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typescript-eslint/eslint-plugin",
|
|
3
|
-
"version": "8.61.1-alpha.
|
|
3
|
+
"version": "8.61.1-alpha.2",
|
|
4
4
|
"description": "TypeScript plugin for ESLint",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"ignore": "^7.0.5",
|
|
53
53
|
"natural-compare": "^1.4.0",
|
|
54
54
|
"ts-api-utils": "^2.5.0",
|
|
55
|
-
"@typescript-eslint/
|
|
56
|
-
"@typescript-eslint/
|
|
57
|
-
"@typescript-eslint/
|
|
58
|
-
"@typescript-eslint/
|
|
55
|
+
"@typescript-eslint/scope-manager": "8.61.1-alpha.2",
|
|
56
|
+
"@typescript-eslint/type-utils": "8.61.1-alpha.2",
|
|
57
|
+
"@typescript-eslint/utils": "8.61.1-alpha.2",
|
|
58
|
+
"@typescript-eslint/visitor-keys": "8.61.1-alpha.2"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"@types/json-schema": "^7.0.15",
|
|
@@ -79,13 +79,13 @@
|
|
|
79
79
|
"typescript": ">=4.8.4 <6.1.0",
|
|
80
80
|
"unist-util-visit": "^5.0.0",
|
|
81
81
|
"vitest": "^4.0.18",
|
|
82
|
-
"@typescript-eslint/rule-schema-to-typescript-types": "8.61.1-alpha.
|
|
83
|
-
"@typescript-eslint/rule-tester": "8.61.1-alpha.
|
|
82
|
+
"@typescript-eslint/rule-schema-to-typescript-types": "8.61.1-alpha.2",
|
|
83
|
+
"@typescript-eslint/rule-tester": "8.61.1-alpha.2"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
86
|
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
|
87
87
|
"typescript": ">=4.8.4 <6.1.0",
|
|
88
|
-
"@typescript-eslint/parser": "^8.61.1-alpha.
|
|
88
|
+
"@typescript-eslint/parser": "^8.61.1-alpha.2"
|
|
89
89
|
},
|
|
90
90
|
"funding": {
|
|
91
91
|
"type": "opencollective",
|