@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
- fix: safeFix
63
- ? (fixer) => {
64
- const key = context.sourceCode.getText(keyType.typeAnnotation);
65
- const value = context.sourceCode.getText(valueType.typeAnnotation);
66
- const record = member.readonly
67
- ? `Readonly<Record<${key}, ${value}>>`
68
- : `Record<${key}, ${value}>`;
69
- return fixer.replaceText(node, `${prefix}${record}${postfix}`);
70
- }
71
- : null,
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 ? 'fix' : 'suggest',
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
- ...(canFix && {
155
- fix: (fixer) => {
156
- const keyType = context.sourceCode.getText(constraint);
157
- const valueType = node.typeAnnotation
158
- ? context.sourceCode.getText(node.typeAnnotation)
159
- : 'any';
160
- let recordText = `Record<${keyType}, ${valueType}>`;
161
- if (node.optional === '+' || node.optional === true) {
162
- recordText = `Partial<${recordText}>`;
163
- }
164
- else if (node.optional === '-') {
165
- recordText = `Required<${recordText}>`;
166
- }
167
- if (node.readonly === '+' || node.readonly === true) {
168
- recordText = `Readonly<${recordText}>`;
169
- }
170
- return fixer.replaceText(node, recordText);
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.0",
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/visitor-keys": "8.61.1-alpha.0",
56
- "@typescript-eslint/scope-manager": "8.61.1-alpha.0",
57
- "@typescript-eslint/type-utils": "8.61.1-alpha.0",
58
- "@typescript-eslint/utils": "8.61.1-alpha.0"
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.0",
83
- "@typescript-eslint/rule-tester": "8.61.1-alpha.0"
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.0"
88
+ "@typescript-eslint/parser": "^8.61.1-alpha.2"
89
89
  },
90
90
  "funding": {
91
91
  "type": "opencollective",