@blumintinc/eslint-plugin-blumint 1.14.0 → 1.16.0
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 +36 -14
- package/lib/index.js +20 -12
- package/lib/rules/avoid-utils-directory.js +0 -4
- package/lib/rules/consistent-callback-naming.js +68 -3
- package/lib/rules/dynamic-https-errors.d.ts +1 -1
- package/lib/rules/dynamic-https-errors.js +119 -49
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +1 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +86 -331
- package/lib/rules/enforce-date-ttime.d.ts +1 -0
- package/lib/rules/enforce-date-ttime.js +156 -0
- package/lib/rules/enforce-dynamic-firebase-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-firebase-imports.js +3 -2
- package/lib/rules/enforce-dynamic-imports.d.ts +2 -1
- package/lib/rules/enforce-dynamic-imports.js +42 -21
- package/lib/rules/enforce-f-extension-for-entry-points.d.ts +8 -0
- package/lib/rules/enforce-f-extension-for-entry-points.js +283 -0
- package/lib/rules/enforce-global-constants.js +3 -3
- package/lib/rules/enforce-id-capitalization.js +1 -1
- package/lib/rules/enforce-memoize-async.js +66 -15
- package/lib/rules/enforce-mui-rounded-icons.js +42 -1
- package/lib/rules/enforce-props-argument-name.js +42 -16
- package/lib/rules/enforce-stable-hash-spread-props.js +3 -3
- package/lib/rules/enforce-transform-memoization.js +1 -1
- package/lib/rules/enforce-verb-noun-naming.js +3817 -4641
- package/lib/rules/global-const-style.js +25 -4
- package/lib/rules/logical-top-to-bottom-grouping.js +80 -2
- package/lib/rules/memo-compare-deeply-complex-props.js +183 -6
- package/lib/rules/memo-nested-react-components.js +243 -103
- package/lib/rules/no-array-length-in-deps.js +74 -3
- package/lib/rules/no-async-foreach.js +7 -2
- package/lib/rules/no-circular-references.d.ts +2 -1
- package/lib/rules/no-circular-references.js +150 -489
- package/lib/rules/no-compositing-layer-props.js +31 -0
- package/lib/rules/no-console-error.js +12 -10
- package/lib/rules/no-empty-dependency-use-callbacks.js +1 -1
- package/lib/rules/no-entire-object-hook-deps.js +147 -65
- package/lib/rules/no-excessive-parent-chain.js +3 -0
- package/lib/rules/no-explicit-return-type.js +6 -0
- package/lib/rules/no-hungarian.js +119 -24
- package/lib/rules/no-inline-component-prop.js +16 -7
- package/lib/rules/no-margin-properties.js +7 -38
- package/lib/rules/no-passthrough-getters.d.ts +2 -2
- package/lib/rules/no-passthrough-getters.js +83 -1
- package/lib/rules/no-redundant-this-params.js +50 -1
- package/lib/rules/no-unmemoized-memo-without-props.js +1 -1
- package/lib/rules/no-unnecessary-destructuring-rename.js +2 -5
- package/lib/rules/no-unnecessary-verb-suffix.js +79 -0
- package/lib/rules/no-unused-props.js +215 -37
- package/lib/rules/no-useless-fragment.js +10 -2
- package/lib/rules/parallelize-async-operations.js +117 -54
- package/lib/rules/prefer-nullish-coalescing-boolean-props.js +109 -4
- package/lib/rules/prefer-params-over-parent-id.js +1 -1
- package/lib/rules/prefer-settings-object.js +27 -10
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +75 -4
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -14
- package/lib/rules/prefer-usememo-over-useeffect-usestate.js +1 -1
- package/lib/rules/prevent-children-clobber.js +9 -5
- package/lib/rules/react-memoize-literals.js +218 -13
- package/lib/rules/require-https-error-cause.js +30 -11
- package/lib/rules/require-memo.js +17 -9
- package/lib/rules/require-migration-script-metadata.d.ts +9 -0
- package/lib/rules/require-migration-script-metadata.js +206 -0
- package/lib/rules/warn-https-error-message-user-friendly.d.ts +1 -0
- package/lib/rules/warn-https-error-message-user-friendly.js +239 -0
- package/lib/utils/ASTHelpers.d.ts +49 -1
- package/lib/utils/ASTHelpers.js +394 -112
- package/package.json +7 -6
- package/release-manifest.json +166 -0
|
@@ -1,9 +1,73 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
26
|
exports.preferNullishCoalescingBooleanProps = void 0;
|
|
4
27
|
const utils_1 = require("@typescript-eslint/utils");
|
|
28
|
+
const ts = __importStar(require("typescript"));
|
|
5
29
|
const createRule_1 = require("../utils/createRule");
|
|
6
30
|
const BOOLEAN_PROP_REGEX = /^(is|has|should|can|will|do|does|did|was|were|enable|disable)/;
|
|
31
|
+
function isBooleanType(type, checker) {
|
|
32
|
+
if (type.isUnion()) {
|
|
33
|
+
return type.types.every((t) => isBooleanType(t, checker));
|
|
34
|
+
}
|
|
35
|
+
// For type parameters, check if the constraint is a boolean type
|
|
36
|
+
if (type.getFlags() & ts.TypeFlags.TypeParameter) {
|
|
37
|
+
if (checker) {
|
|
38
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
39
|
+
if (constraint && constraint !== type) {
|
|
40
|
+
return isBooleanType(constraint, checker);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
return ((type.getFlags() & (ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral)) !==
|
|
46
|
+
0);
|
|
47
|
+
}
|
|
48
|
+
function isPossiblyNullish(type, checker) {
|
|
49
|
+
if (type.isUnion()) {
|
|
50
|
+
return type.types.some((t) => isPossiblyNullish(t, checker));
|
|
51
|
+
}
|
|
52
|
+
// For type parameters, check if the constraint excludes nullish
|
|
53
|
+
if (type.getFlags() & ts.TypeFlags.TypeParameter) {
|
|
54
|
+
if (checker) {
|
|
55
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
56
|
+
if (constraint && constraint !== type) {
|
|
57
|
+
return isPossiblyNullish(constraint, checker);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// If we can't determine the constraint, assume it could be nullish
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
return ((type.getFlags() &
|
|
64
|
+
(ts.TypeFlags.Null |
|
|
65
|
+
ts.TypeFlags.Undefined |
|
|
66
|
+
ts.TypeFlags.Void |
|
|
67
|
+
ts.TypeFlags.Any |
|
|
68
|
+
ts.TypeFlags.Unknown)) !==
|
|
69
|
+
0);
|
|
70
|
+
}
|
|
7
71
|
function isInJSXBooleanAttribute(node) {
|
|
8
72
|
const parent = node.parent;
|
|
9
73
|
if (parent?.type !== utils_1.AST_NODE_TYPES.JSXAttribute)
|
|
@@ -57,7 +121,34 @@ function isInConditionalContext(node) {
|
|
|
57
121
|
/**
|
|
58
122
|
* Determines if a node is within a boolean context in JSX props or other boolean contexts
|
|
59
123
|
*/
|
|
60
|
-
function isInBooleanContext(node) {
|
|
124
|
+
function isInBooleanContext(node, checker, parserServices) {
|
|
125
|
+
if (checker && parserServices) {
|
|
126
|
+
try {
|
|
127
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
128
|
+
const contextualType = checker.getContextualType(tsNode);
|
|
129
|
+
if (contextualType && isBooleanType(contextualType, checker)) {
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
// Also check if the expression itself is a boolean type
|
|
133
|
+
const actualType = checker.getTypeAtLocation(tsNode);
|
|
134
|
+
if (actualType && isBooleanType(actualType, checker)) {
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
// If this is a logical expression, also check if the left operand is a boolean
|
|
138
|
+
if (node.type === utils_1.AST_NODE_TYPES.LogicalExpression) {
|
|
139
|
+
const leftTSNode = parserServices.esTreeNodeToTSNodeMap.get(node.left);
|
|
140
|
+
const leftType = checker.getTypeAtLocation(leftTSNode);
|
|
141
|
+
if (leftType && isBooleanType(leftType, checker)) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
// esTreeNodeToTSNodeMap may fail for synthetic nodes or nodes without
|
|
148
|
+
// source positions; getContextualType may return undefined for non-expression
|
|
149
|
+
// contexts. Fall back to AST-based heuristics in these cases.
|
|
150
|
+
}
|
|
151
|
+
}
|
|
61
152
|
let current = node;
|
|
62
153
|
// Traverse up the AST to find if we're in a boolean context
|
|
63
154
|
while (current && current.parent) {
|
|
@@ -365,7 +456,19 @@ function isInBooleanContext(node) {
|
|
|
365
456
|
/**
|
|
366
457
|
* Checks if the left operand could be nullish (null or undefined)
|
|
367
458
|
*/
|
|
368
|
-
function couldBeNullish(node) {
|
|
459
|
+
function couldBeNullish(node, checker, parserServices) {
|
|
460
|
+
if (checker && parserServices) {
|
|
461
|
+
try {
|
|
462
|
+
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node);
|
|
463
|
+
const type = checker.getTypeAtLocation(tsNode);
|
|
464
|
+
return isPossiblyNullish(type, checker);
|
|
465
|
+
}
|
|
466
|
+
catch {
|
|
467
|
+
// esTreeNodeToTSNodeMap may fail for synthetic nodes or nodes without
|
|
468
|
+
// source positions; getTypeAtLocation may throw for nodes without
|
|
469
|
+
// type information. Fall back to manual check.
|
|
470
|
+
}
|
|
471
|
+
}
|
|
369
472
|
// For literals, check the actual value
|
|
370
473
|
if (node.type === utils_1.AST_NODE_TYPES.Literal) {
|
|
371
474
|
return node.value === null || node.value === undefined;
|
|
@@ -402,16 +505,18 @@ exports.preferNullishCoalescingBooleanProps = (0, createRule_1.createRule)({
|
|
|
402
505
|
},
|
|
403
506
|
defaultOptions: [],
|
|
404
507
|
create(context) {
|
|
508
|
+
const parserServices = utils_1.ESLintUtils.getParserServices(context, true);
|
|
509
|
+
const checker = parserServices?.program?.getTypeChecker();
|
|
405
510
|
return {
|
|
406
511
|
LogicalExpression(node) {
|
|
407
512
|
if (node.operator === '||') {
|
|
408
513
|
// If the node is in a boolean context, we allow logical OR
|
|
409
|
-
if (isInBooleanContext(node)) {
|
|
514
|
+
if (isInBooleanContext(node, checker, parserServices)) {
|
|
410
515
|
return;
|
|
411
516
|
}
|
|
412
517
|
// Check if this could benefit from nullish coalescing
|
|
413
518
|
// We only suggest nullish coalescing when the left operand could be nullish
|
|
414
|
-
if (couldBeNullish(node.left)) {
|
|
519
|
+
if (couldBeNullish(node.left, checker, parserServices)) {
|
|
415
520
|
const sourceCode = context.getSourceCode();
|
|
416
521
|
const leftText = sourceCode.getText(node.left);
|
|
417
522
|
const rightText = sourceCode.getText(node.right);
|
|
@@ -202,7 +202,7 @@ exports.preferParamsOverParentId = (0, createRule_1.createRule)({
|
|
|
202
202
|
meta: {
|
|
203
203
|
type: 'suggestion',
|
|
204
204
|
docs: {
|
|
205
|
-
description: 'Prefer
|
|
205
|
+
description: 'Prefer event.params over ref.parent.id for type-safe Firebase trigger paths.',
|
|
206
206
|
recommended: 'error',
|
|
207
207
|
},
|
|
208
208
|
fixable: 'code',
|
|
@@ -47,6 +47,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
47
47
|
defaultOptions: [defaultOptions],
|
|
48
48
|
create(context, [options]) {
|
|
49
49
|
const finalOptions = { ...defaultOptions, ...options };
|
|
50
|
+
const { sourceCode } = context;
|
|
50
51
|
function getParameterType(param) {
|
|
51
52
|
if (param.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
52
53
|
return getParameterType(param.left);
|
|
@@ -56,13 +57,10 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
56
57
|
const typeNode = param.typeAnnotation.typeAnnotation;
|
|
57
58
|
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
58
59
|
// Include type parameters in the type signature to differentiate generic types
|
|
59
|
-
const typeName = typeNode.typeName
|
|
60
|
-
? typeNode.typeName.name
|
|
61
|
-
: 'unknown';
|
|
60
|
+
const typeName = sourceCode.getText(typeNode.typeName);
|
|
62
61
|
// If there are type parameters, include them in the type signature
|
|
63
62
|
if (typeNode.typeParameters &&
|
|
64
63
|
typeNode.typeParameters.params.length > 0) {
|
|
65
|
-
const sourceCode = context.sourceCode;
|
|
66
64
|
const typeParams = typeNode.typeParameters.params
|
|
67
65
|
.map((param) => sourceCode.getText(param))
|
|
68
66
|
.join(', ');
|
|
@@ -76,13 +74,10 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
76
74
|
const typeNode = param.typeAnnotation.typeAnnotation;
|
|
77
75
|
if (typeNode.type === utils_1.AST_NODE_TYPES.TSTypeReference) {
|
|
78
76
|
// Include type parameters in the type signature to differentiate generic types
|
|
79
|
-
const typeName = typeNode.typeName
|
|
80
|
-
? typeNode.typeName.name
|
|
81
|
-
: 'unknown';
|
|
77
|
+
const typeName = sourceCode.getText(typeNode.typeName);
|
|
82
78
|
// If there are type parameters, include them in the type signature
|
|
83
79
|
if (typeNode.typeParameters &&
|
|
84
80
|
typeNode.typeParameters.params.length > 0) {
|
|
85
|
-
const sourceCode = context.sourceCode;
|
|
86
81
|
const typeParams = typeNode.typeParameters.params
|
|
87
82
|
.map((param) => sourceCode.getText(param))
|
|
88
83
|
.join(', ');
|
|
@@ -98,12 +93,14 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
98
93
|
return 'boolean';
|
|
99
94
|
return typeNode.type;
|
|
100
95
|
}
|
|
101
|
-
return
|
|
96
|
+
return null;
|
|
102
97
|
}
|
|
103
98
|
function findDuplicateParameterType(params) {
|
|
104
99
|
const typeMap = new Map();
|
|
105
100
|
for (const param of params) {
|
|
106
101
|
const type = getParameterType(param);
|
|
102
|
+
if (type === null)
|
|
103
|
+
continue;
|
|
107
104
|
const count = (typeMap.get(type) || 0) + 1;
|
|
108
105
|
typeMap.set(type, count);
|
|
109
106
|
if (count > 1) {
|
|
@@ -248,6 +245,24 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
248
245
|
}
|
|
249
246
|
return false;
|
|
250
247
|
}
|
|
248
|
+
function hasImplicitlyTypedParams(params) {
|
|
249
|
+
return params.some((param) => {
|
|
250
|
+
let current = param;
|
|
251
|
+
if (current.type === utils_1.AST_NODE_TYPES.TSParameterProperty) {
|
|
252
|
+
current = current.parameter;
|
|
253
|
+
}
|
|
254
|
+
if (current.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
|
|
255
|
+
current = current.left;
|
|
256
|
+
}
|
|
257
|
+
if (current.type === utils_1.AST_NODE_TYPES.Identifier ||
|
|
258
|
+
current.type === utils_1.AST_NODE_TYPES.ObjectPattern ||
|
|
259
|
+
current.type === utils_1.AST_NODE_TYPES.ArrayPattern ||
|
|
260
|
+
current.type === utils_1.AST_NODE_TYPES.RestElement) {
|
|
261
|
+
return !current.typeAnnotation;
|
|
262
|
+
}
|
|
263
|
+
return true;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
251
266
|
function shouldIgnoreNode(node) {
|
|
252
267
|
// Ignore built-in objects and third-party modules
|
|
253
268
|
if (isBuiltInOrThirdParty(node))
|
|
@@ -270,7 +285,7 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
270
285
|
parent = parent.parent;
|
|
271
286
|
}
|
|
272
287
|
}
|
|
273
|
-
// Ignore functions with A/B pattern parameters
|
|
288
|
+
// Ignore functions with A/B pattern parameters or missing type annotations
|
|
274
289
|
if ((node.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
275
290
|
node.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
276
291
|
node.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
@@ -279,6 +294,8 @@ exports.preferSettingsObject = (0, createRule_1.createRule)({
|
|
|
279
294
|
Array.isArray(node.params)) {
|
|
280
295
|
if (hasABPattern(node.params))
|
|
281
296
|
return true;
|
|
297
|
+
if (hasImplicitlyTypedParams(node.params))
|
|
298
|
+
return true;
|
|
282
299
|
}
|
|
283
300
|
// Check if the function is a handler with transaction parameter
|
|
284
301
|
// This is a common pattern in Firebase/Firestore handlers
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.preferTypeAliasOverTypeofConstant = void 0;
|
|
4
4
|
const utils_1 = require("@typescript-eslint/utils");
|
|
5
5
|
const createRule_1 = require("../utils/createRule");
|
|
6
|
+
const ASTHelpers_1 = require("../utils/ASTHelpers");
|
|
6
7
|
const PAREN_TYPE = utils_1.AST_NODE_TYPES.TSParenthesizedType ??
|
|
7
8
|
'TSParenthesizedType';
|
|
8
9
|
const isParenthesizedType = (node) => {
|
|
@@ -87,6 +88,71 @@ function collectReferencedTypeNames(node, acc = new Set()) {
|
|
|
87
88
|
collectReferencedTypeNames(e, acc);
|
|
88
89
|
break;
|
|
89
90
|
}
|
|
91
|
+
case utils_1.AST_NODE_TYPES.TSIndexedAccessType: {
|
|
92
|
+
const idx = node;
|
|
93
|
+
collectReferencedTypeNames(idx.objectType, acc);
|
|
94
|
+
collectReferencedTypeNames(idx.indexType, acc);
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
case utils_1.AST_NODE_TYPES.TSMappedType: {
|
|
98
|
+
const mapped = node;
|
|
99
|
+
if (mapped.typeAnnotation)
|
|
100
|
+
collectReferencedTypeNames(mapped.typeAnnotation, acc);
|
|
101
|
+
if (mapped.nameType)
|
|
102
|
+
collectReferencedTypeNames(mapped.nameType, acc);
|
|
103
|
+
if (mapped.typeParameter && mapped.typeParameter.constraint) {
|
|
104
|
+
collectReferencedTypeNames(mapped.typeParameter.constraint, acc);
|
|
105
|
+
}
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
case utils_1.AST_NODE_TYPES.TSConditionalType: {
|
|
109
|
+
const cond = node;
|
|
110
|
+
collectReferencedTypeNames(cond.checkType, acc);
|
|
111
|
+
collectReferencedTypeNames(cond.extendsType, acc);
|
|
112
|
+
collectReferencedTypeNames(cond.trueType, acc);
|
|
113
|
+
collectReferencedTypeNames(cond.falseType, acc);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case utils_1.AST_NODE_TYPES.TSTypeLiteral: {
|
|
117
|
+
const lit = node;
|
|
118
|
+
for (const m of lit.members) {
|
|
119
|
+
if (m.type === utils_1.AST_NODE_TYPES.TSPropertySignature && m.typeAnnotation) {
|
|
120
|
+
collectReferencedTypeNames(m.typeAnnotation.typeAnnotation, acc);
|
|
121
|
+
}
|
|
122
|
+
else if (m.type === utils_1.AST_NODE_TYPES.TSMethodSignature) {
|
|
123
|
+
if (m.returnType) {
|
|
124
|
+
collectReferencedTypeNames(m.returnType.typeAnnotation, acc);
|
|
125
|
+
}
|
|
126
|
+
for (const p of m.params) {
|
|
127
|
+
if (p.typeAnnotation) {
|
|
128
|
+
collectReferencedTypeNames(p.typeAnnotation.typeAnnotation, acc);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
else if (m.type === utils_1.AST_NODE_TYPES.TSIndexSignature) {
|
|
133
|
+
if (m.typeAnnotation) {
|
|
134
|
+
collectReferencedTypeNames(m.typeAnnotation.typeAnnotation, acc);
|
|
135
|
+
}
|
|
136
|
+
for (const p of m.parameters) {
|
|
137
|
+
if (p.typeAnnotation) {
|
|
138
|
+
collectReferencedTypeNames(p.typeAnnotation.typeAnnotation, acc);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else if (m.type === utils_1.AST_NODE_TYPES.TSCallSignatureDeclaration ||
|
|
143
|
+
m.type === utils_1.AST_NODE_TYPES.TSConstructSignatureDeclaration) {
|
|
144
|
+
if (m.returnType) {
|
|
145
|
+
collectReferencedTypeNames(m.returnType.typeAnnotation, acc);
|
|
146
|
+
}
|
|
147
|
+
for (const p of m.params) {
|
|
148
|
+
if (p.typeAnnotation) {
|
|
149
|
+
collectReferencedTypeNames(p.typeAnnotation.typeAnnotation, acc);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
90
156
|
default: {
|
|
91
157
|
if (isParenthesizedType(node)) {
|
|
92
158
|
collectReferencedTypeNames(node.typeAnnotation, acc);
|
|
@@ -225,10 +291,15 @@ exports.preferTypeAliasOverTypeofConstant = (0, createRule_1.createRule)({
|
|
|
225
291
|
TSTypeQuery(node) {
|
|
226
292
|
if (!collected)
|
|
227
293
|
return;
|
|
228
|
-
|
|
229
|
-
if (
|
|
230
|
-
|
|
231
|
-
|
|
294
|
+
const ancestors = ASTHelpers_1.ASTHelpers.getAncestors(context, node);
|
|
295
|
+
// Skip if inside a type alias declaration (Issue #1117, #1175)
|
|
296
|
+
// This allows 'type T = typeof CONST' as the canonical way to define the alias.
|
|
297
|
+
if (ancestors.some((a) => a.type === utils_1.AST_NODE_TYPES.TSTypeAliasDeclaration)) {
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
// Skip `keyof typeof X` as it's a canonical way to derive a union of keys from a constant object.
|
|
301
|
+
if (ancestors.some((a) => a.type === utils_1.AST_NODE_TYPES.TSTypeOperator &&
|
|
302
|
+
a.operator === 'keyof')) {
|
|
232
303
|
return;
|
|
233
304
|
}
|
|
234
305
|
if (node.exprName.type !== utils_1.AST_NODE_TYPES.Identifier) {
|
|
@@ -60,8 +60,6 @@ function isNonPrimitiveWithoutTypes(expr) {
|
|
|
60
60
|
switch (expr.type) {
|
|
61
61
|
case utils_1.AST_NODE_TYPES.ArrayExpression:
|
|
62
62
|
case utils_1.AST_NODE_TYPES.ObjectExpression:
|
|
63
|
-
case utils_1.AST_NODE_TYPES.FunctionExpression:
|
|
64
|
-
case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
|
|
65
63
|
case utils_1.AST_NODE_TYPES.NewExpression:
|
|
66
64
|
case utils_1.AST_NODE_TYPES.ClassExpression:
|
|
67
65
|
return true;
|
|
@@ -74,6 +72,8 @@ function isNonPrimitiveWithoutTypes(expr) {
|
|
|
74
72
|
case utils_1.AST_NODE_TYPES.MemberExpression:
|
|
75
73
|
case utils_1.AST_NODE_TYPES.ChainExpression:
|
|
76
74
|
case utils_1.AST_NODE_TYPES.CallExpression:
|
|
75
|
+
case utils_1.AST_NODE_TYPES.FunctionExpression:
|
|
76
|
+
case utils_1.AST_NODE_TYPES.ArrowFunctionExpression:
|
|
77
77
|
default:
|
|
78
78
|
return false;
|
|
79
79
|
}
|
|
@@ -92,9 +92,9 @@ function isNonPrimitiveWithTypes(context, expr) {
|
|
|
92
92
|
if (!tsNode)
|
|
93
93
|
return false;
|
|
94
94
|
const type = checker.getTypeAtLocation(tsNode);
|
|
95
|
-
//
|
|
95
|
+
// Explicitly ignore functions even though they are non-primitive
|
|
96
96
|
if (type.getCallSignatures().length > 0)
|
|
97
|
-
return
|
|
97
|
+
return false;
|
|
98
98
|
// Avoid guessing for unknown/any or non-function types
|
|
99
99
|
if (type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown))
|
|
100
100
|
return false;
|
|
@@ -243,7 +243,7 @@ function isImportedIdentifier(context, name) {
|
|
|
243
243
|
}
|
|
244
244
|
return false;
|
|
245
245
|
}
|
|
246
|
-
function
|
|
246
|
+
function identifierUsedAsObjectOrArray(callback, name) {
|
|
247
247
|
const stack = [callback.body];
|
|
248
248
|
while (stack.length) {
|
|
249
249
|
const node = stack.pop();
|
|
@@ -268,12 +268,6 @@ function identifierUsedAsObjectOrFunction(callback, name) {
|
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
|
-
if (node.type === utils_1.AST_NODE_TYPES.CallExpression) {
|
|
272
|
-
const callee = node.callee;
|
|
273
|
-
if (callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === name) {
|
|
274
|
-
return true; // function usage
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
271
|
if (node.type === utils_1.AST_NODE_TYPES.JSXSpreadAttribute) {
|
|
278
272
|
if (node.argument.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
279
273
|
node.argument.name === name) {
|
|
@@ -313,13 +307,13 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
|
|
|
313
307
|
meta: {
|
|
314
308
|
type: 'suggestion',
|
|
315
309
|
docs: {
|
|
316
|
-
description: 'Enforce using useDeepCompareMemo when dependency array contains non-primitive values (objects, arrays
|
|
310
|
+
description: 'Enforce using useDeepCompareMemo when dependency array contains non-primitive values (objects, arrays) that are not already memoized. This prevents unnecessary re-renders due to reference changes.',
|
|
317
311
|
recommended: 'error',
|
|
318
312
|
},
|
|
319
313
|
fixable: 'code',
|
|
320
314
|
schema: [],
|
|
321
315
|
messages: {
|
|
322
|
-
preferUseDeepCompareMemo: 'Dependency array for "{{hook}}" includes objects/arrays
|
|
316
|
+
preferUseDeepCompareMemo: 'Dependency array for "{{hook}}" includes objects/arrays that change identity each render, so React treats them as changed and reruns the memoized computation, triggering avoidable renders. Use useDeepCompareMemo (or memoize those dependencies first) so comparisons use deep equality and the memo stays stable.',
|
|
323
317
|
},
|
|
324
318
|
},
|
|
325
319
|
defaultOptions: [],
|
|
@@ -366,7 +360,7 @@ exports.preferUseDeepCompareMemo = (0, createRule_1.createRule)({
|
|
|
366
360
|
if (isImportedIdentifier(context, expr.name)) {
|
|
367
361
|
isNonPrimitive = false;
|
|
368
362
|
}
|
|
369
|
-
else if (
|
|
363
|
+
else if (identifierUsedAsObjectOrArray(callback, expr.name) &&
|
|
370
364
|
!isIdentifierMemoizedAbove(expr.name, memoizedIds)) {
|
|
371
365
|
isNonPrimitive = true;
|
|
372
366
|
}
|
|
@@ -11,7 +11,7 @@ exports.preferUseMemoOverUseEffectUseState = (0, createRule_1.createRule)({
|
|
|
11
11
|
recommended: 'error',
|
|
12
12
|
},
|
|
13
13
|
messages: {
|
|
14
|
-
preferUseMemo: '
|
|
14
|
+
preferUseMemo: 'Use useMemo to compute derived state "{{stateName}}" instead of useEffect + useState to avoid extra render cycles and stale snapshots.',
|
|
15
15
|
},
|
|
16
16
|
schema: [],
|
|
17
17
|
},
|
|
@@ -44,12 +44,12 @@ function resolveFunctionName(node) {
|
|
|
44
44
|
}
|
|
45
45
|
return null;
|
|
46
46
|
}
|
|
47
|
-
function isComponentLike(node) {
|
|
47
|
+
function isComponentLike(node, context) {
|
|
48
48
|
const name = resolveFunctionName(node);
|
|
49
49
|
if (name && /^[A-Z]/.test(name)) {
|
|
50
50
|
return true;
|
|
51
51
|
}
|
|
52
|
-
return ASTHelpers_1.ASTHelpers.returnsJSX(node.body);
|
|
52
|
+
return ASTHelpers_1.ASTHelpers.returnsJSX(node.body, context);
|
|
53
53
|
}
|
|
54
54
|
function patternHasChildrenProperty(pattern) {
|
|
55
55
|
return pattern.properties.some((prop) => {
|
|
@@ -266,7 +266,8 @@ function bindingMayContainChildren(binding, context) {
|
|
|
266
266
|
return false;
|
|
267
267
|
if (binding.typeAnnotationExcludesProperty)
|
|
268
268
|
return false;
|
|
269
|
-
const services = context.sourceCode?.parserServices ??
|
|
269
|
+
const services = context.sourceCode?.parserServices ??
|
|
270
|
+
context.parserServices;
|
|
270
271
|
if (!services?.program || !services?.esTreeNodeToTSNodeMap) {
|
|
271
272
|
return true;
|
|
272
273
|
}
|
|
@@ -410,7 +411,7 @@ exports.preventChildrenClobber = (0, createRule_1.createRule)({
|
|
|
410
411
|
return {
|
|
411
412
|
':function'(node) {
|
|
412
413
|
const ctx = {
|
|
413
|
-
isComponent: isComponentLike(node),
|
|
414
|
+
isComponent: isComponentLike(node, context),
|
|
414
415
|
bindings: new Map(),
|
|
415
416
|
propsLikeIdentifiers: new Set(),
|
|
416
417
|
childrenValueSourceIds: new Map(),
|
|
@@ -510,7 +511,10 @@ exports.preventChildrenClobber = (0, createRule_1.createRule)({
|
|
|
510
511
|
if (!bindingMayContainChildren(binding, context)) {
|
|
511
512
|
continue;
|
|
512
513
|
}
|
|
513
|
-
offendingSpreads.push({
|
|
514
|
+
offendingSpreads.push({
|
|
515
|
+
name,
|
|
516
|
+
childrenSourceId: binding.childrenSourceId,
|
|
517
|
+
});
|
|
514
518
|
}
|
|
515
519
|
if (offendingSpreads.length === 0)
|
|
516
520
|
return;
|