@blumintinc/eslint-plugin-blumint 1.20.68 → 1.20.70
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/lib/index.js +1 -1
- package/lib/rules/class-methods-read-top-to-bottom.js +36 -19
- package/lib/rules/enforce-early-destructuring.js +20 -1
- package/lib/rules/enforce-render-hits-memoization.d.ts +2 -1
- package/lib/rules/enforce-render-hits-memoization.js +149 -93
- package/lib/rules/enforce-transform-memoization.js +27 -1
- package/lib/rules/no-useless-usememo-primitives.js +16 -0
- package/lib/rules/optimize-object-boolean-conditions.js +19 -1
- package/lib/rules/parallelize-async-operations.js +66 -3
- package/lib/rules/prefer-map-over-conditional-dispatch.js +270 -5
- package/lib/rules/prefer-type-over-interface.js +38 -0
- package/lib/rules/use-latest-callback.d.ts +6 -1
- package/lib/rules/use-latest-callback.js +235 -46
- package/package.json +1 -1
- package/release-manifest.json +94 -0
|
@@ -4,6 +4,13 @@ exports.useLatestCallback = void 0;
|
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
6
|
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
7
|
+
/**
|
|
8
|
+
* Matches Prettier's own default. Dropping the dependency array lets the call
|
|
9
|
+
* collapse onto one line, so the fixer decides a line break a formatter owns: a
|
|
10
|
+
* line it emits past this width is rewritten on the next `prettier --write`, and
|
|
11
|
+
* fails `prettier --check` in the meantime.
|
|
12
|
+
*/
|
|
13
|
+
const DEFAULT_PRINT_WIDTH = 80;
|
|
7
14
|
const LATEST_CALLBACK_MODULE = 'use-latest-callback';
|
|
8
15
|
const LATEST_CALLBACK_HOOK = 'useLatestCallback';
|
|
9
16
|
/**
|
|
@@ -44,6 +51,7 @@ const findLatestCallbackImport = (program) => {
|
|
|
44
51
|
}
|
|
45
52
|
return null;
|
|
46
53
|
};
|
|
54
|
+
const isComma = (token) => !!token && token.type === utils_1.AST_TOKEN_TYPES.Punctuator && token.value === ',';
|
|
47
55
|
/** The leading whitespace of the line the offset sits on. */
|
|
48
56
|
const indentationAt = (sourceCode, offset) => {
|
|
49
57
|
const text = sourceCode.getText();
|
|
@@ -51,6 +59,75 @@ const indentationAt = (sourceCode, offset) => {
|
|
|
51
59
|
const match = /^[ \t]*/.exec(text.slice(lineStart, offset));
|
|
52
60
|
return match ? match[0] : '';
|
|
53
61
|
};
|
|
62
|
+
/** The indentation of an offset that starts its own line, else null. */
|
|
63
|
+
const ownLineIndentAt = (sourceCode, offset) => {
|
|
64
|
+
const text = sourceCode.getText();
|
|
65
|
+
const lineStart = text.lastIndexOf('\n', offset - 1) + 1;
|
|
66
|
+
const lead = text.slice(lineStart, offset);
|
|
67
|
+
return lead.trim() === '' ? lead : null;
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* The file's own nesting step, taken as the most common indentation increase
|
|
71
|
+
* between consecutive lines. Reading it from the source keeps emitted code in
|
|
72
|
+
* the author's units instead of assuming a two-space, space-indented file.
|
|
73
|
+
*/
|
|
74
|
+
const indentUnitOf = (sourceCode) => {
|
|
75
|
+
const text = sourceCode.getText();
|
|
76
|
+
const blockComments = sourceCode
|
|
77
|
+
.getAllComments()
|
|
78
|
+
.filter((comment) => comment.type === utils_1.AST_TOKEN_TYPES.Block)
|
|
79
|
+
.map((comment) => comment.range);
|
|
80
|
+
// A block comment's interior lines carry whatever alignment the comment uses
|
|
81
|
+
// — the `*` one column in from its own indentation, or, for commented-out
|
|
82
|
+
// code, the original code's depths. Neither is a nesting step of the file, and
|
|
83
|
+
// counting them makes a JSDoc-heavy file look 1-space indented. Keying on the
|
|
84
|
+
// comment's range rather than a leading `*` also covers a body without them.
|
|
85
|
+
const continuesBlockComment = (offset) => blockComments.some(([start, end]) => start < offset && offset < end);
|
|
86
|
+
const frequencies = new Map();
|
|
87
|
+
let previous = '';
|
|
88
|
+
let offset = 0;
|
|
89
|
+
for (const line of text.split('\n')) {
|
|
90
|
+
const lineStart = offset;
|
|
91
|
+
offset += line.length + 1;
|
|
92
|
+
if (line.trim() === '') {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (continuesBlockComment(lineStart)) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const match = /^[ \t]*/.exec(line);
|
|
99
|
+
const indent = match ? match[0] : '';
|
|
100
|
+
if (indent.length > previous.length && indent.startsWith(previous)) {
|
|
101
|
+
const delta = indent.slice(previous.length);
|
|
102
|
+
frequencies.set(delta, (frequencies.get(delta) ?? 0) + 1);
|
|
103
|
+
}
|
|
104
|
+
previous = indent;
|
|
105
|
+
}
|
|
106
|
+
let unit = ' ';
|
|
107
|
+
let best = 0;
|
|
108
|
+
for (const [delta, count] of frequencies) {
|
|
109
|
+
if (count > best) {
|
|
110
|
+
unit = delta;
|
|
111
|
+
best = count;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return unit;
|
|
115
|
+
};
|
|
116
|
+
/** The source with every planned edit applied, used to measure emitted lines. */
|
|
117
|
+
const applyEdits = (text, edits) => {
|
|
118
|
+
const ordered = [...edits].sort((a, b) => b.range[0] - a.range[0]);
|
|
119
|
+
let result = text;
|
|
120
|
+
for (const edit of ordered) {
|
|
121
|
+
result = `${result.slice(0, edit.range[0])}${edit.text}${result.slice(edit.range[1])}`;
|
|
122
|
+
}
|
|
123
|
+
return result;
|
|
124
|
+
};
|
|
125
|
+
/** The length of the line `offset` sits on. */
|
|
126
|
+
const lineLengthAt = (text, offset) => {
|
|
127
|
+
const lineStart = text.lastIndexOf('\n', offset - 1) + 1;
|
|
128
|
+
const lineBreak = text.indexOf('\n', offset);
|
|
129
|
+
return (lineBreak === -1 ? text.length : lineBreak) - lineStart;
|
|
130
|
+
};
|
|
54
131
|
/**
|
|
55
132
|
* Ranges whose interior line breaks carry string data rather than formatting.
|
|
56
133
|
* A multi-line template literal (or a string spliced together with line
|
|
@@ -65,42 +142,42 @@ const stringDataRangesOf = (sourceCode, node) => sourceCode
|
|
|
65
142
|
token.loc.start.line !== token.loc.end.line)
|
|
66
143
|
.map((token) => token.range);
|
|
67
144
|
/**
|
|
68
|
-
*
|
|
145
|
+
* A per-line transform moving text written at `fromIndent` to `toIndent`, or
|
|
146
|
+
* null when neither indentation is a prefix of the other (tabs against spaces),
|
|
147
|
+
* where no delta can be applied without corrupting the layout.
|
|
148
|
+
*/
|
|
149
|
+
const lineShifterBetween = (fromIndent, toIndent) => {
|
|
150
|
+
if (fromIndent === toIndent) {
|
|
151
|
+
return (line) => line;
|
|
152
|
+
}
|
|
153
|
+
if (fromIndent.startsWith(toIndent)) {
|
|
154
|
+
const removed = fromIndent.slice(toIndent.length);
|
|
155
|
+
return (line) => line.startsWith(removed) ? line.slice(removed.length) : line;
|
|
156
|
+
}
|
|
157
|
+
if (toIndent.startsWith(fromIndent)) {
|
|
158
|
+
const added = toIndent.slice(fromIndent.length);
|
|
159
|
+
return (line) => `${added}${line}`;
|
|
160
|
+
}
|
|
161
|
+
return null;
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* The callback's text with its continuation lines moved from the depth it was
|
|
165
|
+
* written at to `toIndent`, or null when that move is not expressible.
|
|
69
166
|
*
|
|
70
|
-
* Dropping the dependency array
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* and the indentation of the line the call starts on. Preserving the original
|
|
76
|
-
* multi-line layout instead is not an option: a lone function argument is
|
|
77
|
-
* hugged onto the call line by the formatter, so the broken-out form would be
|
|
78
|
-
* reformatted away on the next write.
|
|
167
|
+
* Dropping the dependency array changes the nesting level the callback sits at,
|
|
168
|
+
* so emitting it verbatim would leave every interior line indented for a level
|
|
169
|
+
* it no longer occupies (issue #1559). The first line is excluded because it is
|
|
170
|
+
* spliced in directly after the call's open paren (or after a fresh indent when
|
|
171
|
+
* the call is broken open), so it has no indentation of its own left to adjust.
|
|
79
172
|
*/
|
|
80
|
-
const
|
|
173
|
+
const reindentedText = (sourceCode, callback, fromIndent, toIndent) => {
|
|
81
174
|
const text = sourceCode.getText(callback);
|
|
82
|
-
|
|
83
|
-
const callbackIndent = indentationAt(sourceCode, callback.range[0]);
|
|
84
|
-
// A callback already on the call's line loses no nesting level, so its body
|
|
85
|
-
// must be reproduced byte for byte.
|
|
86
|
-
if (callIndent === callbackIndent) {
|
|
175
|
+
if (!text.includes('\n')) {
|
|
87
176
|
return text;
|
|
88
177
|
}
|
|
89
|
-
const shiftLine = (
|
|
90
|
-
if (callbackIndent.startsWith(callIndent)) {
|
|
91
|
-
const removed = callbackIndent.slice(callIndent.length);
|
|
92
|
-
return (line) => line.startsWith(removed) ? line.slice(removed.length) : line;
|
|
93
|
-
}
|
|
94
|
-
if (callIndent.startsWith(callbackIndent)) {
|
|
95
|
-
const added = callIndent.slice(callbackIndent.length);
|
|
96
|
-
return (line) => `${added}${line}`;
|
|
97
|
-
}
|
|
98
|
-
// Indent characters that disagree give no delta that can be applied
|
|
99
|
-
// without corrupting the layout, so the text is left as the author wrote it.
|
|
100
|
-
return null;
|
|
101
|
-
})();
|
|
178
|
+
const shiftLine = lineShifterBetween(fromIndent, toIndent);
|
|
102
179
|
if (!shiftLine) {
|
|
103
|
-
return
|
|
180
|
+
return null;
|
|
104
181
|
}
|
|
105
182
|
const stringData = stringDataRangesOf(sourceCode, callback);
|
|
106
183
|
const carriesStringData = (offset) => stringData.some(([start, end]) => start < offset && offset < end);
|
|
@@ -110,8 +187,6 @@ const reindentedCallbackText = (sourceCode, call, callback) => {
|
|
|
110
187
|
.map((line, index) => {
|
|
111
188
|
const lineStart = offset;
|
|
112
189
|
offset += line.length + 1;
|
|
113
|
-
// The first line is spliced in after the call's open paren, so it has no
|
|
114
|
-
// indentation of its own left to adjust.
|
|
115
190
|
if (index === 0 || line.trim() === '' || carriesStringData(lineStart)) {
|
|
116
191
|
return line;
|
|
117
192
|
}
|
|
@@ -119,6 +194,75 @@ const reindentedCallbackText = (sourceCode, call, callback) => {
|
|
|
119
194
|
})
|
|
120
195
|
.join('\n');
|
|
121
196
|
};
|
|
197
|
+
/**
|
|
198
|
+
* The callback's text for the collapsed, single-line call form. An indentation
|
|
199
|
+
* delta that cannot be applied leaves the text as the author wrote it, which is
|
|
200
|
+
* still valid code at the depth the formatter will correct it from.
|
|
201
|
+
*/
|
|
202
|
+
const collapsedCallbackText = (sourceCode, call, callback) => reindentedText(sourceCode, callback, indentationAt(sourceCode, callback.range[0]), indentationAt(sourceCode, call.range[0])) ?? sourceCode.getText(callback);
|
|
203
|
+
/**
|
|
204
|
+
* Whether Prettier answers an over-long call by breaking the argument list
|
|
205
|
+
* open. It does for an arrow, and for a parameter-less function expression —
|
|
206
|
+
* but a function expression WITH parameters is instead hugged onto the call
|
|
207
|
+
* line with its parameter list broken, a shape this fixer cannot author. Left
|
|
208
|
+
* collapsed, such a call at least keeps the first line Prettier keeps.
|
|
209
|
+
*/
|
|
210
|
+
const breaksOpenWhenLong = (callback) => callback.type !== utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
211
|
+
callback.params.length === 0;
|
|
212
|
+
/**
|
|
213
|
+
* Whether the callback's own head — its parameter list, up to where its body
|
|
214
|
+
* begins — is spelled across several lines. An arrow written that way is never
|
|
215
|
+
* hugged onto the call's line, because that would leave the call's open paren
|
|
216
|
+
* and the arrow's dangling at the end of one line; Prettier breaks the argument
|
|
217
|
+
* list open instead however short the collapsed line measures. A function
|
|
218
|
+
* expression is the exception handled above: its broken parameter list IS hugged.
|
|
219
|
+
*/
|
|
220
|
+
const headSpansLines = (sourceCode, callback) => callback.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
221
|
+
sourceCode
|
|
222
|
+
.getText()
|
|
223
|
+
.slice(callback.range[0], callback.body.range[0])
|
|
224
|
+
.includes('\n');
|
|
225
|
+
/**
|
|
226
|
+
* Whether the rewritten call should end its argument list with a comma. The
|
|
227
|
+
* answer is the formatter's `trailingComma` setting, which is read off the call
|
|
228
|
+
* being rewritten whenever it was already broken open — that is exactly the
|
|
229
|
+
* shape the setting governs. A call the author kept on one line says nothing
|
|
230
|
+
* about it, so the modern default (and Prettier's own, from v3) is assumed.
|
|
231
|
+
*/
|
|
232
|
+
const wantsTrailingComma = (sourceCode, call) => {
|
|
233
|
+
const lastArgument = call.arguments[call.arguments.length - 1];
|
|
234
|
+
const closeParen = sourceCode.getLastToken(call);
|
|
235
|
+
if (!lastArgument || !closeParen) {
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
if (isComma(sourceCode.getTokenBefore(closeParen))) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
return closeParen.loc.start.line === lastArgument.loc.end.line;
|
|
242
|
+
};
|
|
243
|
+
/**
|
|
244
|
+
* The rewritten call with its argument list broken open — the callee on its own
|
|
245
|
+
* line, the callback one nesting step in, and the closing paren back at the
|
|
246
|
+
* statement's indentation. Null when the callback's lines cannot be moved to
|
|
247
|
+
* that depth, which asks the caller to keep the collapsed form.
|
|
248
|
+
*/
|
|
249
|
+
const brokenOpenCallText = (sourceCode, call, callback, head, indentUnit) => {
|
|
250
|
+
const callIndent = indentationAt(sourceCode, call.range[0]);
|
|
251
|
+
// A callback the author already broke onto its own line keeps the exact
|
|
252
|
+
// indentation it was written at, so that layout survives byte for byte.
|
|
253
|
+
const ownLineIndent = ownLineIndentAt(sourceCode, callback.range[0]);
|
|
254
|
+
const calleeIndent = ownLineIndent !== null &&
|
|
255
|
+
ownLineIndent.length > callIndent.length &&
|
|
256
|
+
ownLineIndent.startsWith(callIndent)
|
|
257
|
+
? ownLineIndent
|
|
258
|
+
: `${callIndent}${indentUnit}`;
|
|
259
|
+
const moved = reindentedText(sourceCode, callback, indentationAt(sourceCode, callback.range[0]), calleeIndent);
|
|
260
|
+
if (moved === null) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const comma = wantsTrailingComma(sourceCode, call) ? ',' : '';
|
|
264
|
+
return `${head}(\n${calleeIndent}${moved}${comma}\n${callIndent})`;
|
|
265
|
+
};
|
|
122
266
|
exports.useLatestCallback = (0, createRule_1.createRule)({
|
|
123
267
|
name: 'use-latest-callback',
|
|
124
268
|
meta: {
|
|
@@ -128,13 +272,27 @@ exports.useLatestCallback = (0, createRule_1.createRule)({
|
|
|
128
272
|
recommended: 'error',
|
|
129
273
|
},
|
|
130
274
|
fixable: 'code',
|
|
131
|
-
schema: [
|
|
275
|
+
schema: [
|
|
276
|
+
{
|
|
277
|
+
type: 'object',
|
|
278
|
+
properties: {
|
|
279
|
+
printWidth: {
|
|
280
|
+
type: 'number',
|
|
281
|
+
minimum: 1,
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
additionalProperties: false,
|
|
285
|
+
},
|
|
286
|
+
],
|
|
132
287
|
messages: {
|
|
133
288
|
useLatestCallback: 'Replace {{currentHook}} with {{recommendedHook}} from "use-latest-callback" so the callback keeps a stable reference while still reading the latest props/state. useCallback recreates functions whenever dependencies change, which can trigger needless renders and stale closures. Drop the dependency array when switching to {{recommendedHook}}.',
|
|
134
289
|
},
|
|
135
290
|
},
|
|
136
|
-
defaultOptions: [],
|
|
137
|
-
create(context) {
|
|
291
|
+
defaultOptions: [{}],
|
|
292
|
+
create(context, [options]) {
|
|
293
|
+
const printWidth = typeof options.printWidth === 'number' && options.printWidth > 0
|
|
294
|
+
? options.printWidth
|
|
295
|
+
: DEFAULT_PRINT_WIDTH;
|
|
138
296
|
const filename = context.getFilename();
|
|
139
297
|
if (filename.includes('/node_modules/')) {
|
|
140
298
|
return {};
|
|
@@ -360,9 +518,6 @@ exports.useLatestCallback = (0, createRule_1.createRule)({
|
|
|
360
518
|
// The react import statement participates in the change set only when
|
|
361
519
|
// it binds useCallback or anchors a React.useCallback member call.
|
|
362
520
|
const touchesImport = specifiers.length > 0 || hasReactMemberUseCallback;
|
|
363
|
-
const isComma = (token) => !!token &&
|
|
364
|
-
token.type === utils_1.AST_TOKEN_TYPES.Punctuator &&
|
|
365
|
-
token.value === ',';
|
|
366
521
|
/**
|
|
367
522
|
* Splices out one named specifier and the comma that separates it from
|
|
368
523
|
* the list, touching nothing else. Re-emitting the surviving specifiers
|
|
@@ -480,13 +635,46 @@ exports.useLatestCallback = (0, createRule_1.createRule)({
|
|
|
480
635
|
}
|
|
481
636
|
return fixes;
|
|
482
637
|
};
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
638
|
+
/**
|
|
639
|
+
* The replacement text for every batched call, collapsed onto one line
|
|
640
|
+
* except where that line would overrun the print width.
|
|
641
|
+
*
|
|
642
|
+
* Prettier hugs a lone function argument onto the call line while it
|
|
643
|
+
* fits, so collapsing is the shape that survives the next write — and
|
|
644
|
+
* over-wrapping is not the safe direction, since a broken-open call
|
|
645
|
+
* short enough to fit is collapsed straight back. Past the width the
|
|
646
|
+
* two swap places, so the emitted line is MEASURED against the source
|
|
647
|
+
* with every collapse already applied, rather than predicted from the
|
|
648
|
+
* call's parts (issue #1579).
|
|
649
|
+
*/
|
|
650
|
+
const conversionTexts = () => {
|
|
651
|
+
const headOf = (call) => `${recommendedHook}${call.typeParameters ? sourceCode.getText(call.typeParameters) : ''}`;
|
|
652
|
+
const collapsed = batchedConversions.map((conversion) => ({
|
|
653
|
+
conversion,
|
|
654
|
+
edit: {
|
|
655
|
+
range: conversion.node.range,
|
|
656
|
+
text: `${headOf(conversion.node)}(${collapsedCallbackText(sourceCode, conversion.node, conversion.node.arguments[0])})`,
|
|
657
|
+
},
|
|
658
|
+
}));
|
|
659
|
+
const source = sourceCode.getText();
|
|
660
|
+
const simulated = applyEdits(source, collapsed.map(({ edit }) => edit));
|
|
661
|
+
const shiftBefore = (offset) => collapsed
|
|
662
|
+
.filter(({ edit }) => edit.range[1] <= offset)
|
|
663
|
+
.reduce((total, { edit }) => total + edit.text.length - (edit.range[1] - edit.range[0]), 0);
|
|
664
|
+
const indentUnit = indentUnitOf(sourceCode);
|
|
665
|
+
const texts = new Map();
|
|
666
|
+
for (const { conversion, edit } of collapsed) {
|
|
667
|
+
const { node } = conversion;
|
|
668
|
+
const start = node.range[0] + shiftBefore(node.range[0]);
|
|
669
|
+
const callback = node.arguments[0];
|
|
670
|
+
const overflows = lineLengthAt(simulated, start) > printWidth ||
|
|
671
|
+
headSpansLines(sourceCode, callback);
|
|
672
|
+
const broken = overflows && breaksOpenWhenLong(callback)
|
|
673
|
+
? brokenOpenCallText(sourceCode, node, callback, headOf(node), indentUnit)
|
|
674
|
+
: null;
|
|
675
|
+
texts.set(node, broken ?? edit.text);
|
|
676
|
+
}
|
|
677
|
+
return texts;
|
|
490
678
|
};
|
|
491
679
|
// Every call-site conversion and the import rewrite ride on ONE fix
|
|
492
680
|
// from ONE report. ESLint discards a multi-part fix wholesale when any
|
|
@@ -512,8 +700,9 @@ exports.useLatestCallback = (0, createRule_1.createRule)({
|
|
|
512
700
|
if (!batchedConversions.every(reachesHook)) {
|
|
513
701
|
return null;
|
|
514
702
|
}
|
|
703
|
+
const texts = conversionTexts();
|
|
515
704
|
return [
|
|
516
|
-
...batchedConversions.map((conversion) =>
|
|
705
|
+
...batchedConversions.map((conversion) => fixer.replaceText(conversion.node, texts.get(conversion.node))),
|
|
517
706
|
...importFixes(fixer),
|
|
518
707
|
];
|
|
519
708
|
},
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,98 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.20.70",
|
|
4
|
+
"date": "2026-08-01T23:31:02.214Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "class-methods-read-top-to-bottom",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1592
|
|
11
|
+
],
|
|
12
|
+
"summary": "preserve the whitespace separating reordered class members (closes #1592)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "no-useless-usememo-primitives",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1591
|
|
19
|
+
],
|
|
20
|
+
"summary": "decline the autofix when inlining would strand a comment (closes #1591)"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "parallelize-async-operations",
|
|
24
|
+
"changeType": "fix",
|
|
25
|
+
"issues": [
|
|
26
|
+
1589
|
|
27
|
+
],
|
|
28
|
+
"summary": "preserve comments between merged awaits (closes #1589)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "prefer-map-over-conditional-dispatch",
|
|
32
|
+
"changeType": "fix",
|
|
33
|
+
"issues": [
|
|
34
|
+
1590
|
|
35
|
+
],
|
|
36
|
+
"summary": "preserve branch comments in the generated map (closes #1590)"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"version": "1.20.69",
|
|
42
|
+
"date": "2026-08-01T20:48:40.484Z",
|
|
43
|
+
"rules": [
|
|
44
|
+
{
|
|
45
|
+
"name": "enforce-early-destructuring",
|
|
46
|
+
"changeType": "fix",
|
|
47
|
+
"issues": [
|
|
48
|
+
1580
|
|
49
|
+
],
|
|
50
|
+
"summary": "stop parenthesizing a source that already binds tighter than ?? (closes #1580)"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "enforce-render-hits-memoization",
|
|
54
|
+
"changeType": "fix",
|
|
55
|
+
"issues": [
|
|
56
|
+
1585,
|
|
57
|
+
1586,
|
|
58
|
+
1588
|
|
59
|
+
],
|
|
60
|
+
"summary": "report shorthand props (closes #1588); accept a module-scope function as already stable (closes #1586); recognise useLatestCallback as a memoization boundary (closes #1585)"
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "enforce-transform-memoization",
|
|
64
|
+
"changeType": "fix",
|
|
65
|
+
"issues": [
|
|
66
|
+
1584
|
|
67
|
+
],
|
|
68
|
+
"summary": "recognise useLatestCallback as a stabilizing wrapper (closes #1584)"
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
"name": "optimize-object-boolean-conditions",
|
|
72
|
+
"changeType": "fix",
|
|
73
|
+
"issues": [
|
|
74
|
+
1581
|
|
75
|
+
],
|
|
76
|
+
"summary": "see through `as const` when judging a primitive (closes #1581)"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"name": "prefer-type-over-interface",
|
|
80
|
+
"changeType": "fix",
|
|
81
|
+
"issues": [
|
|
82
|
+
1583
|
|
83
|
+
],
|
|
84
|
+
"summary": "exempt merged interface declarations (closes #1583)"
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "use-latest-callback",
|
|
88
|
+
"changeType": "fix",
|
|
89
|
+
"issues": [
|
|
90
|
+
1579
|
|
91
|
+
],
|
|
92
|
+
"summary": "keep the call broken open when collapsing overflows the print width (closes #1579)"
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
2
96
|
{
|
|
3
97
|
"version": "1.20.68",
|
|
4
98
|
"date": "2026-08-01T18:03:14.950Z",
|