oxlint-plugin-react-doctor 0.7.6-dev.e141d90 → 0.7.6-dev.eb09902
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 +45 -247
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -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
|
|
@@ -20533,24 +20395,8 @@ const hasDirective = (programNode, directive) => {
|
|
|
20533
20395
|
return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
|
|
20534
20396
|
};
|
|
20535
20397
|
//#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
|
-
};
|
|
20398
|
+
//#region src/plugin/utils/is-component-assignment.ts
|
|
20399
|
+
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
20400
|
//#endregion
|
|
20555
20401
|
//#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
|
|
20556
20402
|
const nextjsAsyncClientComponent = defineRule({
|
|
@@ -20576,10 +20422,10 @@ const nextjsAsyncClientComponent = defineRule({
|
|
|
20576
20422
|
},
|
|
20577
20423
|
VariableDeclarator(node) {
|
|
20578
20424
|
if (!fileHasUseClient) return;
|
|
20425
|
+
if (!isComponentAssignment(node)) return;
|
|
20426
|
+
if (!isInlineFunctionExpression(node.init)) return;
|
|
20427
|
+
if (!node.init.async) return;
|
|
20579
20428
|
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
20429
|
context.report({
|
|
20584
20430
|
node,
|
|
20585
20431
|
message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
|
|
@@ -26315,9 +26161,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
|
26315
26161
|
return hasRenderOutput;
|
|
26316
26162
|
};
|
|
26317
26163
|
//#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
26164
|
//#region src/plugin/utils/is-component-declaration.ts
|
|
26322
26165
|
const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
|
|
26323
26166
|
//#endregion
|
|
@@ -32367,12 +32210,11 @@ const isMemoCall = (node) => {
|
|
|
32367
32210
|
};
|
|
32368
32211
|
const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
|
|
32369
32212
|
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";
|
|
32213
|
+
const isInlineReference = (node) => {
|
|
32214
|
+
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";
|
|
32215
|
+
if (isNodeOfType(node, "ObjectExpression")) return "objects";
|
|
32216
|
+
if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
|
|
32217
|
+
if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
|
|
32376
32218
|
return null;
|
|
32377
32219
|
};
|
|
32378
32220
|
const noInlinePropOnMemoComponent = defineRule({
|
|
@@ -32402,7 +32244,7 @@ const noInlinePropOnMemoComponent = defineRule({
|
|
|
32402
32244
|
let elementName = null;
|
|
32403
32245
|
if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
|
|
32404
32246
|
if (!elementName || !memoizedComponentNames.has(elementName)) return;
|
|
32405
|
-
const propType = isInlineReference(node.value.expression
|
|
32247
|
+
const propType = isInlineReference(node.value.expression);
|
|
32406
32248
|
if (propType) context.report({
|
|
32407
32249
|
node: node.value.expression,
|
|
32408
32250
|
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`
|
|
@@ -43850,13 +43692,13 @@ const resolveTanstackQueryHookName = (callExpression) => {
|
|
|
43850
43692
|
}
|
|
43851
43693
|
return null;
|
|
43852
43694
|
};
|
|
43853
|
-
const resolveHookNameFromInitializer = (initializer
|
|
43695
|
+
const resolveHookNameFromInitializer = (initializer) => {
|
|
43854
43696
|
if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
|
|
43855
43697
|
if (!isNodeOfType(initializer, "Identifier")) return null;
|
|
43856
|
-
const
|
|
43857
|
-
if (
|
|
43858
|
-
if (!isNodeOfType(
|
|
43859
|
-
return resolveTanstackQueryHookName(
|
|
43698
|
+
const binding = findVariableInitializer(initializer, initializer.name);
|
|
43699
|
+
if (!binding?.initializer || !isConstDeclaredBinding(binding)) return null;
|
|
43700
|
+
if (!isNodeOfType(binding.initializer, "CallExpression")) return null;
|
|
43701
|
+
return resolveTanstackQueryHookName(binding.initializer);
|
|
43860
43702
|
};
|
|
43861
43703
|
const queryNoRestDestructuring = defineRule({
|
|
43862
43704
|
id: "query-no-rest-destructuring",
|
|
@@ -43869,7 +43711,7 @@ const queryNoRestDestructuring = defineRule({
|
|
|
43869
43711
|
if (!isNodeOfType(node.id, "ObjectPattern")) return;
|
|
43870
43712
|
if (!node.init) return;
|
|
43871
43713
|
if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
|
|
43872
|
-
const hookName = resolveHookNameFromInitializer(node.init
|
|
43714
|
+
const hookName = resolveHookNameFromInitializer(node.init);
|
|
43873
43715
|
if (!hookName) return;
|
|
43874
43716
|
context.report({
|
|
43875
43717
|
node: node.id,
|
|
@@ -45224,7 +45066,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
|
|
|
45224
45066
|
"useParams",
|
|
45225
45067
|
"usePathname"
|
|
45226
45068
|
]);
|
|
45227
|
-
const findHookCallBindings = (componentBody
|
|
45069
|
+
const findHookCallBindings = (componentBody) => {
|
|
45228
45070
|
const bindings = [];
|
|
45229
45071
|
if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
|
|
45230
45072
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -45235,10 +45077,8 @@ const findHookCallBindings = (componentBody, scopes) => {
|
|
|
45235
45077
|
const callee = declarator.init.callee;
|
|
45236
45078
|
if (!isNodeOfType(callee, "Identifier")) continue;
|
|
45237
45079
|
if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
|
|
45238
|
-
const valueSymbol = scopes.symbolFor(declarator.id);
|
|
45239
|
-
if (!valueSymbol) continue;
|
|
45240
45080
|
bindings.push({
|
|
45241
|
-
|
|
45081
|
+
valueName: declarator.id.name,
|
|
45242
45082
|
hookName: callee.name,
|
|
45243
45083
|
declarator
|
|
45244
45084
|
});
|
|
@@ -45246,49 +45086,6 @@ const findHookCallBindings = (componentBody, scopes) => {
|
|
|
45246
45086
|
}
|
|
45247
45087
|
return bindings;
|
|
45248
45088
|
};
|
|
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
45089
|
const rerenderDeferReadsHook = defineRule({
|
|
45293
45090
|
id: "rerender-defer-reads-hook",
|
|
45294
45091
|
title: "URL hook value only read in handlers",
|
|
@@ -45299,13 +45096,15 @@ const rerenderDeferReadsHook = defineRule({
|
|
|
45299
45096
|
create: (context) => {
|
|
45300
45097
|
const checkComponent = (componentBody) => {
|
|
45301
45098
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
45302
|
-
const bindings = findHookCallBindings(componentBody
|
|
45099
|
+
const bindings = findHookCallBindings(componentBody);
|
|
45303
45100
|
if (bindings.length === 0) return;
|
|
45304
45101
|
const handlerBindingNames = collectHandlerBindingNames(componentBody);
|
|
45305
45102
|
for (const binding of bindings) {
|
|
45306
|
-
const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
|
|
45307
45103
|
const referenceLocations = [];
|
|
45308
|
-
|
|
45104
|
+
walkAst(componentBody, (child) => {
|
|
45105
|
+
if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
|
|
45106
|
+
if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
|
|
45107
|
+
});
|
|
45309
45108
|
if (referenceLocations.length === 0) continue;
|
|
45310
45109
|
if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
|
|
45311
45110
|
context.report({
|
|
@@ -53254,8 +53053,7 @@ const serverCacheWithObjectLiteral = defineRule({
|
|
|
53254
53053
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
53255
53054
|
if (!cachedFunctionNames.has(node.callee.name)) return;
|
|
53256
53055
|
const firstArg = node.arguments?.[0];
|
|
53257
|
-
if (!
|
|
53258
|
-
if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
|
|
53056
|
+
if (!isNodeOfType(firstArg, "ObjectExpression")) return;
|
|
53259
53057
|
context.report({
|
|
53260
53058
|
node,
|
|
53261
53059
|
message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
|