oxlint-plugin-react-doctor 0.7.6-dev.c5a06bd → 0.7.6-dev.c6f996e
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 +75 -546
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1180,8 +1180,8 @@ const stripParenExpression = (node) => {
|
|
|
1180
1180
|
return current;
|
|
1181
1181
|
};
|
|
1182
1182
|
//#endregion
|
|
1183
|
-
//#region src/plugin/utils/get-root-identifier.ts
|
|
1184
|
-
const
|
|
1183
|
+
//#region src/plugin/utils/get-root-identifier-name.ts
|
|
1184
|
+
const getRootIdentifierName = (node, options) => {
|
|
1185
1185
|
if (!node) return null;
|
|
1186
1186
|
const followCallChains = options?.followCallChains === true;
|
|
1187
1187
|
let cursor = node;
|
|
@@ -1192,18 +1192,16 @@ const getRootIdentifier$1 = (node, options) => {
|
|
|
1192
1192
|
continue;
|
|
1193
1193
|
}
|
|
1194
1194
|
if (followCallChains && isNodeOfType(cursor, "CallExpression")) {
|
|
1195
|
-
|
|
1196
|
-
|
|
1195
|
+
const callee = cursor.callee;
|
|
1196
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
1197
|
+
cursor = callee.object;
|
|
1197
1198
|
continue;
|
|
1198
1199
|
}
|
|
1199
1200
|
break;
|
|
1200
1201
|
}
|
|
1201
|
-
return isNodeOfType(cursor, "Identifier") ? cursor : null;
|
|
1202
|
+
return isNodeOfType(cursor, "Identifier") ? cursor.name : null;
|
|
1202
1203
|
};
|
|
1203
1204
|
//#endregion
|
|
1204
|
-
//#region src/plugin/utils/get-root-identifier-name.ts
|
|
1205
|
-
const getRootIdentifierName = (node, options) => getRootIdentifier$1(node, options)?.name ?? null;
|
|
1206
|
-
//#endregion
|
|
1207
1205
|
//#region src/plugin/rules/state-and-effects/advanced-event-handler-refs.ts
|
|
1208
1206
|
const STABLE_HANDLER_HOOK_NAMES = new Set([
|
|
1209
1207
|
"useCallback",
|
|
@@ -5964,7 +5962,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5964
5962
|
//#endregion
|
|
5965
5963
|
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5966
5964
|
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5967
|
-
if (!isNodeOfType(node, "Property")
|
|
5965
|
+
if (!isNodeOfType(node, "Property")) return null;
|
|
5968
5966
|
if (node.computed) {
|
|
5969
5967
|
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
5970
5968
|
return null;
|
|
@@ -20535,24 +20533,8 @@ const hasDirective = (programNode, directive) => {
|
|
|
20535
20533
|
return Boolean(programNode.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === directive));
|
|
20536
20534
|
};
|
|
20537
20535
|
//#endregion
|
|
20538
|
-
//#region src/plugin/utils/
|
|
20539
|
-
const
|
|
20540
|
-
"freeze",
|
|
20541
|
-
"seal",
|
|
20542
|
-
"preventExtensions"
|
|
20543
|
-
]);
|
|
20544
|
-
const OBJECT_FREEZE_OR_SEAL_METHOD_NAMES = new Set(["freeze", "seal"]);
|
|
20545
|
-
const unwrapObjectIntegrityExpression = (node, scopes, methodNames = OBJECT_INTEGRITY_METHOD_NAMES) => {
|
|
20546
|
-
let expression = stripParenExpression(node);
|
|
20547
|
-
while (isNodeOfType(expression, "CallExpression")) {
|
|
20548
|
-
const callee = stripParenExpression(expression.callee);
|
|
20549
|
-
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;
|
|
20550
|
-
const wrappedExpression = expression.arguments[0];
|
|
20551
|
-
if (!wrappedExpression || isNodeOfType(wrappedExpression, "SpreadElement")) break;
|
|
20552
|
-
expression = stripParenExpression(wrappedExpression);
|
|
20553
|
-
}
|
|
20554
|
-
return expression;
|
|
20555
|
-
};
|
|
20536
|
+
//#region src/plugin/utils/is-component-assignment.ts
|
|
20537
|
+
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"));
|
|
20556
20538
|
//#endregion
|
|
20557
20539
|
//#region src/plugin/rules/nextjs/nextjs-async-client-component.ts
|
|
20558
20540
|
const nextjsAsyncClientComponent = defineRule({
|
|
@@ -20578,10 +20560,10 @@ const nextjsAsyncClientComponent = defineRule({
|
|
|
20578
20560
|
},
|
|
20579
20561
|
VariableDeclarator(node) {
|
|
20580
20562
|
if (!fileHasUseClient) return;
|
|
20563
|
+
if (!isComponentAssignment(node)) return;
|
|
20564
|
+
if (!isInlineFunctionExpression(node.init)) return;
|
|
20565
|
+
if (!node.init.async) return;
|
|
20581
20566
|
if (!isNodeOfType(node.id, "Identifier")) return;
|
|
20582
|
-
if (!isUppercaseName(node.id.name) || !node.init) return;
|
|
20583
|
-
const componentFunction = unwrapObjectIntegrityExpression(node.init, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES);
|
|
20584
|
-
if (!isInlineFunctionExpression(componentFunction) || !componentFunction.async) return;
|
|
20585
20567
|
context.report({
|
|
20586
20568
|
node,
|
|
20587
20569
|
message: `Async client component "${node.id.name}" fails to render because client components can't be async.`
|
|
@@ -26317,9 +26299,6 @@ const functionContainsReactRenderOutput = (functionNode, scopes) => {
|
|
|
26317
26299
|
return hasRenderOutput;
|
|
26318
26300
|
};
|
|
26319
26301
|
//#endregion
|
|
26320
|
-
//#region src/plugin/utils/is-component-assignment.ts
|
|
26321
|
-
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"));
|
|
26322
|
-
//#endregion
|
|
26323
26302
|
//#region src/plugin/utils/is-component-declaration.ts
|
|
26324
26303
|
const isComponentDeclaration = (node) => isNodeOfType(node, "FunctionDeclaration") && node.id !== null && Boolean(node.id?.name) && isUppercaseName(node.id.name);
|
|
26325
26304
|
//#endregion
|
|
@@ -31961,26 +31940,6 @@ const noImgLazyWithHighFetchpriority = defineRule({
|
|
|
31961
31940
|
} })
|
|
31962
31941
|
});
|
|
31963
31942
|
//#endregion
|
|
31964
|
-
//#region src/plugin/utils/react-ref-origin.ts
|
|
31965
|
-
const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
31966
|
-
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
31967
|
-
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticPropertyName(memberExpression) !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
|
|
31968
|
-
const symbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
31969
|
-
if (!symbol?.initializer) return null;
|
|
31970
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
31971
|
-
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
31972
|
-
return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
|
|
31973
|
-
};
|
|
31974
|
-
const hasReactRefCurrentOrigin = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
31975
|
-
const expression = stripParenExpression(node);
|
|
31976
|
-
if (resolveReactRefSymbol(expression, scopes)) return true;
|
|
31977
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
31978
|
-
const symbol = resolveConstIdentifierAlias(expression, scopes);
|
|
31979
|
-
if (!symbol?.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
31980
|
-
visitedSymbolIds.add(symbol.id);
|
|
31981
|
-
return hasReactRefCurrentOrigin(symbol.initializer, scopes, visitedSymbolIds);
|
|
31982
|
-
};
|
|
31983
|
-
//#endregion
|
|
31984
31943
|
//#region src/plugin/rules/state-and-effects/no-impure-state-updater.ts
|
|
31985
31944
|
const TIMER_FUNCTION_NAMES = new Set([
|
|
31986
31945
|
"cancelAnimationFrame",
|
|
@@ -32012,33 +31971,6 @@ const NOTIFICATION_METHOD_NAMES = new Set([
|
|
|
32012
31971
|
"success",
|
|
32013
31972
|
"warning"
|
|
32014
31973
|
]);
|
|
32015
|
-
const NOTIFICATION_MODULE_SOURCES = new Set([
|
|
32016
|
-
"@chakra-ui/react",
|
|
32017
|
-
"@heroui/react",
|
|
32018
|
-
"@mantine/notifications",
|
|
32019
|
-
"antd",
|
|
32020
|
-
"react-hot-toast",
|
|
32021
|
-
"react-toastify",
|
|
32022
|
-
"sonner"
|
|
32023
|
-
]);
|
|
32024
|
-
const SYNCHRONOUS_ARRAY_METHOD_NAMES = new Set([
|
|
32025
|
-
"every",
|
|
32026
|
-
"filter",
|
|
32027
|
-
"find",
|
|
32028
|
-
"findIndex",
|
|
32029
|
-
"flatMap",
|
|
32030
|
-
"forEach",
|
|
32031
|
-
"map",
|
|
32032
|
-
"reduce",
|
|
32033
|
-
"reduceRight",
|
|
32034
|
-
"some",
|
|
32035
|
-
"sort"
|
|
32036
|
-
]);
|
|
32037
|
-
const isNotificationModuleSource = (source) => {
|
|
32038
|
-
if (!source) return false;
|
|
32039
|
-
for (const moduleSource of NOTIFICATION_MODULE_SOURCES) if (source === moduleSource || source.startsWith(`${moduleSource}/`)) return true;
|
|
32040
|
-
return /(?:^|[/_.-])(?:notification|toast)s?(?:$|[/_.-])/i.test(source);
|
|
32041
|
-
};
|
|
32042
31974
|
const getMemberCall = (node) => {
|
|
32043
31975
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
32044
31976
|
if (!isNodeOfType(node.callee, "MemberExpression") || node.callee.computed) return null;
|
|
@@ -32050,37 +31982,27 @@ const getMemberCall = (node) => {
|
|
|
32050
31982
|
};
|
|
32051
31983
|
const isNotificationReceiver = (receiver, scopes) => {
|
|
32052
31984
|
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
32053
|
-
|
|
32054
|
-
if (importBinding) return isNotificationModuleSource(importBinding.source) && (importBinding.exportedName === "default" || importBinding.exportedName !== null && NOTIFICATION_RECEIVER_NAMES.has(importBinding.exportedName));
|
|
31985
|
+
if (!NOTIFICATION_RECEIVER_NAMES.has(receiver.name)) return false;
|
|
32055
31986
|
const symbol = scopes.symbolFor(receiver);
|
|
31987
|
+
if (symbol?.kind === "import") return true;
|
|
32056
31988
|
if (!isNodeOfType(symbol?.initializer, "CallExpression")) return false;
|
|
32057
31989
|
const callee = symbol.initializer.callee;
|
|
32058
|
-
|
|
32059
|
-
const hookImport = getImportBindingForName(callee, callee.name);
|
|
32060
|
-
return Boolean(hookImport && isNotificationModuleSource(hookImport.source) && hookImport.exportedName !== null && /^use(?:Message|Notification|Toast)$/.test(hookImport.exportedName));
|
|
32061
|
-
};
|
|
32062
|
-
const isKnownGlobalObject = (node, expectedName, scopes) => isNodeOfType(node, "Identifier") && node.name === expectedName && scopes.isGlobalReference(node);
|
|
32063
|
-
const isGlobalMember = (node, expectedPropertyName, scopes) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.property, "Identifier") && node.property.name === expectedPropertyName && (isKnownGlobalObject(node.object, "window", scopes) || isKnownGlobalObject(node.object, "globalThis", scopes));
|
|
32064
|
-
const isStorageReceiver = (node, scopes) => {
|
|
32065
|
-
if (isNodeOfType(node, "Identifier") && STORAGE_RECEIVER_NAMES.has(node.name) && scopes.isGlobalReference(node)) return true;
|
|
32066
|
-
for (const receiverName of STORAGE_RECEIVER_NAMES) if (isGlobalMember(node, receiverName, scopes)) return true;
|
|
32067
|
-
return false;
|
|
31990
|
+
return isNodeOfType(callee, "Identifier") && /^use(?:Message|Notification|Toast)$/.test(callee.name);
|
|
32068
31991
|
};
|
|
32069
31992
|
const getKnownImpureCall = (callExpression, scopes) => {
|
|
32070
31993
|
if (isNodeOfType(callExpression.callee, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.name) && scopes.isGlobalReference(callExpression.callee)) return `${callExpression.callee.name}()`;
|
|
32071
|
-
if (isNodeOfType(callExpression.callee, "MemberExpression") && !callExpression.callee.computed && isNodeOfType(callExpression.callee.property, "Identifier") && TIMER_FUNCTION_NAMES.has(callExpression.callee.property.name) && (isKnownGlobalObject(callExpression.callee.object, "window", scopes) || isKnownGlobalObject(callExpression.callee.object, "globalThis", scopes))) return `${callExpression.callee.property.name}()`;
|
|
32072
31994
|
const memberCall = getMemberCall(callExpression);
|
|
32073
31995
|
if (!memberCall) return null;
|
|
32074
31996
|
const { methodName, receiver } = memberCall;
|
|
32075
|
-
if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) &&
|
|
32076
|
-
if (EXTERNAL_READ_METHOD_NAMES.has(methodName)
|
|
32077
|
-
|
|
32078
|
-
if (EXTERNAL_READ_METHOD_NAMES.has(methodName) && receiverRoot !== null && isKnownGlobalObject(receiverRoot, "document", scopes)) return `.${methodName}()`;
|
|
32079
|
-
if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNotificationReceiver(receiver, scopes)) return `${methodName}()`;
|
|
31997
|
+
if (STORAGE_MUTATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && STORAGE_RECEIVER_NAMES.has(receiver.name) && scopes.isGlobalReference(receiver)) return `${receiver.name}.${methodName}()`;
|
|
31998
|
+
if (EXTERNAL_READ_METHOD_NAMES.has(methodName)) return `.${methodName}()`;
|
|
31999
|
+
if (NOTIFICATION_METHOD_NAMES.has(methodName) && isNodeOfType(receiver, "Identifier") && isNotificationReceiver(receiver, scopes)) return `${receiver.name}.${methodName}()`;
|
|
32080
32000
|
return null;
|
|
32081
32001
|
};
|
|
32082
32002
|
const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) => {
|
|
32083
|
-
|
|
32003
|
+
let rootIdentifier = null;
|
|
32004
|
+
if (isNodeOfType(assignmentTarget, "Identifier")) rootIdentifier = assignmentTarget;
|
|
32005
|
+
else if (isNodeOfType(assignmentTarget, "MemberExpression") && isNodeOfType(assignmentTarget.object, "Identifier")) rootIdentifier = assignmentTarget.object;
|
|
32084
32006
|
if (!rootIdentifier) return null;
|
|
32085
32007
|
const updaterScope = scopes.ownScopeFor(updater);
|
|
32086
32008
|
if (!updaterScope) return null;
|
|
@@ -32089,26 +32011,12 @@ const getExternalAssignmentDescription = (assignmentTarget, updater, scopes) =>
|
|
|
32089
32011
|
if (symbol.kind === "parameter" && symbol.scope === updaterScope) return `the updater argument "${rootIdentifier.name}"`;
|
|
32090
32012
|
return isDescendantScope(symbol.scope, updaterScope) ? null : `the captured value "${rootIdentifier.name}"`;
|
|
32091
32013
|
};
|
|
32092
|
-
const isArrayValue = (node, scopes) => {
|
|
32093
|
-
const expression = stripParenExpression(node);
|
|
32094
|
-
if (isNodeOfType(expression, "ArrayExpression")) return true;
|
|
32095
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
32096
|
-
return isNodeOfType(resolveConstIdentifierAlias(expression, scopes)?.initializer, "ArrayExpression");
|
|
32097
|
-
};
|
|
32098
|
-
const isDefinitelySynchronousCallback = (callback, scopes) => {
|
|
32099
|
-
const parent = callback.parent;
|
|
32100
|
-
if (!parent) return false;
|
|
32101
|
-
if (isNodeOfType(parent, "CallExpression") && parent.callee === callback) return true;
|
|
32102
|
-
if (!isNodeOfType(parent, "CallExpression") || !parent.arguments?.some((argument) => argument === callback)) return false;
|
|
32103
|
-
if (isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ARRAY_METHOD_NAMES.has(parent.callee.property.name) && isArrayValue(parent.callee.object, scopes)) return true;
|
|
32104
|
-
return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.object, "Identifier") && parent.callee.object.name === "Array" && scopes.isGlobalReference(parent.callee.object) && isNodeOfType(parent.callee.property, "Identifier") && parent.callee.property.name === "from" && parent.arguments[1] === callback;
|
|
32105
|
-
};
|
|
32106
32014
|
const findImpureUpdaterOperation = (updater, scopes) => {
|
|
32107
32015
|
const analysis = getProgramAnalysis(updater);
|
|
32108
32016
|
let operation = null;
|
|
32109
32017
|
walkAst(updater, (child) => {
|
|
32110
32018
|
if (operation) return false;
|
|
32111
|
-
if (child !== updater && isFunctionLike$1(child) && !
|
|
32019
|
+
if (child !== updater && isFunctionLike$1(child) && !executesDuringRender(child, scopes)) return false;
|
|
32112
32020
|
if (isNodeOfType(child, "CallExpression")) {
|
|
32113
32021
|
if (isNodeOfType(child.callee, "Identifier") && analysis) {
|
|
32114
32022
|
const calleeReference = getRef(analysis, child.callee);
|
|
@@ -32140,25 +32048,18 @@ const noImpureStateUpdater = defineRule({
|
|
|
32140
32048
|
severity: "error",
|
|
32141
32049
|
recommendation: "Keep state updater callbacks pure and return only the next state. Move notifications, storage, timers, ref writes, and other external work into the event or effect that queues the update.",
|
|
32142
32050
|
create: (context) => ({ CallExpression(node) {
|
|
32143
|
-
const
|
|
32144
|
-
if (!
|
|
32051
|
+
const updater = node.arguments?.[0];
|
|
32052
|
+
if (!updater || !isFunctionLike$1(updater)) return;
|
|
32145
32053
|
const analysis = getProgramAnalysis(node);
|
|
32146
32054
|
if (!analysis || !isNodeOfType(node.callee, "Identifier")) return;
|
|
32147
32055
|
const calleeReference = getRef(analysis, node.callee);
|
|
32148
32056
|
if (!calleeReference || !isStateSetterCall(analysis, calleeReference)) return;
|
|
32149
32057
|
const stateDeclarator = getUseStateDecl(analysis, calleeReference);
|
|
32150
32058
|
if (!isNodeOfType(stateDeclarator, "VariableDeclarator") || !isNodeOfType(stateDeclarator.init, "CallExpression") || !isReactApiCall(stateDeclarator.init, "useState", context.scopes, { allowGlobalReactNamespace: true })) return;
|
|
32151
|
-
let updater = null;
|
|
32152
|
-
if (isFunctionLike$1(updaterArgument)) updater = updaterArgument;
|
|
32153
|
-
else if (isNodeOfType(updaterArgument, "Identifier")) {
|
|
32154
|
-
const updaterReference = getRef(analysis, updaterArgument);
|
|
32155
|
-
if (updaterReference) updater = resolveToFunction(updaterReference);
|
|
32156
|
-
}
|
|
32157
|
-
if (!updater) return;
|
|
32158
32059
|
const operation = findImpureUpdaterOperation(updater, context.scopes);
|
|
32159
32060
|
if (!operation) return;
|
|
32160
32061
|
context.report({
|
|
32161
|
-
node:
|
|
32062
|
+
node: updater,
|
|
32162
32063
|
message: `This state updater performs ${operation}. React may run updater functions more than once, so side effects here can repeat or observe inconsistent external state.`
|
|
32163
32064
|
});
|
|
32164
32065
|
} })
|
|
@@ -32447,12 +32348,11 @@ const isMemoCall = (node) => {
|
|
|
32447
32348
|
};
|
|
32448
32349
|
const isDefaultEquivalentComparator = (comparator) => isNodeOfType(comparator, "Identifier") && (comparator.name === "undefined" || comparator.name === "shallowEqual");
|
|
32449
32350
|
const hasCustomComparator = (node) => isNodeOfType(node, "CallExpression") && (node.arguments?.length ?? 0) >= 2 && !isDefaultEquivalentComparator(node.arguments?.[1]);
|
|
32450
|
-
const isInlineReference = (node
|
|
32451
|
-
|
|
32452
|
-
if (isNodeOfType(
|
|
32453
|
-
if (isNodeOfType(
|
|
32454
|
-
if (isNodeOfType(
|
|
32455
|
-
if (isNodeOfType(referenceNode, "JSXElement") || isNodeOfType(referenceNode, "JSXFragment")) return "JSX";
|
|
32351
|
+
const isInlineReference = (node) => {
|
|
32352
|
+
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";
|
|
32353
|
+
if (isNodeOfType(node, "ObjectExpression")) return "objects";
|
|
32354
|
+
if (isNodeOfType(node, "ArrayExpression")) return "Arrays";
|
|
32355
|
+
if (isNodeOfType(node, "JSXElement") || isNodeOfType(node, "JSXFragment")) return "JSX";
|
|
32456
32356
|
return null;
|
|
32457
32357
|
};
|
|
32458
32358
|
const noInlinePropOnMemoComponent = defineRule({
|
|
@@ -32482,7 +32382,7 @@ const noInlinePropOnMemoComponent = defineRule({
|
|
|
32482
32382
|
let elementName = null;
|
|
32483
32383
|
if (isNodeOfType(openingElement.name, "JSXIdentifier")) elementName = openingElement.name.name;
|
|
32484
32384
|
if (!elementName || !memoizedComponentNames.has(elementName)) return;
|
|
32485
|
-
const propType = isInlineReference(node.value.expression
|
|
32385
|
+
const propType = isInlineReference(node.value.expression);
|
|
32486
32386
|
if (propType) context.report({
|
|
32487
32387
|
node: node.value.expression,
|
|
32488
32388
|
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`
|
|
@@ -33003,18 +32903,6 @@ const INTL_FORMAT_METHOD_NAMES = new Set([
|
|
|
33003
32903
|
"formatToParts",
|
|
33004
32904
|
"formatRange"
|
|
33005
32905
|
]);
|
|
33006
|
-
const ABSENT_PROPERTY_PROOF = { status: "absent" };
|
|
33007
|
-
const PRESENT_PROPERTY_PROOF = { status: "present" };
|
|
33008
|
-
const UNDEFINED_PROPERTY_PROOF = { status: "undefined" };
|
|
33009
|
-
const UNKNOWN_PROPERTY_PROOF = { status: "unknown" };
|
|
33010
|
-
const READ_ONLY_OBJECT_METHOD_NAMES = new Set([
|
|
33011
|
-
"hasOwnProperty",
|
|
33012
|
-
"isPrototypeOf",
|
|
33013
|
-
"propertyIsEnumerable",
|
|
33014
|
-
"toLocaleString",
|
|
33015
|
-
"toString",
|
|
33016
|
-
"valueOf"
|
|
33017
|
-
]);
|
|
33018
32906
|
const isProvableDateExpression = (expression) => {
|
|
33019
32907
|
if (!expression) return false;
|
|
33020
32908
|
const unwrapped = stripParenExpression(expression);
|
|
@@ -33029,290 +32917,32 @@ const receiverNameLooksDateFlavored = (expression) => {
|
|
|
33029
32917
|
if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
|
|
33030
32918
|
return false;
|
|
33031
32919
|
};
|
|
33032
|
-
const
|
|
33033
|
-
|
|
33034
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return true;
|
|
33035
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
33036
|
-
if (expression.name === "undefined" && scopes.isGlobalReference(expression)) return true;
|
|
33037
|
-
const symbol = scopes.symbolFor(expression);
|
|
33038
|
-
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id)) return false;
|
|
33039
|
-
visitedSymbolIds.add(symbol.id);
|
|
33040
|
-
return isStaticUndefined(symbol.initializer, scopes, visitedSymbolIds);
|
|
33041
|
-
};
|
|
33042
|
-
const isNestedAssignmentTarget = (expression) => {
|
|
33043
|
-
let target = expression;
|
|
33044
|
-
let parent = target.parent;
|
|
33045
|
-
while (parent) {
|
|
33046
|
-
if (isNodeOfType(parent, "AssignmentExpression")) return parent.left === target;
|
|
33047
|
-
if (isNodeOfType(parent, "ForInStatement") || isNodeOfType(parent, "ForOfStatement")) return parent.left === target;
|
|
33048
|
-
if (isNodeOfType(parent, "AssignmentPattern")) {
|
|
33049
|
-
if (parent.left !== target) return false;
|
|
33050
|
-
} else if (isNodeOfType(parent, "RestElement")) {
|
|
33051
|
-
if (parent.argument !== target) return false;
|
|
33052
|
-
} else if (isNodeOfType(parent, "ArrayPattern")) {
|
|
33053
|
-
if (!parent.elements?.some((element) => element === target)) return false;
|
|
33054
|
-
} else if (isNodeOfType(parent, "Property")) {
|
|
33055
|
-
if (parent.value !== target || !isNodeOfType(parent.parent, "ObjectPattern")) return false;
|
|
33056
|
-
} else if (isNodeOfType(parent, "ObjectPattern")) {
|
|
33057
|
-
if (!parent.properties?.some((property) => property === target)) return false;
|
|
33058
|
-
} else return false;
|
|
33059
|
-
target = parent;
|
|
33060
|
-
parent = target.parent;
|
|
33061
|
-
}
|
|
33062
|
-
return false;
|
|
33063
|
-
};
|
|
33064
|
-
const isPotentialMutationReference = (identifier, readNode) => {
|
|
33065
|
-
let expression = identifier;
|
|
33066
|
-
let parent = expression.parent;
|
|
33067
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
|
|
33068
|
-
expression = parent;
|
|
33069
|
-
parent = expression.parent;
|
|
33070
|
-
}
|
|
33071
|
-
const rootExpression = expression;
|
|
33072
|
-
let memberDepth = 0;
|
|
33073
|
-
while (parent && isNodeOfType(parent, "MemberExpression") && parent.object === expression) {
|
|
33074
|
-
memberDepth += 1;
|
|
33075
|
-
expression = parent;
|
|
33076
|
-
parent = expression.parent;
|
|
33077
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
|
|
33078
|
-
expression = parent;
|
|
33079
|
-
parent = expression.parent;
|
|
33080
|
-
}
|
|
33081
|
-
}
|
|
33082
|
-
if (!parent || isAstDescendant(identifier, readNode)) return false;
|
|
33083
|
-
if (isNestedAssignmentTarget(expression)) return true;
|
|
33084
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.left === expression) return true;
|
|
33085
|
-
if (isNodeOfType(parent, "UpdateExpression") && parent.argument === expression) return true;
|
|
33086
|
-
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" && parent.argument === expression) return true;
|
|
33087
|
-
if (isNodeOfType(parent, "CallExpression")) {
|
|
33088
|
-
if (parent.callee === expression) {
|
|
33089
|
-
if (memberDepth === 0) return true;
|
|
33090
|
-
const memberExpression = stripParenExpression(expression);
|
|
33091
|
-
return memberDepth === 1 && isNodeOfType(memberExpression, "MemberExpression") && !READ_ONLY_OBJECT_METHOD_NAMES.has(getStaticPropertyName(memberExpression) ?? "");
|
|
33092
|
-
}
|
|
33093
|
-
return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
|
|
33094
|
-
}
|
|
33095
|
-
if (isNodeOfType(parent, "NewExpression")) return memberDepth === 0 && parent.arguments?.some((argument) => argument === rootExpression);
|
|
33096
|
-
if (isNodeOfType(parent, "AssignmentExpression") && parent.right === rootExpression) return true;
|
|
33097
|
-
return false;
|
|
33098
|
-
};
|
|
33099
|
-
const getSimpleAlias = (identifier, scopes) => {
|
|
33100
|
-
let expression = identifier;
|
|
33101
|
-
let parent = expression.parent;
|
|
33102
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === expression) {
|
|
33103
|
-
expression = parent;
|
|
33104
|
-
parent = expression.parent;
|
|
33105
|
-
}
|
|
33106
|
-
if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== expression || !isNodeOfType(parent.id, "Identifier")) return null;
|
|
33107
|
-
const symbol = scopes.symbolFor(parent.id);
|
|
33108
|
-
return symbol ? {
|
|
33109
|
-
symbol,
|
|
33110
|
-
readNode: parent.id
|
|
33111
|
-
} : null;
|
|
33112
|
-
};
|
|
33113
|
-
const getDirectCallForExpression = (expression) => {
|
|
33114
|
-
let callee = expression;
|
|
33115
|
-
let parent = callee.parent;
|
|
33116
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === callee) {
|
|
33117
|
-
callee = parent;
|
|
33118
|
-
parent = callee.parent;
|
|
33119
|
-
}
|
|
33120
|
-
return isNodeOfType(parent, "CallExpression") && parent.callee === callee ? parent : null;
|
|
33121
|
-
};
|
|
33122
|
-
const getMethodOwner = (functionNode, scopes) => {
|
|
33123
|
-
const methodNode = functionNode.parent;
|
|
33124
|
-
if (isNodeOfType(methodNode, "Property") && methodNode.value === functionNode && isNodeOfType(methodNode.parent, "ObjectExpression")) {
|
|
33125
|
-
const objectParent = methodNode.parent.parent;
|
|
33126
|
-
if (!isNodeOfType(objectParent, "VariableDeclarator") || objectParent.init !== methodNode.parent || !isNodeOfType(objectParent.id, "Identifier")) return null;
|
|
33127
|
-
const ownerSymbol = scopes.symbolFor(objectParent.id);
|
|
33128
|
-
const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
|
|
33129
|
-
return ownerSymbol && methodName ? {
|
|
33130
|
-
methodName,
|
|
33131
|
-
ownerKind: "object",
|
|
33132
|
-
ownerSymbol,
|
|
33133
|
-
isStatic: false
|
|
33134
|
-
} : null;
|
|
33135
|
-
}
|
|
33136
|
-
if (!isNodeOfType(methodNode, "MethodDefinition") || methodNode.value !== functionNode || !isNodeOfType(methodNode.parent, "ClassBody")) return null;
|
|
33137
|
-
const classNode = methodNode.parent.parent;
|
|
33138
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression")) return null;
|
|
33139
|
-
let bindingIdentifier = isNodeOfType(classNode.id, "Identifier") ? classNode.id : null;
|
|
33140
|
-
if (!bindingIdentifier && isNodeOfType(classNode.parent, "VariableDeclarator")) bindingIdentifier = isNodeOfType(classNode.parent.id, "Identifier") ? classNode.parent.id : null;
|
|
33141
|
-
if (!bindingIdentifier) return null;
|
|
33142
|
-
const ownerSymbol = scopes.symbolFor(bindingIdentifier);
|
|
33143
|
-
const methodName = getStaticPropertyKeyName(methodNode, { allowComputedString: true });
|
|
33144
|
-
return ownerSymbol && methodName ? {
|
|
33145
|
-
methodName,
|
|
33146
|
-
ownerKind: "class",
|
|
33147
|
-
ownerSymbol,
|
|
33148
|
-
isStatic: Boolean(methodNode.static)
|
|
33149
|
-
} : null;
|
|
33150
|
-
};
|
|
33151
|
-
const doesSymbolResolveToOwner = (symbol, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
33152
|
-
if (!symbol) return false;
|
|
33153
|
-
if (symbol.declarationNode === ownerSymbol.declarationNode) return true;
|
|
33154
|
-
if (symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
33155
|
-
visitedSymbolIds.add(symbol.id);
|
|
33156
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
33157
|
-
return isNodeOfType(initializer, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(initializer), ownerSymbol, scopes, visitedSymbolIds);
|
|
33158
|
-
};
|
|
33159
|
-
const isClassInstanceExpression = (expression, ownerSymbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
33160
|
-
const candidate = stripParenExpression(expression);
|
|
33161
|
-
if (isNodeOfType(candidate, "NewExpression") && isNodeOfType(candidate.callee, "Identifier")) return doesSymbolResolveToOwner(scopes.symbolFor(candidate.callee), ownerSymbol, scopes);
|
|
33162
|
-
if (!isNodeOfType(candidate, "Identifier")) return false;
|
|
33163
|
-
const symbol = scopes.symbolFor(candidate);
|
|
33164
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
33165
|
-
visitedSymbolIds.add(symbol.id);
|
|
33166
|
-
return isClassInstanceExpression(symbol.initializer, ownerSymbol, scopes, visitedSymbolIds);
|
|
33167
|
-
};
|
|
33168
|
-
const getMethodCalls = (functionNode, scopes) => {
|
|
33169
|
-
const owner = getMethodOwner(functionNode, scopes);
|
|
33170
|
-
if (!owner) return [];
|
|
33171
|
-
const calls = [];
|
|
33172
|
-
walkAst(scopes.rootScope.node, (child) => {
|
|
33173
|
-
if (!isNodeOfType(child, "MemberExpression")) return;
|
|
33174
|
-
if (getStaticPropertyName(child) !== owner.methodName) return;
|
|
33175
|
-
const receiver = stripParenExpression(child.object);
|
|
33176
|
-
let doesReceiverMatch = isNodeOfType(receiver, "Identifier") && doesSymbolResolveToOwner(scopes.symbolFor(receiver), owner.ownerSymbol, scopes);
|
|
33177
|
-
if (owner.ownerKind === "class" && !owner.isStatic) doesReceiverMatch = isClassInstanceExpression(receiver, owner.ownerSymbol, scopes);
|
|
33178
|
-
if (!doesReceiverMatch) return;
|
|
33179
|
-
const call = getDirectCallForExpression(child);
|
|
33180
|
-
if (call) calls.push(call);
|
|
33181
|
-
});
|
|
33182
|
-
return calls;
|
|
33183
|
-
};
|
|
33184
|
-
const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, scopes, visitedSymbolIds, visitedFunctionNodes = /* @__PURE__ */ new Set()) => {
|
|
33185
|
-
if (visitedFunctionNodes.has(functionNode)) return false;
|
|
33186
|
-
visitedFunctionNodes.add(functionNode);
|
|
33187
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
33188
|
-
const immediateCall = getDirectCallForExpression(functionNode);
|
|
33189
|
-
if (immediateCall) {
|
|
33190
|
-
const immediateCallFunction = findEnclosingFunction(immediateCall);
|
|
33191
|
-
if (immediateCallFunction === usageFunction) {
|
|
33192
|
-
const immediateCallStart = getRangeStart(immediateCall);
|
|
33193
|
-
return immediateCallStart === null || immediateCallStart < usageBoundary;
|
|
33194
|
-
}
|
|
33195
|
-
if (!immediateCallFunction) return usageFunction !== null;
|
|
33196
|
-
return isFunctionInvokedBeforeUsage(immediateCallFunction, usageNode, usageBoundary, scopes, visitedSymbolIds, new Set(visitedFunctionNodes));
|
|
33197
|
-
}
|
|
33198
|
-
for (const methodCall of getMethodCalls(functionNode, scopes)) {
|
|
33199
|
-
const methodCallFunction = findEnclosingFunction(methodCall);
|
|
33200
|
-
if (methodCallFunction === usageFunction) {
|
|
33201
|
-
const methodCallStart = getRangeStart(methodCall);
|
|
33202
|
-
if (methodCallStart === null || methodCallStart < usageBoundary) return true;
|
|
33203
|
-
continue;
|
|
33204
|
-
}
|
|
33205
|
-
if (!methodCallFunction) {
|
|
33206
|
-
if (usageFunction) return true;
|
|
33207
|
-
continue;
|
|
33208
|
-
}
|
|
33209
|
-
if (isFunctionInvokedBeforeUsage(methodCallFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes))) return true;
|
|
33210
|
-
}
|
|
33211
|
-
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
33212
|
-
if (!bindingIdentifier) return false;
|
|
33213
|
-
const symbol = scopes.symbolFor(bindingIdentifier);
|
|
33214
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
33215
|
-
visitedSymbolIds.add(symbol.id);
|
|
33216
|
-
let wasInvokedBeforeUsage = false;
|
|
33217
|
-
walkAst(scopes.rootScope.node, (child) => {
|
|
33218
|
-
if (wasInvokedBeforeUsage || !isNodeOfType(child, "Identifier")) return;
|
|
33219
|
-
if (scopes.symbolFor(child)?.declarationNode !== symbol.declarationNode) return;
|
|
33220
|
-
const call = getDirectCallForExpression(child);
|
|
33221
|
-
if (!call) return false;
|
|
33222
|
-
const callFunction = findEnclosingFunction(call);
|
|
33223
|
-
if (callFunction === usageFunction) {
|
|
33224
|
-
const callStart = getRangeStart(call);
|
|
33225
|
-
wasInvokedBeforeUsage = callStart === null || callStart < usageBoundary;
|
|
33226
|
-
return;
|
|
33227
|
-
}
|
|
33228
|
-
if (!callFunction) {
|
|
33229
|
-
wasInvokedBeforeUsage = usageFunction !== null;
|
|
33230
|
-
return;
|
|
33231
|
-
}
|
|
33232
|
-
wasInvokedBeforeUsage = isFunctionInvokedBeforeUsage(callFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes));
|
|
33233
|
-
});
|
|
33234
|
-
return wasInvokedBeforeUsage;
|
|
33235
|
-
};
|
|
33236
|
-
const getMutationUsageBoundary = (usageNode, readNode) => {
|
|
33237
|
-
const readStart = getRangeStart(readNode);
|
|
33238
|
-
let readExpression = readNode;
|
|
33239
|
-
let readParent = readExpression.parent;
|
|
33240
|
-
while (readParent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(readParent.type) && "expression" in readParent && readParent.expression === readExpression) {
|
|
33241
|
-
readExpression = readParent;
|
|
33242
|
-
readParent = readExpression.parent;
|
|
33243
|
-
}
|
|
33244
|
-
if ((isNodeOfType(usageNode, "CallExpression") || isNodeOfType(usageNode, "NewExpression")) && usageNode.arguments?.some((argument) => argument === readExpression)) return usageNode.range?.[1] ?? null;
|
|
33245
|
-
if (isAstDescendant(readNode, usageNode)) return readStart;
|
|
33246
|
-
return getRangeStart(usageNode);
|
|
33247
|
-
};
|
|
33248
|
-
const wasMutatedBeforeUsage = (symbol, usageNode, readNode, scopes, visitedMutationSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
|
|
33249
|
-
if (visitedMutationSymbolIds.has(symbol.id)) return false;
|
|
33250
|
-
visitedMutationSymbolIds.add(symbol.id);
|
|
33251
|
-
const usageBoundary = inheritedUsageBoundary === void 0 ? getMutationUsageBoundary(usageNode, readNode) : inheritedUsageBoundary;
|
|
33252
|
-
if (typeof usageBoundary !== "number") return true;
|
|
33253
|
-
const usageFunction = findEnclosingFunction(usageNode);
|
|
33254
|
-
return symbol.references.some((reference) => {
|
|
33255
|
-
const referenceStart = getRangeStart(reference.identifier);
|
|
33256
|
-
const simpleAlias = getSimpleAlias(reference.identifier, scopes);
|
|
33257
|
-
if (simpleAlias) return wasMutatedBeforeUsage(simpleAlias.symbol, usageNode, readNodesBySymbolId.get(simpleAlias.symbol.id) ?? simpleAlias.readNode, scopes, new Set(visitedMutationSymbolIds), usageBoundary, readNodesBySymbolId);
|
|
33258
|
-
if (!isPotentialMutationReference(reference.identifier, readNode)) return false;
|
|
33259
|
-
if (referenceStart === null) return true;
|
|
33260
|
-
const mutationFunction = findEnclosingFunction(reference.identifier);
|
|
33261
|
-
if (mutationFunction === usageFunction) return referenceStart < usageBoundary;
|
|
33262
|
-
if (!mutationFunction) return usageFunction !== null;
|
|
33263
|
-
if (usageFunction && isAstDescendant(usageFunction, mutationFunction)) return true;
|
|
33264
|
-
return isFunctionInvokedBeforeUsage(mutationFunction, usageNode, usageBoundary, scopes, /* @__PURE__ */ new Set());
|
|
33265
|
-
});
|
|
33266
|
-
};
|
|
33267
|
-
const getObjectPropertyProof = (objectExpression, propertyName, scopes, usageNode, visitedSymbolIds = /* @__PURE__ */ new Set(), inheritedUsageBoundary, readNodesBySymbolId = /* @__PURE__ */ new Map()) => {
|
|
33268
|
-
if (!objectExpression) return ABSENT_PROPERTY_PROOF;
|
|
32920
|
+
const objectLiteralHasProperty = (objectExpression, propertyName) => {
|
|
32921
|
+
if (!objectExpression) return false;
|
|
33269
32922
|
const unwrapped = stripParenExpression(objectExpression);
|
|
33270
|
-
if (isNodeOfType(unwrapped, "
|
|
33271
|
-
|
|
33272
|
-
|
|
33273
|
-
|
|
33274
|
-
if (
|
|
33275
|
-
|
|
33276
|
-
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier") || visitedSymbolIds.has(symbol.id) || wasMutatedBeforeUsage(symbol, usageNode, unwrapped, scopes, /* @__PURE__ */ new Set(), usageBoundary, nextReadNodesBySymbolId)) return UNKNOWN_PROPERTY_PROOF;
|
|
33277
|
-
visitedSymbolIds.add(symbol.id);
|
|
33278
|
-
return getObjectPropertyProof(symbol.initializer, propertyName, scopes, usageNode, visitedSymbolIds, usageBoundary, nextReadNodesBySymbolId);
|
|
33279
|
-
}
|
|
33280
|
-
if (isNodeOfType(unwrapped, "ConditionalExpression")) {
|
|
33281
|
-
const consequent = getObjectPropertyProof(unwrapped.consequent, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
|
|
33282
|
-
const alternate = getObjectPropertyProof(unwrapped.alternate, propertyName, scopes, usageNode, new Set(visitedSymbolIds), inheritedUsageBoundary, new Map(readNodesBySymbolId));
|
|
33283
|
-
return consequent.status === alternate.status ? consequent : UNKNOWN_PROPERTY_PROOF;
|
|
33284
|
-
}
|
|
33285
|
-
if (isNodeOfType(unwrapped, "CallExpression") && isNodeOfType(unwrapped.callee, "MemberExpression") && !unwrapped.callee.computed && isNodeOfType(unwrapped.callee.object, "Identifier") && unwrapped.callee.object.name === "Object" && scopes.isGlobalReference(unwrapped.callee.object) && isNodeOfType(unwrapped.callee.property, "Identifier") && unwrapped.callee.property.name === "freeze") return getObjectPropertyProof(unwrapped.arguments?.[0], propertyName, scopes, usageNode, visitedSymbolIds, inheritedUsageBoundary, readNodesBySymbolId);
|
|
33286
|
-
if (!isNodeOfType(unwrapped, "ObjectExpression")) return UNKNOWN_PROPERTY_PROOF;
|
|
33287
|
-
const properties = unwrapped.properties ?? [];
|
|
33288
|
-
for (let propertyIndex = properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
|
|
33289
|
-
const property = properties[propertyIndex];
|
|
33290
|
-
if (!property) continue;
|
|
33291
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
33292
|
-
const spreadProof = getObjectPropertyProof(property.argument, propertyName, scopes, unwrapped, new Set(visitedSymbolIds), void 0);
|
|
33293
|
-
if (spreadProof.status !== "absent") return spreadProof;
|
|
33294
|
-
continue;
|
|
33295
|
-
}
|
|
33296
|
-
if (!isNodeOfType(property, "Property") || getStaticPropertyKeyName(property) !== propertyName) continue;
|
|
33297
|
-
return isStaticUndefined(property.value, scopes) ? UNDEFINED_PROPERTY_PROOF : PRESENT_PROPERTY_PROOF;
|
|
32923
|
+
if (!isNodeOfType(unwrapped, "ObjectExpression")) return false;
|
|
32924
|
+
for (const property of unwrapped.properties ?? []) {
|
|
32925
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
32926
|
+
if (property.computed) continue;
|
|
32927
|
+
if (isNodeOfType(property.key, "Identifier") && property.key.name === propertyName) return true;
|
|
32928
|
+
if (isNodeOfType(property.key, "Literal") && property.key.value === propertyName) return true;
|
|
33298
32929
|
}
|
|
33299
|
-
return
|
|
32930
|
+
return false;
|
|
33300
32931
|
};
|
|
33301
|
-
const
|
|
33302
|
-
const hasExplicitLocaleArgument = (argument, scopes) => {
|
|
32932
|
+
const hasExplicitLocaleArgument = (argument) => {
|
|
33303
32933
|
if (!argument) return false;
|
|
33304
32934
|
const unwrapped = stripParenExpression(argument);
|
|
33305
|
-
if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined"
|
|
32935
|
+
if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
|
|
33306
32936
|
return true;
|
|
33307
32937
|
};
|
|
33308
|
-
const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate
|
|
32938
|
+
const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
|
|
33309
32939
|
const localeArgument = call.arguments?.[0];
|
|
33310
|
-
if (!hasExplicitLocaleArgument(localeArgument
|
|
32940
|
+
if (!hasExplicitLocaleArgument(localeArgument)) return false;
|
|
33311
32941
|
const optionsArgument = call.arguments?.[1];
|
|
33312
|
-
if (
|
|
32942
|
+
if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
|
|
33313
32943
|
return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
|
|
33314
32944
|
};
|
|
33315
|
-
const matchLocaleMethodCall = (call
|
|
32945
|
+
const matchLocaleMethodCall = (call) => {
|
|
33316
32946
|
const callee = call.callee;
|
|
33317
32947
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
|
|
33318
32948
|
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
@@ -33320,7 +32950,7 @@ const matchLocaleMethodCall = (call, scopes) => {
|
|
|
33320
32950
|
if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
|
|
33321
32951
|
const receiverIsProvablyDate = isProvableDateExpression(callee.object);
|
|
33322
32952
|
if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
|
|
33323
|
-
if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate
|
|
32953
|
+
if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
|
|
33324
32954
|
return {
|
|
33325
32955
|
node: call,
|
|
33326
32956
|
display: `${methodName}()`
|
|
@@ -33336,13 +32966,13 @@ const getIntlFormatterName = (expression) => {
|
|
|
33336
32966
|
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
33337
32967
|
return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
|
|
33338
32968
|
};
|
|
33339
|
-
const isDeterministicIntlConstruction = (construction, formatterName
|
|
32969
|
+
const isDeterministicIntlConstruction = (construction, formatterName) => {
|
|
33340
32970
|
if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
|
|
33341
|
-
if (!hasExplicitLocaleArgument(construction.arguments?.[0]
|
|
32971
|
+
if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
|
|
33342
32972
|
if (formatterName !== "DateTimeFormat") return true;
|
|
33343
|
-
return
|
|
32973
|
+
return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
|
|
33344
32974
|
};
|
|
33345
|
-
const matchIntlFormatCall = (call
|
|
32975
|
+
const matchIntlFormatCall = (call) => {
|
|
33346
32976
|
const callee = call.callee;
|
|
33347
32977
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
|
|
33348
32978
|
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
@@ -33355,7 +32985,7 @@ const matchIntlFormatCall = (call, scopes) => {
|
|
|
33355
32985
|
if (!construction) return null;
|
|
33356
32986
|
const formatterName = getIntlFormatterName(construction);
|
|
33357
32987
|
if (!formatterName) return null;
|
|
33358
|
-
if (isDeterministicIntlConstruction(construction, formatterName
|
|
32988
|
+
if (isDeterministicIntlConstruction(construction, formatterName)) return null;
|
|
33359
32989
|
return {
|
|
33360
32990
|
node: call,
|
|
33361
32991
|
display: `Intl.${formatterName}().${callee.property.name}()`
|
|
@@ -33424,7 +33054,7 @@ const noLocaleFormatInRender = defineRule({
|
|
|
33424
33054
|
fileIsEmailTemplate = hasEmailTemplateImport(node);
|
|
33425
33055
|
},
|
|
33426
33056
|
CallExpression(node) {
|
|
33427
|
-
const match = matchLocaleMethodCall(node
|
|
33057
|
+
const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
|
|
33428
33058
|
if (match) reportIfRenderPhase(match);
|
|
33429
33059
|
},
|
|
33430
33060
|
TemplateLiteral(node) {
|
|
@@ -33444,7 +33074,7 @@ const noLocaleFormatInRender = defineRule({
|
|
|
33444
33074
|
walkAst(helperNode.body ?? helperNode, (child) => {
|
|
33445
33075
|
if (isFunctionLike$1(child)) return false;
|
|
33446
33076
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
33447
|
-
const match = matchLocaleMethodCall(child
|
|
33077
|
+
const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
|
|
33448
33078
|
if (!match || reportedNodes.has(match.node)) return;
|
|
33449
33079
|
if (fileIsEmailTemplate) return;
|
|
33450
33080
|
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
@@ -37270,85 +36900,28 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
37270
36900
|
});
|
|
37271
36901
|
//#endregion
|
|
37272
36902
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
37273
|
-
const
|
|
37274
|
-
"
|
|
37275
|
-
"
|
|
37276
|
-
|
|
37277
|
-
|
|
37278
|
-
|
|
37279
|
-
|
|
36903
|
+
const resolveReactRefSymbol = (memberExpression, scopes) => {
|
|
36904
|
+
const receiver = isNodeOfType(memberExpression, "MemberExpression") ? stripParenExpression(memberExpression.object) : null;
|
|
36905
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.computed || !isNodeOfType(memberExpression.property, "Identifier") || memberExpression.property.name !== "current" || !isNodeOfType(receiver, "Identifier")) return null;
|
|
36906
|
+
const symbol = resolveConstIdentifierAlias(receiver, scopes);
|
|
36907
|
+
if (!symbol?.initializer) return null;
|
|
36908
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
36909
|
+
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
36910
|
+
return isReactApiCall(initializer, "useRef", scopes, { allowGlobalReactNamespace: true }) ? symbol : null;
|
|
36911
|
+
};
|
|
37280
36912
|
const isSameRefCurrentMember = (node, refSymbol, scopes) => {
|
|
37281
|
-
if (!isNodeOfType(node, "MemberExpression") ||
|
|
36913
|
+
if (!isNodeOfType(node, "MemberExpression") || node.computed || !isNodeOfType(node.property, "Identifier") || node.property.name !== "current") return false;
|
|
37282
36914
|
const receiver = stripParenExpression(node.object);
|
|
37283
36915
|
return isNodeOfType(receiver, "Identifier") && resolveConstIdentifierAlias(receiver, scopes)?.id === refSymbol.id;
|
|
37284
36916
|
};
|
|
37285
|
-
const isSameRefCurrentAlias = (node, refSymbol, scopes) => {
|
|
37286
|
-
if (isSameRefCurrentMember(node, refSymbol, scopes)) return true;
|
|
37287
|
-
if (!isNodeOfType(node, "Identifier")) return false;
|
|
37288
|
-
const aliasSymbol = scopes.symbolFor(node);
|
|
37289
|
-
return aliasSymbol?.kind === "const" && aliasSymbol.initializer !== null && isSameRefCurrentMember(stripParenExpression(aliasSymbol.initializer), refSymbol, scopes);
|
|
37290
|
-
};
|
|
37291
|
-
const isEmptySentinel = (node, scopes) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined" && scopes.isGlobalReference(node);
|
|
37292
|
-
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
37293
|
-
let ancestor = node.parent;
|
|
37294
|
-
while (ancestor && ancestor !== stop) {
|
|
37295
|
-
if (isFunctionLike$1(ancestor) || REPEATED_ANCESTOR_TYPES.has(ancestor.type)) return true;
|
|
37296
|
-
ancestor = ancestor.parent;
|
|
37297
|
-
}
|
|
37298
|
-
return ancestor !== stop;
|
|
37299
|
-
};
|
|
37300
|
-
const getBranchConstraints = (node, branchRoot) => {
|
|
37301
|
-
const constraints = /* @__PURE__ */ new Map();
|
|
37302
|
-
let descendant = node;
|
|
37303
|
-
let ancestor = descendant.parent;
|
|
37304
|
-
while (ancestor && descendant !== branchRoot) {
|
|
37305
|
-
if (isNodeOfType(ancestor, "IfStatement")) {
|
|
37306
|
-
if (ancestor.consequent === descendant) constraints.set(ancestor, true);
|
|
37307
|
-
if (ancestor.alternate === descendant) constraints.set(ancestor, false);
|
|
37308
|
-
}
|
|
37309
|
-
descendant = ancestor;
|
|
37310
|
-
ancestor = ancestor.parent;
|
|
37311
|
-
}
|
|
37312
|
-
return constraints;
|
|
37313
|
-
};
|
|
37314
|
-
const canExecuteTogether = (firstConstraints, secondConstraints) => {
|
|
37315
|
-
for (const [statement, branch] of firstConstraints) {
|
|
37316
|
-
const otherBranch = secondConstraints.get(statement);
|
|
37317
|
-
if (otherBranch !== void 0 && otherBranch !== branch) return false;
|
|
37318
|
-
}
|
|
37319
|
-
return true;
|
|
37320
|
-
};
|
|
37321
|
-
const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
|
|
37322
|
-
const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
|
|
37323
|
-
const assignmentStart = getRangeStart(assignmentExpression);
|
|
37324
|
-
let hasCoExecutableWrite = false;
|
|
37325
|
-
walkAst(branchRoot, (child) => {
|
|
37326
|
-
if (hasCoExecutableWrite) return false;
|
|
37327
|
-
const childStart = getRangeStart(child);
|
|
37328
|
-
if (child === assignmentExpression || !isNodeOfType(child, "AssignmentExpression") || assignmentStart === null || childStart === null || childStart >= assignmentStart || resolveReactRefSymbol(child.left, scopes)?.id !== refSymbol.id || hasRepeatedExecutionAncestor(child, branchRoot)) return;
|
|
37329
|
-
if (canExecuteTogether(assignmentConstraints, getBranchConstraints(child, branchRoot))) {
|
|
37330
|
-
hasCoExecutableWrite = true;
|
|
37331
|
-
return false;
|
|
37332
|
-
}
|
|
37333
|
-
});
|
|
37334
|
-
return !hasCoExecutableWrite;
|
|
37335
|
-
};
|
|
37336
36917
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
37337
|
-
if (assignmentExpression.operator
|
|
37338
|
-
if (assignmentExpression.operator !== "=") return false;
|
|
36918
|
+
if (assignmentExpression.operator !== "=" || !isNodeOfType(assignmentExpression.right, "NewExpression")) return false;
|
|
37339
36919
|
let descendant = assignmentExpression;
|
|
37340
36920
|
let ancestor = descendant.parent;
|
|
37341
36921
|
while (ancestor) {
|
|
37342
|
-
if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(ancestor.test, "BinaryExpression") &&
|
|
37343
|
-
"===",
|
|
37344
|
-
"==",
|
|
37345
|
-
"!==",
|
|
37346
|
-
"!="
|
|
37347
|
-
].includes(ancestor.test.operator)) {
|
|
36922
|
+
if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant && isNodeOfType(ancestor.test, "BinaryExpression") && (ancestor.test.operator === "===" || ancestor.test.operator === "==")) {
|
|
37348
36923
|
const { left, right } = ancestor.test;
|
|
37349
|
-
|
|
37350
|
-
const guardedBranch = ancestor.test.operator === "===" || ancestor.test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
37351
|
-
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && hasNoPriorCoExecutableWrite(assignmentExpression, guardedBranch, refSymbol, scopes)) return true;
|
|
36924
|
+
if (isSameRefCurrentMember(left, refSymbol, scopes) && isNodeOfType(right, "Literal") && right.value === null || isSameRefCurrentMember(right, refSymbol, scopes) && isNodeOfType(left, "Literal") && left.value === null) return true;
|
|
37352
36925
|
}
|
|
37353
36926
|
descendant = ancestor;
|
|
37354
36927
|
ancestor = descendant.parent;
|
|
@@ -45631,7 +45204,7 @@ const DEFERRABLE_HOOK_NAMES = new Set([
|
|
|
45631
45204
|
"useParams",
|
|
45632
45205
|
"usePathname"
|
|
45633
45206
|
]);
|
|
45634
|
-
const findHookCallBindings = (componentBody
|
|
45207
|
+
const findHookCallBindings = (componentBody) => {
|
|
45635
45208
|
const bindings = [];
|
|
45636
45209
|
if (!isNodeOfType(componentBody, "BlockStatement")) return bindings;
|
|
45637
45210
|
for (const statement of componentBody.body ?? []) {
|
|
@@ -45642,10 +45215,8 @@ const findHookCallBindings = (componentBody, scopes) => {
|
|
|
45642
45215
|
const callee = declarator.init.callee;
|
|
45643
45216
|
if (!isNodeOfType(callee, "Identifier")) continue;
|
|
45644
45217
|
if (!DEFERRABLE_HOOK_NAMES.has(callee.name)) continue;
|
|
45645
|
-
const valueSymbol = scopes.symbolFor(declarator.id);
|
|
45646
|
-
if (!valueSymbol) continue;
|
|
45647
45218
|
bindings.push({
|
|
45648
|
-
|
|
45219
|
+
valueName: declarator.id.name,
|
|
45649
45220
|
hookName: callee.name,
|
|
45650
45221
|
declarator
|
|
45651
45222
|
});
|
|
@@ -45653,49 +45224,6 @@ const findHookCallBindings = (componentBody, scopes) => {
|
|
|
45653
45224
|
}
|
|
45654
45225
|
return bindings;
|
|
45655
45226
|
};
|
|
45656
|
-
const collectExactAliasSymbols = (componentBody, sourceSymbol, scopes) => {
|
|
45657
|
-
const symbols = [sourceSymbol];
|
|
45658
|
-
const symbolIds = new Set([sourceSymbol.id]);
|
|
45659
|
-
const aliasSourceIdentifiers = /* @__PURE__ */ new Set();
|
|
45660
|
-
if (!isNodeOfType(componentBody, "BlockStatement")) return {
|
|
45661
|
-
symbols,
|
|
45662
|
-
aliasSourceIdentifiers
|
|
45663
|
-
};
|
|
45664
|
-
let didFindAlias = true;
|
|
45665
|
-
while (didFindAlias) {
|
|
45666
|
-
didFindAlias = false;
|
|
45667
|
-
for (const statement of componentBody.body ?? []) {
|
|
45668
|
-
if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const") continue;
|
|
45669
|
-
for (const declarator of statement.declarations ?? []) {
|
|
45670
|
-
let aliasIdentifier = null;
|
|
45671
|
-
let sourceIdentifier = null;
|
|
45672
|
-
const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
|
|
45673
|
-
const arrayBinding = isNodeOfType(declarator.id, "ArrayPattern") ? declarator.id.elements[0] : null;
|
|
45674
|
-
const arrayValueNode = isNodeOfType(initializer, "ArrayExpression") ? initializer.elements[0] : null;
|
|
45675
|
-
const arrayValue = arrayValueNode && !isNodeOfType(arrayValueNode, "SpreadElement") ? stripParenExpression(arrayValueNode) : null;
|
|
45676
|
-
if (isNodeOfType(declarator.id, "Identifier") && isNodeOfType(initializer, "Identifier")) {
|
|
45677
|
-
aliasIdentifier = declarator.id;
|
|
45678
|
-
sourceIdentifier = initializer;
|
|
45679
|
-
} else if (isNodeOfType(declarator.id, "ArrayPattern") && declarator.id.elements.length === 1 && isNodeOfType(arrayBinding, "Identifier") && isNodeOfType(initializer, "ArrayExpression") && initializer.elements.length === 1 && isNodeOfType(arrayValue, "Identifier")) {
|
|
45680
|
-
aliasIdentifier = arrayBinding;
|
|
45681
|
-
sourceIdentifier = arrayValue;
|
|
45682
|
-
}
|
|
45683
|
-
if (!aliasIdentifier || !sourceIdentifier) continue;
|
|
45684
|
-
const referencedSymbol = scopes.symbolFor(sourceIdentifier);
|
|
45685
|
-
const aliasSymbol = scopes.symbolFor(aliasIdentifier);
|
|
45686
|
-
if (!referencedSymbol || !symbolIds.has(referencedSymbol.id) || !aliasSymbol || aliasSymbol.kind !== "const" || symbolIds.has(aliasSymbol.id)) continue;
|
|
45687
|
-
symbolIds.add(aliasSymbol.id);
|
|
45688
|
-
symbols.push(aliasSymbol);
|
|
45689
|
-
aliasSourceIdentifiers.add(sourceIdentifier);
|
|
45690
|
-
didFindAlias = true;
|
|
45691
|
-
}
|
|
45692
|
-
}
|
|
45693
|
-
}
|
|
45694
|
-
return {
|
|
45695
|
-
symbols,
|
|
45696
|
-
aliasSourceIdentifiers
|
|
45697
|
-
};
|
|
45698
|
-
};
|
|
45699
45227
|
const rerenderDeferReadsHook = defineRule({
|
|
45700
45228
|
id: "rerender-defer-reads-hook",
|
|
45701
45229
|
title: "URL hook value only read in handlers",
|
|
@@ -45706,13 +45234,15 @@ const rerenderDeferReadsHook = defineRule({
|
|
|
45706
45234
|
create: (context) => {
|
|
45707
45235
|
const checkComponent = (componentBody) => {
|
|
45708
45236
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
45709
|
-
const bindings = findHookCallBindings(componentBody
|
|
45237
|
+
const bindings = findHookCallBindings(componentBody);
|
|
45710
45238
|
if (bindings.length === 0) return;
|
|
45711
45239
|
const handlerBindingNames = collectHandlerBindingNames(componentBody);
|
|
45712
45240
|
for (const binding of bindings) {
|
|
45713
|
-
const { symbols, aliasSourceIdentifiers } = collectExactAliasSymbols(componentBody, binding.valueSymbol, context.scopes);
|
|
45714
45241
|
const referenceLocations = [];
|
|
45715
|
-
|
|
45242
|
+
walkAst(componentBody, (child) => {
|
|
45243
|
+
if (isNodeOfType(binding.declarator, "VariableDeclarator") && child === binding.declarator.id) return;
|
|
45244
|
+
if (isNodeOfType(child, "Identifier") && child.name === binding.valueName) referenceLocations.push(child);
|
|
45245
|
+
});
|
|
45716
45246
|
if (referenceLocations.length === 0) continue;
|
|
45717
45247
|
if (!referenceLocations.every((ref) => isInsideEventHandler(ref, handlerBindingNames))) continue;
|
|
45718
45248
|
context.report({
|
|
@@ -53661,8 +53191,7 @@ const serverCacheWithObjectLiteral = defineRule({
|
|
|
53661
53191
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
53662
53192
|
if (!cachedFunctionNames.has(node.callee.name)) return;
|
|
53663
53193
|
const firstArg = node.arguments?.[0];
|
|
53664
|
-
if (!
|
|
53665
|
-
if (!isNodeOfType(unwrapObjectIntegrityExpression(firstArg, context.scopes, OBJECT_FREEZE_OR_SEAL_METHOD_NAMES), "ObjectExpression")) return;
|
|
53194
|
+
if (!isNodeOfType(firstArg, "ObjectExpression")) return;
|
|
53666
53195
|
context.report({
|
|
53667
53196
|
node,
|
|
53668
53197
|
message: `Passing a new object to React.cache() each render misses the cache, so it refetches every request.`
|