@blumintinc/eslint-plugin-blumint 1.15.0 → 1.16.1
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 +28 -5
- package/lib/index.js +7 -1
- package/lib/rules/consistent-callback-naming.js +26 -0
- package/lib/rules/dynamic-https-errors.js +5 -26
- package/lib/rules/enforce-assert-safe-object-key.js +29 -0
- package/lib/rules/enforce-boolean-naming-prefixes.d.ts +1 -0
- package/lib/rules/enforce-boolean-naming-prefixes.js +86 -119
- package/lib/rules/enforce-dynamic-imports.d.ts +5 -1
- package/lib/rules/enforce-dynamic-imports.js +90 -19
- package/lib/rules/enforce-memoize-async.js +1 -4
- package/lib/rules/enforce-mui-rounded-icons.js +42 -1
- package/lib/rules/enforce-verb-noun-naming.js +3 -0
- package/lib/rules/global-const-style.js +9 -0
- package/lib/rules/logical-top-to-bottom-grouping.js +80 -2
- package/lib/rules/memo-compare-deeply-complex-props.js +167 -3
- package/lib/rules/memo-nested-react-components.js +143 -8
- package/lib/rules/no-array-length-in-deps.js +74 -3
- package/lib/rules/no-circular-references.js +145 -482
- package/lib/rules/no-compositing-layer-props.js +31 -0
- package/lib/rules/no-entire-object-hook-deps.js +132 -97
- package/lib/rules/no-explicit-return-type.js +6 -0
- package/lib/rules/no-hungarian.js +119 -24
- package/lib/rules/no-margin-properties.js +7 -38
- 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 +1 -3
- package/lib/rules/prefer-type-alias-over-typeof-constant.js +73 -11
- package/lib/rules/prefer-use-deep-compare-memo.js +8 -14
- package/lib/rules/react-memoize-literals.js +87 -1
- package/lib/rules/require-memo.js +8 -0
- 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 +15 -0
- package/lib/utils/ASTHelpers.js +48 -0
- package/package.json +7 -6
- package/release-manifest.json +196 -0
- package/lib/rules/prefer-memoized-props.d.ts +0 -3
- package/lib/rules/prefer-memoized-props.js +0 -445
- package/lib/rules/prefer-nullish-coalescing-override.d.ts +0 -7
- package/lib/rules/prefer-nullish-coalescing-override.js +0 -188
- package/lib/rules/require-usememo-object-literals.d.ts +0 -4
- package/lib/rules/require-usememo-object-literals.js +0 -64
|
@@ -1,445 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.preferMemoizedProps = void 0;
|
|
4
|
-
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
-
const createRule_1 = require("../utils/createRule");
|
|
6
|
-
const HOOK_NAMES = new Set(['useMemo', 'useCallback']);
|
|
7
|
-
exports.preferMemoizedProps = (0, createRule_1.createRule)({
|
|
8
|
-
name: 'prefer-memoized-props',
|
|
9
|
-
meta: {
|
|
10
|
-
type: 'suggestion',
|
|
11
|
-
docs: {
|
|
12
|
-
description: 'Require memoizing reference props (objects, arrays, functions) inside React.memo components while avoiding unnecessary useMemo for pass-through values.',
|
|
13
|
-
recommended: 'error',
|
|
14
|
-
},
|
|
15
|
-
schema: [],
|
|
16
|
-
messages: {
|
|
17
|
-
memoizeReferenceProp: 'Prop "{{propName}}" in a React.memo component receives a {{kind}} that is recreated every render, so memoized children lose referential equality and re-render. Memoize this {{kind}} with useMemo/useCallback or hoist a stable constant so the prop reference stays stable.',
|
|
18
|
-
avoidPrimitiveMemo: 'useMemo around "{{value}}" only wraps a pass-through value. Primitives already compare by value, and wrapping existing references without creating new objects does not improve stability. Return the value directly instead of adding memoization noise.',
|
|
19
|
-
},
|
|
20
|
-
},
|
|
21
|
-
defaultOptions: [],
|
|
22
|
-
create(context) {
|
|
23
|
-
const sourceCode = context.getSourceCode();
|
|
24
|
-
const visitorKeys = sourceCode.visitorKeys;
|
|
25
|
-
const memoizedComponents = new WeakSet();
|
|
26
|
-
const memoScopes = [];
|
|
27
|
-
const componentStack = [];
|
|
28
|
-
const scopedFunctions = new WeakSet();
|
|
29
|
-
function pushMemoScope() {
|
|
30
|
-
memoScopes.push(new Map());
|
|
31
|
-
}
|
|
32
|
-
function popMemoScope() {
|
|
33
|
-
memoScopes.pop();
|
|
34
|
-
}
|
|
35
|
-
function currentMemoScope() {
|
|
36
|
-
return memoScopes[memoScopes.length - 1];
|
|
37
|
-
}
|
|
38
|
-
function declareFunctionBinding(name, node) {
|
|
39
|
-
const scope = currentMemoScope();
|
|
40
|
-
if (!scope)
|
|
41
|
-
return;
|
|
42
|
-
scope.set(name, node);
|
|
43
|
-
}
|
|
44
|
-
function resolveFunctionBinding(name) {
|
|
45
|
-
for (let i = memoScopes.length - 1; i >= 0; i -= 1) {
|
|
46
|
-
const binding = memoScopes[i].get(name);
|
|
47
|
-
if (binding) {
|
|
48
|
-
return binding;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
return undefined;
|
|
52
|
-
}
|
|
53
|
-
function hoistFunctionDeclarations(statements) {
|
|
54
|
-
const scope = currentMemoScope();
|
|
55
|
-
if (!scope)
|
|
56
|
-
return;
|
|
57
|
-
for (const statement of statements) {
|
|
58
|
-
if (statement.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
59
|
-
statement.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
60
|
-
scope.set(statement.id.name, statement);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
function registerFunctionVariable(node) {
|
|
65
|
-
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier)
|
|
66
|
-
return;
|
|
67
|
-
const { init } = node;
|
|
68
|
-
if (init &&
|
|
69
|
-
(init.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
70
|
-
init.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
71
|
-
declareFunctionBinding(node.id.name, init);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function isReactMemoCallee(callee) {
|
|
75
|
-
if (callee.type === utils_1.AST_NODE_TYPES.Identifier && callee.name === 'memo') {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
if (callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
79
|
-
!callee.computed &&
|
|
80
|
-
callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
81
|
-
callee.property.name === 'memo') {
|
|
82
|
-
return true;
|
|
83
|
-
}
|
|
84
|
-
return false;
|
|
85
|
-
}
|
|
86
|
-
function isHookCall(node, name) {
|
|
87
|
-
if (node.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
88
|
-
node.callee.name === name) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
return (node.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
92
|
-
!node.callee.computed &&
|
|
93
|
-
node.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
94
|
-
node.callee.property.name === name);
|
|
95
|
-
}
|
|
96
|
-
function collectMemoizedComponents(node) {
|
|
97
|
-
pushMemoScope();
|
|
98
|
-
if (node.type === utils_1.AST_NODE_TYPES.Program) {
|
|
99
|
-
hoistFunctionDeclarations(node.body);
|
|
100
|
-
}
|
|
101
|
-
function traverse(current) {
|
|
102
|
-
const isFunctionLike = current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
103
|
-
current.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
|
|
104
|
-
current.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression;
|
|
105
|
-
if (current.type === utils_1.AST_NODE_TYPES.FunctionDeclaration &&
|
|
106
|
-
current.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
107
|
-
declareFunctionBinding(current.id.name, current);
|
|
108
|
-
}
|
|
109
|
-
let pushedScope = false;
|
|
110
|
-
if (current.type === utils_1.AST_NODE_TYPES.BlockStatement || isFunctionLike) {
|
|
111
|
-
pushMemoScope();
|
|
112
|
-
pushedScope = true;
|
|
113
|
-
if (isFunctionLike &&
|
|
114
|
-
'id' in current &&
|
|
115
|
-
current.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
116
|
-
declareFunctionBinding(current.id.name, current);
|
|
117
|
-
}
|
|
118
|
-
if (current.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
119
|
-
hoistFunctionDeclarations(current.body);
|
|
120
|
-
}
|
|
121
|
-
else if (isFunctionLike &&
|
|
122
|
-
current.body.type === utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
123
|
-
hoistFunctionDeclarations(current.body.body);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
if (current.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
|
|
127
|
-
registerFunctionVariable(current);
|
|
128
|
-
}
|
|
129
|
-
if (current.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
130
|
-
isReactMemoCallee(current.callee)) {
|
|
131
|
-
const [firstArg] = current.arguments;
|
|
132
|
-
if (firstArg &&
|
|
133
|
-
(firstArg.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
134
|
-
firstArg.type === utils_1.AST_NODE_TYPES.FunctionExpression)) {
|
|
135
|
-
memoizedComponents.add(firstArg);
|
|
136
|
-
}
|
|
137
|
-
else if (firstArg && firstArg.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
138
|
-
const binding = resolveFunctionBinding(firstArg.name);
|
|
139
|
-
if (binding) {
|
|
140
|
-
memoizedComponents.add(binding);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const keys = visitorKeys[current.type] ?? [];
|
|
145
|
-
for (const key of keys) {
|
|
146
|
-
const value = current[key];
|
|
147
|
-
if (Array.isArray(value)) {
|
|
148
|
-
for (const child of value) {
|
|
149
|
-
if (child && typeof child === 'object') {
|
|
150
|
-
traverse(child);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
else if (value && typeof value === 'object') {
|
|
155
|
-
traverse(value);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
if (pushedScope) {
|
|
159
|
-
popMemoScope();
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
traverse(node);
|
|
163
|
-
popMemoScope();
|
|
164
|
-
}
|
|
165
|
-
collectMemoizedComponents(sourceCode.ast);
|
|
166
|
-
function isMemoizedComponent(node) {
|
|
167
|
-
return memoizedComponents.has(node);
|
|
168
|
-
}
|
|
169
|
-
function pushComponent(node) {
|
|
170
|
-
componentStack.push({ node, scopes: [new Map()] });
|
|
171
|
-
}
|
|
172
|
-
function popComponent(node) {
|
|
173
|
-
const top = componentStack[componentStack.length - 1];
|
|
174
|
-
if (top && top.node === node) {
|
|
175
|
-
componentStack.pop();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
function currentComponent() {
|
|
179
|
-
return componentStack[componentStack.length - 1];
|
|
180
|
-
}
|
|
181
|
-
function currentScope() {
|
|
182
|
-
const component = currentComponent();
|
|
183
|
-
if (!component)
|
|
184
|
-
return undefined;
|
|
185
|
-
return component.scopes[component.scopes.length - 1];
|
|
186
|
-
}
|
|
187
|
-
function pushScopeForNestedFunction(node) {
|
|
188
|
-
const component = currentComponent();
|
|
189
|
-
if (!component)
|
|
190
|
-
return;
|
|
191
|
-
component.scopes.push(new Map());
|
|
192
|
-
scopedFunctions.add(node);
|
|
193
|
-
}
|
|
194
|
-
function popScopeForNestedFunction(node) {
|
|
195
|
-
const component = currentComponent();
|
|
196
|
-
if (!component)
|
|
197
|
-
return;
|
|
198
|
-
if (!scopedFunctions.has(node))
|
|
199
|
-
return;
|
|
200
|
-
component.scopes.pop();
|
|
201
|
-
scopedFunctions.delete(node);
|
|
202
|
-
}
|
|
203
|
-
function unwrapExpression(expression) {
|
|
204
|
-
let current = expression;
|
|
205
|
-
while (true) {
|
|
206
|
-
if (current.type === utils_1.AST_NODE_TYPES.TSAsExpression ||
|
|
207
|
-
current.type === utils_1.AST_NODE_TYPES.TSSatisfiesExpression ||
|
|
208
|
-
current.type === utils_1.AST_NODE_TYPES.TSTypeAssertion) {
|
|
209
|
-
current = current.expression;
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (current.type === utils_1.AST_NODE_TYPES.TSNonNullExpression) {
|
|
213
|
-
current = current.expression;
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
break;
|
|
217
|
-
}
|
|
218
|
-
return current;
|
|
219
|
-
}
|
|
220
|
-
function getBindingKind(init) {
|
|
221
|
-
if (!init) {
|
|
222
|
-
return null;
|
|
223
|
-
}
|
|
224
|
-
const unwrapped = unwrapExpression(init);
|
|
225
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.CallExpression &&
|
|
226
|
-
((unwrapped.callee.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
227
|
-
HOOK_NAMES.has(unwrapped.callee.name)) ||
|
|
228
|
-
(unwrapped.callee.type === utils_1.AST_NODE_TYPES.MemberExpression &&
|
|
229
|
-
!unwrapped.callee.computed &&
|
|
230
|
-
unwrapped.callee.property.type === utils_1.AST_NODE_TYPES.Identifier &&
|
|
231
|
-
HOOK_NAMES.has(unwrapped.callee.property.name)))) {
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.ObjectExpression)
|
|
235
|
-
return 'object';
|
|
236
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.ArrayExpression)
|
|
237
|
-
return 'array';
|
|
238
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
239
|
-
unwrapped.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
240
|
-
return 'function';
|
|
241
|
-
}
|
|
242
|
-
return null;
|
|
243
|
-
}
|
|
244
|
-
function recordBinding(node) {
|
|
245
|
-
const scope = currentScope();
|
|
246
|
-
if (!scope)
|
|
247
|
-
return;
|
|
248
|
-
if (node.id.type !== utils_1.AST_NODE_TYPES.Identifier || !node.init) {
|
|
249
|
-
return;
|
|
250
|
-
}
|
|
251
|
-
const initializer = unwrapExpression(node.init);
|
|
252
|
-
const aliasKind = initializer.type === utils_1.AST_NODE_TYPES.Identifier
|
|
253
|
-
? findBindingKind(initializer.name)
|
|
254
|
-
: null;
|
|
255
|
-
const kind = aliasKind ?? getBindingKind(initializer);
|
|
256
|
-
if (kind)
|
|
257
|
-
scope.set(node.id.name, kind);
|
|
258
|
-
}
|
|
259
|
-
function findBindingKind(name) {
|
|
260
|
-
for (let i = componentStack.length - 1; i >= 0; i -= 1) {
|
|
261
|
-
const component = componentStack[i];
|
|
262
|
-
for (let j = component.scopes.length - 1; j >= 0; j -= 1) {
|
|
263
|
-
const binding = component.scopes[j].get(name);
|
|
264
|
-
if (binding)
|
|
265
|
-
return binding;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return null;
|
|
269
|
-
}
|
|
270
|
-
function getAttributeName(name) {
|
|
271
|
-
if (name.type === utils_1.AST_NODE_TYPES.JSXIdentifier) {
|
|
272
|
-
return name.name;
|
|
273
|
-
}
|
|
274
|
-
if (name.type === utils_1.AST_NODE_TYPES.JSXNamespacedName) {
|
|
275
|
-
return name.name.name;
|
|
276
|
-
}
|
|
277
|
-
return 'prop';
|
|
278
|
-
}
|
|
279
|
-
function reportReference(node, propName, kind) {
|
|
280
|
-
context.report({
|
|
281
|
-
node,
|
|
282
|
-
messageId: 'memoizeReferenceProp',
|
|
283
|
-
data: { propName, kind },
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
function isEnvironmentFreeLiteral(expression) {
|
|
287
|
-
const unwrapped = unwrapExpression(expression);
|
|
288
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.Literal)
|
|
289
|
-
return true;
|
|
290
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.TemplateLiteral &&
|
|
291
|
-
unwrapped.expressions.length === 0) {
|
|
292
|
-
return true;
|
|
293
|
-
}
|
|
294
|
-
return false;
|
|
295
|
-
}
|
|
296
|
-
function isStableReference(expression) {
|
|
297
|
-
const unwrapped = unwrapExpression(expression);
|
|
298
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.Literal)
|
|
299
|
-
return true;
|
|
300
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.TemplateLiteral) {
|
|
301
|
-
return unwrapped.expressions.every((expr) => isStableReference(expr));
|
|
302
|
-
}
|
|
303
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.Identifier)
|
|
304
|
-
return true;
|
|
305
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.MemberExpression)
|
|
306
|
-
return true;
|
|
307
|
-
if (unwrapped.type === utils_1.AST_NODE_TYPES.ChainExpression &&
|
|
308
|
-
unwrapped.expression.type === utils_1.AST_NODE_TYPES.MemberExpression) {
|
|
309
|
-
return true;
|
|
310
|
-
}
|
|
311
|
-
return false;
|
|
312
|
-
}
|
|
313
|
-
function extractReturnExpression(callback) {
|
|
314
|
-
if (callback.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
|
|
315
|
-
return callback.body;
|
|
316
|
-
}
|
|
317
|
-
const [statement] = callback.body.body;
|
|
318
|
-
if (statement &&
|
|
319
|
-
statement.type === utils_1.AST_NODE_TYPES.ReturnStatement &&
|
|
320
|
-
statement.argument) {
|
|
321
|
-
return statement.argument;
|
|
322
|
-
}
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
function handleUseMemo(node) {
|
|
326
|
-
if (!currentComponent() || !isHookCall(node, 'useMemo')) {
|
|
327
|
-
return;
|
|
328
|
-
}
|
|
329
|
-
const [callback, deps] = node.arguments;
|
|
330
|
-
if (!callback ||
|
|
331
|
-
(callback.type !== utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
|
|
332
|
-
callback.type !== utils_1.AST_NODE_TYPES.FunctionExpression)) {
|
|
333
|
-
return;
|
|
334
|
-
}
|
|
335
|
-
const returnExpression = extractReturnExpression(callback);
|
|
336
|
-
if (!returnExpression)
|
|
337
|
-
return;
|
|
338
|
-
const dependencies = deps && deps.type === utils_1.AST_NODE_TYPES.ArrayExpression ? deps : null;
|
|
339
|
-
if (dependencies &&
|
|
340
|
-
dependencies.elements.length === 0 &&
|
|
341
|
-
!isEnvironmentFreeLiteral(returnExpression)) {
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
if (isStableReference(returnExpression)) {
|
|
345
|
-
const valueText = sourceCode.getText(returnExpression).slice(0, 80);
|
|
346
|
-
context.report({
|
|
347
|
-
node,
|
|
348
|
-
messageId: 'avoidPrimitiveMemo',
|
|
349
|
-
data: { value: valueText },
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
function handleJSXAttribute(node) {
|
|
354
|
-
if (!currentComponent())
|
|
355
|
-
return;
|
|
356
|
-
if (!node.value ||
|
|
357
|
-
node.value.type !== utils_1.AST_NODE_TYPES.JSXExpressionContainer) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
const rawExpression = node.value.expression;
|
|
361
|
-
if (rawExpression.type === utils_1.AST_NODE_TYPES.JSXEmptyExpression) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const expression = unwrapExpression(rawExpression);
|
|
365
|
-
const propName = getAttributeName(node.name);
|
|
366
|
-
if (expression.type === utils_1.AST_NODE_TYPES.ObjectExpression) {
|
|
367
|
-
reportReference(expression, propName, 'object');
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if (expression.type === utils_1.AST_NODE_TYPES.ArrayExpression) {
|
|
371
|
-
reportReference(expression, propName, 'array');
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
if (expression.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
|
|
375
|
-
expression.type === utils_1.AST_NODE_TYPES.FunctionExpression) {
|
|
376
|
-
reportReference(expression, propName, 'function');
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
if (expression.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
380
|
-
const bindingKind = findBindingKind(expression.name);
|
|
381
|
-
if (bindingKind) {
|
|
382
|
-
reportReference(expression, propName, bindingKind);
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
return {
|
|
387
|
-
FunctionDeclaration(node) {
|
|
388
|
-
if (isMemoizedComponent(node)) {
|
|
389
|
-
pushComponent(node);
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
const scope = currentScope();
|
|
393
|
-
if (scope && node.id?.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
394
|
-
scope.set(node.id.name, 'function');
|
|
395
|
-
}
|
|
396
|
-
if (currentComponent()) {
|
|
397
|
-
pushScopeForNestedFunction(node);
|
|
398
|
-
}
|
|
399
|
-
},
|
|
400
|
-
'FunctionDeclaration:exit'(node) {
|
|
401
|
-
if (isMemoizedComponent(node)) {
|
|
402
|
-
popComponent(node);
|
|
403
|
-
return;
|
|
404
|
-
}
|
|
405
|
-
popScopeForNestedFunction(node);
|
|
406
|
-
},
|
|
407
|
-
FunctionExpression(node) {
|
|
408
|
-
if (isMemoizedComponent(node)) {
|
|
409
|
-
pushComponent(node);
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
if (currentComponent()) {
|
|
413
|
-
pushScopeForNestedFunction(node);
|
|
414
|
-
}
|
|
415
|
-
},
|
|
416
|
-
'FunctionExpression:exit'(node) {
|
|
417
|
-
if (isMemoizedComponent(node)) {
|
|
418
|
-
popComponent(node);
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
popScopeForNestedFunction(node);
|
|
422
|
-
},
|
|
423
|
-
ArrowFunctionExpression(node) {
|
|
424
|
-
if (isMemoizedComponent(node)) {
|
|
425
|
-
pushComponent(node);
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
if (currentComponent()) {
|
|
429
|
-
pushScopeForNestedFunction(node);
|
|
430
|
-
}
|
|
431
|
-
},
|
|
432
|
-
'ArrowFunctionExpression:exit'(node) {
|
|
433
|
-
if (isMemoizedComponent(node)) {
|
|
434
|
-
popComponent(node);
|
|
435
|
-
return;
|
|
436
|
-
}
|
|
437
|
-
popScopeForNestedFunction(node);
|
|
438
|
-
},
|
|
439
|
-
VariableDeclarator: recordBinding,
|
|
440
|
-
CallExpression: handleUseMemo,
|
|
441
|
-
JSXAttribute: handleJSXAttribute,
|
|
442
|
-
};
|
|
443
|
-
},
|
|
444
|
-
});
|
|
445
|
-
//# sourceMappingURL=prefer-memoized-props.js.map
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { TSESLint } from '@typescript-eslint/utils';
|
|
2
|
-
/**
|
|
3
|
-
* This rule overrides the behavior of @typescript-eslint/prefer-nullish-coalescing
|
|
4
|
-
* to only suggest using the nullish coalescing operator when checking for null/undefined,
|
|
5
|
-
* not when intentionally checking for all falsy values.
|
|
6
|
-
*/
|
|
7
|
-
export declare const preferNullishCoalescingOverride: TSESLint.RuleModule<"preferNullishCoalescing", [], TSESLint.RuleListener>;
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.preferNullishCoalescingOverride = void 0;
|
|
4
|
-
const utils_1 = require("@typescript-eslint/utils");
|
|
5
|
-
const createRule_1 = require("../utils/createRule");
|
|
6
|
-
/**
|
|
7
|
-
* This rule overrides the behavior of @typescript-eslint/prefer-nullish-coalescing
|
|
8
|
-
* to only suggest using the nullish coalescing operator when checking for null/undefined,
|
|
9
|
-
* not when intentionally checking for all falsy values.
|
|
10
|
-
*/
|
|
11
|
-
exports.preferNullishCoalescingOverride = (0, createRule_1.createRule)({
|
|
12
|
-
name: 'prefer-nullish-coalescing-override',
|
|
13
|
-
meta: {
|
|
14
|
-
type: 'suggestion',
|
|
15
|
-
docs: {
|
|
16
|
-
description: 'Enforce using nullish coalescing operator instead of logical OR operator, but only when appropriate',
|
|
17
|
-
recommended: 'warn',
|
|
18
|
-
},
|
|
19
|
-
fixable: 'code',
|
|
20
|
-
schema: [],
|
|
21
|
-
messages: {
|
|
22
|
-
preferNullishCoalescing: 'Replace "{{left}} || {{right}}" with nullish coalescing when you only want a fallback for null or undefined. The logical OR operator treats "", 0, and false as missing values and silently swaps in the fallback, which hides valid data. Use "{{left}} ?? {{right}}" so only nullish inputs trigger the fallback.',
|
|
23
|
-
},
|
|
24
|
-
},
|
|
25
|
-
defaultOptions: [],
|
|
26
|
-
create(context) {
|
|
27
|
-
const contextWithSource = context;
|
|
28
|
-
const sourceCode = contextWithSource.sourceCode ?? contextWithSource.getSourceCode();
|
|
29
|
-
/**
|
|
30
|
-
* Checks if a node is in a boolean context where truthiness matters
|
|
31
|
-
*/
|
|
32
|
-
function isInBooleanContext(node) {
|
|
33
|
-
const parent = node.parent;
|
|
34
|
-
if (!parent)
|
|
35
|
-
return false;
|
|
36
|
-
switch (parent.type) {
|
|
37
|
-
// Direct boolean contexts
|
|
38
|
-
case utils_1.AST_NODE_TYPES.IfStatement:
|
|
39
|
-
case utils_1.AST_NODE_TYPES.WhileStatement:
|
|
40
|
-
case utils_1.AST_NODE_TYPES.DoWhileStatement:
|
|
41
|
-
case utils_1.AST_NODE_TYPES.ForStatement:
|
|
42
|
-
return parent.test === node;
|
|
43
|
-
case utils_1.AST_NODE_TYPES.ConditionalExpression:
|
|
44
|
-
return parent.test === node;
|
|
45
|
-
// Logical expressions (&&, ||, !)
|
|
46
|
-
case utils_1.AST_NODE_TYPES.LogicalExpression:
|
|
47
|
-
return true;
|
|
48
|
-
case utils_1.AST_NODE_TYPES.UnaryExpression:
|
|
49
|
-
return parent.operator === '!';
|
|
50
|
-
// JSX expressions that expect boolean values
|
|
51
|
-
case utils_1.AST_NODE_TYPES.JSXExpressionContainer:
|
|
52
|
-
return isJSXBooleanContext(parent);
|
|
53
|
-
default:
|
|
54
|
-
return false;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Checks if a JSX expression container is in a boolean context
|
|
59
|
-
*/
|
|
60
|
-
function isJSXBooleanContext(jsxContainer) {
|
|
61
|
-
const parent = jsxContainer.parent;
|
|
62
|
-
if (!parent)
|
|
63
|
-
return false;
|
|
64
|
-
// Check if used as a child expression (conditional rendering or child selection).
|
|
65
|
-
// Other guards (rightOperandSuggestsFalsyHandling, etc.) will prevent unsafe conversions.
|
|
66
|
-
if (parent.type === utils_1.AST_NODE_TYPES.JSXElement ||
|
|
67
|
-
parent.type === utils_1.AST_NODE_TYPES.JSXFragment) {
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Checks if the right operand suggests we want to handle all falsy values
|
|
74
|
-
*/
|
|
75
|
-
function rightOperandSuggestsFalsyHandling(right) {
|
|
76
|
-
// String literals suggest we want to handle empty strings too
|
|
77
|
-
if (right.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
78
|
-
typeof right.value === 'string') {
|
|
79
|
-
return true;
|
|
80
|
-
}
|
|
81
|
-
// Number literals (especially 0) suggest we want to handle falsy numbers
|
|
82
|
-
if (right.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
83
|
-
typeof right.value === 'number') {
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
// Boolean literals suggest boolean logic
|
|
87
|
-
if (right.type === utils_1.AST_NODE_TYPES.Literal &&
|
|
88
|
-
typeof right.value === 'boolean') {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Checks if the left operand is likely to be used for boolean logic
|
|
95
|
-
*/
|
|
96
|
-
function leftOperandSuggestsBooleanLogic(left) {
|
|
97
|
-
// Variables with boolean-like names
|
|
98
|
-
if (left.type === utils_1.AST_NODE_TYPES.Identifier) {
|
|
99
|
-
const name = left.name.toLowerCase();
|
|
100
|
-
const booleanPrefixes = [
|
|
101
|
-
'is',
|
|
102
|
-
'has',
|
|
103
|
-
'can',
|
|
104
|
-
'should',
|
|
105
|
-
'will',
|
|
106
|
-
'was',
|
|
107
|
-
'were',
|
|
108
|
-
'are',
|
|
109
|
-
];
|
|
110
|
-
const booleanSuffixes = [
|
|
111
|
-
'enabled',
|
|
112
|
-
'disabled',
|
|
113
|
-
'active',
|
|
114
|
-
'inactive',
|
|
115
|
-
'valid',
|
|
116
|
-
'invalid',
|
|
117
|
-
];
|
|
118
|
-
return (booleanPrefixes.some((prefix) => name.startsWith(prefix)) ||
|
|
119
|
-
booleanSuffixes.some((suffix) => name.endsWith(suffix)));
|
|
120
|
-
}
|
|
121
|
-
// Binary expressions that result in booleans
|
|
122
|
-
if (left.type === utils_1.AST_NODE_TYPES.BinaryExpression) {
|
|
123
|
-
const comparisonOperators = [
|
|
124
|
-
'===',
|
|
125
|
-
'!==',
|
|
126
|
-
'==',
|
|
127
|
-
'!=',
|
|
128
|
-
'<',
|
|
129
|
-
'>',
|
|
130
|
-
'<=',
|
|
131
|
-
'>=',
|
|
132
|
-
];
|
|
133
|
-
return comparisonOperators.includes(left.operator);
|
|
134
|
-
}
|
|
135
|
-
return false;
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Checks if this is a variable assignment context where falsy handling is expected
|
|
139
|
-
*/
|
|
140
|
-
function isVariableAssignmentWithFalsyHandling(node) {
|
|
141
|
-
const parent = node.parent;
|
|
142
|
-
if (!parent)
|
|
143
|
-
return false;
|
|
144
|
-
// Variable declarations like: const name = username || 'Anonymous'
|
|
145
|
-
if (parent.type === utils_1.AST_NODE_TYPES.VariableDeclarator &&
|
|
146
|
-
parent.init === node) {
|
|
147
|
-
return rightOperandSuggestsFalsyHandling(node.right);
|
|
148
|
-
}
|
|
149
|
-
// Assignment expressions like: this.name = username || 'Anonymous'
|
|
150
|
-
if (parent.type === utils_1.AST_NODE_TYPES.AssignmentExpression &&
|
|
151
|
-
parent.right === node) {
|
|
152
|
-
return rightOperandSuggestsFalsyHandling(node.right);
|
|
153
|
-
}
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
/**
|
|
157
|
-
* Checks if this logical OR should be converted to nullish coalescing
|
|
158
|
-
*/
|
|
159
|
-
function shouldConvertToNullishCoalescing(node) {
|
|
160
|
-
void node;
|
|
161
|
-
return false;
|
|
162
|
-
}
|
|
163
|
-
void isInBooleanContext;
|
|
164
|
-
void leftOperandSuggestsBooleanLogic;
|
|
165
|
-
void rightOperandSuggestsFalsyHandling;
|
|
166
|
-
void isVariableAssignmentWithFalsyHandling;
|
|
167
|
-
return {
|
|
168
|
-
LogicalExpression(node) {
|
|
169
|
-
if (shouldConvertToNullishCoalescing(node)) {
|
|
170
|
-
const leftText = sourceCode.getText(node.left);
|
|
171
|
-
const rightText = sourceCode.getText(node.right);
|
|
172
|
-
context.report({
|
|
173
|
-
node,
|
|
174
|
-
messageId: 'preferNullishCoalescing',
|
|
175
|
-
data: {
|
|
176
|
-
left: leftText,
|
|
177
|
-
right: rightText,
|
|
178
|
-
},
|
|
179
|
-
fix(fixer) {
|
|
180
|
-
return fixer.replaceText(node, `${leftText} ?? ${rightText}`);
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
},
|
|
185
|
-
};
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
//# sourceMappingURL=prefer-nullish-coalescing-override.js.map
|