oxlint-plugin-react-doctor 0.7.6-dev.81e6647 → 0.7.6-dev.8528def
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/dist/index.js +76 -356
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6021,7 +6021,7 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
6021
6021
|
//#endregion
|
|
6022
6022
|
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
6023
6023
|
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
6024
|
-
if (!isNodeOfType(identifier, "Identifier")
|
|
6024
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
6025
6025
|
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
6026
6026
|
let symbol = scopes.symbolFor(identifier);
|
|
6027
6027
|
while (symbol?.kind === "const") {
|
|
@@ -6296,131 +6296,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
|
|
|
6296
6296
|
return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
|
|
6297
6297
|
};
|
|
6298
6298
|
//#endregion
|
|
6299
|
-
//#region src/plugin/utils/collect-safely-validated-local-storage-keys.ts
|
|
6300
|
-
const isLocalStorageCall = (node, methodName) => {
|
|
6301
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
6302
|
-
const callee = stripParenExpression(node.callee);
|
|
6303
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
6304
|
-
const receiver = stripParenExpression(callee.object);
|
|
6305
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "localStorage" && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
|
|
6306
|
-
};
|
|
6307
|
-
const catchReturnsFallback = (tryStatement) => {
|
|
6308
|
-
const handler = tryStatement.handler;
|
|
6309
|
-
if (!handler) return false;
|
|
6310
|
-
let hasReturn = false;
|
|
6311
|
-
let hasThrow = false;
|
|
6312
|
-
walkAst(handler.body, (child) => {
|
|
6313
|
-
if (isFunctionLike$1(child)) return false;
|
|
6314
|
-
if (isNodeOfType(child, "ReturnStatement")) hasReturn = true;
|
|
6315
|
-
if (isNodeOfType(child, "ThrowStatement")) hasThrow = true;
|
|
6316
|
-
});
|
|
6317
|
-
return hasReturn && !hasThrow;
|
|
6318
|
-
};
|
|
6319
|
-
const resolveValidatorFunction = (callee, scopes) => {
|
|
6320
|
-
const unwrappedCallee = stripParenExpression(callee);
|
|
6321
|
-
if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
|
|
6322
|
-
const symbol = scopes.symbolFor(unwrappedCallee);
|
|
6323
|
-
if (!symbol) return null;
|
|
6324
|
-
const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
|
|
6325
|
-
return isFunctionLike$1(candidate) ? candidate : null;
|
|
6326
|
-
};
|
|
6327
|
-
const validatorChecksPayloadProperties = (validatorFunction, scopes) => {
|
|
6328
|
-
if (!isFunctionLike$1(validatorFunction)) return false;
|
|
6329
|
-
const firstParameter = validatorFunction.params?.[0];
|
|
6330
|
-
if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
|
|
6331
|
-
const parameterSymbol = scopes.symbolFor(firstParameter);
|
|
6332
|
-
if (!parameterSymbol) return false;
|
|
6333
|
-
const payloadSymbolIds = new Set([parameterSymbol.id]);
|
|
6334
|
-
let hasPropertyTypeCheck = false;
|
|
6335
|
-
walkAst(validatorFunction.body, (child) => {
|
|
6336
|
-
if (isFunctionLike$1(child)) return false;
|
|
6337
|
-
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.init) {
|
|
6338
|
-
const initializer = stripParenExpression(child.init);
|
|
6339
|
-
if (isNodeOfType(initializer, "Identifier")) {
|
|
6340
|
-
const initializerSymbol = scopes.symbolFor(initializer);
|
|
6341
|
-
if (initializerSymbol && payloadSymbolIds.has(initializerSymbol.id)) {
|
|
6342
|
-
const aliasSymbol = scopes.symbolFor(child.id);
|
|
6343
|
-
if (aliasSymbol) payloadSymbolIds.add(aliasSymbol.id);
|
|
6344
|
-
}
|
|
6345
|
-
}
|
|
6346
|
-
}
|
|
6347
|
-
if (!isNodeOfType(child, "UnaryExpression") || child.operator !== "typeof") return;
|
|
6348
|
-
const checkedValue = stripParenExpression(child.argument);
|
|
6349
|
-
if (!isNodeOfType(checkedValue, "MemberExpression")) return;
|
|
6350
|
-
const receiver = stripParenExpression(checkedValue.object);
|
|
6351
|
-
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
6352
|
-
const receiverSymbol = scopes.symbolFor(receiver);
|
|
6353
|
-
if (receiverSymbol && payloadSymbolIds.has(receiverSymbol.id)) hasPropertyTypeCheck = true;
|
|
6354
|
-
});
|
|
6355
|
-
return hasPropertyTypeCheck;
|
|
6356
|
-
};
|
|
6357
|
-
const incrementCount = (counts, keyValue) => {
|
|
6358
|
-
counts.set(keyValue, (counts.get(keyValue) ?? 0) + 1);
|
|
6359
|
-
};
|
|
6360
|
-
const isReturnedExpression = (expression) => {
|
|
6361
|
-
const parent = expression.parent;
|
|
6362
|
-
return Boolean(parent && isNodeOfType(parent, "ReturnStatement") && parent.argument === expression);
|
|
6363
|
-
};
|
|
6364
|
-
const collectSafelyValidatedLocalStorageKeys = ({ programNode, scopes, resolveKey }) => {
|
|
6365
|
-
const readCountsByKey = /* @__PURE__ */ new Map();
|
|
6366
|
-
const safeReadCountsByKey = /* @__PURE__ */ new Map();
|
|
6367
|
-
const safeReadSymbolIds = /* @__PURE__ */ new Set();
|
|
6368
|
-
walkAst(programNode, (child) => {
|
|
6369
|
-
if (!isNodeOfType(child, "CallExpression") || !isLocalStorageCall(child, "getItem")) return;
|
|
6370
|
-
const keyArgument = child.arguments?.[0];
|
|
6371
|
-
if (!keyArgument) return;
|
|
6372
|
-
const keyValue = resolveKey(keyArgument);
|
|
6373
|
-
if (keyValue !== null) incrementCount(readCountsByKey, keyValue);
|
|
6374
|
-
});
|
|
6375
|
-
walkAst(programNode, (child) => {
|
|
6376
|
-
if (!isNodeOfType(child, "TryStatement") || !catchReturnsFallback(child)) return;
|
|
6377
|
-
const rawValueKeys = /* @__PURE__ */ new Map();
|
|
6378
|
-
const parsedValueSources = /* @__PURE__ */ new Map();
|
|
6379
|
-
walkAst(child.block, (tryChild) => {
|
|
6380
|
-
if (isFunctionLike$1(tryChild)) return false;
|
|
6381
|
-
if (isNodeOfType(tryChild, "VariableDeclarator") && isNodeOfType(tryChild.id, "Identifier") && tryChild.init) {
|
|
6382
|
-
const initializer = stripParenExpression(tryChild.init);
|
|
6383
|
-
if (isNodeOfType(initializer, "CallExpression") && isLocalStorageCall(initializer, "getItem")) {
|
|
6384
|
-
const keyArgument = initializer.arguments?.[0];
|
|
6385
|
-
const bindingSymbol = scopes.symbolFor(tryChild.id);
|
|
6386
|
-
if (keyArgument && bindingSymbol) {
|
|
6387
|
-
const keyValue = resolveKey(keyArgument);
|
|
6388
|
-
if (keyValue !== null) rawValueKeys.set(bindingSymbol.id, keyValue);
|
|
6389
|
-
}
|
|
6390
|
-
}
|
|
6391
|
-
if (isNodeOfType(initializer, "CallExpression") && isGlobalMethodCall(initializer, "JSON", "parse")) {
|
|
6392
|
-
const rawValueArgument = initializer.arguments?.[0];
|
|
6393
|
-
const parsedValueSymbol = scopes.symbolFor(tryChild.id);
|
|
6394
|
-
if (rawValueArgument && isNodeOfType(rawValueArgument, "Identifier") && parsedValueSymbol) {
|
|
6395
|
-
const rawValueSymbol = scopes.symbolFor(rawValueArgument);
|
|
6396
|
-
if (rawValueSymbol && rawValueKeys.has(rawValueSymbol.id)) parsedValueSources.set(parsedValueSymbol.id, rawValueSymbol.id);
|
|
6397
|
-
}
|
|
6398
|
-
}
|
|
6399
|
-
}
|
|
6400
|
-
if (!isNodeOfType(tryChild, "ConditionalExpression") || !isReturnedExpression(tryChild)) return;
|
|
6401
|
-
const test = stripParenExpression(tryChild.test);
|
|
6402
|
-
if (!isNodeOfType(test, "CallExpression")) return;
|
|
6403
|
-
const testedValue = test.arguments?.[0];
|
|
6404
|
-
if (!testedValue || !isNodeOfType(testedValue, "Identifier")) return;
|
|
6405
|
-
const parsedValueSymbol = scopes.symbolFor(testedValue);
|
|
6406
|
-
if (!parsedValueSymbol) return;
|
|
6407
|
-
const rawValueSymbolId = parsedValueSources.get(parsedValueSymbol.id);
|
|
6408
|
-
if (rawValueSymbolId === void 0) return;
|
|
6409
|
-
const returnedValue = stripParenExpression(tryChild.consequent);
|
|
6410
|
-
if (!isNodeOfType(returnedValue, "Identifier") || scopes.symbolFor(returnedValue)?.id !== parsedValueSymbol.id) return;
|
|
6411
|
-
const validatorFunction = resolveValidatorFunction(test.callee, scopes);
|
|
6412
|
-
if (!validatorFunction || !validatorChecksPayloadProperties(validatorFunction, scopes)) return;
|
|
6413
|
-
const keyValue = rawValueKeys.get(rawValueSymbolId);
|
|
6414
|
-
if (keyValue === void 0 || safeReadSymbolIds.has(rawValueSymbolId)) return;
|
|
6415
|
-
safeReadSymbolIds.add(rawValueSymbolId);
|
|
6416
|
-
incrementCount(safeReadCountsByKey, keyValue);
|
|
6417
|
-
});
|
|
6418
|
-
});
|
|
6419
|
-
const safeKeys = /* @__PURE__ */ new Set();
|
|
6420
|
-
for (const [keyValue, readCount] of readCountsByKey) if (safeReadCountsByKey.get(keyValue) === readCount) safeKeys.add(keyValue);
|
|
6421
|
-
return safeKeys;
|
|
6422
|
-
};
|
|
6423
|
-
//#endregion
|
|
6424
6299
|
//#region src/plugin/rules/client/client-localstorage-no-version.ts
|
|
6425
6300
|
const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
|
|
6426
6301
|
const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
|
|
@@ -6442,39 +6317,26 @@ const clientLocalstorageNoVersion = defineRule({
|
|
|
6442
6317
|
severity: "warn",
|
|
6443
6318
|
category: "Correctness",
|
|
6444
6319
|
recommendation: "Put a version in the storage key (e.g. \"myKey:v1\"). If you change the data shape later, old saved data can be ignored instead of crashing the app.",
|
|
6445
|
-
create: (context) => {
|
|
6446
|
-
|
|
6447
|
-
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6457
|
-
|
|
6458
|
-
|
|
6459
|
-
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6463
|
-
|
|
6464
|
-
|
|
6465
|
-
if (keyValue === null) return;
|
|
6466
|
-
if (isVersionedKey(keyValue)) return;
|
|
6467
|
-
if (safelyValidatedStorageKeys.has(keyValue)) return;
|
|
6468
|
-
const valueArg = node.arguments?.[1];
|
|
6469
|
-
if (!valueArg) return;
|
|
6470
|
-
if (!isJsonStringifyCall(valueArg)) return;
|
|
6471
|
-
context.report({
|
|
6472
|
-
node: keyArg,
|
|
6473
|
-
message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
|
|
6474
|
-
});
|
|
6475
|
-
}
|
|
6476
|
-
};
|
|
6477
|
-
}
|
|
6320
|
+
create: (context) => ({ CallExpression(node) {
|
|
6321
|
+
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
6322
|
+
const receiver = stripParenExpression(node.callee.object);
|
|
6323
|
+
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
6324
|
+
if (receiver.name !== "localStorage") return;
|
|
6325
|
+
if (!isNodeOfType(node.callee.property, "Identifier")) return;
|
|
6326
|
+
if (node.callee.property.name !== "setItem") return;
|
|
6327
|
+
const keyArg = node.arguments?.[0];
|
|
6328
|
+
if (!keyArg) return;
|
|
6329
|
+
const keyValue = resolveStringKey(keyArg, context);
|
|
6330
|
+
if (keyValue === null) return;
|
|
6331
|
+
if (isVersionedKey(keyValue)) return;
|
|
6332
|
+
const valueArg = node.arguments?.[1];
|
|
6333
|
+
if (!valueArg) return;
|
|
6334
|
+
if (!isJsonStringifyCall(valueArg)) return;
|
|
6335
|
+
context.report({
|
|
6336
|
+
node: keyArg,
|
|
6337
|
+
message: `${receiver.name}.setItem("${keyValue}", JSON.stringify(...)) has no version, so changing the data shape later crashes your users' saved sessions. Add one to the key (e.g. "${keyValue}:v1").`
|
|
6338
|
+
});
|
|
6339
|
+
} })
|
|
6478
6340
|
});
|
|
6479
6341
|
//#endregion
|
|
6480
6342
|
//#region src/plugin/rules/client/client-passive-event-listeners.ts
|
|
@@ -14749,18 +14611,6 @@ const jsHoistRegexp = defineRule({
|
|
|
14749
14611
|
}, { treatIteratorCallbacksAsLoops: true })
|
|
14750
14612
|
});
|
|
14751
14613
|
//#endregion
|
|
14752
|
-
//#region src/plugin/utils/resolve-first-argument-binding.ts
|
|
14753
|
-
const resolveFirstArgumentBinding = (firstParameter) => {
|
|
14754
|
-
if (!firstParameter) return null;
|
|
14755
|
-
if (!isNodeOfType(firstParameter, "RestElement")) return firstParameter;
|
|
14756
|
-
if (!isNodeOfType(firstParameter.argument, "ArrayPattern")) return null;
|
|
14757
|
-
const elements = firstParameter.argument.elements ?? [];
|
|
14758
|
-
if (elements.length !== 1) return null;
|
|
14759
|
-
const firstBinding = elements[0];
|
|
14760
|
-
if (!firstBinding || isNodeOfType(firstBinding, "RestElement")) return null;
|
|
14761
|
-
return firstBinding;
|
|
14762
|
-
};
|
|
14763
|
-
//#endregion
|
|
14764
14614
|
//#region src/plugin/rules/js-performance/js-index-maps.ts
|
|
14765
14615
|
const referencesParameter = (expression, parameterName) => {
|
|
14766
14616
|
if (!expression) return false;
|
|
@@ -14771,7 +14621,7 @@ const referencesParameter = (expression, parameterName) => {
|
|
|
14771
14621
|
const isSingleFieldEqualityPredicate = (node) => {
|
|
14772
14622
|
const callback = node.arguments?.[0];
|
|
14773
14623
|
if (!isInlineFunctionExpression(callback)) return false;
|
|
14774
|
-
const firstParameter =
|
|
14624
|
+
const firstParameter = callback.params?.[0];
|
|
14775
14625
|
if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
|
|
14776
14626
|
let predicate = null;
|
|
14777
14627
|
const body = callback.body;
|
|
@@ -16110,21 +15960,14 @@ const jsxFilenameExtension = defineRule({
|
|
|
16110
15960
|
});
|
|
16111
15961
|
//#endregion
|
|
16112
15962
|
//#region src/plugin/utils/is-jsx-fragment-element.ts
|
|
16113
|
-
const isJsxFragmentElement = (node
|
|
15963
|
+
const isJsxFragmentElement = (node) => {
|
|
16114
15964
|
if (!isNodeOfType(node, "JSXOpeningElement")) return false;
|
|
16115
15965
|
const elementName = node.name;
|
|
16116
|
-
if (isNodeOfType(elementName, "JSXIdentifier"))
|
|
16117
|
-
if (!scopes) return elementName.name === "Fragment";
|
|
16118
|
-
const symbol = resolveConstIdentifierAlias(elementName, scopes);
|
|
16119
|
-
if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
|
|
16120
|
-
return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
|
|
16121
|
-
}
|
|
15966
|
+
if (isNodeOfType(elementName, "JSXIdentifier")) return elementName.name === "Fragment";
|
|
16122
15967
|
if (isNodeOfType(elementName, "JSXMemberExpression")) {
|
|
16123
15968
|
if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
|
|
16124
|
-
if (elementName.
|
|
16125
|
-
|
|
16126
|
-
if (isReactNamespaceImport(elementName.object, scopes)) return true;
|
|
16127
|
-
return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
|
|
15969
|
+
if (elementName.object.name !== "React") return false;
|
|
15970
|
+
return elementName.property.name === "Fragment";
|
|
16128
15971
|
}
|
|
16129
15972
|
return false;
|
|
16130
15973
|
};
|
|
@@ -16150,7 +15993,7 @@ const jsxFragments = defineRule({
|
|
|
16150
15993
|
if (mode !== "syntax") return;
|
|
16151
15994
|
if (!node.closingElement) return;
|
|
16152
15995
|
const openingElement = node.openingElement;
|
|
16153
|
-
if (!isJsxFragmentElement(openingElement
|
|
15996
|
+
if (!isJsxFragmentElement(openingElement)) return;
|
|
16154
15997
|
if (openingElement.attributes.length > 0) return;
|
|
16155
15998
|
context.report({
|
|
16156
15999
|
node: openingElement,
|
|
@@ -19291,7 +19134,7 @@ const jsxNoUselessFragment = defineRule({
|
|
|
19291
19134
|
return {
|
|
19292
19135
|
JSXElement(node) {
|
|
19293
19136
|
const openingElement = node.openingElement;
|
|
19294
|
-
if (!isJsxFragmentElement(openingElement
|
|
19137
|
+
if (!isJsxFragmentElement(openingElement)) return;
|
|
19295
19138
|
if (hasJsxKeyAttribute(openingElement)) return;
|
|
19296
19139
|
if (checkChildren(node, openingElement, node.children)) return;
|
|
19297
19140
|
if (isChildOfHtmlElement(node)) context.report({
|
|
@@ -20533,24 +20376,8 @@ const hasDirective = (programNode, directive) => {
|
|
|
20533
20376
|
return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
|
|
20534
20377
|
};
|
|
20535
20378
|
//#endregion
|
|
20536
|
-
//#region src/plugin/utils/
|
|
20537
|
-
const
|
|
20538
|
-
"freeze",
|
|
20539
|
-
"seal",
|
|
20540
|
-
"preventExtensions"
|
|
20541
|
-
]);
|
|
20542
|
-
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
20543
|
-
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
20544
|
-
let expression = stripParenExpression(node);
|
|
20545
|
-
while (isNodeOfType(expression, "CallExpression")) {
|
|
20546
|
-
const callee = stripParenExpression(expression.callee);
|
|
20547
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.object, "Identifier") || callee.object.name !== "Object" || !scopes.isGlobalReference(callee.object) || !isNodeOfType(callee.property, "Identifier") || !methodNames.has(callee.property.name)) break;
|
|
20548
|
-
const wrappedExpression = expression.arguments[0];
|
|
20549
|
-
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
20550
|
-
expression = stripParenExpression(wrappedExpression);
|
|
20551
|
-
}
|
|
20552
|
-
return expression;
|
|
20553
|
-
};
|
|
20379
|
+
//#region src/plugin/utils/is-component-assignment.ts
|
|
20380
|
+
const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
|
|
20554
20381
|
//#endregion
|
|
20555
20382
|
//#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
|
|
20556
20383
|
const nextjsAsyncClientComponent = defineRule({
|
|
@@ -20576,10 +20403,10 @@ const nextjsAsyncClientComponent = defineRule({
|
|
|
20576
20403
|
},
|
|
20577
20404
|
VariableDeclarator(node) {
|
|
20578
20405
|
if (!fileHasUseClient) return;
|
|
20406
|
+
if (!isComponentAssignment(node)) return;
|
|
20407
|
+
if (!isInlineFunctionExpression(node.init)) return;
|
|
20408
|
+
if (!node.init.async) return;
|
|
20579
20409
|
if (!isNodeOfType(node.id, "Identifier")) return;
|
|
20580
|
-
if (!isUppercaseName(node.id.name) || !node.init) return;
|
|
20581
|
-
const componentFunction = unwrapObjectIntegrityExpression(node.init, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES);
|
|
20582
|
-
if (!isInlineFunctionExpression(componentFunction) || !componentFunction.async) return;
|
|
20583
20410
|
context.report({
|
|
20584
20411
|
node,
|
|
20585
20412
|
message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
|
|
@@ -23216,15 +23043,10 @@ const isProp = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
|
23216
23043
|
if (!declaringNode) return false;
|
|
23217
23044
|
return isReactFunctionalComponent(declaringNode) && !isReactFunctionalHOC(analysis, declaringNode) || isCustomHook(declaringNode);
|
|
23218
23045
|
}));
|
|
23219
|
-
const isWholePropsParameterBinding = (bindingNode) => {
|
|
23220
|
-
if (isFunctionLike$1(bindingNode.parent)) return true;
|
|
23221
|
-
let declaringFunction = bindingNode.parent;
|
|
23222
|
-
while (declaringFunction && !isFunctionLike$1(declaringFunction)) declaringFunction = declaringFunction.parent;
|
|
23223
|
-
return Boolean(declaringFunction && resolveFirstArgumentBinding(declaringFunction.params?.[0]) === bindingNode);
|
|
23224
|
-
};
|
|
23225
23046
|
const isWholePropsObjectReference = (analysis, ref) => isProp(analysis, ref) && Boolean(ref.resolved?.defs.some((def) => {
|
|
23226
23047
|
if (def.type !== "Parameter") return false;
|
|
23227
|
-
|
|
23048
|
+
const bindingParent = def.name.parent;
|
|
23049
|
+
return isFunctionLike$1(bindingParent);
|
|
23228
23050
|
}));
|
|
23229
23051
|
const isIdentifierOrMemberExpression = (node) => isNodeOfType(node, "Identifier") || isNodeOfType(node, "MemberExpression");
|
|
23230
23052
|
const isPropAlias = (analysis, ref) => {
|
|
@@ -26221,56 +26043,6 @@ const noBarrelImport = defineRule({
|
|
|
26221
26043
|
}
|
|
26222
26044
|
});
|
|
26223
26045
|
//#endregion
|
|
26224
|
-
//#region src/plugin/utils/function-returns-matching-expression.ts
|
|
26225
|
-
const collectReturnedExpressions = (functionNode) => {
|
|
26226
|
-
if (!isFunctionLike$1(functionNode)) return [];
|
|
26227
|
-
const body = functionNode.body;
|
|
26228
|
-
if (!body) return [];
|
|
26229
|
-
if (!isNodeOfType(body, "BlockStatement")) return [body];
|
|
26230
|
-
const returnedExpressions = [];
|
|
26231
|
-
walkAst(body, (node) => {
|
|
26232
|
-
if (node !== body && (isFunctionLike$1(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
|
|
26233
|
-
if (isNodeOfType(node, "ReturnStatement") && node.argument) returnedExpressions.push(node.argument);
|
|
26234
|
-
});
|
|
26235
|
-
return returnedExpressions;
|
|
26236
|
-
};
|
|
26237
|
-
const functionReturnsMatchingExpression = (functionNode, scopes, matchesExpression) => {
|
|
26238
|
-
const visitedExpressions = /* @__PURE__ */ new Set();
|
|
26239
|
-
const visitedFunctions = /* @__PURE__ */ new Set();
|
|
26240
|
-
const functionMatches = (candidateFunction) => {
|
|
26241
|
-
if (visitedFunctions.has(candidateFunction)) return false;
|
|
26242
|
-
visitedFunctions.add(candidateFunction);
|
|
26243
|
-
return collectReturnedExpressions(candidateFunction).some(expressionMatches);
|
|
26244
|
-
};
|
|
26245
|
-
const expressionMatches = (expression) => {
|
|
26246
|
-
const unwrappedExpression = stripParenExpression(expression);
|
|
26247
|
-
if (visitedExpressions.has(unwrappedExpression)) return false;
|
|
26248
|
-
visitedExpressions.add(unwrappedExpression);
|
|
26249
|
-
if (matchesExpression(unwrappedExpression)) return true;
|
|
26250
|
-
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
26251
|
-
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
26252
|
-
if (!symbol || symbol.kind !== "const" || !symbol.initializer) return false;
|
|
26253
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
26254
|
-
if (isFunctionLike$1(initializer)) return false;
|
|
26255
|
-
return expressionMatches(initializer);
|
|
26256
|
-
}
|
|
26257
|
-
if (isNodeOfType(unwrappedExpression, "CallExpression")) {
|
|
26258
|
-
if (unwrappedExpression.arguments.length !== 0) return false;
|
|
26259
|
-
if (!isNodeOfType(unwrappedExpression.callee, "Identifier")) return false;
|
|
26260
|
-
const symbol = scopes.symbolFor(unwrappedExpression.callee);
|
|
26261
|
-
if (!symbol || symbol.kind !== "const" && symbol.kind !== "function") return false;
|
|
26262
|
-
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
26263
|
-
const candidateFunction = isFunctionLike$1(initializer) ? initializer : isFunctionLike$1(symbol.declarationNode) ? symbol.declarationNode : null;
|
|
26264
|
-
if (!candidateFunction || candidateFunction.async || candidateFunction.generator || candidateFunction.params.length !== 0) return false;
|
|
26265
|
-
return functionMatches(candidateFunction);
|
|
26266
|
-
}
|
|
26267
|
-
if (isNodeOfType(unwrappedExpression, "ConditionalExpression")) return expressionMatches(unwrappedExpression.consequent) || expressionMatches(unwrappedExpression.alternate);
|
|
26268
|
-
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return expressionMatches(unwrappedExpression.left) || expressionMatches(unwrappedExpression.right);
|
|
26269
|
-
return false;
|
|
26270
|
-
};
|
|
26271
|
-
return functionMatches(functionNode);
|
|
26272
|
-
};
|
|
26273
|
-
//#endregion
|
|
26274
26046
|
//#region src/plugin/utils/function-contains-react-render-output.ts
|
|
26275
26047
|
const NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES = new Set([
|
|
26276
26048
|
"FunctionDeclaration",
|
|
@@ -26290,13 +26062,12 @@ const isCallArgumentFunctionExpression = (node) => {
|
|
|
26290
26062
|
return parent.arguments.some((argumentNode) => argumentNode === node);
|
|
26291
26063
|
};
|
|
26292
26064
|
const isNestedRenderEvidenceBoundary = (node) => NESTED_RENDER_EVIDENCE_BOUNDARY_TYPES.has(node.type) && !isCallArgumentFunctionExpression(node);
|
|
26293
|
-
const isRenderOutputExpression = (node, scopes) => node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS);
|
|
26294
26065
|
const containsRenderOutput$1 = (rootNode, scopes) => {
|
|
26295
26066
|
let hasRenderOutput = false;
|
|
26296
26067
|
walkAst(rootNode, (node) => {
|
|
26297
26068
|
if (hasRenderOutput) return false;
|
|
26298
26069
|
if (node !== rootNode && isNestedRenderEvidenceBoundary(node)) return false;
|
|
26299
|
-
if (
|
|
26070
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment" || isReactApiCall(node, "createElement", scopes, REACT_CREATE_ELEMENT_OPTIONS)) {
|
|
26300
26071
|
hasRenderOutput = true;
|
|
26301
26072
|
return false;
|
|
26302
26073
|
}
|
|
@@ -26307,7 +26078,7 @@ const renderOutputCache = /* @__PURE__ */ new WeakMap();
|
|
|
26307
26078
|
const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
26308
26079
|
const cachedEntry = renderOutputCache.get(functionNode);
|
|
26309
26080
|
if (cachedEntry && cachedEntry.scopes === scopes) return cachedEntry.hasRenderOutput;
|
|
26310
|
-
const hasRenderOutput = containsRenderOutput$1(functionNode, scopes)
|
|
26081
|
+
const hasRenderOutput = containsRenderOutput$1(functionNode, scopes);
|
|
26311
26082
|
renderOutputCache.set(functionNode, {
|
|
26312
26083
|
scopes,
|
|
26313
26084
|
hasRenderOutput
|
|
@@ -26315,9 +26086,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
|
26315
26086
|
return hasRenderOutput;
|
|
26316
26087
|
};
|
|
26317
26088
|
//#endregion
|
|
26318
|
-
//#region src/plugin/utils/is-component-assignment.ts
|
|
26319
|
-
const isComponentAssignment = (node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "Identifier") && isUppercaseName(node.id.name) && Boolean(node.init) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
|
|
26320
|
-
//#endregion
|
|
26321
26089
|
//#region src/plugin/utils/is-component-declaration.ts
|
|
26322
26090
|
const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
|
|
26323
26091
|
//#endregion
|
|
@@ -32367,12 +32135,11 @@ const isMemoCall = (node) => {
|
|
|
32367
32135
|
};
|
|
32368
32136
|
const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
|
|
32369
32137
|
const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
|
|
32370
|
-
const isInlineReference = (node
|
|
32371
|
-
|
|
32372
|
-
if (isNodeOfType(
|
|
32373
|
-
if (isNodeOfType(
|
|
32374
|
-
if (isNodeOfType(
|
|
32375
|
-
if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
|
|
32138
|
+
const isInlineReference = (node) => {
|
|
32139
|
+
if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "bind") return "functions";
|
|
32140
|
+
if (isNodeOfType(node, "ObjectExpression")) return "objects";
|
|
32141
|
+
if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
|
|
32142
|
+
if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
|
|
32376
32143
|
return null;
|
|
32377
32144
|
};
|
|
32378
32145
|
const noInlinePropOnMemoComponent = defineRule({
|
|
@@ -32402,7 +32169,7 @@ const noInlinePropOnMemoComponent = defineRule({
|
|
|
32402
32169
|
let elementName = null;
|
|
32403
32170
|
if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
|
|
32404
32171
|
if (!elementName || !memoizedComponentNames.has(elementName)) return;
|
|
32405
|
-
const propType = isInlineReference(node.value.expression
|
|
32172
|
+
const propType = isInlineReference(node.value.expression);
|
|
32406
32173
|
if (propType) context.report({
|
|
32407
32174
|
node: node.value.expression,
|
|
32408
32175
|
message: `This redraws ${elementName} on every render because the prop is ${propType} built right here, so memo() can't skip it. Move it to a stable value with useMemo, useCallback, or module scope`
|
|
@@ -33261,13 +33028,11 @@ const noManyBooleanProps = defineRule({
|
|
|
33261
33028
|
};
|
|
33262
33029
|
const checkComponent = (functionNode, param, body, componentName, reportNode) => {
|
|
33263
33030
|
if (!param) return;
|
|
33264
|
-
const propsBinding = resolveFirstArgumentBinding(param);
|
|
33265
|
-
if (!propsBinding) return;
|
|
33266
33031
|
if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
|
|
33267
|
-
if (isNodeOfType(
|
|
33032
|
+
if (isNodeOfType(param, "ObjectPattern")) {
|
|
33268
33033
|
const callbackUsedNames = collectCallbackUsedNames(body, param, context.scopes);
|
|
33269
33034
|
const booleanLikePropNames = [];
|
|
33270
|
-
for (const property of
|
|
33035
|
+
for (const property of param.properties ?? []) {
|
|
33271
33036
|
if (!isNodeOfType(property, "Property")) continue;
|
|
33272
33037
|
const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : null;
|
|
33273
33038
|
if (!keyName) continue;
|
|
@@ -33278,7 +33043,7 @@ const noManyBooleanProps = defineRule({
|
|
|
33278
33043
|
reportIfMany(booleanLikePropNames, componentName, reportNode);
|
|
33279
33044
|
return;
|
|
33280
33045
|
}
|
|
33281
|
-
if (isNodeOfType(
|
|
33046
|
+
if (isNodeOfType(param, "Identifier")) reportIfMany([...collectBooleanLikePropsFromBody(body, param.name)], componentName, reportNode);
|
|
33282
33047
|
};
|
|
33283
33048
|
return {
|
|
33284
33049
|
FunctionDeclaration(node) {
|
|
@@ -40339,21 +40104,20 @@ const expressionContainsJsxOrCreateElement = (root) => {
|
|
|
40339
40104
|
});
|
|
40340
40105
|
return found;
|
|
40341
40106
|
};
|
|
40342
|
-
const functionContainsJsxOrCreateElement = (functionNode, scopes) => expressionContainsJsxOrCreateElement(functionNode) || functionReturnsMatchingExpression(functionNode, scopes, expressionContainsJsxOrCreateElement);
|
|
40343
40107
|
const isReactClassComponent = (classNode) => {
|
|
40344
40108
|
if (isEs6Component(classNode)) return true;
|
|
40345
40109
|
return expressionContainsJsxOrCreateElement(classNode);
|
|
40346
40110
|
};
|
|
40347
|
-
const findEnclosingComponent = (node
|
|
40111
|
+
const findEnclosingComponent = (node) => {
|
|
40348
40112
|
let walker = node.parent;
|
|
40349
40113
|
while (walker) {
|
|
40350
40114
|
if (isFunctionLike$1(walker)) {
|
|
40351
40115
|
const componentName = inferFunctionLikeName(walker);
|
|
40352
|
-
if (componentName && isReactComponentName(componentName) &&
|
|
40116
|
+
if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
|
|
40353
40117
|
component: walker,
|
|
40354
40118
|
name: componentName
|
|
40355
40119
|
};
|
|
40356
|
-
if (!componentName &&
|
|
40120
|
+
if (!componentName && expressionContainsJsxOrCreateElement(walker) && walker.parent && isNodeOfType(walker.parent, "ExportDefaultDeclaration")) return {
|
|
40357
40121
|
component: walker,
|
|
40358
40122
|
name: null
|
|
40359
40123
|
};
|
|
@@ -40592,7 +40356,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
40592
40356
|
if (renderPropRegex.test(propInfo.propName)) return;
|
|
40593
40357
|
if (settings.allowAsProps) return;
|
|
40594
40358
|
}
|
|
40595
|
-
const enclosing = findEnclosingComponent(candidateNode
|
|
40359
|
+
const enclosing = findEnclosingComponent(candidateNode);
|
|
40596
40360
|
if (!enclosing) return;
|
|
40597
40361
|
const gatedName = propInfo ? null : requiredInstantiationName;
|
|
40598
40362
|
queuedReports.push({
|
|
@@ -40603,7 +40367,7 @@ const noUnstableNestedComponents = defineRule({
|
|
|
40603
40367
|
});
|
|
40604
40368
|
};
|
|
40605
40369
|
const checkFunctionLike = (node) => {
|
|
40606
|
-
if (!
|
|
40370
|
+
if (!expressionContainsJsxOrCreateElement(node)) return;
|
|
40607
40371
|
const inferredName = inferFunctionLikeName(node);
|
|
40608
40372
|
const propInfo = isComponentDeclaredInProp(node);
|
|
40609
40373
|
const isObjectCallback = isObjectCallbackCandidate(node);
|
|
@@ -42040,9 +41804,8 @@ const CROSS_CUTTING_STATE_BOOLEAN_NAMES = new Set([
|
|
|
42040
41804
|
]);
|
|
42041
41805
|
const collectBooleanPropBindings = (param) => {
|
|
42042
41806
|
const bindings = /* @__PURE__ */ new Set();
|
|
42043
|
-
|
|
42044
|
-
|
|
42045
|
-
for (const property of propsBinding.properties ?? []) {
|
|
41807
|
+
if (!param || !isNodeOfType(param, "ObjectPattern")) return bindings;
|
|
41808
|
+
for (const property of param.properties ?? []) {
|
|
42046
41809
|
if (!isNodeOfType(property, "Property")) continue;
|
|
42047
41810
|
if (property.computed) continue;
|
|
42048
41811
|
if (!isNodeOfType(property.key, "Identifier")) continue;
|
|
@@ -43850,13 +43613,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
|
|
|
43850
43613
|
}
|
|
43851
43614
|
return null;
|
|
43852
43615
|
};
|
|
43853
|
-
const resolveHookNameFromInitializer = (initializer
|
|
43616
|
+
const resolveHookNameFromInitializer = (initializer) => {
|
|
43854
43617
|
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
43855
43618
|
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
43856
|
-
const
|
|
43857
|
-
if (
|
|
43858
|
-
if (!isNodeOfType(
|
|
43859
|
-
return resolveTanstackQueryHookName(
|
|
43619
|
+
const binding = findVariableInitializer(initializer, initializer.name);
|
|
43620
|
+
if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
|
|
43621
|
+
if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
43622
|
+
return resolveTanstackQueryHookName(binding.initializer);
|
|
43860
43623
|
};
|
|
43861
43624
|
const queryNoRestDestructuring = defineRule({
|
|
43862
43625
|
id: "query-no-rest-destructuring",
|
|
@@ -43869,7 +43632,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
43869
43632
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
43870
43633
|
if (!node.init) return;
|
|
43871
43634
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
43872
|
-
const hookName = resolveHookNameFromInitializer(node.init
|
|
43635
|
+
const hookName = resolveHookNameFromInitializer(node.init);
|
|
43873
43636
|
if (!hookName) return;
|
|
43874
43637
|
context.report({
|
|
43875
43638
|
node: node.id,
|
|
@@ -45224,7 +44987,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
|
|
|
45224
44987
|
"useParams",
|
|
45225
44988
|
"usePathname"
|
|
45226
44989
|
]);
|
|
45227
|
-
const findHookCallBindings = (componentBody
|
|
44990
|
+
const findHookCallBindings = (componentBody) => {
|
|
45228
44991
|
const bindings = [];
|
|
45229
44992
|
if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
|
|
45230
44993
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -45235,10 +44998,8 @@ const findHookCallBindings = (componentBody, scopes) => {
|
|
|
45235
44998
|
const callee = declarator.init.callee;
|
|
45236
44999
|
if (!isNodeOfType(callee, "Identifier")) continue;
|
|
45237
45000
|
if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
|
|
45238
|
-
const valueSymbol = scopes.symbolFor(declarator.id);
|
|
45239
|
-
if (!valueSymbol) continue;
|
|
45240
45001
|
bindings.push({
|
|
45241
|
-
|
|
45002
|
+
valueName: declarator.id.name,
|
|
45242
45003
|
hookName: callee.name,
|
|
45243
45004
|
declarator
|
|
45244
45005
|
});
|
|
@@ -45246,49 +45007,6 @@ const findHookCallBindings = (componentBody, scopes) => {
|
|
|
45246
45007
|
}
|
|
45247
45008
|
return bindings;
|
|
45248
45009
|
};
|
|
45249
|
-
const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
|
|
45250
|
-
const symbols = [sourceSymbol];
|
|
45251
|
-
const symbolIds = new Set([sourceSymbol.id]);
|
|
45252
|
-
const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
|
|
45253
|
-
if (!isNodeOfType(componentBody, "BlockStatement")) return {
|
|
45254
|
-
symbols,
|
|
45255
|
-
aliasSourceIdentifiers
|
|
45256
|
-
};
|
|
45257
|
-
let didFindAlias = true;
|
|
45258
|
-
while (didFindAlias) {
|
|
45259
|
-
didFindAlias = false;
|
|
45260
|
-
for (const statement of componentBody.body ?? []) {
|
|
45261
|
-
if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
|
|
45262
|
-
for (const declarator of statement.declarations ?? []) {
|
|
45263
|
-
let aliasIdentifier = null;
|
|
45264
|
-
let sourceIdentifier = null;
|
|
45265
|
-
const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
|
|
45266
|
-
const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
|
|
45267
|
-
const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
|
|
45268
|
-
const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
|
|
45269
|
-
if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
|
|
45270
|
-
aliasIdentifier = declarator.id;
|
|
45271
|
-
sourceIdentifier = initializer;
|
|
45272
|
-
} else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
|
|
45273
|
-
aliasIdentifier = arrayBinding;
|
|
45274
|
-
sourceIdentifier = arrayValue;
|
|
45275
|
-
}
|
|
45276
|
-
if (!aliasIdentifier || !sourceIdentifier) continue;
|
|
45277
|
-
const referencedSymbol = scopes.symbolFor(sourceIdentifier);
|
|
45278
|
-
const aliasSymbol = scopes.symbolFor(aliasIdentifier);
|
|
45279
|
-
if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
|
|
45280
|
-
symbolIds.add(aliasSymbol.id);
|
|
45281
|
-
symbols.push(aliasSymbol);
|
|
45282
|
-
aliasSourceIdentifiers.add(sourceIdentifier);
|
|
45283
|
-
didFindAlias = true;
|
|
45284
|
-
}
|
|
45285
|
-
}
|
|
45286
|
-
}
|
|
45287
|
-
return {
|
|
45288
|
-
symbols,
|
|
45289
|
-
aliasSourceIdentifiers
|
|
45290
|
-
};
|
|
45291
|
-
};
|
|
45292
45010
|
const rerenderDeferReadsHook = defineRule({
|
|
45293
45011
|
id: "rerender-defer-reads-hook",
|
|
45294
45012
|
title: "URL hook value only read in handlers",
|
|
@@ -45299,13 +45017,15 @@ const rerenderDeferReadsHook = defineRule({
|
|
|
45299
45017
|
create: (context) => {
|
|
45300
45018
|
const checkComponent = (componentBody) => {
|
|
45301
45019
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
45302
|
-
const bindings = findHookCallBindings(componentBody
|
|
45020
|
+
const bindings = findHookCallBindings(componentBody);
|
|
45303
45021
|
if (bindings.length === 0) return;
|
|
45304
45022
|
const handlerBindingNames = collectHandlerBindingNames(componentBody);
|
|
45305
45023
|
for (const binding of bindings) {
|
|
45306
|
-
const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
|
|
45307
45024
|
const referenceLocations = [];
|
|
45308
|
-
|
|
45025
|
+
walkAst(componentBody, (child) => {
|
|
45026
|
+
if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
|
|
45027
|
+
if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
|
|
45028
|
+
});
|
|
45309
45029
|
if (referenceLocations.length === 0) continue;
|
|
45310
45030
|
if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
|
|
45311
45031
|
context.report({
|
|
@@ -45676,10 +45396,14 @@ const rerenderLazyStateInit = defineRule({
|
|
|
45676
45396
|
//#endregion
|
|
45677
45397
|
//#region src/plugin/rules/performance/rerender-memo-before-early-return.ts
|
|
45678
45398
|
const isJsxExpression = (node) => Boolean(node && isJsxElementOrFragment(stripParenExpression(node)));
|
|
45679
|
-
const callbackReturnsJsx = (callback
|
|
45399
|
+
const callbackReturnsJsx = (callback) => {
|
|
45680
45400
|
if (!callback) return false;
|
|
45681
45401
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
45682
|
-
|
|
45402
|
+
const body = callback.body;
|
|
45403
|
+
if (isJsxExpression(body)) return true;
|
|
45404
|
+
if (!isNodeOfType(body, "BlockStatement")) return false;
|
|
45405
|
+
for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isJsxExpression(stmt.argument)) return true;
|
|
45406
|
+
return false;
|
|
45683
45407
|
};
|
|
45684
45408
|
const returnArgumentUsesAnyName = (returnStatement, names) => {
|
|
45685
45409
|
if (!isNodeOfType(returnStatement, "ReturnStatement") || !returnStatement.argument) return false;
|
|
@@ -45757,7 +45481,7 @@ const rerenderMemoBeforeEarlyReturn = defineRule({
|
|
|
45757
45481
|
if (!isNodeOfType(stmt, "VariableDeclaration")) continue;
|
|
45758
45482
|
for (const declarator of stmt.declarations ?? []) {
|
|
45759
45483
|
const init = declarator.init;
|
|
45760
|
-
if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0]
|
|
45484
|
+
if (isNodeOfType(init, "CallExpression") && isHookCall$2(init, "useMemo") && callbackReturnsJsx(init.arguments?.[0])) {
|
|
45761
45485
|
memoNode = declarator;
|
|
45762
45486
|
callbackGuardTests = collectLeadingCallbackGuardTests(init.arguments?.[0]);
|
|
45763
45487
|
if (isNodeOfType(declarator.id, "Identifier")) memoConsumerNames.add(declarator.id.name);
|
|
@@ -45823,11 +45547,8 @@ const collectFromObjectPattern = (pattern, bindings) => {
|
|
|
45823
45547
|
const collectDefaultedEmptyBindings = (functionNode) => {
|
|
45824
45548
|
const bindings = /* @__PURE__ */ new Map();
|
|
45825
45549
|
const params = functionNode.params ?? [];
|
|
45826
|
-
for (const
|
|
45827
|
-
|
|
45828
|
-
if (parameterBinding) collectFromObjectPattern(parameterBinding, bindings);
|
|
45829
|
-
}
|
|
45830
|
-
const propsParam = resolveFirstArgumentBinding(params[0]);
|
|
45550
|
+
for (const param of params) collectFromObjectPattern(param, bindings);
|
|
45551
|
+
const propsParam = params[0];
|
|
45831
45552
|
const body = functionNode.body;
|
|
45832
45553
|
if (!propsParam || !isNodeOfType(propsParam, "Identifier") || !body) return bindings;
|
|
45833
45554
|
if (!isNodeOfType(body, "BlockStatement")) return bindings;
|
|
@@ -53254,8 +52975,7 @@ const serverCacheWithObjectLiteral = defineRule({
|
|
|
53254
52975
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
53255
52976
|
if (!cachedFunctionNames.has(node.callee.name)) return;
|
|
53256
52977
|
const firstArg = node.arguments?.[0];
|
|
53257
|
-
if (!
|
|
53258
|
-
if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
|
|
52978
|
+
if (!isNodeOfType(firstArg, "ObjectExpression")) return;
|
|
53259
52979
|
context.report({
|
|
53260
52980
|
node,
|
|
53261
52981
|
message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
|