@blumintinc/eslint-plugin-blumint 1.17.0 → 1.17.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.
- package/lib/index.js +1 -1
- package/lib/rules/no-explicit-return-type.js +40 -1
- package/lib/rules/no-hungarian.js +31 -39
- package/lib/rules/no-type-assertion-returns.js +7 -24
- package/lib/rules/no-unnecessary-verb-suffix.js +0 -3
- package/lib/rules/react-memoize-literals.js +106 -1
- package/package.json +1 -1
- package/release-manifest.json +60 -0
package/lib/index.js
CHANGED
|
@@ -241,6 +241,41 @@ function isTypeGuardFunction(node) {
|
|
|
241
241
|
}
|
|
242
242
|
return false;
|
|
243
243
|
}
|
|
244
|
+
// The names below are the built-in TypeScript read-only wrapper types. When a
|
|
245
|
+
// function is annotated with one of these, the annotation is NOT redundant:
|
|
246
|
+
// TypeScript always infers the mutable concrete type (e.g. Set<T> not
|
|
247
|
+
// ReadonlySet<T>), so stripping the annotation silently changes the public
|
|
248
|
+
// return type and lets callers mutate internal state that the author intended
|
|
249
|
+
// to protect.
|
|
250
|
+
const READONLY_TYPE_NAMES = new Set([
|
|
251
|
+
'ReadonlySet',
|
|
252
|
+
'ReadonlyMap',
|
|
253
|
+
'ReadonlyArray',
|
|
254
|
+
'Readonly',
|
|
255
|
+
]);
|
|
256
|
+
/**
|
|
257
|
+
* Returns true when `returnType` is a read-only widening annotation — i.e.
|
|
258
|
+
* one that TypeScript would NOT infer on its own and whose removal therefore
|
|
259
|
+
* changes the public API. Two forms are covered:
|
|
260
|
+
*
|
|
261
|
+
* TSTypeReference — ReadonlySet<T>, ReadonlyMap<K,V>, ReadonlyArray<T>,
|
|
262
|
+
* Readonly<T>
|
|
263
|
+
* TSTypeOperator — `readonly T[]` and `readonly [a, b]` tuples
|
|
264
|
+
*/
|
|
265
|
+
function isReadonlyWideningReturnType(returnType) {
|
|
266
|
+
const typeAnnotation = returnType.typeAnnotation;
|
|
267
|
+
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
268
|
+
const typeName = typeAnnotation.typeName;
|
|
269
|
+
return (typeName.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
270
|
+
READONLY_TYPE_NAMES.has(typeName.name));
|
|
271
|
+
}
|
|
272
|
+
// `readonly T[]` and `readonly [a, b]` are represented as TSTypeOperator
|
|
273
|
+
// nodes with operator === 'readonly'.
|
|
274
|
+
if (typeAnnotation.type === utils_1.AST_NODE_TYPES.TSTypeOperator) {
|
|
275
|
+
return typeAnnotation.operator === 'readonly';
|
|
276
|
+
}
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
244
279
|
exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
245
280
|
name: 'no-explicit-return-type',
|
|
246
281
|
meta: {
|
|
@@ -297,6 +332,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
297
332
|
if (!returnType)
|
|
298
333
|
return;
|
|
299
334
|
if (isTypeGuardFunction(node) ||
|
|
335
|
+
isReadonlyWideningReturnType(returnType) ||
|
|
300
336
|
(mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node))) {
|
|
301
337
|
return;
|
|
302
338
|
}
|
|
@@ -320,6 +356,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
320
356
|
return;
|
|
321
357
|
}
|
|
322
358
|
if (isTypeGuardFunction(node) ||
|
|
359
|
+
isReadonlyWideningReturnType(returnType) ||
|
|
323
360
|
(mergedOptions.allowRecursiveFunctions && isRecursiveFunction(node))) {
|
|
324
361
|
return;
|
|
325
362
|
}
|
|
@@ -334,7 +371,8 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
334
371
|
const returnType = node.returnType;
|
|
335
372
|
if (!returnType)
|
|
336
373
|
return;
|
|
337
|
-
if (isTypeGuardFunction(node)
|
|
374
|
+
if (isTypeGuardFunction(node) ||
|
|
375
|
+
isReadonlyWideningReturnType(returnType)) {
|
|
338
376
|
return;
|
|
339
377
|
}
|
|
340
378
|
context.report({
|
|
@@ -366,6 +404,7 @@ exports.noExplicitReturnType = (0, createRule_1.createRule)({
|
|
|
366
404
|
if (!returnType)
|
|
367
405
|
return;
|
|
368
406
|
if (isTypeGuardFunction(node.value) ||
|
|
407
|
+
isReadonlyWideningReturnType(returnType) ||
|
|
369
408
|
(mergedOptions.allowAbstractMethodSignatures &&
|
|
370
409
|
isInterfaceOrAbstractMethodSignature(node))) {
|
|
371
410
|
return;
|
|
@@ -10,7 +10,12 @@ const COMMON_TYPES = [
|
|
|
10
10
|
'Boolean',
|
|
11
11
|
'Array',
|
|
12
12
|
'Object',
|
|
13
|
-
'Function',
|
|
13
|
+
// 'Function' is intentionally excluded. Unlike an incidental, rot-prone data
|
|
14
|
+
// type (String/Number/...), a value named *Function is intrinsically and
|
|
15
|
+
// permanently callable, so the marker can never become misleading. Fn/Func/
|
|
16
|
+
// Function are function-ROLE designators (like callback/handler/predicate),
|
|
17
|
+
// not Hungarian type tags — compareFn/mapFn are the ECMAScript/MDN-canonical
|
|
18
|
+
// parameter names. See the ABBREVIATION_MARKERS note and issue #1255.
|
|
14
19
|
// 'Date', too many false positives
|
|
15
20
|
'RegExp',
|
|
16
21
|
'Promise',
|
|
@@ -20,16 +25,11 @@ const COMMON_TYPES = [
|
|
|
20
25
|
// Abbreviation type markers (e.g. str, arr, obj). No English word is spelled this
|
|
21
26
|
// way, so their presence as a segment — even in the middle of a name — is
|
|
22
27
|
// unambiguously a type tag (strName, USER_STR_NAME, ConfigArrSettings).
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'arr',
|
|
29
|
-
'obj',
|
|
30
|
-
'fn',
|
|
31
|
-
'func',
|
|
32
|
-
];
|
|
28
|
+
// `fn`/`func` are deliberately excluded: they abbreviate a value's callable ROLE
|
|
29
|
+
// (like callback/handler/predicate), which is intrinsic and never rots, so
|
|
30
|
+
// checkFn/compareFn/mapFn/renderFunc are legitimate role names, not Hungarian
|
|
31
|
+
// type tags (#1255).
|
|
32
|
+
const ABBREVIATION_MARKERS = ['str', 'num', 'int', 'bool', 'arr', 'obj'];
|
|
33
33
|
// Combined type markers (former Hungarian prefixes and type suffixes)
|
|
34
34
|
const TYPE_MARKERS = [
|
|
35
35
|
...ABBREVIATION_MARKERS,
|
|
@@ -53,7 +53,7 @@ const SINGLE_LETTER_PREFIXES = new Set(['b', 'i']);
|
|
|
53
53
|
// allowed compound noun PhoneNumber. Abbreviation markers (str/arr/obj/...) are
|
|
54
54
|
// deliberately excluded: no English word is spelled that way, so their presence
|
|
55
55
|
// as a segment is unambiguously a type tag even inside a type name.
|
|
56
|
-
const FULL_TYPE_WORDS = new Set(
|
|
56
|
+
const FULL_TYPE_WORDS = new Set(COMMON_TYPES.map((word) => word.toLowerCase()));
|
|
57
57
|
// Allowed descriptive suffixes that should not be flagged as Hungarian notation
|
|
58
58
|
const ALLOWED_SUFFIXES = [
|
|
59
59
|
'Formatted',
|
|
@@ -218,7 +218,7 @@ function splitCamelSegments(name) {
|
|
|
218
218
|
// A TYPE name (alias/interface/class) is exempt from a full-type-word marker when
|
|
219
219
|
// that marker is one clean PascalCase segment among OTHER descriptive segments —
|
|
220
220
|
// i.e. the word denotes a type concept/relation, not a redundant type tag.
|
|
221
|
-
// Examples: StringToNumber, CapitalizedString, PromiseOrValue
|
|
221
|
+
// Examples: StringToNumber, CapitalizedString, PromiseOrValue.
|
|
222
222
|
// Abbreviation markers (str/arr/obj/...) never qualify, so genuine Hungarian type
|
|
223
223
|
// names like UserStrName / ConfigArrSettings / UserObjData still fire.
|
|
224
224
|
function isSemanticTypeConcept(typeName) {
|
|
@@ -353,36 +353,28 @@ exports.noHungarian = (0, createRule_1.createRule)({
|
|
|
353
353
|
return true;
|
|
354
354
|
}
|
|
355
355
|
// Abbreviation markers (str, num, int, bool, arr, obj, fn, func) are
|
|
356
|
-
// short enough that
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
// still does.
|
|
356
|
+
// short enough that a raw-character boundary check can fire on them as
|
|
357
|
+
// substrings inside real English words (e.g. "int" inside "Mint", "str"
|
|
358
|
+
// inside "stream"). Require an exact match against a full camelCase
|
|
359
|
+
// token so that "Mint" → ["Mint"] never matches "int", while genuine
|
|
360
|
+
// Hungarian like intValue → ["int","Value"] still does.
|
|
362
361
|
if (ABBREVIATION_MARKER_SET.has(normalizedMarker)) {
|
|
363
362
|
const segments = splitCamelSegments(variableName);
|
|
364
363
|
return segments.some((s) => s.toLowerCase() === normalizedMarker);
|
|
365
364
|
}
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
//
|
|
377
|
-
//
|
|
378
|
-
|
|
379
|
-
preMarkerPrefix === '_' ||
|
|
380
|
-
/[A-Z]/.test(preMarkerPrefix) ||
|
|
381
|
-
/[A-Z]/.test(variableName[markerIndex]);
|
|
382
|
-
const hasEndBoundary = markerIndex + normalizedMarker.length === normalizedVarName.length ||
|
|
383
|
-
suffix === '_' ||
|
|
384
|
-
/[A-Z]/.test(suffix);
|
|
385
|
-
return hasStartBoundary && hasEndBoundary;
|
|
365
|
+
// Full type-word markers (non-abbreviations: String, Number, Function,
|
|
366
|
+
// Array, Object, Boolean, …) are Hungarian only when they occupy the
|
|
367
|
+
// first or last camelCase segment — a genuine prefix or suffix that
|
|
368
|
+
// tags the entity's runtime type. The prefix/suffix character checks
|
|
369
|
+
// above already return `true` for those positions, so reaching this
|
|
370
|
+
// point means the marker sits in a middle segment of the identifier
|
|
371
|
+
// (e.g. cloud·Function·Registry, user·String·Name). A middle
|
|
372
|
+
// full-type-word qualifies a domain concept ("a registry of cloud
|
|
373
|
+
// functions") rather than redundantly encoding the entity's type.
|
|
374
|
+
// Accepting the resulting false negatives is the deliberate trade-off:
|
|
375
|
+
// middle-segment full-type-words are overwhelmingly domain vocabulary,
|
|
376
|
+
// not type tags.
|
|
377
|
+
return false;
|
|
386
378
|
});
|
|
387
379
|
}
|
|
388
380
|
// Check if the identifier is a built-in method or imported from an external module
|
|
@@ -177,9 +177,13 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
|
|
|
177
177
|
if (node.parent?.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
178
178
|
return true;
|
|
179
179
|
}
|
|
180
|
-
// Allow type assertions
|
|
181
|
-
|
|
182
|
-
|
|
180
|
+
// Allow type assertions that are direct arguments of any call or new expression.
|
|
181
|
+
// The asserted value is an argument — TypeScript structurally checks it against the
|
|
182
|
+
// parameter type — so the returned value is the call's result, not the cast itself.
|
|
183
|
+
// This covers method calls (obj.method(x as T)), plain calls (fn(x as T)), and
|
|
184
|
+
// constructors (new Ctor(x as T)) uniformly.
|
|
185
|
+
if (node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression ||
|
|
186
|
+
node.parent?.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
183
187
|
return true;
|
|
184
188
|
}
|
|
185
189
|
// Allow type assertions within array includes checks
|
|
@@ -195,27 +199,6 @@ exports.noTypeAssertionReturns = (0, createRule_1.createRule)({
|
|
|
195
199
|
if (isPropertyAccess(node) && isInsideConditionalStatement(node)) {
|
|
196
200
|
return true;
|
|
197
201
|
}
|
|
198
|
-
// Allow type assertions in object instantiations (as constructor arguments)
|
|
199
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
200
|
-
node.parent.parent?.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
201
|
-
return true;
|
|
202
|
-
}
|
|
203
|
-
// Allow type assertions as arguments to constructors
|
|
204
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.NewExpression ||
|
|
205
|
-
(node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
206
|
-
node.parent.parent?.type === utils_1.AST_NODE_TYPES.NewExpression)) {
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
// Allow type assertions as arguments to constructor calls
|
|
210
|
-
if (node.parent?.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
211
|
-
let current = node.parent;
|
|
212
|
-
while (current?.parent) {
|
|
213
|
-
if (current.parent.type === utils_1.AST_NODE_TYPES.NewExpression) {
|
|
214
|
-
return true;
|
|
215
|
-
}
|
|
216
|
-
current = current.parent;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
202
|
// Allow type assertions in JSX attributes/props or object properties
|
|
220
203
|
if (isInsideJSXAttributeOrObjectProperty(node)) {
|
|
221
204
|
return true;
|
|
@@ -485,6 +485,98 @@ function isStyleJSXAttributeValue(node) {
|
|
|
485
485
|
}
|
|
486
486
|
return false;
|
|
487
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Returns true when the node is a JSX element or fragment. JSX nodes are
|
|
490
|
+
* always fresh references each render — wrapping a containing literal in
|
|
491
|
+
* useMemo provides no referential-stability benefit because the JSX member
|
|
492
|
+
* changes on every call regardless of the wrapper.
|
|
493
|
+
*/
|
|
494
|
+
function isJSXNode(node) {
|
|
495
|
+
return (node.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
496
|
+
node.type === utils_1.AST_NODE_TYPES.JSXFragment);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Collects the names of variables declared in the owning function's top-level
|
|
500
|
+
* block body whose initializers resolve to JSX elements or fragments. Used to
|
|
501
|
+
* detect shorthand object properties like `{ Portal }` where
|
|
502
|
+
* `const Portal = <div />` precedes the return statement.
|
|
503
|
+
*
|
|
504
|
+
* Only top-level declarations are scanned; inner function scopes have their
|
|
505
|
+
* own render lifecycles and are not candidates for this exemption.
|
|
506
|
+
*/
|
|
507
|
+
function collectLocalJSXBindings(owner) {
|
|
508
|
+
const bindings = new Set();
|
|
509
|
+
const body = owner.body;
|
|
510
|
+
if (body.type !== utils_1.AST_NODE_TYPES.BlockStatement)
|
|
511
|
+
return bindings;
|
|
512
|
+
for (const stmt of body.body) {
|
|
513
|
+
if (stmt.type !== utils_1.AST_NODE_TYPES.VariableDeclaration)
|
|
514
|
+
continue;
|
|
515
|
+
for (const declarator of stmt.declarations) {
|
|
516
|
+
if (declarator.id.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
517
|
+
continue;
|
|
518
|
+
if (!declarator.init)
|
|
519
|
+
continue;
|
|
520
|
+
const unwrapped = unwrapNestedExpressions(declarator.init);
|
|
521
|
+
if (isJSXNode(unwrapped)) {
|
|
522
|
+
bindings.add(declarator.id.name);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return bindings;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Returns true when an ObjectExpression returned from a hook contains at
|
|
530
|
+
* least one property whose value is a JSX element or fragment — either
|
|
531
|
+
* directly inline (`{ Portal: <div /> }`) or via a shorthand identifier
|
|
532
|
+
* (`{ Portal }`) that resolves to a JSX initializer in the same function
|
|
533
|
+
* scope. Such objects cannot be stabilised by wrapping them in useMemo
|
|
534
|
+
* because the JSX member is a fresh reference on every render regardless of
|
|
535
|
+
* the wrapper. This is the same "no stability benefit" rationale applied to
|
|
536
|
+
* sx/style JSX attribute values.
|
|
537
|
+
*/
|
|
538
|
+
function objectLiteralContainsJSXValue(node, owner) {
|
|
539
|
+
// Resolve shorthand bindings lazily — only when at least one shorthand
|
|
540
|
+
// property is present, to avoid the body scan for the common inline case.
|
|
541
|
+
let localJSXBindings = null;
|
|
542
|
+
for (const prop of node.properties) {
|
|
543
|
+
if (prop.type === utils_1.AST_NODE_TYPES.SpreadElement)
|
|
544
|
+
continue;
|
|
545
|
+
const value = unwrapNestedExpressions(prop.value);
|
|
546
|
+
// Direct inline JSX: { Portal: <div /> } or { el: <></> }
|
|
547
|
+
if (isJSXNode(value)) {
|
|
548
|
+
return true;
|
|
549
|
+
}
|
|
550
|
+
// Shorthand identifier: { Portal } — resolve to a local JSX binding.
|
|
551
|
+
if (prop.shorthand && value.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
552
|
+
if (!localJSXBindings) {
|
|
553
|
+
localJSXBindings = collectLocalJSXBindings(owner);
|
|
554
|
+
}
|
|
555
|
+
if (localJSXBindings.has(value.name)) {
|
|
556
|
+
return true;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
return false;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Returns true when an ArrayExpression returned from a hook contains at least
|
|
564
|
+
* one element that is a JSX element or fragment (after unwrapping expression
|
|
565
|
+
* wrappers). The same "no stability benefit" rationale applies: a JSX element
|
|
566
|
+
* is a fresh reference each render, so wrapping the array in useMemo would
|
|
567
|
+
* recompute on every call without improving referential stability.
|
|
568
|
+
*/
|
|
569
|
+
function arrayLiteralContainsJSXValue(node) {
|
|
570
|
+
for (const element of node.elements) {
|
|
571
|
+
if (!element)
|
|
572
|
+
continue;
|
|
573
|
+
const unwrapped = unwrapNestedExpressions(element);
|
|
574
|
+
if (isJSXNode(unwrapped)) {
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
488
580
|
/**
|
|
489
581
|
* Formats a readable label for diagnostics based on the owning function.
|
|
490
582
|
* @param fn Owning component or hook function.
|
|
@@ -511,7 +603,7 @@ exports.reactMemoizeLiterals = (0, createRule_1.createRule)({
|
|
|
511
603
|
messages: {
|
|
512
604
|
componentLiteral: 'New {{literalType}} inside {{context}} is created on every render → Breaks referential stability for hooks/props and can re-run effects or re-render children → Hoist it to a module-level constant or wrap it in {{memoHook}} with the right dependencies.',
|
|
513
605
|
nestedHookLiteral: 'Nested {{literalType}} inside {{hookName}} arguments is recreated on every render → Dependency/reference comparisons change each time and defeat memoization/caching → Extract it into a memoized value (useMemo/useCallback) or hoist it to a module constant before passing it to {{hookName}}.',
|
|
514
|
-
hookReturnLiteral: '{{hookName}} returns
|
|
606
|
+
hookReturnLiteral: '{{hookName}} returns an {{literalType}} on each render → Callers receive a fresh reference and may re-render or re-run effects → Memoize the returned value with useMemo/useCallback or return pre-memoized pieces so callers see stable references.',
|
|
515
607
|
memoizeLiteralSuggestion: 'This {{literalType}} is created inline → It produces a new reference each render → Wrap it in {{memoHook}} and include every closed-over value in the dependency array.',
|
|
516
608
|
},
|
|
517
609
|
},
|
|
@@ -659,6 +751,19 @@ exports.reactMemoizeLiterals = (0, createRule_1.createRule)({
|
|
|
659
751
|
return;
|
|
660
752
|
}
|
|
661
753
|
if (isReturnValueFromHook(node, owner)) {
|
|
754
|
+
// A returned literal whose members include a JSX element or fragment
|
|
755
|
+
// cannot be stabilised by useMemo: JSX nodes are inherently fresh
|
|
756
|
+
// references each render, so the wrapper recomputes every call
|
|
757
|
+
// regardless. This mirrors the "no stability benefit" carve-out for
|
|
758
|
+
// sx/style JSX attribute values (see isStyleJSXAttributeValue).
|
|
759
|
+
if (node.type === utils_1.AST_NODE_TYPES.ObjectExpression &&
|
|
760
|
+
objectLiteralContainsJSXValue(node, owner)) {
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (node.type === utils_1.AST_NODE_TYPES.ArrayExpression &&
|
|
764
|
+
arrayLiteralContainsJSXValue(node)) {
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
662
767
|
const hookName = getFunctionName(owner) ?? 'this hook';
|
|
663
768
|
context.report({
|
|
664
769
|
node,
|
package/package.json
CHANGED
package/release-manifest.json
CHANGED
|
@@ -1,4 +1,64 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"version": "1.17.2",
|
|
4
|
+
"date": "2026-07-02T12:29:19.590Z",
|
|
5
|
+
"rules": [
|
|
6
|
+
{
|
|
7
|
+
"name": "no-explicit-return-type",
|
|
8
|
+
"changeType": "fix",
|
|
9
|
+
"issues": [
|
|
10
|
+
1253
|
|
11
|
+
],
|
|
12
|
+
"summary": "exempt read-only widening return types from removal (closes #1253)"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "no-hungarian",
|
|
16
|
+
"changeType": "fix",
|
|
17
|
+
"issues": [
|
|
18
|
+
1255
|
|
19
|
+
],
|
|
20
|
+
"summary": "treat Fn/Func/Function as function-role designators, not type tags (closes #1255)"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "no-type-assertion-returns",
|
|
24
|
+
"changeType": "fix",
|
|
25
|
+
"issues": [
|
|
26
|
+
1254
|
|
27
|
+
],
|
|
28
|
+
"summary": "allow type assertions as call/new arguments in return position (closes #1254)"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"name": "no-unnecessary-verb-suffix",
|
|
32
|
+
"changeType": "fix",
|
|
33
|
+
"issues": [
|
|
34
|
+
1252
|
|
35
|
+
],
|
|
36
|
+
"summary": "stop flagging Async/Sync execution-model suffixes (closes #1252)"
|
|
37
|
+
}
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"version": "1.17.1",
|
|
42
|
+
"date": "2026-07-01T22:31:34.840Z",
|
|
43
|
+
"rules": [
|
|
44
|
+
{
|
|
45
|
+
"name": "no-hungarian",
|
|
46
|
+
"changeType": "fix",
|
|
47
|
+
"issues": [
|
|
48
|
+
1250
|
|
49
|
+
],
|
|
50
|
+
"summary": "exempt middle-segment full-type-words from Hungarian detection (closes #1250)"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
"name": "react-memoize-literals",
|
|
54
|
+
"changeType": "fix",
|
|
55
|
+
"issues": [
|
|
56
|
+
1251
|
|
57
|
+
],
|
|
58
|
+
"summary": "exempt hook returns containing JSX-valued members (closes #1251)"
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
},
|
|
2
62
|
{
|
|
3
63
|
"version": "1.17.0",
|
|
4
64
|
"date": "2026-07-01T05:16:18.225Z",
|