oxlint-plugin-react-doctor 0.7.2-dev.6ae8e46 → 0.7.2-dev.82e0475
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.d.ts +0 -46
- package/dist/index.js +299 -962
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2277,7 +2277,6 @@ const resolveStaticStringValues = (rawExpression, scopes, remainingHops) => {
|
|
|
2277
2277
|
if (remainingHops === 0) return null;
|
|
2278
2278
|
const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
|
|
2279
2279
|
if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
|
|
2280
|
-
if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
2281
2280
|
return resolveStaticStringValues(symbol.initializer, scopes, remainingHops - 1);
|
|
2282
2281
|
}
|
|
2283
2282
|
return null;
|
|
@@ -2317,8 +2316,7 @@ const isDirectChildOfLinkComponent = (openingElement) => {
|
|
|
2317
2316
|
const isKeyboardOperableWidgetAnchor = (openingElement) => Boolean(hasJsxPropIgnoreCase(openingElement.attributes, "role")) && Boolean(hasJsxPropIgnoreCase(openingElement.attributes, "tabindex")) && (Boolean(hasJsxPropIgnoreCase(openingElement.attributes, "onkeydown")) || Boolean(hasJsxPropIgnoreCase(openingElement.attributes, "onkeyup")));
|
|
2318
2317
|
const isInvalidHref = (value, validHrefs) => {
|
|
2319
2318
|
if (validHrefs.has(value)) return false;
|
|
2320
|
-
|
|
2321
|
-
return value === "" || value === "#" || withoutLeadingNonWord.startsWith("javascript:");
|
|
2319
|
+
return value === "" || value === "#" || value === "javascript:void(0)";
|
|
2322
2320
|
};
|
|
2323
2321
|
const isNullishOrFragmentHref = (value) => {
|
|
2324
2322
|
if (isNodeOfType(value, "JSXExpressionContainer")) {
|
|
@@ -5193,7 +5191,7 @@ const authTokenInWebStorage = defineRule({
|
|
|
5193
5191
|
const callee = node.callee;
|
|
5194
5192
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
5195
5193
|
if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "setItem") return;
|
|
5196
|
-
if (!isWebStorageObject(
|
|
5194
|
+
if (!isWebStorageObject(callee.object)) return;
|
|
5197
5195
|
const keyArgument = node.arguments?.[0];
|
|
5198
5196
|
if (!keyArgument) return;
|
|
5199
5197
|
const keyString = resolveStaticKeyString(keyArgument);
|
|
@@ -6133,21 +6131,19 @@ const clickjackingRedirectRisk = defineRule({
|
|
|
6133
6131
|
})
|
|
6134
6132
|
});
|
|
6135
6133
|
//#endregion
|
|
6136
|
-
//#region src/plugin/utils/is-global-method-call.ts
|
|
6137
|
-
const isGlobalMethodCall = (node, objectName, methodName) => {
|
|
6138
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
6139
|
-
const callee = stripParenExpression(node.callee);
|
|
6140
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
|
|
6141
|
-
const receiver = stripParenExpression(callee.object);
|
|
6142
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === objectName && isNodeOfType(callee.property, "Identifier") && callee.property.name === methodName;
|
|
6143
|
-
};
|
|
6144
|
-
//#endregion
|
|
6145
6134
|
//#region src/plugin/rules/client/client-localstorage-no-version.ts
|
|
6146
6135
|
const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
|
|
6147
6136
|
const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
|
|
6148
6137
|
const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
|
|
6149
6138
|
const isVersionedKey = (key) => VERSIONED_KEY_PATTERN.test(key) || CAMEL_CASE_VERSIONED_KEY_PATTERN.test(key);
|
|
6150
|
-
const isJsonStringifyCall = (node) =>
|
|
6139
|
+
const isJsonStringifyCall = (node) => {
|
|
6140
|
+
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
6141
|
+
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
6142
|
+
if (!isNodeOfType(node.callee.object, "Identifier")) return false;
|
|
6143
|
+
if (node.callee.object.name !== "JSON") return false;
|
|
6144
|
+
if (!isNodeOfType(node.callee.property, "Identifier")) return false;
|
|
6145
|
+
return node.callee.property.name === "stringify";
|
|
6146
|
+
};
|
|
6151
6147
|
const resolveStringKey = (keyArg, context) => {
|
|
6152
6148
|
if (isNodeOfType(keyArg, "Literal")) return typeof keyArg.value === "string" ? keyArg.value : null;
|
|
6153
6149
|
if (!isNodeOfType(keyArg, "Identifier")) return null;
|
|
@@ -6166,9 +6162,8 @@ const clientLocalstorageNoVersion = defineRule({
|
|
|
6166
6162
|
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.",
|
|
6167
6163
|
create: (context) => ({ CallExpression(node) {
|
|
6168
6164
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
6169
|
-
|
|
6170
|
-
if (!
|
|
6171
|
-
if (!STORAGE_OBJECTS.has(receiver.name)) return;
|
|
6165
|
+
if (!isNodeOfType(node.callee.object, "Identifier")) return;
|
|
6166
|
+
if (!STORAGE_OBJECTS.has(node.callee.object.name)) return;
|
|
6172
6167
|
if (!isNodeOfType(node.callee.property, "Identifier")) return;
|
|
6173
6168
|
if (node.callee.property.name !== "setItem") return;
|
|
6174
6169
|
const keyArg = node.arguments?.[0];
|
|
@@ -6181,7 +6176,7 @@ const clientLocalstorageNoVersion = defineRule({
|
|
|
6181
6176
|
if (!isJsonStringifyCall(valueArg)) return;
|
|
6182
6177
|
context.report({
|
|
6183
6178
|
node: keyArg,
|
|
6184
|
-
message: `${
|
|
6179
|
+
message: `${node.callee.object.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").`
|
|
6185
6180
|
});
|
|
6186
6181
|
} })
|
|
6187
6182
|
});
|
|
@@ -7723,7 +7718,6 @@ const isResultDiscardedCall = (callExpression) => {
|
|
|
7723
7718
|
let parent = node.parent;
|
|
7724
7719
|
while (parent) {
|
|
7725
7720
|
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
7726
|
-
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void") return true;
|
|
7727
7721
|
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
|
|
7728
7722
|
if (isNodeOfType(parent, "ChainExpression")) {
|
|
7729
7723
|
node = parent;
|
|
@@ -7958,13 +7952,6 @@ const collectCleanupBindings = (effectCallback) => {
|
|
|
7958
7952
|
};
|
|
7959
7953
|
if (!isNodeOfType(effectCallback, "ArrowFunctionExpression") && !isNodeOfType(effectCallback, "FunctionExpression")) return bindings;
|
|
7960
7954
|
if (!isNodeOfType(effectCallback.body, "BlockStatement")) return bindings;
|
|
7961
|
-
walkAst(effectCallback.body, (child) => {
|
|
7962
|
-
if (!isSubscribeOrObserveCall(child)) return;
|
|
7963
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
7964
|
-
if (!isNodeOfType(child.callee, "MemberExpression")) return;
|
|
7965
|
-
if (!isNodeOfType(child.callee.object, "Identifier")) return;
|
|
7966
|
-
bindings.subscriptionNames.add(child.callee.object.name);
|
|
7967
|
-
});
|
|
7968
7955
|
walkInsideStatementBlocks(effectCallback.body, (child) => {
|
|
7969
7956
|
if (!isNodeOfType(child, "VariableDeclaration")) return;
|
|
7970
7957
|
for (const declarator of child.declarations ?? []) {
|
|
@@ -8007,22 +7994,6 @@ const getRangeStart = (node) => {
|
|
|
8007
7994
|
const rangeStart = node.range?.[0];
|
|
8008
7995
|
return typeof rangeStart === "number" ? rangeStart : null;
|
|
8009
7996
|
};
|
|
8010
|
-
const removeSynchronouslyReleasedUsages = (callback, usages) => {
|
|
8011
|
-
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
8012
|
-
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
8013
|
-
const releaseStarts = [];
|
|
8014
|
-
walkInsideStatementBlocks(callback.body, (child) => {
|
|
8015
|
-
if (!isReleaseLikeCall(child, EMPTY_NAME_SET$1, EMPTY_NAME_SET$1)) return;
|
|
8016
|
-
const releaseStart = getRangeStart(child);
|
|
8017
|
-
if (releaseStart !== null) releaseStarts.push(releaseStart);
|
|
8018
|
-
});
|
|
8019
|
-
if (releaseStarts.length === 0) return usages;
|
|
8020
|
-
return usages.filter((usage) => {
|
|
8021
|
-
const usageStart = getRangeStart(usage.node);
|
|
8022
|
-
if (usageStart === null) return true;
|
|
8023
|
-
return !releaseStarts.some((releaseStart) => releaseStart > usageStart);
|
|
8024
|
-
});
|
|
8025
|
-
};
|
|
8026
7997
|
const cleanupReturnRunsAfterUsage = (returnStatement, usages) => {
|
|
8027
7998
|
if (returnStatement.argument && isCleanupReturningSubscribeLikeCallExpression(returnStatement.argument)) return true;
|
|
8028
7999
|
const returnStart = getRangeStart(returnStatement);
|
|
@@ -8046,14 +8017,7 @@ const effectHasCleanupReturn = (callback, usages) => {
|
|
|
8046
8017
|
return didFindCleanupReturn;
|
|
8047
8018
|
};
|
|
8048
8019
|
const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
|
|
8049
|
-
const
|
|
8050
|
-
if (!isNodeOfType(property, "Property")) return false;
|
|
8051
|
-
const keyName = isNodeOfType(property.key, "Identifier") ? property.key.name : isNodeOfType(property.key, "Literal") ? property.key.value : null;
|
|
8052
|
-
if (keyName === "signal") return true;
|
|
8053
|
-
if (keyName !== "once") return false;
|
|
8054
|
-
return isNodeOfType(property.value, "Literal") && property.value.value === true;
|
|
8055
|
-
};
|
|
8056
|
-
const hasSelfReleasingListenerOptions = (node) => isNodeOfType(node, "CallExpression") && (node.arguments ?? []).some((argument) => isNodeOfType(argument, "ObjectExpression") && (argument.properties ?? []).some(isSelfReleasingListenerOptionProperty));
|
|
8020
|
+
const hasSelfReleasingListenerOptions = (node) => isNodeOfType(node, "CallExpression") && (node.arguments ?? []).some((argument) => isNodeOfType(argument, "ObjectExpression") && (argument.properties ?? []).some((property) => isNodeOfType(property, "Property") && isNodeOfType(property.key, "Identifier") && (property.key.name === "signal" || property.key.name === "once" && isNodeOfType(property.value, "Literal") && property.value.value === true)));
|
|
8057
8021
|
const PAIRED_RELEASE_VERB_NAMES_BY_REGISTRATION_VERB = new Map([
|
|
8058
8022
|
["addEventListener", new Set(["removeEventListener", "abort"])],
|
|
8059
8023
|
["addListener", new Set([
|
|
@@ -8128,12 +8092,10 @@ const findRetainedFunctionLeak = (retainedFunction) => {
|
|
|
8128
8092
|
if (!isFunctionLike$1(retainedFunction)) return null;
|
|
8129
8093
|
const body = retainedFunction.body;
|
|
8130
8094
|
if (!body) return null;
|
|
8131
|
-
const conciseReturnValue = isNodeOfType(body, "BlockStatement") ? null : body;
|
|
8132
8095
|
let leak = null;
|
|
8133
8096
|
walkAst(body, (child) => {
|
|
8134
8097
|
if (leak !== null) return false;
|
|
8135
8098
|
if (isFunctionLike$1(child)) return false;
|
|
8136
|
-
if (child === conciseReturnValue && isNodeOfType(child, "CallExpression")) return;
|
|
8137
8099
|
if (isSocketConstruction(child) && isResultDiscardedCall(child) && !bodyContainsPairedReleaseCall(body, SOCKET_RELEASE_VERB_NAMES)) {
|
|
8138
8100
|
leak = {
|
|
8139
8101
|
kind: "socket",
|
|
@@ -8195,7 +8157,7 @@ const effectNeedsCleanup = defineRule({
|
|
|
8195
8157
|
if (!isHookCall$2(node, EFFECT_HOOK_NAMES$1)) return;
|
|
8196
8158
|
const callback = getEffectCallback(node);
|
|
8197
8159
|
if (!callback) return;
|
|
8198
|
-
const usages =
|
|
8160
|
+
const usages = findSubscribeLikeUsages(callback);
|
|
8199
8161
|
if (usages.length === 0) return;
|
|
8200
8162
|
if (effectHasCleanupReturn(callback, usages)) return;
|
|
8201
8163
|
const firstUsage = usages[0];
|
|
@@ -8980,24 +8942,6 @@ const isAstDescendant = (inner, outer) => {
|
|
|
8980
8942
|
return false;
|
|
8981
8943
|
};
|
|
8982
8944
|
//#endregion
|
|
8983
|
-
//#region src/plugin/utils/is-imported-from-non-react-module.ts
|
|
8984
|
-
const isImportedFromNonReactModule = (contextNode, localIdentifierName) => {
|
|
8985
|
-
const importSource = getImportSourceForName(contextNode, localIdentifierName);
|
|
8986
|
-
if (importSource === null) return false;
|
|
8987
|
-
return !REACT_RUNTIME_MODULE_SOURCES.has(importSource);
|
|
8988
|
-
};
|
|
8989
|
-
//#endregion
|
|
8990
|
-
//#region src/plugin/utils/is-non-react-effect-event-callee.ts
|
|
8991
|
-
const resolvesToLocalNonImportBinding = (identifier, scopes) => {
|
|
8992
|
-
const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
|
|
8993
|
-
return Boolean(symbol && symbol.kind !== "import");
|
|
8994
|
-
};
|
|
8995
|
-
const isNonReactEffectEventCallee = (callee, contextNode, scopes) => {
|
|
8996
|
-
if (isNodeOfType(callee, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes);
|
|
8997
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier")) return isImportedFromNonReactModule(contextNode, callee.object.name);
|
|
8998
|
-
return false;
|
|
8999
|
-
};
|
|
9000
|
-
//#endregion
|
|
9001
8945
|
//#region src/plugin/rules/react-builtins/exhaustive-deps-symbol-stability.ts
|
|
9002
8946
|
/**
|
|
9003
8947
|
* Symbol-stability helpers consumed by the `exhaustive-deps` rule.
|
|
@@ -9062,11 +9006,10 @@ const symbolHasStableHookOrigin = (symbol) => {
|
|
|
9062
9006
|
}
|
|
9063
9007
|
return false;
|
|
9064
9008
|
};
|
|
9065
|
-
const symbolHasUseEffectEventOrigin = (symbol
|
|
9009
|
+
const symbolHasUseEffectEventOrigin = (symbol) => {
|
|
9066
9010
|
const initializer = symbol.initializer ? unwrapExpression$3(symbol.initializer) : null;
|
|
9067
9011
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
9068
|
-
|
|
9069
|
-
return !isNonReactEffectEventCallee(initializer.callee, initializer, scopes);
|
|
9012
|
+
return getHookName(initializer.callee) === "useEffectEvent";
|
|
9070
9013
|
};
|
|
9071
9014
|
const getFunctionValueNode = (symbol) => {
|
|
9072
9015
|
if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return symbol.declarationNode;
|
|
@@ -9803,7 +9746,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
9803
9746
|
if (isLiteralOrEmptyTemplate(stripped)) continue;
|
|
9804
9747
|
if (isNodeOfType(stripped, "Identifier")) {
|
|
9805
9748
|
const depSymbol = context.scopes.symbolFor(stripped);
|
|
9806
|
-
if (depSymbol && symbolHasUseEffectEventOrigin(depSymbol
|
|
9749
|
+
if (depSymbol && symbolHasUseEffectEventOrigin(depSymbol)) {
|
|
9807
9750
|
context.report({
|
|
9808
9751
|
node: elementNode,
|
|
9809
9752
|
message: buildEffectEventDepMessage()
|
|
@@ -10229,10 +10172,7 @@ const PRAGMA = "React";
|
|
|
10229
10172
|
const isReactFunctionCall = (node, expectedCall) => {
|
|
10230
10173
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
10231
10174
|
if (getCalleeName$2(node) !== expectedCall) return false;
|
|
10232
|
-
if (isNodeOfType(node.callee, "MemberExpression"))
|
|
10233
|
-
const receiver = stripParenExpression(node.callee.object);
|
|
10234
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === PRAGMA;
|
|
10235
|
-
}
|
|
10175
|
+
if (isNodeOfType(node.callee, "MemberExpression")) return isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === PRAGMA;
|
|
10236
10176
|
return true;
|
|
10237
10177
|
};
|
|
10238
10178
|
//#endregion
|
|
@@ -12309,15 +12249,14 @@ const jsCacheStorage = defineRule({
|
|
|
12309
12249
|
"ArrowFunctionExpression:exit": exitFunctionScope,
|
|
12310
12250
|
CallExpression(node) {
|
|
12311
12251
|
if (!isMemberProperty(node.callee, "getItem")) return;
|
|
12312
|
-
|
|
12313
|
-
if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS$1.has(receiver.name)) return;
|
|
12252
|
+
if (!isNodeOfType(node.callee.object, "Identifier") || !STORAGE_OBJECTS$1.has(node.callee.object.name)) return;
|
|
12314
12253
|
if (!isNodeOfType(node.arguments?.[0], "Literal")) return;
|
|
12315
12254
|
const storageReadCounts = storageReadCountStack[storageReadCountStack.length - 1];
|
|
12316
12255
|
const storageKey = String(node.arguments[0].value);
|
|
12317
12256
|
const readCount = (storageReadCounts.get(storageKey) ?? 0) + 1;
|
|
12318
12257
|
storageReadCounts.set(storageKey, readCount);
|
|
12319
12258
|
if (readCount === 2) {
|
|
12320
|
-
const storageName =
|
|
12259
|
+
const storageName = node.callee.object.name;
|
|
12321
12260
|
context.report({
|
|
12322
12261
|
node,
|
|
12323
12262
|
message: `This is slow because ${storageName}.getItem("${storageKey}") runs several times & re-parses the data each call, so read it once & reuse the value`
|
|
@@ -12331,16 +12270,13 @@ const jsCacheStorage = defineRule({
|
|
|
12331
12270
|
//#region src/plugin/rules/js-performance/js-combine-iterations.ts
|
|
12332
12271
|
const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
|
|
12333
12272
|
const callee = callExpression.callee;
|
|
12334
|
-
if (isNodeOfType(callee, "MemberExpression"))
|
|
12335
|
-
const receiver = stripParenExpression(callee.object);
|
|
12336
|
-
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Iterator" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from") return true;
|
|
12337
|
-
if (isNodeOfType(callee.property, "Identifier") && ITERATOR_PRODUCING_METHOD_NAMES.has(callee.property.name)) {
|
|
12338
|
-
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Object") return false;
|
|
12339
|
-
return true;
|
|
12340
|
-
}
|
|
12341
|
-
return false;
|
|
12342
|
-
}
|
|
12273
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Iterator" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from") return true;
|
|
12343
12274
|
if (isNodeOfType(callee, "Identifier") && generatorNamesInFile.has(callee.name)) return true;
|
|
12275
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ITERATOR_PRODUCING_METHOD_NAMES.has(callee.property.name)) {
|
|
12276
|
+
const receiver = callee.object;
|
|
12277
|
+
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Object") return false;
|
|
12278
|
+
return true;
|
|
12279
|
+
}
|
|
12344
12280
|
return false;
|
|
12345
12281
|
};
|
|
12346
12282
|
const isChainPassThroughCall = (callExpression) => {
|
|
@@ -12352,7 +12288,10 @@ const isChainPassThroughCall = (callExpression) => {
|
|
|
12352
12288
|
const isReceiverChainIteratorRooted = (receiverNode, generatorNamesInFile) => {
|
|
12353
12289
|
let cursor = receiverNode;
|
|
12354
12290
|
while (cursor) {
|
|
12355
|
-
|
|
12291
|
+
if (isNodeOfType(cursor, "ChainExpression")) {
|
|
12292
|
+
cursor = cursor.expression;
|
|
12293
|
+
continue;
|
|
12294
|
+
}
|
|
12356
12295
|
if (!isNodeOfType(cursor, "CallExpression")) return false;
|
|
12357
12296
|
if (isIteratorProducingCall(cursor, generatorNamesInFile)) return true;
|
|
12358
12297
|
if (!isChainPassThroughCall(cursor)) return false;
|
|
@@ -12428,7 +12367,10 @@ const isStringSplitRootedChain = (receiverNode) => {
|
|
|
12428
12367
|
let hops = 0;
|
|
12429
12368
|
while (cursor && hops < 12) {
|
|
12430
12369
|
hops += 1;
|
|
12431
|
-
|
|
12370
|
+
if (isNodeOfType(cursor, "ChainExpression")) {
|
|
12371
|
+
cursor = cursor.expression;
|
|
12372
|
+
continue;
|
|
12373
|
+
}
|
|
12432
12374
|
if (!isNodeOfType(cursor, "CallExpression")) return false;
|
|
12433
12375
|
const callee = cursor.callee;
|
|
12434
12376
|
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
@@ -12452,7 +12394,10 @@ const isSmallLiteralArray = (node) => {
|
|
|
12452
12394
|
const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
|
|
12453
12395
|
let cursor = receiverNode;
|
|
12454
12396
|
while (cursor) {
|
|
12455
|
-
|
|
12397
|
+
if (isNodeOfType(cursor, "ChainExpression")) {
|
|
12398
|
+
cursor = cursor.expression;
|
|
12399
|
+
continue;
|
|
12400
|
+
}
|
|
12456
12401
|
if (isNodeOfType(cursor, "ArrayExpression")) return isSmallLiteralArray(cursor);
|
|
12457
12402
|
if (isNodeOfType(cursor, "Identifier")) return smallConstArrayNames.has(cursor.name);
|
|
12458
12403
|
if (!isNodeOfType(cursor, "CallExpression")) return false;
|
|
@@ -12517,7 +12462,7 @@ const jsCombineIterations = defineRule({
|
|
|
12517
12462
|
if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
|
|
12518
12463
|
const outerMethod = node.callee.property.name;
|
|
12519
12464
|
if (!CHAINABLE_ITERATION_METHODS.has(outerMethod)) return;
|
|
12520
|
-
const innerCall =
|
|
12465
|
+
const innerCall = node.callee.object;
|
|
12521
12466
|
if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
|
|
12522
12467
|
const innerMethod = innerCall.callee.property.name;
|
|
12523
12468
|
if (!CHAINABLE_ITERATION_METHODS.has(innerMethod)) return;
|
|
@@ -12590,10 +12535,10 @@ const jsFlatmapFilter = defineRule({
|
|
|
12590
12535
|
if (!filterArgument) return;
|
|
12591
12536
|
const isIdentityArrow = isNodeOfType(filterArgument, "ArrowFunctionExpression") && filterArgument.params?.length === 1 && isNodeOfType(filterArgument.body, "Identifier") && isNodeOfType(filterArgument.params[0], "Identifier") && filterArgument.body.name === filterArgument.params[0].name;
|
|
12592
12537
|
if (!(isNodeOfType(filterArgument, "Identifier") && filterArgument.name === "Boolean" || isIdentityArrow)) return;
|
|
12593
|
-
const innerCall =
|
|
12538
|
+
const innerCall = node.callee.object;
|
|
12594
12539
|
if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
|
|
12595
12540
|
if (innerCall.callee.property.name !== "map") return;
|
|
12596
|
-
const receiver =
|
|
12541
|
+
const receiver = innerCall.callee.object;
|
|
12597
12542
|
if (receiver && isNodeOfType(receiver, "ArrayExpression")) {
|
|
12598
12543
|
const elements = receiver.elements ?? [];
|
|
12599
12544
|
if (elements.length > 0 && elements.length <= 8 && elements.every((element) => element == null || !isNodeOfType(element, "SpreadElement"))) return;
|
|
@@ -13853,7 +13798,7 @@ const jsTosortedImmutable = defineRule({
|
|
|
13853
13798
|
recommendation: "Use `array.toSorted()` (ES2023) instead of `[...array].sort()` so you sort without copying the array first",
|
|
13854
13799
|
create: (context) => ({ CallExpression(node) {
|
|
13855
13800
|
if (!isMemberProperty(node.callee, "sort")) return;
|
|
13856
|
-
const receiver =
|
|
13801
|
+
const receiver = node.callee.object;
|
|
13857
13802
|
if (isNodeOfType(receiver, "ArrayExpression") && receiver.elements?.length === 1 && isNodeOfType(receiver.elements[0], "SpreadElement")) {
|
|
13858
13803
|
const spreadArgument = receiver.elements[0].argument;
|
|
13859
13804
|
if (isFreshOrIteratorAllocation(spreadArgument)) return;
|
|
@@ -18890,8 +18835,7 @@ const isInsidePollingLoop = (navigationNode, effectCallback, timerScheduledNames
|
|
|
18890
18835
|
};
|
|
18891
18836
|
const describeClientSideNavigation = (node) => {
|
|
18892
18837
|
if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression")) {
|
|
18893
|
-
const
|
|
18894
|
-
const objectName = isNodeOfType(receiver, "Identifier") ? receiver.name : null;
|
|
18838
|
+
const objectName = isNodeOfType(node.callee.object, "Identifier") ? node.callee.object.name : null;
|
|
18895
18839
|
const methodName = isNodeOfType(node.callee.property, "Identifier") ? node.callee.property.name : null;
|
|
18896
18840
|
if (objectName === "router" && (methodName === "push" || methodName === "replace")) return `router.${methodName}() in useEffect flashes the wrong page before redirecting.`;
|
|
18897
18841
|
}
|
|
@@ -19511,7 +19455,7 @@ const getCookieMutationMethodName = (node, locallyScopedCookieBindings) => {
|
|
|
19511
19455
|
if (!isNodeOfType(node.callee, "MemberExpression")) return null;
|
|
19512
19456
|
if (!isNodeOfType(node.callee.property, "Identifier")) return null;
|
|
19513
19457
|
if (!COOKIE_MUTATION_METHOD_NAMES.has(node.callee.property.name)) return null;
|
|
19514
|
-
if (!isCookieReceiver(
|
|
19458
|
+
if (!isCookieReceiver(node.callee.object, locallyScopedCookieBindings)) return null;
|
|
19515
19459
|
return node.callee.property.name;
|
|
19516
19460
|
};
|
|
19517
19461
|
const isMutatingFetchCall = (node) => {
|
|
@@ -19525,7 +19469,7 @@ const isMutatingDbCall = (node, locallyScopedSafeBindings) => {
|
|
|
19525
19469
|
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
19526
19470
|
const { property, object } = node.callee;
|
|
19527
19471
|
if (!isNodeOfType(property, "Identifier") || !MUTATION_METHOD_NAMES.has(property.name)) return false;
|
|
19528
|
-
if (isSafeReceiverChain(
|
|
19472
|
+
if (isSafeReceiverChain(object, locallyScopedSafeBindings)) return false;
|
|
19529
19473
|
return true;
|
|
19530
19474
|
};
|
|
19531
19475
|
const getDbCallDescription = (node) => {
|
|
@@ -19533,8 +19477,7 @@ const getDbCallDescription = (node) => {
|
|
|
19533
19477
|
if (!isNodeOfType(node.callee, "MemberExpression")) return ".unknown()";
|
|
19534
19478
|
if (!isNodeOfType(node.callee.property, "Identifier")) return ".unknown()";
|
|
19535
19479
|
const methodName = node.callee.property.name;
|
|
19536
|
-
const
|
|
19537
|
-
const rootObjectName = isNodeOfType(receiver, "Identifier") ? receiver.name : null;
|
|
19480
|
+
const rootObjectName = isNodeOfType(node.callee.object, "Identifier") ? node.callee.object.name : null;
|
|
19538
19481
|
return rootObjectName ? `${rootObjectName}.${methodName}()` : `.${methodName}()`;
|
|
19539
19482
|
};
|
|
19540
19483
|
const findSideEffect = (node, options = {}) => {
|
|
@@ -20934,13 +20877,13 @@ const getCallExpr = (ref, current = ref.identifier.parent) => {
|
|
|
20934
20877
|
if (isNodeOfType(current, "CallExpression")) {
|
|
20935
20878
|
let node = ref.identifier;
|
|
20936
20879
|
let parent = node.parent;
|
|
20937
|
-
while (parent &&
|
|
20880
|
+
while (parent && isNodeOfType(parent, "MemberExpression")) {
|
|
20938
20881
|
node = parent;
|
|
20939
20882
|
parent = node.parent;
|
|
20940
20883
|
}
|
|
20941
20884
|
if (current.callee === node) return current;
|
|
20942
20885
|
}
|
|
20943
|
-
if (isNodeOfType(current, "MemberExpression")
|
|
20886
|
+
if (isNodeOfType(current, "MemberExpression")) return getCallExpr(ref, current.parent);
|
|
20944
20887
|
return null;
|
|
20945
20888
|
};
|
|
20946
20889
|
const getArgsUpstreamRefs = (analysis, ref) => {
|
|
@@ -21075,7 +21018,7 @@ const isReactNamedImportReference = (ref, importedName) => Boolean(ref?.resolved
|
|
|
21075
21018
|
const importDeclaration = declarationNode.parent;
|
|
21076
21019
|
return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && isNodeOfType(importDeclaration.source, "Literal") && importDeclaration.source.value === "react");
|
|
21077
21020
|
}));
|
|
21078
|
-
const isHookCallee
|
|
21021
|
+
const isHookCallee = (analysis, node, hookName) => {
|
|
21079
21022
|
if (!node) return false;
|
|
21080
21023
|
if (isNodeOfType(node, "Identifier")) {
|
|
21081
21024
|
if (node.name === hookName) return true;
|
|
@@ -21084,19 +21027,15 @@ const isHookCallee$1 = (analysis, node, hookName) => {
|
|
|
21084
21027
|
if (parent && isNodeOfType(parent, "MemberExpression") && isNodeOfType(parent.object, "Identifier") && parent.object.name === "React" && isNodeOfType(parent.property, "Identifier") && parent.property.name === hookName) return true;
|
|
21085
21028
|
return false;
|
|
21086
21029
|
}
|
|
21087
|
-
if (isNodeOfType(node, "MemberExpression"))
|
|
21088
|
-
const receiver = stripParenExpression(node.object);
|
|
21089
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
21090
|
-
}
|
|
21030
|
+
if (isNodeOfType(node, "MemberExpression")) return isNodeOfType(node.object, "Identifier") && node.object.name === "React" && isNodeOfType(node.property, "Identifier") && node.property.name === hookName;
|
|
21091
21031
|
return false;
|
|
21092
21032
|
};
|
|
21093
21033
|
const isUseEffect = (node) => {
|
|
21094
21034
|
if (!node || !isNodeOfType(node, "CallExpression")) return false;
|
|
21095
21035
|
const callee = node.callee;
|
|
21096
21036
|
if (isNodeOfType(callee, "Identifier") && callee.name === "useEffect") return true;
|
|
21097
|
-
if (
|
|
21098
|
-
|
|
21099
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect";
|
|
21037
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "React" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useEffect") return true;
|
|
21038
|
+
return false;
|
|
21100
21039
|
};
|
|
21101
21040
|
const getEffectFn = (analysis, node) => {
|
|
21102
21041
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
@@ -21124,7 +21063,7 @@ const isState = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
|
21124
21063
|
const node = def.node;
|
|
21125
21064
|
if (!isNodeOfType(node, "VariableDeclarator")) return false;
|
|
21126
21065
|
if (!isNodeOfType(node.init, "CallExpression")) return false;
|
|
21127
|
-
if (!isHookCallee
|
|
21066
|
+
if (!isHookCallee(analysis, node.init.callee, "useState")) return false;
|
|
21128
21067
|
if (!isNodeOfType(node.id, "ArrayPattern")) return false;
|
|
21129
21068
|
const elements = node.id.elements ?? [];
|
|
21130
21069
|
if (elements.length !== 1 && elements.length !== 2) return false;
|
|
@@ -21135,7 +21074,7 @@ const isStateSetter = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) =
|
|
|
21135
21074
|
const node = def.node;
|
|
21136
21075
|
if (!isNodeOfType(node, "VariableDeclarator")) return false;
|
|
21137
21076
|
if (!isNodeOfType(node.init, "CallExpression")) return false;
|
|
21138
|
-
if (!isHookCallee
|
|
21077
|
+
if (!isHookCallee(analysis, node.init.callee, "useState")) return false;
|
|
21139
21078
|
if (!isNodeOfType(node.id, "ArrayPattern")) return false;
|
|
21140
21079
|
const elements = node.id.elements ?? [];
|
|
21141
21080
|
if (elements.length !== 2) return false;
|
|
@@ -21182,7 +21121,7 @@ const isRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
|
21182
21121
|
const node = def.node;
|
|
21183
21122
|
if (!isNodeOfType(node, "VariableDeclarator")) return false;
|
|
21184
21123
|
if (!isNodeOfType(node.init, "CallExpression")) return false;
|
|
21185
|
-
return isHookCallee
|
|
21124
|
+
return isHookCallee(analysis, node.init.callee, "useRef");
|
|
21186
21125
|
}));
|
|
21187
21126
|
const isRefCurrent = (ref) => {
|
|
21188
21127
|
const parent = ref.identifier.parent;
|
|
@@ -21195,15 +21134,11 @@ const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(ana
|
|
|
21195
21134
|
const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
|
|
21196
21135
|
const isPropCallbackInvocationRef = (analysis, ref) => {
|
|
21197
21136
|
if (!isPropAlias(analysis, ref)) return false;
|
|
21198
|
-
|
|
21199
|
-
|
|
21200
|
-
while (parent && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(parent.type) && "expression" in parent && parent.expression === effectiveNode) {
|
|
21201
|
-
effectiveNode = parent;
|
|
21202
|
-
parent = effectiveNode.parent;
|
|
21203
|
-
}
|
|
21137
|
+
const identifier = ref.identifier;
|
|
21138
|
+
const parent = identifier.parent;
|
|
21204
21139
|
if (!parent) return false;
|
|
21205
|
-
if (isNodeOfType(parent, "CallExpression") && parent.callee ===
|
|
21206
|
-
if (isNodeOfType(parent, "MemberExpression") && parent.object ===
|
|
21140
|
+
if (isNodeOfType(parent, "CallExpression") && parent.callee === identifier) return true;
|
|
21141
|
+
if (isNodeOfType(parent, "MemberExpression") && parent.object === identifier) {
|
|
21207
21142
|
const memberParent = parent.parent;
|
|
21208
21143
|
if (isNodeOfType(memberParent, "CallExpression") && memberParent.callee === parent) {
|
|
21209
21144
|
if (!parent.computed && isNodeOfType(parent.property, "Identifier") && HANDLER_NAMED_METHOD_PATTERN.test(parent.property.name)) return true;
|
|
@@ -21214,7 +21149,7 @@ const isPropCallbackInvocationRef = (analysis, ref) => {
|
|
|
21214
21149
|
};
|
|
21215
21150
|
const isRefCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isRefCurrent(innerRef) || isRef(analysis, innerRef));
|
|
21216
21151
|
const getUseStateDecl = (analysis, ref) => {
|
|
21217
|
-
let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee
|
|
21152
|
+
let node = getUpstreamRefs(analysis, ref).find((upRef) => isHookCallee(analysis, upRef.identifier, "useState"))?.identifier;
|
|
21218
21153
|
while (node && !isNodeOfType(node, "VariableDeclarator")) node = node.parent;
|
|
21219
21154
|
return node ?? null;
|
|
21220
21155
|
};
|
|
@@ -21460,23 +21395,7 @@ const ALWAYS_FOCUSABLE_TAGS = new Set([
|
|
|
21460
21395
|
"summary",
|
|
21461
21396
|
"textarea"
|
|
21462
21397
|
]);
|
|
21463
|
-
const isStaticallyFalseBooleanAttribute = (attribute) => {
|
|
21464
|
-
const value = attribute.value;
|
|
21465
|
-
if (!value || !isNodeOfType(value, "JSXExpressionContainer")) return false;
|
|
21466
|
-
const expression = value.expression;
|
|
21467
|
-
return isNodeOfType(expression, "Literal") && expression.value === false;
|
|
21468
|
-
};
|
|
21469
|
-
const DISABLEABLE_TAGS = new Set([
|
|
21470
|
-
"button",
|
|
21471
|
-
"input",
|
|
21472
|
-
"select",
|
|
21473
|
-
"textarea"
|
|
21474
|
-
]);
|
|
21475
21398
|
const isNativelyFocusable = (tagName, openingElement) => {
|
|
21476
|
-
if (DISABLEABLE_TAGS.has(tagName)) {
|
|
21477
|
-
const disabledAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "disabled");
|
|
21478
|
-
if (disabledAttribute && !isStaticallyFalseBooleanAttribute(disabledAttribute)) return false;
|
|
21479
|
-
}
|
|
21480
21399
|
if (ALWAYS_FOCUSABLE_TAGS.has(tagName)) return true;
|
|
21481
21400
|
switch (tagName) {
|
|
21482
21401
|
case "input": {
|
|
@@ -21490,10 +21409,7 @@ const isNativelyFocusable = (tagName, openingElement) => {
|
|
|
21490
21409
|
case "a":
|
|
21491
21410
|
case "area": return hasJsxPropIgnoreCase(openingElement.attributes, "href") !== void 0;
|
|
21492
21411
|
case "audio":
|
|
21493
|
-
case "video":
|
|
21494
|
-
const controlsAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "controls");
|
|
21495
|
-
return controlsAttribute !== void 0 && !isStaticallyFalseBooleanAttribute(controlsAttribute);
|
|
21496
|
-
}
|
|
21412
|
+
case "video": return hasJsxPropIgnoreCase(openingElement.attributes, "controls") !== void 0;
|
|
21497
21413
|
default: return false;
|
|
21498
21414
|
}
|
|
21499
21415
|
};
|
|
@@ -22457,8 +22373,7 @@ const SECOND_INDEX_METHODS = new Set([
|
|
|
22457
22373
|
"some"
|
|
22458
22374
|
]);
|
|
22459
22375
|
const THIRD_INDEX_METHODS = new Set(["reduce", "reduceRight"]);
|
|
22460
|
-
const isPositionallyStableIterationReceiver = (
|
|
22461
|
-
const receiver = stripParenExpression(receiverNode);
|
|
22376
|
+
const isPositionallyStableIterationReceiver = (receiver) => {
|
|
22462
22377
|
if (isAllLiteralArrayExpression(receiver)) return true;
|
|
22463
22378
|
if (isNodeOfType(receiver, "ArrayExpression") && receiver.elements?.length === 1) {
|
|
22464
22379
|
const only = receiver.elements[0];
|
|
@@ -22469,13 +22384,17 @@ const isPositionallyStableIterationReceiver = (receiverNode) => {
|
|
|
22469
22384
|
}
|
|
22470
22385
|
if (!isNodeOfType(receiver, "CallExpression")) return false;
|
|
22471
22386
|
const callee = receiver.callee;
|
|
22472
|
-
if (
|
|
22387
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from" && receiver.arguments.length >= 1 && isNodeOfType(receiver.arguments[0], "ObjectExpression")) return true;
|
|
22473
22388
|
if (isNodeOfType(callee, "Identifier") && callee.name === "Array") return true;
|
|
22474
22389
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "split") return true;
|
|
22475
22390
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "fill" || callee.property.name === "flat")) return isPositionallyStableIterationReceiver(callee.object);
|
|
22476
22391
|
return false;
|
|
22477
22392
|
};
|
|
22478
|
-
const isArrayFromMapperCallback = (parentCall, callback) =>
|
|
22393
|
+
const isArrayFromMapperCallback = (parentCall, callback) => {
|
|
22394
|
+
if (parentCall.arguments[1] !== callback) return false;
|
|
22395
|
+
const callee = parentCall.callee;
|
|
22396
|
+
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from";
|
|
22397
|
+
};
|
|
22479
22398
|
const isArrayFromSourcePositionallyStable = (source) => {
|
|
22480
22399
|
if (isNodeOfType(source, "ObjectExpression")) {
|
|
22481
22400
|
for (const property of source.properties ?? []) {
|
|
@@ -22534,12 +22453,18 @@ const expressionUsesIndex = (expression, paramName) => {
|
|
|
22534
22453
|
return false;
|
|
22535
22454
|
}
|
|
22536
22455
|
if (isNodeOfType(expression, "CallExpression")) {
|
|
22537
|
-
if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier") && expression.callee.property.name === "toString" && isIndexReference(
|
|
22456
|
+
if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier") && expression.callee.property.name === "toString" && isIndexReference(expression.callee.object, paramName)) return true;
|
|
22538
22457
|
if (isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "String" && expression.arguments.length > 0 && isIndexReference(expression.arguments[0], paramName)) return true;
|
|
22539
22458
|
}
|
|
22540
22459
|
return false;
|
|
22541
22460
|
};
|
|
22542
|
-
const isReactCloneElement = (callExpression) =>
|
|
22461
|
+
const isReactCloneElement = (callExpression) => {
|
|
22462
|
+
const callee = callExpression.callee;
|
|
22463
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
22464
|
+
if (!isNodeOfType(callee.property, "Identifier")) return false;
|
|
22465
|
+
if (callee.property.name !== "cloneElement") return false;
|
|
22466
|
+
return isNodeOfType(callee.object, "Identifier") && callee.object.name === "React";
|
|
22467
|
+
};
|
|
22543
22468
|
const noArrayIndexKey = defineRule({
|
|
22544
22469
|
id: "no-array-index-key",
|
|
22545
22470
|
title: "Array index used as a key",
|
|
@@ -23263,7 +23188,7 @@ const isAsyncFunctionLike = (node) => {
|
|
|
23263
23188
|
if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) return Boolean(node.async);
|
|
23264
23189
|
return false;
|
|
23265
23190
|
};
|
|
23266
|
-
const SYNCHRONOUS_ITERATION_METHOD_NAMES
|
|
23191
|
+
const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
|
|
23267
23192
|
"forEach",
|
|
23268
23193
|
"map",
|
|
23269
23194
|
"filter",
|
|
@@ -23284,51 +23209,30 @@ const runsOnEffectDispatch = (functionNode) => {
|
|
|
23284
23209
|
if (parent.callee === functionNode) return true;
|
|
23285
23210
|
if (!(parent.arguments ?? []).some((argument) => argument === functionNode)) return false;
|
|
23286
23211
|
const callee = parent.callee;
|
|
23287
|
-
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES
|
|
23212
|
+
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(callee.property.name);
|
|
23288
23213
|
};
|
|
23289
23214
|
const isTerminatingStatement = (statement) => isNodeOfType(statement, "BreakStatement") || isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement");
|
|
23290
|
-
const
|
|
23291
|
-
|
|
23215
|
+
const isGuardWithTerminatingBranch = (statement) => {
|
|
23216
|
+
if (!isNodeOfType(statement, "IfStatement")) return null;
|
|
23217
|
+
if (statement.alternate) return null;
|
|
23218
|
+
const consequent = statement.consequent;
|
|
23219
|
+
if (isTerminatingStatement(consequent)) return consequent;
|
|
23220
|
+
if (isNodeOfType(consequent, "BlockStatement") && (consequent.body ?? []).some((inner) => isTerminatingStatement(inner))) return consequent;
|
|
23221
|
+
return null;
|
|
23222
|
+
};
|
|
23223
|
+
const countStatementSequenceSetStateCalls = (statements, context) => {
|
|
23292
23224
|
let fallThroughCount = 0;
|
|
23293
|
-
let
|
|
23225
|
+
let maxTerminatingPathCount = 0;
|
|
23294
23226
|
for (const statement of statements) {
|
|
23295
|
-
|
|
23296
|
-
|
|
23297
|
-
|
|
23298
|
-
const elseSummary = statement.alternate ? analyzeBranchStatements(statement.alternate, context) : {
|
|
23299
|
-
fallThroughCount: 0,
|
|
23300
|
-
maxTerminatedCount: 0,
|
|
23301
|
-
doAllPathsTerminate: false
|
|
23302
|
-
};
|
|
23303
|
-
maxTerminatedCount = Math.max(maxTerminatedCount, fallThroughCount + thenSummary.maxTerminatedCount, fallThroughCount + elseSummary.maxTerminatedCount);
|
|
23304
|
-
if (thenSummary.doAllPathsTerminate && elseSummary.doAllPathsTerminate) return {
|
|
23305
|
-
fallThroughCount: 0,
|
|
23306
|
-
maxTerminatedCount,
|
|
23307
|
-
doAllPathsTerminate: true
|
|
23308
|
-
};
|
|
23309
|
-
const fallThroughBranchCounts = [...thenSummary.doAllPathsTerminate ? [] : [thenSummary.fallThroughCount], ...elseSummary.doAllPathsTerminate ? [] : [elseSummary.fallThroughCount]];
|
|
23310
|
-
fallThroughCount += Math.max(...fallThroughBranchCounts);
|
|
23227
|
+
const guardBranch = isGuardWithTerminatingBranch(statement);
|
|
23228
|
+
if (guardBranch) {
|
|
23229
|
+
maxTerminatingPathCount = Math.max(maxTerminatingPathCount, fallThroughCount + countMaxPathSetStateCalls(guardBranch, context));
|
|
23311
23230
|
continue;
|
|
23312
23231
|
}
|
|
23313
|
-
if (isTerminatingStatement(statement))
|
|
23314
|
-
const terminatedPathCount = fallThroughCount + countMaxPathSetStateCalls(statement, context);
|
|
23315
|
-
return {
|
|
23316
|
-
fallThroughCount: 0,
|
|
23317
|
-
maxTerminatedCount: Math.max(maxTerminatedCount, terminatedPathCount),
|
|
23318
|
-
doAllPathsTerminate: true
|
|
23319
|
-
};
|
|
23320
|
-
}
|
|
23232
|
+
if (isTerminatingStatement(statement)) break;
|
|
23321
23233
|
fallThroughCount += countMaxPathSetStateCalls(statement, context);
|
|
23322
23234
|
}
|
|
23323
|
-
return
|
|
23324
|
-
fallThroughCount,
|
|
23325
|
-
maxTerminatedCount,
|
|
23326
|
-
doAllPathsTerminate: false
|
|
23327
|
-
};
|
|
23328
|
-
};
|
|
23329
|
-
const countStatementSequenceSetStateCalls = (statements, context) => {
|
|
23330
|
-
const summary = analyzeStatementSequence(statements, context);
|
|
23331
|
-
return Math.max(summary.fallThroughCount, summary.maxTerminatedCount);
|
|
23235
|
+
return Math.max(maxTerminatingPathCount, fallThroughCount);
|
|
23332
23236
|
};
|
|
23333
23237
|
const collectLocalHelperFunctions = (root) => {
|
|
23334
23238
|
const helpersByName = /* @__PURE__ */ new Map();
|
|
@@ -23459,9 +23363,7 @@ const isDevOnlyGuardedEffect = (callback) => {
|
|
|
23459
23363
|
if (!body || !isNodeOfType(body, "BlockStatement")) return false;
|
|
23460
23364
|
const firstStatement = (body.body ?? [])[0];
|
|
23461
23365
|
if (!firstStatement) return false;
|
|
23462
|
-
if (!
|
|
23463
|
-
const consequent = firstStatement.consequent;
|
|
23464
|
-
if (!(isTerminatingStatement(consequent) || isNodeOfType(consequent, "BlockStatement") && (consequent.body ?? []).some((inner) => isTerminatingStatement(inner)))) return false;
|
|
23366
|
+
if (!isGuardWithTerminatingBranch(firstStatement)) return false;
|
|
23465
23367
|
return mentionsDevEnvFlag(firstStatement.test);
|
|
23466
23368
|
};
|
|
23467
23369
|
const noCascadingSetState = defineRule({
|
|
@@ -24790,21 +24692,11 @@ const isInitialOnlySetterCall = (callExpr) => {
|
|
|
24790
24692
|
return isInitialOnlyPropName(arg.name);
|
|
24791
24693
|
};
|
|
24792
24694
|
//#endregion
|
|
24793
|
-
//#region src/plugin/utils/is-no-op-statement.ts
|
|
24794
|
-
const isNoOpStatement = (statement) => {
|
|
24795
|
-
if (isNodeOfType(statement, "EmptyStatement")) return true;
|
|
24796
|
-
if (!isNodeOfType(statement, "ExpressionStatement")) return false;
|
|
24797
|
-
const expression = stripParenExpression(statement.expression);
|
|
24798
|
-
if (isNodeOfType(expression, "Literal")) return true;
|
|
24799
|
-
if (isNodeOfType(expression, "Identifier")) return expression.name === "undefined";
|
|
24800
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "void") return isNodeOfType(stripParenExpression(expression.argument), "Literal");
|
|
24801
|
-
return false;
|
|
24802
|
-
};
|
|
24803
|
-
//#endregion
|
|
24804
24695
|
//#region src/plugin/utils/get-callback-statements.ts
|
|
24805
24696
|
const getCallbackStatements = (callback) => {
|
|
24806
24697
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") && !isNodeOfType(callback, "FunctionDeclaration")) return [];
|
|
24807
|
-
|
|
24698
|
+
if (isNodeOfType(callback.body, "BlockStatement")) return callback.body.body ?? [];
|
|
24699
|
+
return callback.body ? [callback.body] : [];
|
|
24808
24700
|
};
|
|
24809
24701
|
//#endregion
|
|
24810
24702
|
//#region src/plugin/rules/state-and-effects/no-derived-state-effect.ts
|
|
@@ -24846,7 +24738,6 @@ const collectValueIdentifierNames = (node, into, localBindingNames = /* @__PURE_
|
|
|
24846
24738
|
const flattenGuardedStatements = (statements) => {
|
|
24847
24739
|
const flattened = [];
|
|
24848
24740
|
for (const statement of statements) {
|
|
24849
|
-
if (isNoOpStatement(statement)) continue;
|
|
24850
24741
|
if (isNodeOfType(statement, "ExpressionStatement")) {
|
|
24851
24742
|
flattened.push(statement);
|
|
24852
24743
|
continue;
|
|
@@ -25531,7 +25422,7 @@ const noDidMountSetState = defineRule({
|
|
|
25531
25422
|
const { mode } = resolveSettings$20(context.settings);
|
|
25532
25423
|
return { CallExpression(node) {
|
|
25533
25424
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
25534
|
-
if (!isNodeOfType(
|
|
25425
|
+
if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
|
|
25535
25426
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
25536
25427
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$2, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
25537
25428
|
if (isMountFlagArgument(node.arguments?.[0])) return;
|
|
@@ -25656,7 +25547,7 @@ const noDidUpdateSetState = defineRule({
|
|
|
25656
25547
|
const { mode } = resolveSettings$19(context.settings);
|
|
25657
25548
|
return { CallExpression(node) {
|
|
25658
25549
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
25659
|
-
if (!isNodeOfType(
|
|
25550
|
+
if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
|
|
25660
25551
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
25661
25552
|
if (!isSetStateCallInLifecycle(node, LIFECYCLE_NAMES$1, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
25662
25553
|
if (isInsideDiffGuard(node)) return;
|
|
@@ -25779,33 +25670,6 @@ const initializerMarksPlainState = (initializerArgument) => {
|
|
|
25779
25670
|
}
|
|
25780
25671
|
return producesPlainStateValue(unwrapped);
|
|
25781
25672
|
};
|
|
25782
|
-
const producesOpaqueInstanceValue = (expression) => {
|
|
25783
|
-
if (isNodeOfType(expression, "NewExpression")) return true;
|
|
25784
|
-
return isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "MemberExpression") && !isPlainDataProducerCall(expression);
|
|
25785
|
-
};
|
|
25786
|
-
const collectSetterValueObservations = (componentBody, setterNames) => {
|
|
25787
|
-
const plainFedSetterNames = /* @__PURE__ */ new Set();
|
|
25788
|
-
const opaqueFedSetterNames = /* @__PURE__ */ new Set();
|
|
25789
|
-
walkAst(componentBody, (node) => {
|
|
25790
|
-
if (!isNodeOfType(node, "CallExpression")) return;
|
|
25791
|
-
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
25792
|
-
const setterName = node.callee.name;
|
|
25793
|
-
if (!setterNames.has(setterName)) return;
|
|
25794
|
-
const argument = node.arguments?.[0];
|
|
25795
|
-
if (!argument) return;
|
|
25796
|
-
const unwrapped = stripParenExpression(argument);
|
|
25797
|
-
if (isNullOrUndefinedExpression(unwrapped)) return;
|
|
25798
|
-
if (producesPlainStateValue(unwrapped)) {
|
|
25799
|
-
plainFedSetterNames.add(setterName);
|
|
25800
|
-
return;
|
|
25801
|
-
}
|
|
25802
|
-
if (producesOpaqueInstanceValue(unwrapped)) opaqueFedSetterNames.add(setterName);
|
|
25803
|
-
});
|
|
25804
|
-
return {
|
|
25805
|
-
plainFedSetterNames,
|
|
25806
|
-
opaqueFedSetterNames
|
|
25807
|
-
};
|
|
25808
|
-
};
|
|
25809
25673
|
const collectCallbackRefSetterNames = (componentBody) => {
|
|
25810
25674
|
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
25811
25675
|
walkAst(componentBody, (node) => {
|
|
@@ -25875,15 +25739,11 @@ const noDirectStateMutation = defineRule({
|
|
|
25875
25739
|
if (bindings.length === 0) return;
|
|
25876
25740
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
25877
25741
|
const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
|
|
25878
|
-
const setterValueObservations = collectSetterValueObservations(componentBody, new Set(bindings.map((binding) => binding.setterName)));
|
|
25879
25742
|
const plainObjectStateValueNames = /* @__PURE__ */ new Set();
|
|
25880
25743
|
for (const binding of bindings) {
|
|
25881
25744
|
if (callbackRefSetterNames.has(binding.setterName)) continue;
|
|
25882
25745
|
if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
|
|
25883
|
-
|
|
25884
|
-
if (!initializerMarksPlainState(initializerArgument)) continue;
|
|
25885
|
-
if ((!initializerArgument || isNullOrUndefinedExpression(stripParenExpression(initializerArgument))) && setterValueObservations.opaqueFedSetterNames.has(binding.setterName) && !setterValueObservations.plainFedSetterNames.has(binding.setterName)) continue;
|
|
25886
|
-
plainObjectStateValueNames.add(binding.valueName);
|
|
25746
|
+
if (initializerMarksPlainState(binding.declarator.init.arguments?.[0])) plainObjectStateValueNames.add(binding.valueName);
|
|
25887
25747
|
}
|
|
25888
25748
|
const visitMutationCandidate = (child, currentlyShadowed) => {
|
|
25889
25749
|
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
@@ -26008,10 +25868,9 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
26008
25868
|
create: (context) => ({ CallExpression(node) {
|
|
26009
25869
|
const callee = node.callee;
|
|
26010
25870
|
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
26011
|
-
|
|
26012
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
|
|
25871
|
+
if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
|
|
26013
25872
|
if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "startViewTransition") return;
|
|
26014
|
-
if (context.scopes.symbolFor(
|
|
25873
|
+
if (context.scopes.symbolFor(callee.object) !== null) return;
|
|
26015
25874
|
if (!importsReactViewTransition(node)) return;
|
|
26016
25875
|
context.report({
|
|
26017
25876
|
node,
|
|
@@ -26031,8 +25890,7 @@ const noDocumentWrite = defineRule({
|
|
|
26031
25890
|
create: (context) => ({ CallExpression(node) {
|
|
26032
25891
|
const callee = node.callee;
|
|
26033
25892
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
26034
|
-
|
|
26035
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
|
|
25893
|
+
if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "document") return;
|
|
26036
25894
|
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
26037
25895
|
context.report({
|
|
26038
25896
|
node,
|
|
@@ -26664,6 +26522,13 @@ const noEffectEventHandler = defineRule({
|
|
|
26664
26522
|
}
|
|
26665
26523
|
});
|
|
26666
26524
|
//#endregion
|
|
26525
|
+
//#region src/plugin/utils/is-imported-from-non-react-module.ts
|
|
26526
|
+
const isImportedFromNonReactModule = (contextNode, localIdentifierName) => {
|
|
26527
|
+
const importSource = getImportSourceForName(contextNode, localIdentifierName);
|
|
26528
|
+
if (importSource === null) return false;
|
|
26529
|
+
return !REACT_RUNTIME_MODULE_SOURCES.has(importSource);
|
|
26530
|
+
};
|
|
26531
|
+
//#endregion
|
|
26667
26532
|
//#region src/plugin/rules/state-and-effects/no-effect-event-in-deps.ts
|
|
26668
26533
|
const createComponentBindingStackTracker = (callbacks) => {
|
|
26669
26534
|
const componentBindingStack = [];
|
|
@@ -26717,7 +26582,11 @@ const noEffectEventInDeps = defineRule({
|
|
|
26717
26582
|
const initializer = declaratorNode.init;
|
|
26718
26583
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
|
|
26719
26584
|
if (!isHookCall$2(initializer, "useEffectEvent")) return;
|
|
26720
|
-
if (
|
|
26585
|
+
if (isNodeOfType(initializer.callee, "Identifier")) {
|
|
26586
|
+
if (isImportedFromNonReactModule(declaratorNode, initializer.callee.name)) return;
|
|
26587
|
+
const calleeSymbol = context.scopes.referenceFor(initializer.callee)?.resolvedSymbol;
|
|
26588
|
+
if (calleeSymbol && calleeSymbol.kind !== "import") return;
|
|
26589
|
+
}
|
|
26721
26590
|
componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
|
|
26722
26591
|
} });
|
|
26723
26592
|
return {
|
|
@@ -29297,7 +29166,7 @@ const noIsMounted = defineRule({
|
|
|
29297
29166
|
recommendation: "`isMounted` doesn't work in modern React. Track mount state with a ref, or cancel the async work instead.",
|
|
29298
29167
|
create: (context) => ({ CallExpression(node) {
|
|
29299
29168
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
29300
|
-
if (!isNodeOfType(
|
|
29169
|
+
if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
|
|
29301
29170
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "isMounted") return;
|
|
29302
29171
|
if (!getParentComponent(node)) return;
|
|
29303
29172
|
context.report({
|
|
@@ -29312,9 +29181,7 @@ const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializin
|
|
|
29312
29181
|
const isJsonMethodCall = (node, method) => {
|
|
29313
29182
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
29314
29183
|
const callee = node.callee;
|
|
29315
|
-
|
|
29316
|
-
const receiver = stripParenExpression(callee.object);
|
|
29317
|
-
return isNodeOfType(receiver, "Identifier") && receiver.name === "JSON" && isNodeOfType(callee.property, "Identifier") && callee.property.name === method;
|
|
29184
|
+
return isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && callee.object.name === "JSON" && isNodeOfType(callee.property, "Identifier") && callee.property.name === method;
|
|
29318
29185
|
};
|
|
29319
29186
|
const SNAPSHOT_FUNCTION_NAME_PATTERN = /snapshot|serializ|tojson/i;
|
|
29320
29187
|
const NORMALIZATION_BINDING_NAME_PATTERN = /normali[sz]/i;
|
|
@@ -29725,378 +29592,6 @@ const noLegacyContextApi = defineRule({
|
|
|
29725
29592
|
}
|
|
29726
29593
|
});
|
|
29727
29594
|
//#endregion
|
|
29728
|
-
//#region src/plugin/utils/executes-during-render.ts
|
|
29729
|
-
const SYNCHRONOUS_ITERATION_METHOD_NAMES = new Set([
|
|
29730
|
-
"map",
|
|
29731
|
-
"filter",
|
|
29732
|
-
"forEach",
|
|
29733
|
-
"flatMap",
|
|
29734
|
-
"reduce",
|
|
29735
|
-
"reduceRight",
|
|
29736
|
-
"some",
|
|
29737
|
-
"every",
|
|
29738
|
-
"find",
|
|
29739
|
-
"findIndex",
|
|
29740
|
-
"findLast",
|
|
29741
|
-
"findLastIndex",
|
|
29742
|
-
"sort",
|
|
29743
|
-
"toSorted"
|
|
29744
|
-
]);
|
|
29745
|
-
const executesDuringRender = (functionNode) => {
|
|
29746
|
-
const parent = functionNode.parent;
|
|
29747
|
-
if (!isNodeOfType(parent, "CallExpression")) return false;
|
|
29748
|
-
if (parent.callee === functionNode) return true;
|
|
29749
|
-
if (isHookCall$2(parent, "useMemo") && parent.arguments?.[0] === functionNode) return true;
|
|
29750
|
-
return isNodeOfType(parent.callee, "MemberExpression") && !parent.callee.computed && isNodeOfType(parent.callee.property, "Identifier") && SYNCHRONOUS_ITERATION_METHOD_NAMES.has(parent.callee.property.name) && parent.arguments?.[0] === functionNode;
|
|
29751
|
-
};
|
|
29752
|
-
//#endregion
|
|
29753
|
-
//#region src/plugin/utils/has-suppress-hydration-warning-attribute.ts
|
|
29754
|
-
const hasSuppressHydrationWarningAttribute = (openingElement) => {
|
|
29755
|
-
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
29756
|
-
for (const attr of openingElement.attributes ?? []) if (isNodeOfType(attr, "JSXAttribute") && isNodeOfType(attr.name, "JSXIdentifier") && attr.name.name === "suppressHydrationWarning") return true;
|
|
29757
|
-
return false;
|
|
29758
|
-
};
|
|
29759
|
-
//#endregion
|
|
29760
|
-
//#region src/plugin/utils/find-declarator-for-binding.ts
|
|
29761
|
-
const findDeclaratorForBinding = (bindingIdentifier) => {
|
|
29762
|
-
let ancestor = bindingIdentifier.parent;
|
|
29763
|
-
while (ancestor) {
|
|
29764
|
-
if (isNodeOfType(ancestor, "VariableDeclarator")) return ancestor;
|
|
29765
|
-
if (isNodeOfType(ancestor, "FunctionDeclaration") || isNodeOfType(ancestor, "FunctionExpression") || isNodeOfType(ancestor, "ArrowFunctionExpression") || isNodeOfType(ancestor, "Program")) return null;
|
|
29766
|
-
ancestor = ancestor.parent ?? null;
|
|
29767
|
-
}
|
|
29768
|
-
return null;
|
|
29769
|
-
};
|
|
29770
|
-
//#endregion
|
|
29771
|
-
//#region src/plugin/utils/references-falsy-initial-state.ts
|
|
29772
|
-
const isFalsyLiteral = (node) => {
|
|
29773
|
-
if (!node) return true;
|
|
29774
|
-
if (isNodeOfType(node, "Literal")) return !node.value;
|
|
29775
|
-
return isNodeOfType(node, "Identifier") && node.name === "undefined";
|
|
29776
|
-
};
|
|
29777
|
-
const isFalsyInitialStateBinding = (identifier) => {
|
|
29778
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
29779
|
-
const binding = findVariableInitializer(identifier, identifier.name);
|
|
29780
|
-
if (!binding) return false;
|
|
29781
|
-
const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
|
|
29782
|
-
if (!declarator?.init) return false;
|
|
29783
|
-
const init = stripParenExpression(declarator.init);
|
|
29784
|
-
if (!isHookCall$2(init, "useState") || !isNodeOfType(init, "CallExpression")) return false;
|
|
29785
|
-
if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
|
|
29786
|
-
if (declarator.id.elements?.[0] !== binding.bindingIdentifier) return false;
|
|
29787
|
-
return isFalsyLiteral(init.arguments?.[0]);
|
|
29788
|
-
};
|
|
29789
|
-
const referencesFalsyInitialState = (expression) => flattenLogicalAndChain(stripParenExpression(expression)).some((operand) => isFalsyInitialStateBinding(stripParenExpression(operand)));
|
|
29790
|
-
//#endregion
|
|
29791
|
-
//#region src/plugin/utils/is-gated-by-falsy-initial-state.ts
|
|
29792
|
-
const isGatedByFalsyInitialState = (node) => {
|
|
29793
|
-
let cursor = node;
|
|
29794
|
-
let parent = node.parent;
|
|
29795
|
-
while (parent) {
|
|
29796
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesFalsyInitialState(parent.left)) return true;
|
|
29797
|
-
if (isNodeOfType(parent, "ConditionalExpression") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
|
|
29798
|
-
if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
|
|
29799
|
-
cursor = parent;
|
|
29800
|
-
parent = parent.parent ?? null;
|
|
29801
|
-
}
|
|
29802
|
-
return false;
|
|
29803
|
-
};
|
|
29804
|
-
//#endregion
|
|
29805
|
-
//#region src/plugin/utils/references-client-only-flag.ts
|
|
29806
|
-
const CLIENT_ONLY_FLAG_NAME_PATTERN = /^(?:is|has|did)?_?(?:client|mounted|hydrated|browser)(?:_?(?:side|ready|only))?$/i;
|
|
29807
|
-
const referencesClientOnlyFlag = (expression) => {
|
|
29808
|
-
const unwrapped = stripParenExpression(expression);
|
|
29809
|
-
if (isNodeOfType(unwrapped, "Identifier")) return CLIENT_ONLY_FLAG_NAME_PATTERN.test(unwrapped.name);
|
|
29810
|
-
if (isNodeOfType(unwrapped, "MemberExpression")) {
|
|
29811
|
-
const property = unwrapped.property;
|
|
29812
|
-
return isNodeOfType(property, "Identifier") && CLIENT_ONLY_FLAG_NAME_PATTERN.test(property.name);
|
|
29813
|
-
}
|
|
29814
|
-
if (isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "!") return referencesClientOnlyFlag(unwrapped.argument);
|
|
29815
|
-
if (isNodeOfType(unwrapped, "LogicalExpression")) return referencesClientOnlyFlag(unwrapped.left) || referencesClientOnlyFlag(unwrapped.right);
|
|
29816
|
-
return false;
|
|
29817
|
-
};
|
|
29818
|
-
//#endregion
|
|
29819
|
-
//#region src/plugin/utils/is-inside-client-only-guard.ts
|
|
29820
|
-
const isInsideClientOnlyGuard = (node) => {
|
|
29821
|
-
let cursor = node;
|
|
29822
|
-
let parent = node.parent;
|
|
29823
|
-
while (parent) {
|
|
29824
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesClientOnlyFlag(parent.left)) return true;
|
|
29825
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === cursor || parent.alternate === cursor) && referencesClientOnlyFlag(parent.test)) return true;
|
|
29826
|
-
if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesClientOnlyFlag(parent.test)) return true;
|
|
29827
|
-
cursor = parent;
|
|
29828
|
-
parent = parent.parent ?? null;
|
|
29829
|
-
}
|
|
29830
|
-
return false;
|
|
29831
|
-
};
|
|
29832
|
-
//#endregion
|
|
29833
|
-
//#region src/plugin/rules/performance/no-locale-format-in-render.ts
|
|
29834
|
-
const LOCALE_FORMAT_METHOD_NAMES = new Set([
|
|
29835
|
-
"toLocaleString",
|
|
29836
|
-
"toLocaleDateString",
|
|
29837
|
-
"toLocaleTimeString"
|
|
29838
|
-
]);
|
|
29839
|
-
const DATE_ONLY_LOCALE_METHOD_NAMES = new Set(["toLocaleDateString", "toLocaleTimeString"]);
|
|
29840
|
-
const INTL_FORMATTER_NAMES = new Set(["DateTimeFormat", "RelativeTimeFormat"]);
|
|
29841
|
-
const INTL_FORMAT_METHOD_NAMES = new Set([
|
|
29842
|
-
"format",
|
|
29843
|
-
"formatToParts",
|
|
29844
|
-
"formatRange"
|
|
29845
|
-
]);
|
|
29846
|
-
const isProvableDateExpression = (expression) => {
|
|
29847
|
-
if (!expression) return false;
|
|
29848
|
-
const unwrapped = stripParenExpression(expression);
|
|
29849
|
-
return isNodeOfType(unwrapped, "NewExpression") && isNodeOfType(unwrapped.callee, "Identifier") && unwrapped.callee.name === "Date";
|
|
29850
|
-
};
|
|
29851
|
-
const DATE_FLAVORED_NAME_PATTERN = /(date|time|timestamp|deadline|created|updated|scheduled|expire|moment|when|birthday|dob)|(at)$/i;
|
|
29852
|
-
const receiverNameLooksDateFlavored = (expression) => {
|
|
29853
|
-
if (!expression) return false;
|
|
29854
|
-
const unwrapped = stripParenExpression(expression);
|
|
29855
|
-
if (isNodeOfType(unwrapped, "Identifier")) return DATE_FLAVORED_NAME_PATTERN.test(unwrapped.name);
|
|
29856
|
-
if (isNodeOfType(unwrapped, "MemberExpression") && !unwrapped.computed) return isNodeOfType(unwrapped.property, "Identifier") && DATE_FLAVORED_NAME_PATTERN.test(unwrapped.property.name);
|
|
29857
|
-
if (isNodeOfType(unwrapped, "CallExpression")) return receiverNameLooksDateFlavored(unwrapped.callee);
|
|
29858
|
-
return false;
|
|
29859
|
-
};
|
|
29860
|
-
const objectLiteralHasProperty = (objectExpression, propertyName) => {
|
|
29861
|
-
if (!objectExpression) return false;
|
|
29862
|
-
const unwrapped = stripParenExpression(objectExpression);
|
|
29863
|
-
if (!isNodeOfType(unwrapped, "ObjectExpression")) return false;
|
|
29864
|
-
for (const property of unwrapped.properties ?? []) {
|
|
29865
|
-
if (!isNodeOfType(property, "Property")) continue;
|
|
29866
|
-
if (property.computed) continue;
|
|
29867
|
-
if (isNodeOfType(property.key, "Identifier") && property.key.name === propertyName) return true;
|
|
29868
|
-
if (isNodeOfType(property.key, "Literal") && property.key.value === propertyName) return true;
|
|
29869
|
-
}
|
|
29870
|
-
return false;
|
|
29871
|
-
};
|
|
29872
|
-
const hasExplicitLocaleArgument = (argument) => {
|
|
29873
|
-
if (!argument) return false;
|
|
29874
|
-
const unwrapped = stripParenExpression(argument);
|
|
29875
|
-
if (isNodeOfType(unwrapped, "Identifier") && unwrapped.name === "undefined") return false;
|
|
29876
|
-
return true;
|
|
29877
|
-
};
|
|
29878
|
-
const isDeterministicLocaleMethodCall = (call, methodName, receiverIsProvablyDate) => {
|
|
29879
|
-
const localeArgument = call.arguments?.[0];
|
|
29880
|
-
if (!hasExplicitLocaleArgument(localeArgument)) return false;
|
|
29881
|
-
const optionsArgument = call.arguments?.[1];
|
|
29882
|
-
if (objectLiteralHasProperty(optionsArgument, "timeZone")) return true;
|
|
29883
|
-
return !DATE_ONLY_LOCALE_METHOD_NAMES.has(methodName) && !receiverIsProvablyDate;
|
|
29884
|
-
};
|
|
29885
|
-
const matchLocaleMethodCall = (call) => {
|
|
29886
|
-
const callee = call.callee;
|
|
29887
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
|
|
29888
|
-
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
29889
|
-
const methodName = callee.property.name;
|
|
29890
|
-
if (!LOCALE_FORMAT_METHOD_NAMES.has(methodName)) return null;
|
|
29891
|
-
const receiverIsProvablyDate = isProvableDateExpression(callee.object);
|
|
29892
|
-
if (methodName === "toLocaleString" && !receiverIsProvablyDate && !receiverNameLooksDateFlavored(callee.object)) return null;
|
|
29893
|
-
if (isDeterministicLocaleMethodCall(call, methodName, receiverIsProvablyDate)) return null;
|
|
29894
|
-
return {
|
|
29895
|
-
node: call,
|
|
29896
|
-
display: `${methodName}()`
|
|
29897
|
-
};
|
|
29898
|
-
};
|
|
29899
|
-
const getIntlFormatterName = (expression) => {
|
|
29900
|
-
if (!expression) return null;
|
|
29901
|
-
const unwrapped = stripParenExpression(expression);
|
|
29902
|
-
if (!isNodeOfType(unwrapped, "CallExpression") && !isNodeOfType(unwrapped, "NewExpression")) return null;
|
|
29903
|
-
const callee = unwrapped.callee;
|
|
29904
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
|
|
29905
|
-
if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== "Intl") return null;
|
|
29906
|
-
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
29907
|
-
return INTL_FORMATTER_NAMES.has(callee.property.name) ? callee.property.name : null;
|
|
29908
|
-
};
|
|
29909
|
-
const isDeterministicIntlConstruction = (construction, formatterName) => {
|
|
29910
|
-
if (!isNodeOfType(construction, "CallExpression") && !isNodeOfType(construction, "NewExpression")) return false;
|
|
29911
|
-
if (!hasExplicitLocaleArgument(construction.arguments?.[0])) return false;
|
|
29912
|
-
if (formatterName !== "DateTimeFormat") return true;
|
|
29913
|
-
return objectLiteralHasProperty(construction.arguments?.[1], "timeZone");
|
|
29914
|
-
};
|
|
29915
|
-
const matchIntlFormatCall = (call) => {
|
|
29916
|
-
const callee = call.callee;
|
|
29917
|
-
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return null;
|
|
29918
|
-
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
29919
|
-
if (!INTL_FORMAT_METHOD_NAMES.has(callee.property.name)) return null;
|
|
29920
|
-
let construction = stripParenExpression(callee.object);
|
|
29921
|
-
if (isNodeOfType(construction, "Identifier")) {
|
|
29922
|
-
const binding = findVariableInitializer(construction, construction.name);
|
|
29923
|
-
construction = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
29924
|
-
}
|
|
29925
|
-
if (!construction) return null;
|
|
29926
|
-
const formatterName = getIntlFormatterName(construction);
|
|
29927
|
-
if (!formatterName) return null;
|
|
29928
|
-
if (isDeterministicIntlConstruction(construction, formatterName)) return null;
|
|
29929
|
-
return {
|
|
29930
|
-
node: call,
|
|
29931
|
-
display: `Intl.${formatterName}().${callee.property.name}()`
|
|
29932
|
-
};
|
|
29933
|
-
};
|
|
29934
|
-
const isDeterministicInputDateConstruction = (expression) => {
|
|
29935
|
-
if (!expression) return false;
|
|
29936
|
-
const unwrapped = stripParenExpression(expression);
|
|
29937
|
-
if (!isNodeOfType(unwrapped, "NewExpression")) return false;
|
|
29938
|
-
if (!isNodeOfType(unwrapped.callee, "Identifier") || unwrapped.callee.name !== "Date") return false;
|
|
29939
|
-
return (unwrapped.arguments?.length ?? 0) > 0;
|
|
29940
|
-
};
|
|
29941
|
-
const matchDateDefaultStringification = (node) => {
|
|
29942
|
-
if (isNodeOfType(node, "CallExpression")) {
|
|
29943
|
-
const callee = node.callee;
|
|
29944
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "toString" && isDeterministicInputDateConstruction(callee.object)) return {
|
|
29945
|
-
node,
|
|
29946
|
-
display: "Date.prototype.toString()"
|
|
29947
|
-
};
|
|
29948
|
-
if (isNodeOfType(callee, "Identifier") && callee.name === "String" && isDeterministicInputDateConstruction(node.arguments?.[0])) return {
|
|
29949
|
-
node,
|
|
29950
|
-
display: "String(new Date(…))"
|
|
29951
|
-
};
|
|
29952
|
-
return null;
|
|
29953
|
-
}
|
|
29954
|
-
if (isNodeOfType(node, "TemplateLiteral")) {
|
|
29955
|
-
for (const expression of node.expressions ?? []) if (isDeterministicInputDateConstruction(expression)) return {
|
|
29956
|
-
node: expression,
|
|
29957
|
-
display: "`${new Date(…)}`"
|
|
29958
|
-
};
|
|
29959
|
-
}
|
|
29960
|
-
return null;
|
|
29961
|
-
};
|
|
29962
|
-
const findRenderPhaseComponentOrHook = (node) => {
|
|
29963
|
-
let functionNode = findEnclosingFunction(node);
|
|
29964
|
-
while (functionNode) {
|
|
29965
|
-
if (componentOrHookDisplayNameForFunction(functionNode)) return functionNode;
|
|
29966
|
-
if (!executesDuringRender(functionNode)) return null;
|
|
29967
|
-
functionNode = findEnclosingFunction(functionNode);
|
|
29968
|
-
}
|
|
29969
|
-
return null;
|
|
29970
|
-
};
|
|
29971
|
-
const hasClientRenderEvidence = (componentOrHookNode, fileHasUseClientDirective) => {
|
|
29972
|
-
if (fileHasUseClientDirective) return true;
|
|
29973
|
-
const displayName = componentOrHookDisplayNameForFunction(componentOrHookNode);
|
|
29974
|
-
if (displayName && isReactHookName(displayName)) return true;
|
|
29975
|
-
let callsHook = false;
|
|
29976
|
-
walkAst((isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null) ?? componentOrHookNode, (child) => {
|
|
29977
|
-
if (callsHook) return false;
|
|
29978
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && isReactHookName(child.callee.name)) {
|
|
29979
|
-
callsHook = true;
|
|
29980
|
-
return false;
|
|
29981
|
-
}
|
|
29982
|
-
});
|
|
29983
|
-
return callsHook;
|
|
29984
|
-
};
|
|
29985
|
-
const isAfterClientOnlyEarlyReturn = (node, componentOrHookNode) => {
|
|
29986
|
-
const body = isFunctionLike$1(componentOrHookNode) ? componentOrHookNode.body : null;
|
|
29987
|
-
if (!isNodeOfType(body, "BlockStatement")) return false;
|
|
29988
|
-
const ancestors = /* @__PURE__ */ new Set();
|
|
29989
|
-
let cursor = node;
|
|
29990
|
-
while (cursor) {
|
|
29991
|
-
ancestors.add(cursor);
|
|
29992
|
-
cursor = cursor.parent ?? null;
|
|
29993
|
-
}
|
|
29994
|
-
for (const statement of body.body ?? []) {
|
|
29995
|
-
if (ancestors.has(statement)) return false;
|
|
29996
|
-
if (!isNodeOfType(statement, "IfStatement")) continue;
|
|
29997
|
-
if (!referencesClientOnlyFlag(statement.test) && !referencesFalsyInitialState(statement.test)) continue;
|
|
29998
|
-
let returnsEarly = false;
|
|
29999
|
-
walkAst(statement.consequent, (child) => {
|
|
30000
|
-
if (isFunctionLike$1(child)) return false;
|
|
30001
|
-
if (isNodeOfType(child, "ReturnStatement")) {
|
|
30002
|
-
returnsEarly = true;
|
|
30003
|
-
return false;
|
|
30004
|
-
}
|
|
30005
|
-
});
|
|
30006
|
-
if (returnsEarly) return true;
|
|
30007
|
-
}
|
|
30008
|
-
return false;
|
|
30009
|
-
};
|
|
30010
|
-
const findEnclosingJsxOpeningElement = (node) => {
|
|
30011
|
-
let cursor = node.parent;
|
|
30012
|
-
while (cursor) {
|
|
30013
|
-
if (isNodeOfType(cursor, "JSXElement")) return cursor.openingElement;
|
|
30014
|
-
if (isNodeOfType(cursor, "JSXFragment")) return null;
|
|
30015
|
-
cursor = cursor.parent ?? null;
|
|
30016
|
-
}
|
|
30017
|
-
return null;
|
|
30018
|
-
};
|
|
30019
|
-
const noLocaleFormatInRender = defineRule({
|
|
30020
|
-
id: "no-locale-format-in-render",
|
|
30021
|
-
title: "Locale/timezone formatting during render",
|
|
30022
|
-
severity: "warn",
|
|
30023
|
-
category: "Correctness",
|
|
30024
|
-
disabledWhen: [
|
|
30025
|
-
"vite",
|
|
30026
|
-
"cra",
|
|
30027
|
-
"expo",
|
|
30028
|
-
"react-native",
|
|
30029
|
-
"unknown"
|
|
30030
|
-
],
|
|
30031
|
-
recommendation: "Format locale/timezone-dependent values in a post-mount useEffect + state, or pass an explicit locale and timeZone so the server and the browser render the same text. Only runs on SSR-capable projects.",
|
|
30032
|
-
create: (context) => {
|
|
30033
|
-
if (isTestlikeFilename(context.filename)) return {};
|
|
30034
|
-
if (classifyReactNativeFileTarget(context) === "react-native") return {};
|
|
30035
|
-
let fileHasUseClientDirective = false;
|
|
30036
|
-
let fileIsEmailTemplate = false;
|
|
30037
|
-
const reportedNodes = /* @__PURE__ */ new Set();
|
|
30038
|
-
const reportIfRenderPhase = (match) => {
|
|
30039
|
-
if (reportedNodes.has(match.node)) return;
|
|
30040
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(match.node);
|
|
30041
|
-
if (!componentOrHookNode) return;
|
|
30042
|
-
if (fileIsEmailTemplate) return;
|
|
30043
|
-
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
30044
|
-
if (isInsideClientOnlyGuard(match.node)) return;
|
|
30045
|
-
if (isGatedByFalsyInitialState(match.node)) return;
|
|
30046
|
-
if (isAfterClientOnlyEarlyReturn(match.node, componentOrHookNode)) return;
|
|
30047
|
-
if (hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(match.node))) return;
|
|
30048
|
-
if (isGeneratedImageRenderContext(context, findEnclosingJsxOpeningElement(match.node)?.parent ?? match.node)) return;
|
|
30049
|
-
reportedNodes.add(match.node);
|
|
30050
|
-
context.report({
|
|
30051
|
-
node: match.node,
|
|
30052
|
-
message: `This can cause a hydration mismatch because ${match.display} formats with the server's locale and timezone during server rendering but the user's in the browser. Format it in a post-mount useEffect, or pass an explicit locale and timeZone.`
|
|
30053
|
-
});
|
|
30054
|
-
};
|
|
30055
|
-
return {
|
|
30056
|
-
Program(node) {
|
|
30057
|
-
fileHasUseClientDirective = hasDirective(node, "use client");
|
|
30058
|
-
fileIsEmailTemplate = hasEmailTemplateImport(node);
|
|
30059
|
-
},
|
|
30060
|
-
CallExpression(node) {
|
|
30061
|
-
const match = matchLocaleMethodCall(node) ?? matchIntlFormatCall(node) ?? matchDateDefaultStringification(node);
|
|
30062
|
-
if (match) reportIfRenderPhase(match);
|
|
30063
|
-
},
|
|
30064
|
-
TemplateLiteral(node) {
|
|
30065
|
-
const match = matchDateDefaultStringification(node);
|
|
30066
|
-
if (match) reportIfRenderPhase(match);
|
|
30067
|
-
},
|
|
30068
|
-
JSXExpressionContainer(node) {
|
|
30069
|
-
const expression = stripParenExpression(node.expression);
|
|
30070
|
-
if (!isNodeOfType(expression, "CallExpression")) return;
|
|
30071
|
-
if (!isNodeOfType(expression.callee, "Identifier")) return;
|
|
30072
|
-
const helperName = expression.callee.name;
|
|
30073
|
-
const componentOrHookNode = findRenderPhaseComponentOrHook(node);
|
|
30074
|
-
if (!componentOrHookNode) return;
|
|
30075
|
-
const helperNode = findVariableInitializer(expression.callee, helperName)?.initializer;
|
|
30076
|
-
if (!helperNode || !isFunctionLike$1(helperNode)) return;
|
|
30077
|
-
if (componentOrHookDisplayNameForFunction(helperNode)) return;
|
|
30078
|
-
walkAst(helperNode.body ?? helperNode, (child) => {
|
|
30079
|
-
if (isFunctionLike$1(child)) return false;
|
|
30080
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
30081
|
-
const match = matchLocaleMethodCall(child) ?? matchIntlFormatCall(child);
|
|
30082
|
-
if (!match || reportedNodes.has(match.node)) return;
|
|
30083
|
-
if (fileIsEmailTemplate) return;
|
|
30084
|
-
if (!hasClientRenderEvidence(componentOrHookNode, fileHasUseClientDirective)) return;
|
|
30085
|
-
if (isInsideClientOnlyGuard(node)) return;
|
|
30086
|
-
if (isGatedByFalsyInitialState(node)) return;
|
|
30087
|
-
if (isAfterClientOnlyEarlyReturn(node, componentOrHookNode)) return;
|
|
30088
|
-
if (hasSuppressHydrationWarningAttribute(findEnclosingJsxOpeningElement(node))) return;
|
|
30089
|
-
reportedNodes.add(match.node);
|
|
30090
|
-
context.report({
|
|
30091
|
-
node: match.node,
|
|
30092
|
-
message: `This can cause a hydration mismatch because ${match.display} (reached from JSX through "${helperName}") formats with the server's locale and timezone during server rendering but the user's in the browser. Format it in a post-mount useEffect, or pass an explicit locale and timeZone.`
|
|
30093
|
-
});
|
|
30094
|
-
});
|
|
30095
|
-
}
|
|
30096
|
-
};
|
|
30097
|
-
}
|
|
30098
|
-
});
|
|
30099
|
-
//#endregion
|
|
30100
29595
|
//#region src/plugin/rules/design/no-long-transition-duration.ts
|
|
30101
29596
|
const hasInfiniteIterationCount = (properties) => properties.some((property) => {
|
|
30102
29597
|
if (getStylePropertyKey(property) !== "animationIterationCount") return false;
|
|
@@ -32209,12 +31704,12 @@ const noPassDataToParent = defineRule({
|
|
|
32209
31704
|
if (calleeNode === identifier) {
|
|
32210
31705
|
if (!isDirectParentCallbackRef(analysis, ref)) continue;
|
|
32211
31706
|
if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
|
|
32212
|
-
} else if (isNodeOfType(calleeNode, "MemberExpression") &&
|
|
31707
|
+
} else if (isNodeOfType(calleeNode, "MemberExpression") && unwrapChainExpression(calleeNode.object) === identifier) {
|
|
32213
31708
|
if (!isWholePropsObjectReference(analysis, ref)) continue;
|
|
32214
31709
|
if (isCustomHookParameter(ref)) continue;
|
|
32215
31710
|
} else continue;
|
|
32216
31711
|
const methodName = getCallMethodName(calleeNode);
|
|
32217
|
-
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") &&
|
|
31712
|
+
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && isNodeOfType(calleeNode, "MemberExpression") && calleeNode.object === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
32218
31713
|
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
32219
31714
|
if (methodName && COMMAND_PROP_NAME_PATTERN.test(methodName)) continue;
|
|
32220
31715
|
if (isNamespacedApiCallee(calleeNode)) continue;
|
|
@@ -32405,7 +31900,7 @@ const noPassLiveStateToParent = defineRule({
|
|
|
32405
31900
|
if (isCallResultConsumedAsArgument(callExpr)) continue;
|
|
32406
31901
|
const calleeNode = callExpr.callee;
|
|
32407
31902
|
const methodName = calleeNode ? getCallMethodName(calleeNode) : null;
|
|
32408
|
-
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") &&
|
|
31903
|
+
const isPropCallbackNamedLikeStringRead = Boolean(methodName && STRING_READ_METHOD_NAMES.has(methodName) && calleeNode && isNodeOfType(calleeNode, "MemberExpression") && calleeNode.object === ref.identifier && isWholePropsObjectReference(analysis, ref));
|
|
32409
31904
|
if (methodName && DATA_SINK_METHOD_NAMES.has(methodName) && !isPropCallbackNamedLikeStringRead) continue;
|
|
32410
31905
|
if (calleeNode && isNamespacedApiCallee(calleeNode)) continue;
|
|
32411
31906
|
const stateArgRefs = collectPropCallbackBoundStateRefs(analysis, ref, (innerRef) => isParentNotificationCallbackRef(analysis, innerRef));
|
|
@@ -32939,7 +32434,7 @@ const isAlwaysFreshExpression = (expression) => {
|
|
|
32939
32434
|
return `${callee.name}()`;
|
|
32940
32435
|
}
|
|
32941
32436
|
if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
|
|
32942
|
-
const receiver =
|
|
32437
|
+
const receiver = callee.object;
|
|
32943
32438
|
const property = callee.property;
|
|
32944
32439
|
if (!isNodeOfType(property, "Identifier")) return null;
|
|
32945
32440
|
if (isNodeOfType(receiver, "Identifier")) {
|
|
@@ -33060,10 +32555,8 @@ const createDeprecatedReactImportRule = ({ source, messages, handleExtraSource }
|
|
|
33060
32555
|
if (typeof sourceValue !== "string") return;
|
|
33061
32556
|
if (handleExtraSource?.(node, context)) return;
|
|
33062
32557
|
if (sourceValue !== source) return;
|
|
33063
|
-
if (isTypeOnlyImport(node)) return;
|
|
33064
32558
|
for (const specifier of node.specifiers ?? []) {
|
|
33065
32559
|
if (isNodeOfType(specifier, "ImportSpecifier")) {
|
|
33066
|
-
if (specifier.importKind === "type") continue;
|
|
33067
32560
|
const importedName = getImportedName$1(specifier);
|
|
33068
32561
|
if (!importedName) continue;
|
|
33069
32562
|
const message = messages.get(importedName);
|
|
@@ -33082,9 +32575,8 @@ const createDeprecatedReactImportRule = ({ source, messages, handleExtraSource }
|
|
|
33082
32575
|
MemberExpression(node) {
|
|
33083
32576
|
if (namespaceBindings.size === 0) return;
|
|
33084
32577
|
if (node.computed) return;
|
|
33085
|
-
|
|
33086
|
-
if (!
|
|
33087
|
-
if (!namespaceBindings.has(receiver.name)) return;
|
|
32578
|
+
if (!isNodeOfType(node.object, "Identifier")) return;
|
|
32579
|
+
if (!namespaceBindings.has(node.object.name)) return;
|
|
33088
32580
|
if (!isNodeOfType(node.property, "Identifier")) return;
|
|
33089
32581
|
const message = messages.get(node.property.name);
|
|
33090
32582
|
if (message) context.report({
|
|
@@ -33149,7 +32641,7 @@ const noReactDomDeprecatedApis = defineRule({
|
|
|
33149
32641
|
});
|
|
33150
32642
|
//#endregion
|
|
33151
32643
|
//#region src/plugin/rules/architecture/no-react19-deprecated-apis.ts
|
|
33152
|
-
const REACT_19_DEPRECATED_MESSAGES = new Map([["forwardRef", "forwardRef is dead weight in React 19, since ref is a normal prop now, so drop it & pass ref straight through."]]);
|
|
32644
|
+
const REACT_19_DEPRECATED_MESSAGES = new Map([["forwardRef", "forwardRef is dead weight in React 19, since ref is a normal prop now, so drop it & pass ref straight through."], ["useContext", "useContext is replaced by `use()` in React 19, which reads context inside ifs & loops too, so switch to `import { use } from 'react'`."]]);
|
|
33153
32645
|
const isVendoredShadcnUiFilename = (rawFilename) => {
|
|
33154
32646
|
if (!rawFilename) return false;
|
|
33155
32647
|
const filename = rawFilename.replaceAll("\\", "/");
|
|
@@ -33187,7 +32679,7 @@ const noReact19DeprecatedApis = defineRule({
|
|
|
33187
32679
|
requires: ["react:19"],
|
|
33188
32680
|
tags: ["test-noise", "migration-hint"],
|
|
33189
32681
|
severity: "warn",
|
|
33190
|
-
recommendation: "Pass `ref` as a normal prop on function components, since `forwardRef` isn't needed in React 19. Only runs on React 19+ projects.",
|
|
32682
|
+
recommendation: "Pass `ref` as a normal prop on function components, since `forwardRef` isn't needed in React 19. Replace `useContext(X)` with `use(X)`. Only runs on React 19+ projects.",
|
|
33191
32683
|
create: (context) => {
|
|
33192
32684
|
if (isVendoredShadcnUiFilename(context.filename)) return {};
|
|
33193
32685
|
return deprecatedReactImportRule.create(buildOncePerApiContext(context));
|
|
@@ -33511,11 +33003,34 @@ const noRedundantShouldComponentUpdate = defineRule({
|
|
|
33511
33003
|
});
|
|
33512
33004
|
//#endregion
|
|
33513
33005
|
//#region src/plugin/rules/architecture/no-render-in-render.ts
|
|
33006
|
+
const tracesToPropOrParameter = (symbol, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
33007
|
+
if (!symbol || visitedSymbols.has(symbol)) return false;
|
|
33008
|
+
visitedSymbols.add(symbol);
|
|
33009
|
+
if (isComponentParameterSymbol(symbol)) return true;
|
|
33010
|
+
if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
33011
|
+
const source = symbol.initializer;
|
|
33012
|
+
if (!source) return false;
|
|
33013
|
+
return initializerRootsInProps(source, scopes, visitedSymbols);
|
|
33014
|
+
};
|
|
33015
|
+
const initializerRootsInProps = (node, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
33016
|
+
if (isNodeOfType(node, "LogicalExpression")) return initializerRootsInProps(node.left, scopes, visitedSymbols) || initializerRootsInProps(node.right, scopes, visitedSymbols);
|
|
33017
|
+
if (isNodeOfType(node, "ConditionalExpression")) return initializerRootsInProps(node.consequent, scopes, visitedSymbols) || initializerRootsInProps(node.alternate, scopes, visitedSymbols);
|
|
33018
|
+
return rootsInProps(node, scopes, visitedSymbols);
|
|
33019
|
+
};
|
|
33020
|
+
const rootsInProps = (node, scopes, visitedSymbols = /* @__PURE__ */ new Set()) => {
|
|
33021
|
+
let current = node;
|
|
33022
|
+
while (isNodeOfType(current, "MemberExpression")) {
|
|
33023
|
+
if (isNodeOfType(current.object, "ThisExpression") && isNodeOfType(current.property, "Identifier") && current.property.name === "props") return true;
|
|
33024
|
+
current = current.object;
|
|
33025
|
+
}
|
|
33026
|
+
if (isNodeOfType(current, "Identifier")) return tracesToPropOrParameter(scopes.symbolFor(current), scopes, visitedSymbols);
|
|
33027
|
+
return false;
|
|
33028
|
+
};
|
|
33514
33029
|
const isInsideComponentContext = (node) => {
|
|
33515
33030
|
let cursor = node.parent;
|
|
33516
33031
|
while (cursor) {
|
|
33032
|
+
if (isNodeOfType(cursor, "ClassDeclaration") || isNodeOfType(cursor, "ClassExpression")) return true;
|
|
33517
33033
|
if (isFunctionLike$1(cursor) && isComponentFunction$1(cursor)) return true;
|
|
33518
|
-
if (isEs5Component(cursor) || isEs6Component(cursor)) return true;
|
|
33519
33034
|
cursor = cursor.parent ?? null;
|
|
33520
33035
|
}
|
|
33521
33036
|
return false;
|
|
@@ -33525,28 +33040,24 @@ const functionBodyOf = (node) => {
|
|
|
33525
33040
|
if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init.body ?? null;
|
|
33526
33041
|
return null;
|
|
33527
33042
|
};
|
|
33528
|
-
const isHookCallee = (callee) => {
|
|
33529
|
-
if (isNodeOfType(callee, "Identifier")) return isReactHookName(callee.name);
|
|
33530
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && isUppercaseName(callee.object.name) && isNodeOfType(callee.property, "Identifier")) return isReactHookName(callee.property.name);
|
|
33531
|
-
return false;
|
|
33532
|
-
};
|
|
33533
33043
|
const containsHookCall = (body) => {
|
|
33534
33044
|
let found = false;
|
|
33535
33045
|
walkAst(body, (child) => {
|
|
33536
|
-
if (found) return
|
|
33537
|
-
if (child !== body && isFunctionLike$1(child) && isComponentFunction$1(child)) return false;
|
|
33046
|
+
if (found) return;
|
|
33538
33047
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
33539
|
-
|
|
33048
|
+
const name = getCalleeName$2(child);
|
|
33049
|
+
if (name && isReactHookName(name)) found = true;
|
|
33540
33050
|
});
|
|
33541
33051
|
return found;
|
|
33542
33052
|
};
|
|
33543
|
-
const
|
|
33053
|
+
const isModuleScopeHookFreeHelper = (symbol) => {
|
|
33544
33054
|
if (!symbol) return false;
|
|
33545
33055
|
const declaration = symbol.declarationNode;
|
|
33546
33056
|
if (!isNodeOfType(declaration, "FunctionDeclaration") && !isNodeOfType(declaration, "VariableDeclarator")) return false;
|
|
33547
33057
|
const body = functionBodyOf(declaration);
|
|
33548
33058
|
if (!body) return false;
|
|
33549
|
-
|
|
33059
|
+
if (findEnclosingFunction(declaration) !== null) return false;
|
|
33060
|
+
return !containsHookCall(body);
|
|
33550
33061
|
};
|
|
33551
33062
|
const noRenderInRender = defineRule({
|
|
33552
33063
|
id: "no-render-in-render",
|
|
@@ -33555,13 +33066,20 @@ const noRenderInRender = defineRule({
|
|
|
33555
33066
|
tags: ["test-noise"],
|
|
33556
33067
|
recommendation: "Make it a named component rendered as JSX so React can track it and preserve its state.",
|
|
33557
33068
|
create: (context) => ({ JSXExpressionContainer(node) {
|
|
33558
|
-
const expression =
|
|
33069
|
+
const expression = node.expression;
|
|
33559
33070
|
if (!isNodeOfType(expression, "CallExpression")) return;
|
|
33560
|
-
|
|
33561
|
-
|
|
33562
|
-
if (
|
|
33071
|
+
let calleeName = null;
|
|
33072
|
+
if (isNodeOfType(expression.callee, "Identifier")) calleeName = expression.callee.name;
|
|
33073
|
+
else if (isNodeOfType(expression.callee, "MemberExpression") && isNodeOfType(expression.callee.property, "Identifier")) calleeName = expression.callee.property.name;
|
|
33074
|
+
if (!calleeName || !RENDER_FUNCTION_PATTERN.test(calleeName)) return;
|
|
33563
33075
|
if (!isInsideComponentContext(node)) return;
|
|
33564
|
-
if (
|
|
33076
|
+
if (isNodeOfType(expression.callee, "Identifier")) {
|
|
33077
|
+
const calleeSymbol = context.scopes.symbolFor(expression.callee);
|
|
33078
|
+
if (tracesToPropOrParameter(calleeSymbol, context.scopes)) return;
|
|
33079
|
+
if (isModuleScopeHookFreeHelper(calleeSymbol)) return;
|
|
33080
|
+
} else if (isNodeOfType(expression.callee, "MemberExpression")) {
|
|
33081
|
+
if (rootsInProps(expression.callee.object, context.scopes)) return;
|
|
33082
|
+
}
|
|
33565
33083
|
context.report({
|
|
33566
33084
|
node: expression,
|
|
33567
33085
|
message: `"${calleeName}()" hides a component behind an inline call, so pull it into its own component and render it as JSX so React can track it.`
|
|
@@ -33622,7 +33140,13 @@ const noRenderPropChildren = defineRule({
|
|
|
33622
33140
|
//#endregion
|
|
33623
33141
|
//#region src/plugin/rules/react-builtins/no-render-return-value.ts
|
|
33624
33142
|
const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
|
|
33625
|
-
const isReactDomRenderCall = (node) =>
|
|
33143
|
+
const isReactDomRenderCall = (node) => {
|
|
33144
|
+
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
33145
|
+
if (!isNodeOfType(node.callee.object, "Identifier")) return false;
|
|
33146
|
+
if (node.callee.object.name !== "ReactDOM") return false;
|
|
33147
|
+
if (!isNodeOfType(node.callee.property, "Identifier")) return false;
|
|
33148
|
+
return node.callee.property.name === "render";
|
|
33149
|
+
};
|
|
33626
33150
|
const isUsedAsReturnValue = (parent) => {
|
|
33627
33151
|
if (!parent) return false;
|
|
33628
33152
|
if (isNodeOfType(parent, "VariableDeclarator") || isNodeOfType(parent, "Property") || isNodeOfType(parent, "ReturnStatement") || isNodeOfType(parent, "AssignmentExpression")) return true;
|
|
@@ -34458,7 +33982,7 @@ const noSetState = defineRule({
|
|
|
34458
33982
|
category: "Architecture",
|
|
34459
33983
|
create: (context) => ({ CallExpression(node) {
|
|
34460
33984
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
34461
|
-
if (!isNodeOfType(
|
|
33985
|
+
if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
|
|
34462
33986
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
34463
33987
|
if (!getParentComponent(node)) return;
|
|
34464
33988
|
context.report({
|
|
@@ -36904,36 +36428,6 @@ const isTriviallyCheapExpression = (node) => {
|
|
|
36904
36428
|
if (isNodeOfType(innerExpression, "MemberExpression")) return false;
|
|
36905
36429
|
return true;
|
|
36906
36430
|
};
|
|
36907
|
-
const isTrivialContainerLiteral = (node) => {
|
|
36908
|
-
if (!node) return false;
|
|
36909
|
-
const innerExpression = stripParenExpression(node);
|
|
36910
|
-
if (isNodeOfType(innerExpression, "ArrayExpression")) return (innerExpression.elements ?? []).every((element) => element !== null && isSimpleExpression(element));
|
|
36911
|
-
if (isNodeOfType(innerExpression, "ObjectExpression")) return (innerExpression.properties ?? []).every((property) => isNodeOfType(property, "Property") && !property.computed && isSimpleExpression(property.value));
|
|
36912
|
-
return false;
|
|
36913
|
-
};
|
|
36914
|
-
const isNonEscapingRead = (identifier) => {
|
|
36915
|
-
const readRoot = findTransparentExpressionRoot(identifier);
|
|
36916
|
-
const memberNode = readRoot.parent;
|
|
36917
|
-
if (!isNodeOfType(memberNode, "MemberExpression") || memberNode.object !== readRoot) return false;
|
|
36918
|
-
const memberUse = findTransparentExpressionRoot(memberNode);
|
|
36919
|
-
const memberUseParent = memberUse.parent;
|
|
36920
|
-
if (isNodeOfType(memberUseParent, "AssignmentExpression") && memberUseParent.left === memberUse) return false;
|
|
36921
|
-
if (isNodeOfType(memberUseParent, "UpdateExpression")) return false;
|
|
36922
|
-
if (isNodeOfType(memberUseParent, "UnaryExpression") && memberUseParent.operator === "delete") return false;
|
|
36923
|
-
return !(isNodeOfType(memberUseParent, "CallExpression") && memberUseParent.callee === memberUse && !memberNode.computed && isNodeOfType(memberNode.property, "Identifier") && MUTATING_ARRAY_METHODS.has(memberNode.property.name));
|
|
36924
|
-
};
|
|
36925
|
-
const isMemoIdentityUnused = (memoCallNode, scopes) => {
|
|
36926
|
-
const memoUsageRoot = findTransparentExpressionRoot(memoCallNode);
|
|
36927
|
-
const parentNode = memoUsageRoot.parent;
|
|
36928
|
-
if (isNodeOfType(parentNode, "ExpressionStatement")) return true;
|
|
36929
|
-
if (!isNodeOfType(parentNode, "VariableDeclarator") || parentNode.init !== memoUsageRoot) return false;
|
|
36930
|
-
const bindingTarget = parentNode.id;
|
|
36931
|
-
if (isNodeOfType(bindingTarget, "ArrayPattern") || isNodeOfType(bindingTarget, "ObjectPattern")) return true;
|
|
36932
|
-
if (!isNodeOfType(bindingTarget, "Identifier")) return false;
|
|
36933
|
-
const symbol = scopes.symbolFor(bindingTarget);
|
|
36934
|
-
if (!symbol) return false;
|
|
36935
|
-
return symbol.references.every((reference) => reference.flag === "read" && isNonEscapingRead(reference.identifier));
|
|
36936
|
-
};
|
|
36937
36431
|
const noUsememoSimpleExpression = defineRule({
|
|
36938
36432
|
id: "no-usememo-simple-expression",
|
|
36939
36433
|
title: "useMemo on a cheap value",
|
|
@@ -36955,17 +36449,9 @@ const noUsememoSimpleExpression = defineRule({
|
|
|
36955
36449
|
let returnExpression = null;
|
|
36956
36450
|
if (!isNodeOfType(callback.body, "BlockStatement")) returnExpression = callback.body;
|
|
36957
36451
|
else if (callback.body.body?.length === 1 && isNodeOfType(callback.body.body[0], "ReturnStatement")) returnExpression = callback.body.body[0].argument;
|
|
36958
|
-
if (
|
|
36959
|
-
if (isTriviallyCheapExpression(returnExpression)) {
|
|
36960
|
-
context.report({
|
|
36961
|
-
node,
|
|
36962
|
-
message: "This costs more than it saves because useMemo is wrapping a value that's already cheap, so remove the useMemo"
|
|
36963
|
-
});
|
|
36964
|
-
return;
|
|
36965
|
-
}
|
|
36966
|
-
if (isTrivialContainerLiteral(returnExpression) && isMemoIdentityUnused(node, context.scopes)) context.report({
|
|
36452
|
+
if (returnExpression && isTriviallyCheapExpression(returnExpression)) context.report({
|
|
36967
36453
|
node,
|
|
36968
|
-
message: "This
|
|
36454
|
+
message: "This costs more than it saves because useMemo is wrapping a value that's already cheap, so remove the useMemo"
|
|
36969
36455
|
});
|
|
36970
36456
|
} })
|
|
36971
36457
|
});
|
|
@@ -37071,7 +36557,7 @@ const noWillUpdateSetState = defineRule({
|
|
|
37071
36557
|
const activeLifecycleNames = isReactBelow16_3(context.settings) ? new Set(["componentWillUpdate"]) : LIFECYCLE_NAMES;
|
|
37072
36558
|
return { CallExpression(node) {
|
|
37073
36559
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
37074
|
-
if (!isNodeOfType(
|
|
36560
|
+
if (!isNodeOfType(node.callee.object, "ThisExpression")) return;
|
|
37075
36561
|
if (!isNodeOfType(node.callee.property, "Identifier") || node.callee.property.name !== "setState") return;
|
|
37076
36562
|
if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
|
|
37077
36563
|
context.report({
|
|
@@ -37388,10 +36874,6 @@ const isRouteFactoryCall = (expression) => {
|
|
|
37388
36874
|
}
|
|
37389
36875
|
return false;
|
|
37390
36876
|
};
|
|
37391
|
-
const isConfigOnlyFactoryCall = (call) => call.arguments.every((argument) => {
|
|
37392
|
-
const expression = skipTsExpression(argument);
|
|
37393
|
-
return isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "Literal") || isNodeOfType(expression, "TemplateLiteral");
|
|
37394
|
-
});
|
|
37395
36877
|
const isReactHocName = (name, state) => state.customHocs.has(name);
|
|
37396
36878
|
const isHocCallee = (callee, state) => {
|
|
37397
36879
|
if (isNodeOfType(callee, "Identifier")) return isReactHocName(callee.name, state);
|
|
@@ -37431,7 +36913,7 @@ const objectExpressionBundlesComponents = (objectExpression, state) => {
|
|
|
37431
36913
|
continue;
|
|
37432
36914
|
}
|
|
37433
36915
|
if (!(!property.computed && isNodeOfType(property.key, "Identifier") && isReactComponentName(property.key.name))) continue;
|
|
37434
|
-
if (
|
|
36916
|
+
if (isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) return true;
|
|
37435
36917
|
if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
|
|
37436
36918
|
}
|
|
37437
36919
|
return false;
|
|
@@ -37539,22 +37021,18 @@ const onlyExportComponents = defineRule({
|
|
|
37539
37021
|
customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
|
|
37540
37022
|
allowExportNames: new Set(settings.allowExportNames),
|
|
37541
37023
|
allowConstantExport: settings.allowConstantExport,
|
|
37542
|
-
localComponentNames
|
|
37543
|
-
scopes: context.scopes
|
|
37024
|
+
localComponentNames
|
|
37544
37025
|
};
|
|
37545
37026
|
for (const child of componentCandidates) {
|
|
37546
37027
|
if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
|
|
37547
|
-
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child)
|
|
37028
|
+
if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
37548
37029
|
}
|
|
37549
37030
|
if (isNodeOfType(child, "ClassDeclaration") && child.id) {
|
|
37550
37031
|
if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
37551
37032
|
}
|
|
37552
37033
|
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
|
|
37553
37034
|
const initializer = child.init;
|
|
37554
|
-
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child))
|
|
37555
|
-
const expression = initializer ? skipTsExpression(initializer) : null;
|
|
37556
|
-
if (!(expression !== null && (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression"))) || functionContainsReactRenderOutput(expression, context.scopes)) localComponentNames.add(child.id.name);
|
|
37557
|
-
}
|
|
37035
|
+
if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
|
|
37558
37036
|
}
|
|
37559
37037
|
}
|
|
37560
37038
|
const exports = [];
|
|
@@ -37624,10 +37102,6 @@ const onlyExportComponents = defineRule({
|
|
|
37624
37102
|
return false;
|
|
37625
37103
|
})();
|
|
37626
37104
|
if (isHoc && firstArgIsValid) hasReactExport = true;
|
|
37627
|
-
else if (!isHoc && isConfigOnlyFactoryCall(stripped)) exports.push({
|
|
37628
|
-
kind: "non-component",
|
|
37629
|
-
reportNode: stripped
|
|
37630
|
-
});
|
|
37631
37105
|
else context.report({
|
|
37632
37106
|
node: stripped,
|
|
37633
37107
|
message: ANONYMOUS_MESSAGE
|
|
@@ -39223,22 +38697,11 @@ const getSubscriptionHandlerArgument = (subscribeCall, effectBodyStatements) =>
|
|
|
39223
38697
|
}
|
|
39224
38698
|
return null;
|
|
39225
38699
|
};
|
|
39226
|
-
const isTrivialLiteralExpression = (expression) => {
|
|
39227
|
-
if (isNodeOfType(expression, "Literal")) return true;
|
|
39228
|
-
if (isNodeOfType(expression, "Identifier")) return expression.name === "undefined";
|
|
39229
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "-") return isNodeOfType(expression.argument, "Literal");
|
|
39230
|
-
if (isNodeOfType(expression, "TemplateLiteral")) return (expression.expressions?.length ?? 0) === 0;
|
|
39231
|
-
return false;
|
|
39232
|
-
};
|
|
39233
38700
|
const getSingleSetterCallFromHandler = (handler) => {
|
|
39234
38701
|
const handlerStatements = getCallbackStatements(handler);
|
|
39235
38702
|
if (handlerStatements.length !== 1) return null;
|
|
39236
38703
|
const onlyStatement = handlerStatements[0];
|
|
39237
|
-
|
|
39238
|
-
if (isNodeOfType(onlyStatement, "ExpressionStatement")) expression = onlyStatement.expression;
|
|
39239
|
-
if (isNodeOfType(onlyStatement, "ReturnStatement")) expression = onlyStatement.argument;
|
|
39240
|
-
if (!expression) return null;
|
|
39241
|
-
expression = stripParenExpression(expression);
|
|
38704
|
+
const expression = isNodeOfType(onlyStatement, "ExpressionStatement") ? onlyStatement.expression : onlyStatement;
|
|
39242
38705
|
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
39243
38706
|
if (!isNodeOfType(expression.callee, "Identifier")) return null;
|
|
39244
38707
|
if (!isSetterIdentifier(expression.callee.name)) return null;
|
|
@@ -39248,106 +38711,6 @@ const getSingleSetterCallFromHandler = (handler) => {
|
|
|
39248
38711
|
setterArgument: expression.arguments[0]
|
|
39249
38712
|
};
|
|
39250
38713
|
};
|
|
39251
|
-
const isListenerCollectionInitializer = (init) => {
|
|
39252
|
-
if (!init) return false;
|
|
39253
|
-
if (isNodeOfType(init, "ArrayExpression")) return true;
|
|
39254
|
-
return isNodeOfType(init, "NewExpression") && isNodeOfType(init.callee, "Identifier") && init.callee.name === "Set";
|
|
39255
|
-
};
|
|
39256
|
-
const functionRegistersParameterIntoCollection = (functionNode, listenerCollectionNames) => {
|
|
39257
|
-
if (!isFunctionLike$1(functionNode)) return false;
|
|
39258
|
-
const firstParam = functionNode.params?.[0];
|
|
39259
|
-
if (!isNodeOfType(firstParam, "Identifier")) return false;
|
|
39260
|
-
const listenerParamName = firstParam.name;
|
|
39261
|
-
let registersListener = false;
|
|
39262
|
-
walkAst(functionNode.body, (child) => {
|
|
39263
|
-
if (registersListener) return false;
|
|
39264
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
39265
|
-
if (!isNodeOfType(child.callee, "MemberExpression")) return;
|
|
39266
|
-
if (!isNodeOfType(child.callee.object, "Identifier")) return;
|
|
39267
|
-
if (!listenerCollectionNames.has(child.callee.object.name)) return;
|
|
39268
|
-
if (!isNodeOfType(child.callee.property, "Identifier")) return;
|
|
39269
|
-
if (child.callee.property.name !== "add" && child.callee.property.name !== "push") return;
|
|
39270
|
-
const registeredArgument = child.arguments?.[0];
|
|
39271
|
-
if (isNodeOfType(registeredArgument, "Identifier") && registeredArgument.name === listenerParamName) registersListener = true;
|
|
39272
|
-
});
|
|
39273
|
-
return registersListener;
|
|
39274
|
-
};
|
|
39275
|
-
const buildModuleScopeStoreIndex = (programRoot) => {
|
|
39276
|
-
const mutableBindingNames = /* @__PURE__ */ new Set();
|
|
39277
|
-
const listenerCollectionNames = /* @__PURE__ */ new Set();
|
|
39278
|
-
const moduleFunctionsByName = /* @__PURE__ */ new Map();
|
|
39279
|
-
if (!isNodeOfType(programRoot, "Program")) return {
|
|
39280
|
-
mutableBindingNames,
|
|
39281
|
-
subscribeFunctionNames: /* @__PURE__ */ new Set()
|
|
39282
|
-
};
|
|
39283
|
-
for (const statement of programRoot.body ?? []) {
|
|
39284
|
-
const unwrapped = isNodeOfType(statement, "ExportNamedDeclaration") && statement.declaration ? statement.declaration : statement;
|
|
39285
|
-
if (isNodeOfType(unwrapped, "FunctionDeclaration") && unwrapped.id) {
|
|
39286
|
-
moduleFunctionsByName.set(unwrapped.id.name, unwrapped);
|
|
39287
|
-
continue;
|
|
39288
|
-
}
|
|
39289
|
-
if (!isNodeOfType(unwrapped, "VariableDeclaration")) continue;
|
|
39290
|
-
for (const declarator of unwrapped.declarations ?? []) {
|
|
39291
|
-
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
39292
|
-
const init = declarator.init ?? null;
|
|
39293
|
-
if ((unwrapped.kind === "let" || unwrapped.kind === "var") && init && !isFunctionLike$1(init)) {
|
|
39294
|
-
mutableBindingNames.add(declarator.id.name);
|
|
39295
|
-
continue;
|
|
39296
|
-
}
|
|
39297
|
-
if (isListenerCollectionInitializer(init)) {
|
|
39298
|
-
listenerCollectionNames.add(declarator.id.name);
|
|
39299
|
-
continue;
|
|
39300
|
-
}
|
|
39301
|
-
if (init && isFunctionLike$1(init)) moduleFunctionsByName.set(declarator.id.name, init);
|
|
39302
|
-
}
|
|
39303
|
-
}
|
|
39304
|
-
const subscribeFunctionNames = /* @__PURE__ */ new Set();
|
|
39305
|
-
for (const [functionName, functionNode] of moduleFunctionsByName) if (functionRegistersParameterIntoCollection(functionNode, listenerCollectionNames)) subscribeFunctionNames.add(functionName);
|
|
39306
|
-
return {
|
|
39307
|
-
mutableBindingNames,
|
|
39308
|
-
subscribeFunctionNames
|
|
39309
|
-
};
|
|
39310
|
-
};
|
|
39311
|
-
const getModuleStoreSnapshotName = (useStateCall, storeIndex) => {
|
|
39312
|
-
if (!isNodeOfType(useStateCall, "CallExpression")) return null;
|
|
39313
|
-
let initialArgument = stripParenExpression(useStateCall.arguments?.[0]);
|
|
39314
|
-
if (initialArgument && isFunctionLike$1(initialArgument) && !isNodeOfType(initialArgument.body, "BlockStatement")) initialArgument = stripParenExpression(initialArgument.body);
|
|
39315
|
-
if (!isNodeOfType(initialArgument, "Identifier")) return null;
|
|
39316
|
-
if (!storeIndex.mutableBindingNames.has(initialArgument.name)) return null;
|
|
39317
|
-
const binding = findVariableInitializer(initialArgument, initialArgument.name);
|
|
39318
|
-
if (!binding || !isNodeOfType(binding.scopeOwner, "Program")) return null;
|
|
39319
|
-
return initialArgument.name;
|
|
39320
|
-
};
|
|
39321
|
-
const argumentForwardsSetter = (argument, setterName) => {
|
|
39322
|
-
if (!argument) return false;
|
|
39323
|
-
const unwrapped = stripParenExpression(argument);
|
|
39324
|
-
if (isNodeOfType(unwrapped, "Identifier")) return unwrapped.name === setterName;
|
|
39325
|
-
if (!isFunctionLike$1(unwrapped)) return false;
|
|
39326
|
-
let callsSetter = false;
|
|
39327
|
-
walkAst(unwrapped.body, (child) => {
|
|
39328
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && child.callee.name === setterName) {
|
|
39329
|
-
callsSetter = true;
|
|
39330
|
-
return false;
|
|
39331
|
-
}
|
|
39332
|
-
});
|
|
39333
|
-
return callsSetter;
|
|
39334
|
-
};
|
|
39335
|
-
const findModuleSubscribeCallForwardingSetter = (effectCallback, setterName, storeIndex) => {
|
|
39336
|
-
let matchedCall = null;
|
|
39337
|
-
walkAst((isFunctionLike$1(effectCallback) ? effectCallback.body : null) ?? effectCallback, (child) => {
|
|
39338
|
-
if (matchedCall) return false;
|
|
39339
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
39340
|
-
if (!isNodeOfType(child.callee, "Identifier")) return;
|
|
39341
|
-
if (!storeIndex.subscribeFunctionNames.has(child.callee.name)) return;
|
|
39342
|
-
const binding = findVariableInitializer(child.callee, child.callee.name);
|
|
39343
|
-
if (!binding || !isNodeOfType(binding.scopeOwner, "Program")) return;
|
|
39344
|
-
for (const argument of child.arguments ?? []) if (argumentForwardsSetter(argument, setterName)) {
|
|
39345
|
-
matchedCall = child;
|
|
39346
|
-
return false;
|
|
39347
|
-
}
|
|
39348
|
-
});
|
|
39349
|
-
return matchedCall;
|
|
39350
|
-
};
|
|
39351
38714
|
const cleanupReleasesSubscription = (effectBodyStatements, boundReleaseName, boundSubscriptionName) => {
|
|
39352
38715
|
const lastStatement = effectBodyStatements[effectBodyStatements.length - 1];
|
|
39353
38716
|
if (!isNodeOfType(lastStatement, "ReturnStatement")) return false;
|
|
@@ -39364,16 +38727,6 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
39364
38727
|
severity: "warn",
|
|
39365
38728
|
recommendation: "Replace the `useState(getSnapshot())` + `useEffect(() => store.subscribe(() => setSnapshot(getSnapshot())))` pair with `useSyncExternalStore(store.subscribe, getSnapshot)`. The hook gets this right during concurrent rendering and on the server; the hand-rolled version doesn't.",
|
|
39366
38729
|
create: (context) => {
|
|
39367
|
-
let cachedStoreIndex = null;
|
|
39368
|
-
const storeIndexFor = (node) => {
|
|
39369
|
-
if (cachedStoreIndex) return cachedStoreIndex;
|
|
39370
|
-
const programRoot = findProgramRoot(node);
|
|
39371
|
-
cachedStoreIndex = programRoot ? buildModuleScopeStoreIndex(programRoot) : {
|
|
39372
|
-
mutableBindingNames: /* @__PURE__ */ new Set(),
|
|
39373
|
-
subscribeFunctionNames: /* @__PURE__ */ new Set()
|
|
39374
|
-
};
|
|
39375
|
-
return cachedStoreIndex;
|
|
39376
|
-
};
|
|
39377
38730
|
const checkComponent = (componentBody) => {
|
|
39378
38731
|
if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
|
|
39379
38732
|
const useStateBindings = collectUseStateBindings(componentBody);
|
|
@@ -39411,7 +38764,6 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
39411
38764
|
const useStateInitializer = useStateInitializerByValueName.get(valueName);
|
|
39412
38765
|
if (!useStateInitializer) continue;
|
|
39413
38766
|
if (!areExpressionsStructurallyEqual(useStateInitializer, setterPayload.setterArgument)) continue;
|
|
39414
|
-
if (isTrivialLiteralExpression(setterPayload.setterArgument)) continue;
|
|
39415
38767
|
if (!cleanupReleasesSubscription(effectBodyStatements, subscription.boundReleaseName, subscription.boundSubscriptionName)) continue;
|
|
39416
38768
|
const matchingBinding = useStateBindings.find((binding) => binding.valueName === valueName);
|
|
39417
38769
|
context.report({
|
|
@@ -39419,47 +38771,14 @@ const preferUseSyncExternalStore = defineRule({
|
|
|
39419
38771
|
message: `Your users can see stale or torn values because useState "${valueName}" syncs an outside store through a useEffect.`
|
|
39420
38772
|
});
|
|
39421
38773
|
}
|
|
39422
|
-
checkModuleStoreShape(componentBody, useStateBindings);
|
|
39423
|
-
};
|
|
39424
|
-
const checkModuleStoreShape = (componentBody, useStateBindings) => {
|
|
39425
|
-
const storeIndex = storeIndexFor(componentBody);
|
|
39426
|
-
if (storeIndex.mutableBindingNames.size === 0) return;
|
|
39427
|
-
if (storeIndex.subscribeFunctionNames.size === 0) return;
|
|
39428
|
-
const snapshotBindings = useStateBindings.map((binding) => ({
|
|
39429
|
-
binding,
|
|
39430
|
-
storeName: isNodeOfType(binding.declarator.init, "CallExpression") ? getModuleStoreSnapshotName(binding.declarator.init, storeIndex) : null
|
|
39431
|
-
})).filter((candidate) => candidate.storeName !== null);
|
|
39432
|
-
if (snapshotBindings.length === 0) return;
|
|
39433
|
-
const reportedDeclarators = /* @__PURE__ */ new Set();
|
|
39434
|
-
for (const effectCall of findUseEffectsInComponent(componentBody)) {
|
|
39435
|
-
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
39436
|
-
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
39437
|
-
const depsNode = effectCall.arguments[1];
|
|
39438
|
-
if (!isNodeOfType(depsNode, "ArrayExpression")) continue;
|
|
39439
|
-
if ((depsNode.elements?.length ?? 0) !== 0) continue;
|
|
39440
|
-
const callback = getEffectCallback(effectCall);
|
|
39441
|
-
if (!callback || !isFunctionLike$1(callback)) continue;
|
|
39442
|
-
for (const { binding, storeName } of snapshotBindings) {
|
|
39443
|
-
if (reportedDeclarators.has(binding.declarator)) continue;
|
|
39444
|
-
if (!findModuleSubscribeCallForwardingSetter(callback, binding.setterName, storeIndex)) continue;
|
|
39445
|
-
reportedDeclarators.add(binding.declarator);
|
|
39446
|
-
context.report({
|
|
39447
|
-
node: binding.declarator,
|
|
39448
|
-
message: `Your users can miss updates or see torn values because useState "${binding.valueName}" snapshots module store "${storeName}" at render but only subscribes later in a useEffect.`
|
|
39449
|
-
});
|
|
39450
|
-
}
|
|
39451
|
-
}
|
|
39452
38774
|
};
|
|
39453
38775
|
return {
|
|
39454
38776
|
FunctionDeclaration(node) {
|
|
39455
|
-
|
|
39456
|
-
if (!functionName) return;
|
|
39457
|
-
if (!isUppercaseName(functionName) && !isReactHookName(functionName)) return;
|
|
38777
|
+
if (!node.id?.name || !isUppercaseName(node.id.name)) return;
|
|
39458
38778
|
checkComponent(node.body);
|
|
39459
38779
|
},
|
|
39460
38780
|
VariableDeclarator(node) {
|
|
39461
|
-
|
|
39462
|
-
if (!isComponentAssignment(node) && !isHookAssignment) return;
|
|
38781
|
+
if (!isComponentAssignment(node)) return;
|
|
39463
38782
|
if (!isNodeOfType(node.init, "ArrowFunctionExpression") && !isNodeOfType(node.init, "FunctionExpression")) return;
|
|
39464
38783
|
checkComponent(node.init.body);
|
|
39465
38784
|
}
|
|
@@ -40069,7 +39388,7 @@ const queryNoVoidQueryFn = defineRule({
|
|
|
40069
39388
|
if (isNodeOfType(queryFnValue, "ArrowFunctionExpression") || isNodeOfType(queryFnValue, "FunctionExpression")) {
|
|
40070
39389
|
const body = queryFnValue.body;
|
|
40071
39390
|
if (!isNodeOfType(body, "BlockStatement")) return;
|
|
40072
|
-
if ((body.body ?? []).
|
|
39391
|
+
if ((body.body ?? []).length === 0) context.report({
|
|
40073
39392
|
node: queryFnProperty,
|
|
40074
39393
|
message: "This empty queryFn caches undefined, so the component never gets data."
|
|
40075
39394
|
});
|
|
@@ -40576,6 +39895,17 @@ const renderingHoistJsx = defineRule({
|
|
|
40576
39895
|
}
|
|
40577
39896
|
});
|
|
40578
39897
|
//#endregion
|
|
39898
|
+
//#region src/plugin/utils/find-declarator-for-binding.ts
|
|
39899
|
+
const findDeclaratorForBinding = (bindingIdentifier) => {
|
|
39900
|
+
let ancestor = bindingIdentifier.parent;
|
|
39901
|
+
while (ancestor) {
|
|
39902
|
+
if (isNodeOfType(ancestor, "VariableDeclarator")) return ancestor;
|
|
39903
|
+
if (isNodeOfType(ancestor, "FunctionDeclaration") || isNodeOfType(ancestor, "FunctionExpression") || isNodeOfType(ancestor, "ArrowFunctionExpression") || isNodeOfType(ancestor, "Program")) return null;
|
|
39904
|
+
ancestor = ancestor.parent ?? null;
|
|
39905
|
+
}
|
|
39906
|
+
return null;
|
|
39907
|
+
};
|
|
39908
|
+
//#endregion
|
|
40579
39909
|
//#region src/plugin/rules/performance/rendering-hydration-mismatch-time.ts
|
|
40580
39910
|
const NONDETERMINISTIC_RENDER_PATTERNS = [
|
|
40581
39911
|
{
|
|
@@ -40584,19 +39914,19 @@ const NONDETERMINISTIC_RENDER_PATTERNS = [
|
|
|
40584
39914
|
},
|
|
40585
39915
|
{
|
|
40586
39916
|
display: "Date.now()",
|
|
40587
|
-
matches: (node) =>
|
|
39917
|
+
matches: (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "Date" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "now"
|
|
40588
39918
|
},
|
|
40589
39919
|
{
|
|
40590
39920
|
display: "Math.random()",
|
|
40591
|
-
matches: (node) =>
|
|
39921
|
+
matches: (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "Math" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "random"
|
|
40592
39922
|
},
|
|
40593
39923
|
{
|
|
40594
39924
|
display: "performance.now()",
|
|
40595
|
-
matches: (node) =>
|
|
39925
|
+
matches: (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "performance" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "now"
|
|
40596
39926
|
},
|
|
40597
39927
|
{
|
|
40598
39928
|
display: "crypto.randomUUID()",
|
|
40599
|
-
matches: (node) =>
|
|
39929
|
+
matches: (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && node.callee.object.name === "crypto" && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "randomUUID"
|
|
40600
39930
|
}
|
|
40601
39931
|
];
|
|
40602
39932
|
const findOpeningElementOfChild = (jsxNode) => {
|
|
@@ -40608,6 +39938,54 @@ const findOpeningElementOfChild = (jsxNode) => {
|
|
|
40608
39938
|
}
|
|
40609
39939
|
return null;
|
|
40610
39940
|
};
|
|
39941
|
+
const executesDuringRender = (functionNode) => {
|
|
39942
|
+
const parent = functionNode.parent;
|
|
39943
|
+
if (!isNodeOfType(parent, "CallExpression")) return false;
|
|
39944
|
+
if (parent.callee === functionNode) return true;
|
|
39945
|
+
return isHookCall$2(parent, "useMemo") && parent.arguments?.[0] === functionNode;
|
|
39946
|
+
};
|
|
39947
|
+
const CLIENT_ONLY_FLAG_NAME_PATTERN = /^(?:is|has|did)?_?(?:client|mounted|hydrated|browser)(?:_?(?:side|ready|only))?$/i;
|
|
39948
|
+
const referencesClientOnlyFlag = (expression) => {
|
|
39949
|
+
const unwrapped = stripParenExpression(expression);
|
|
39950
|
+
if (isNodeOfType(unwrapped, "Identifier")) return CLIENT_ONLY_FLAG_NAME_PATTERN.test(unwrapped.name);
|
|
39951
|
+
if (isNodeOfType(unwrapped, "MemberExpression")) {
|
|
39952
|
+
const property = unwrapped.property;
|
|
39953
|
+
return isNodeOfType(property, "Identifier") && CLIENT_ONLY_FLAG_NAME_PATTERN.test(property.name);
|
|
39954
|
+
}
|
|
39955
|
+
if (isNodeOfType(unwrapped, "UnaryExpression") && unwrapped.operator === "!") return referencesClientOnlyFlag(unwrapped.argument);
|
|
39956
|
+
if (isNodeOfType(unwrapped, "LogicalExpression")) return referencesClientOnlyFlag(unwrapped.left) || referencesClientOnlyFlag(unwrapped.right);
|
|
39957
|
+
return false;
|
|
39958
|
+
};
|
|
39959
|
+
const isFalsyLiteral = (node) => {
|
|
39960
|
+
if (!node) return true;
|
|
39961
|
+
if (isNodeOfType(node, "Literal")) return !node.value;
|
|
39962
|
+
return isNodeOfType(node, "Identifier") && node.name === "undefined";
|
|
39963
|
+
};
|
|
39964
|
+
const isFalsyInitialStateBinding = (identifier) => {
|
|
39965
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
39966
|
+
const binding = findVariableInitializer(identifier, identifier.name);
|
|
39967
|
+
if (!binding) return false;
|
|
39968
|
+
const declarator = findDeclaratorForBinding(binding.bindingIdentifier);
|
|
39969
|
+
if (!declarator?.init) return false;
|
|
39970
|
+
const init = stripParenExpression(declarator.init);
|
|
39971
|
+
if (!isHookCall$2(init, "useState") || !isNodeOfType(init, "CallExpression")) return false;
|
|
39972
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
|
|
39973
|
+
if (declarator.id.elements?.[0] !== binding.bindingIdentifier) return false;
|
|
39974
|
+
return isFalsyLiteral(init.arguments?.[0]);
|
|
39975
|
+
};
|
|
39976
|
+
const referencesFalsyInitialState = (expression) => flattenLogicalAndChain(stripParenExpression(expression)).some((operand) => isFalsyInitialStateBinding(stripParenExpression(operand)));
|
|
39977
|
+
const isGatedByFalsyInitialState = (node) => {
|
|
39978
|
+
let cursor = node;
|
|
39979
|
+
let parent = node.parent;
|
|
39980
|
+
while (parent) {
|
|
39981
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesFalsyInitialState(parent.left)) return true;
|
|
39982
|
+
if (isNodeOfType(parent, "ConditionalExpression") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
|
|
39983
|
+
if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesFalsyInitialState(parent.test)) return true;
|
|
39984
|
+
cursor = parent;
|
|
39985
|
+
parent = parent.parent ?? null;
|
|
39986
|
+
}
|
|
39987
|
+
return false;
|
|
39988
|
+
};
|
|
40611
39989
|
const isYearOnlyDateRead = (dateNode) => {
|
|
40612
39990
|
const member = dateNode.parent;
|
|
40613
39991
|
if (!isNodeOfType(member, "MemberExpression") || member.object !== dateNode) return false;
|
|
@@ -40631,6 +40009,23 @@ const isInsideMotionTransitionAttribute = (node) => {
|
|
|
40631
40009
|
}
|
|
40632
40010
|
return false;
|
|
40633
40011
|
};
|
|
40012
|
+
const isInsideClientOnlyGuard = (node) => {
|
|
40013
|
+
let cursor = node;
|
|
40014
|
+
let parent = node.parent;
|
|
40015
|
+
while (parent) {
|
|
40016
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&" && parent.right === cursor && referencesClientOnlyFlag(parent.left)) return true;
|
|
40017
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === cursor || parent.alternate === cursor) && referencesClientOnlyFlag(parent.test)) return true;
|
|
40018
|
+
if (isNodeOfType(parent, "IfStatement") && parent.consequent === cursor && referencesClientOnlyFlag(parent.test)) return true;
|
|
40019
|
+
cursor = parent;
|
|
40020
|
+
parent = parent.parent ?? null;
|
|
40021
|
+
}
|
|
40022
|
+
return false;
|
|
40023
|
+
};
|
|
40024
|
+
const hasSuppressHydrationWarningAttribute = (openingElement) => {
|
|
40025
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
40026
|
+
for (const attr of openingElement.attributes ?? []) if (isNodeOfType(attr, "JSXAttribute") && isNodeOfType(attr.name, "JSXIdentifier") && attr.name.name === "suppressHydrationWarning") return true;
|
|
40027
|
+
return false;
|
|
40028
|
+
};
|
|
40634
40029
|
const renderingHydrationMismatchTime = defineRule({
|
|
40635
40030
|
id: "rendering-hydration-mismatch-time",
|
|
40636
40031
|
title: "Time or random value in JSX",
|
|
@@ -40678,35 +40073,6 @@ const renderingHydrationMismatchTime = defineRule({
|
|
|
40678
40073
|
}
|
|
40679
40074
|
});
|
|
40680
40075
|
//#endregion
|
|
40681
|
-
//#region src/plugin/utils/contains-locale-environment-read.ts
|
|
40682
|
-
const LOCALE_ENVIRONMENT_METHOD_NAMES = new Set([
|
|
40683
|
-
"toLocaleString",
|
|
40684
|
-
"toLocaleDateString",
|
|
40685
|
-
"toLocaleTimeString",
|
|
40686
|
-
"getTimezoneOffset"
|
|
40687
|
-
]);
|
|
40688
|
-
const containsLocaleEnvironmentRead = (expression) => {
|
|
40689
|
-
let readsLocaleEnvironment = false;
|
|
40690
|
-
walkAst(expression, (child) => {
|
|
40691
|
-
if (readsLocaleEnvironment) return false;
|
|
40692
|
-
if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.object, "Identifier")) {
|
|
40693
|
-
if (child.object.name === "Intl") {
|
|
40694
|
-
readsLocaleEnvironment = true;
|
|
40695
|
-
return false;
|
|
40696
|
-
}
|
|
40697
|
-
if (child.object.name === "navigator" && isNodeOfType(child.property, "Identifier") && (child.property.name === "language" || child.property.name === "languages")) {
|
|
40698
|
-
readsLocaleEnvironment = true;
|
|
40699
|
-
return false;
|
|
40700
|
-
}
|
|
40701
|
-
}
|
|
40702
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "MemberExpression") && !child.callee.computed && isNodeOfType(child.callee.property, "Identifier") && LOCALE_ENVIRONMENT_METHOD_NAMES.has(child.callee.property.name)) {
|
|
40703
|
-
readsLocaleEnvironment = true;
|
|
40704
|
-
return false;
|
|
40705
|
-
}
|
|
40706
|
-
});
|
|
40707
|
-
return readsLocaleEnvironment;
|
|
40708
|
-
};
|
|
40709
|
-
//#endregion
|
|
40710
40076
|
//#region src/plugin/rules/performance/rendering-hydration-no-flicker.ts
|
|
40711
40077
|
const USE_EFFECT_ONLY = new Set(["useEffect"]);
|
|
40712
40078
|
const argumentsReadRefCurrent = (callArguments) => callArguments.some((argument) => {
|
|
@@ -40772,15 +40138,14 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
40772
40138
|
if (!isNodeOfType(depsNode, "ArrayExpression") || depsNode.elements?.length !== 0) return;
|
|
40773
40139
|
const callback = getEffectCallback(node);
|
|
40774
40140
|
if (!callback || !isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
|
|
40775
|
-
const bodyStatements =
|
|
40776
|
-
if (bodyStatements.length !== 1) return;
|
|
40141
|
+
const bodyStatements = isNodeOfType(callback.body, "BlockStatement") ? callback.body.body : [callback.body];
|
|
40142
|
+
if (!bodyStatements || bodyStatements.length !== 1) return;
|
|
40777
40143
|
const soleStatement = bodyStatements[0];
|
|
40778
40144
|
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
40779
40145
|
const expression = soleStatement.expression;
|
|
40780
40146
|
if (isSetterCall(expression) && isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && isUseStateSetterInScope(expression, expression.callee.name)) {
|
|
40781
40147
|
if (argumentsReadRefCurrent(expression.arguments ?? [])) return;
|
|
40782
40148
|
if (isStateUsedOnlyInIdOrAriaAttributes(expression, expression.callee.name)) return;
|
|
40783
|
-
if ((expression.arguments ?? []).some(containsLocaleEnvironmentRead)) return;
|
|
40784
40149
|
context.report({
|
|
40785
40150
|
node,
|
|
40786
40151
|
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
@@ -41599,9 +40964,6 @@ const rerenderFunctionalSetstate = defineRule({
|
|
|
41599
40964
|
} })
|
|
41600
40965
|
});
|
|
41601
40966
|
//#endregion
|
|
41602
|
-
//#region src/plugin/utils/is-trivial-built-in-construction.ts
|
|
41603
|
-
const isTrivialBuiltInConstruction = (expression) => isNodeOfType(expression, "NewExpression") && isNodeOfType(expression.callee, "Identifier") && TRIVIAL_CONSTRUCTOR_NAMES.has(expression.callee.name) && (expression.arguments ?? []).length === 0;
|
|
41604
|
-
//#endregion
|
|
41605
40967
|
//#region src/plugin/rules/state-and-effects/rerender-lazy-ref-init.ts
|
|
41606
40968
|
const rerenderLazyRefInit = defineRule({
|
|
41607
40969
|
id: "rerender-lazy-ref-init",
|
|
@@ -41612,7 +40974,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
41612
40974
|
recommendation: "Initialize the ref lazily so expensive values are not rebuilt and discarded on every render.",
|
|
41613
40975
|
create: (context) => ({ CallExpression(node) {
|
|
41614
40976
|
if (!isHookCall$2(node, "useRef") || !node.arguments?.length) return;
|
|
41615
|
-
const initializer =
|
|
40977
|
+
const initializer = node.arguments[0];
|
|
41616
40978
|
const isPlainCall = isNodeOfType(initializer, "CallExpression");
|
|
41617
40979
|
const isNewCall = isNodeOfType(initializer, "NewExpression");
|
|
41618
40980
|
if (!isPlainCall && !isNewCall) return;
|
|
@@ -41620,7 +40982,7 @@ const rerenderLazyRefInit = defineRule({
|
|
|
41620
40982
|
const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
|
|
41621
40983
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
|
|
41622
40984
|
if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
|
|
41623
|
-
if (
|
|
40985
|
+
if (isNewCall && TRIVIAL_CONSTRUCTOR_NAMES.has(calleeName)) return;
|
|
41624
40986
|
if (isPlainCall && isReactHookName(calleeName)) return;
|
|
41625
40987
|
const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
|
|
41626
40988
|
context.report({
|
|
@@ -41656,11 +41018,11 @@ const TRIVIAL_DATE_GETTER_NAMES = new Set([
|
|
|
41656
41018
|
const EAGER_CALL_RESOLUTION_DEPTH_LIMIT = 4;
|
|
41657
41019
|
const findEagerInitializerCall = (expression, depth = 0) => {
|
|
41658
41020
|
if (depth > EAGER_CALL_RESOLUTION_DEPTH_LIMIT) return null;
|
|
41659
|
-
|
|
41660
|
-
if (isNodeOfType(
|
|
41661
|
-
if (isNodeOfType(
|
|
41662
|
-
if (isNodeOfType(
|
|
41663
|
-
if (isNodeOfType(
|
|
41021
|
+
if (isNodeOfType(expression, "CallExpression") || isNodeOfType(expression, "NewExpression")) return expression;
|
|
41022
|
+
if (isNodeOfType(expression, "ChainExpression")) return findEagerInitializerCall(expression.expression, depth + 1);
|
|
41023
|
+
if (isNodeOfType(expression, "LogicalExpression")) return findEagerInitializerCall(expression.left, depth + 1);
|
|
41024
|
+
if (isNodeOfType(expression, "MemberExpression")) return findEagerInitializerCall(expression.object, depth + 1);
|
|
41025
|
+
if (isNodeOfType(expression, "ArrayExpression")) for (const element of expression.elements ?? []) {
|
|
41664
41026
|
if (!element || !isNodeOfType(element, "SpreadElement")) continue;
|
|
41665
41027
|
const spreadCall = findEagerInitializerCall(element.argument, depth + 1);
|
|
41666
41028
|
if (spreadCall) return spreadCall;
|
|
@@ -41683,7 +41045,7 @@ const rerenderLazyStateInit = defineRule({
|
|
|
41683
41045
|
const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
|
|
41684
41046
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
|
|
41685
41047
|
if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
|
|
41686
|
-
if (
|
|
41048
|
+
if (isConstructor && TRIVIAL_CONSTRUCTOR_NAMES.has(calleeName)) return;
|
|
41687
41049
|
if (memberPropertyName && (initializer.arguments ?? []).length === 0 && TRIVIAL_DATE_GETTER_NAMES.has(memberPropertyName)) return;
|
|
41688
41050
|
if (isReactHookName(calleeName)) return;
|
|
41689
41051
|
const callDescription = isConstructor ? `new ${calleeName}()` : `${calleeName}()`;
|
|
@@ -42350,7 +41712,7 @@ const rnAnimationReactionAsDerived = defineRule({
|
|
|
42350
41712
|
const body = reactionFn.body;
|
|
42351
41713
|
let singleAssignment = null;
|
|
42352
41714
|
if (isNodeOfType(body, "BlockStatement")) {
|
|
42353
|
-
const statements =
|
|
41715
|
+
const statements = body.body ?? [];
|
|
42354
41716
|
if (statements.length !== 1) return;
|
|
42355
41717
|
const onlyStatement = statements[0];
|
|
42356
41718
|
if (!isNodeOfType(onlyStatement, "ExpressionStatement")) return;
|
|
@@ -42424,8 +41786,7 @@ const PROMISE_SETTLE_METHODS = new Set([
|
|
|
42424
41786
|
"catch",
|
|
42425
41787
|
"finally"
|
|
42426
41788
|
]);
|
|
42427
|
-
const findChainRoot = (
|
|
42428
|
-
const node = stripParenExpression(wrappedNode);
|
|
41789
|
+
const findChainRoot = (node) => {
|
|
42429
41790
|
if (isNodeOfType(node, "CallExpression")) {
|
|
42430
41791
|
if (isNodeOfType(node.callee, "Identifier")) return {
|
|
42431
41792
|
calleeName: node.callee.name,
|
|
@@ -43013,21 +42374,20 @@ const isBindingReactNativeDimensions = (node, binding, localName) => {
|
|
|
43013
42374
|
};
|
|
43014
42375
|
const isReactNativeDimensionsCallee = (node, callee) => {
|
|
43015
42376
|
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
43016
|
-
|
|
43017
|
-
|
|
43018
|
-
const localName = receiver.name;
|
|
42377
|
+
if (isNodeOfType(callee.object, "Identifier")) {
|
|
42378
|
+
const localName = callee.object.name;
|
|
43019
42379
|
const binding = findVariableInitializer(node, localName);
|
|
43020
42380
|
if (binding !== null) return isBindingReactNativeDimensions(node, binding, localName);
|
|
43021
42381
|
return localName === "Dimensions";
|
|
43022
42382
|
}
|
|
43023
|
-
if (isNodeOfType(
|
|
43024
|
-
if (getInitializerModuleSource(node,
|
|
43025
|
-
const rootName = getRootIdentifierName(
|
|
42383
|
+
if (isNodeOfType(callee.object, "MemberExpression") && !callee.object.computed && isNodeOfType(callee.object.property, "Identifier") && callee.object.property.name === "Dimensions") {
|
|
42384
|
+
if (getInitializerModuleSource(node, callee.object.object) === REACT_NATIVE_MODULE) return true;
|
|
42385
|
+
const rootName = getRootIdentifierName(callee.object.object);
|
|
43026
42386
|
if (rootName === null) return false;
|
|
43027
42387
|
const importBinding = getImportBindingForName(node, rootName);
|
|
43028
42388
|
return importBinding !== null && importBinding.isNamespace && importBinding.source === REACT_NATIVE_MODULE;
|
|
43029
42389
|
}
|
|
43030
|
-
if (getRequireCallSource(
|
|
42390
|
+
if (getRequireCallSource(callee.object) === REACT_NATIVE_MODULE) return isNodeOfType(callee.object, "MemberExpression") && !callee.object.computed && isNodeOfType(callee.object.property, "Identifier") && callee.object.property.name === "Dimensions";
|
|
43031
42391
|
return false;
|
|
43032
42392
|
};
|
|
43033
42393
|
const STYLE_FACTORY_CALLEE_PATTERN$1 = /(?:^|\.)(?:make|create)(?:Use)?Styles$/;
|
|
@@ -43471,8 +42831,7 @@ const rnNoLegacyShadowStyles = defineRule({
|
|
|
43471
42831
|
},
|
|
43472
42832
|
CallExpression(node) {
|
|
43473
42833
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
43474
|
-
|
|
43475
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "StyleSheet") return;
|
|
42834
|
+
if (!isNodeOfType(node.callee.object, "Identifier") || node.callee.object.name !== "StyleSheet") return;
|
|
43476
42835
|
if (!isMemberProperty(node.callee, "create")) return;
|
|
43477
42836
|
const stylesArgument = node.arguments?.[0];
|
|
43478
42837
|
if (!isNodeOfType(stylesArgument, "ObjectExpression")) return;
|
|
@@ -44322,7 +43681,7 @@ const rnNoSetNativeProps = defineRule({
|
|
|
44322
43681
|
const callee = node.callee;
|
|
44323
43682
|
if (!isStaticMemberNamed(callee, "setNativeProps")) return;
|
|
44324
43683
|
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
44325
|
-
if (!isStaticMemberNamed(
|
|
43684
|
+
if (!isStaticMemberNamed(callee.object, "current")) return;
|
|
44326
43685
|
context.report({
|
|
44327
43686
|
node,
|
|
44328
43687
|
message: "`setNativeProps` is a silent no-op under the New Architecture (Fabric), so this imperative update won't change the view. Drive the prop via state, an Animated.Value, or a Reanimated shared value."
|
|
@@ -44550,15 +43909,14 @@ const analyzeGestureChain = (expression) => {
|
|
|
44550
43909
|
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
44551
43910
|
if (!isNodeOfType(callee.property, "Identifier")) return null;
|
|
44552
43911
|
const methodName = callee.property.name;
|
|
44553
|
-
|
|
44554
|
-
if (isNodeOfType(receiver, "Identifier") && receiver.name === "Gesture") return {
|
|
43912
|
+
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Gesture") return {
|
|
44555
43913
|
factoryName: methodName,
|
|
44556
43914
|
chainMethodNames,
|
|
44557
43915
|
numberOfTapsArgument
|
|
44558
43916
|
};
|
|
44559
43917
|
if (methodName === "numberOfTaps" && numberOfTapsArgument === null && callExpression.arguments?.length === 1) numberOfTapsArgument = callExpression.arguments[0] ?? null;
|
|
44560
43918
|
chainMethodNames.push(methodName);
|
|
44561
|
-
cursor =
|
|
43919
|
+
cursor = callee.object;
|
|
44562
43920
|
}
|
|
44563
43921
|
return null;
|
|
44564
43922
|
};
|
|
@@ -48224,8 +47582,6 @@ const roleSupportsAriaProps = defineRule({
|
|
|
48224
47582
|
const propName = propRawName.toLowerCase();
|
|
48225
47583
|
if (!propName.startsWith("aria-")) continue;
|
|
48226
47584
|
if (!ARIA_PROPERTIES.has(propName)) continue;
|
|
48227
|
-
const attributeValue = attributeNode.value;
|
|
48228
|
-
if (attributeValue && isNodeOfType(attributeValue, "JSXExpressionContainer") && isNullishExpression(attributeValue.expression)) continue;
|
|
48229
47585
|
(ariaAttributes ??= []).push({
|
|
48230
47586
|
attribute,
|
|
48231
47587
|
propName
|
|
@@ -48600,6 +47956,11 @@ const isUseEffectEventSymbol = (symbol) => {
|
|
|
48600
47956
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
48601
47957
|
return getHookNameFromCallee(initializer.callee) === "useEffectEvent";
|
|
48602
47958
|
};
|
|
47959
|
+
const resolvesToLocalNonImportBinding = (identifier, scopes) => {
|
|
47960
|
+
const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
|
|
47961
|
+
return Boolean(symbol && symbol.kind !== "import");
|
|
47962
|
+
};
|
|
47963
|
+
const isNonReactEffectEventCallee = (callee, contextNode, scopes) => isNodeOfType(callee, "Identifier") && (isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes));
|
|
48603
47964
|
const isNonReactEffectEventSymbol = (symbol, contextNode, scopes) => {
|
|
48604
47965
|
const initializer = symbol.initializer;
|
|
48605
47966
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
@@ -48943,8 +48304,7 @@ const serverAfterNonblocking = defineRule({
|
|
|
48943
48304
|
if (!fileHasUseServerDirective && serverFunctionDepth === 0) return;
|
|
48944
48305
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
48945
48306
|
if (!isNodeOfType(node.callee.property, "Identifier")) return;
|
|
48946
|
-
const
|
|
48947
|
-
const objectName = isNodeOfType(receiver, "Identifier") ? receiver.name : null;
|
|
48307
|
+
const objectName = isNodeOfType(node.callee.object, "Identifier") ? node.callee.object.name : null;
|
|
48948
48308
|
if (!objectName) return;
|
|
48949
48309
|
const methodName = node.callee.property.name;
|
|
48950
48310
|
if (!isDeferrableSideEffectCall(objectName, methodName)) return;
|
|
@@ -49620,17 +48980,7 @@ const getMemberPropertyName$1 = (memberExpression) => {
|
|
|
49620
48980
|
};
|
|
49621
48981
|
const ascendMemberChain = (referenceIdentifier) => {
|
|
49622
48982
|
let chainTip = referenceIdentifier;
|
|
49623
|
-
while (chainTip.parent)
|
|
49624
|
-
if (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(chainTip.parent.type) && "expression" in chainTip.parent && chainTip.parent.expression === chainTip) {
|
|
49625
|
-
chainTip = chainTip.parent;
|
|
49626
|
-
continue;
|
|
49627
|
-
}
|
|
49628
|
-
if (isNodeOfType(chainTip.parent, "MemberExpression") && chainTip.parent.object === chainTip) {
|
|
49629
|
-
chainTip = chainTip.parent;
|
|
49630
|
-
continue;
|
|
49631
|
-
}
|
|
49632
|
-
break;
|
|
49633
|
-
}
|
|
48983
|
+
while (chainTip.parent && isNodeOfType(chainTip.parent, "MemberExpression") && chainTip.parent.object === chainTip) chainTip = chainTip.parent;
|
|
49634
48984
|
return chainTip;
|
|
49635
48985
|
};
|
|
49636
48986
|
const isDirectContentsMutation = (referenceIdentifier) => {
|
|
@@ -49655,8 +49005,7 @@ const isMutatedThroughCallArgument = (referenceIdentifier, scopes, mayFollowCall
|
|
|
49655
49005
|
const callee = callExpression.callee;
|
|
49656
49006
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
49657
49007
|
const methodName = getMemberPropertyName$1(callee);
|
|
49658
|
-
|
|
49659
|
-
return Boolean(isNodeOfType(calleeReceiver, "Identifier") && calleeReceiver.name === "Object" && methodName !== null && OBJECT_MUTATING_METHODS.has(methodName) && referenceArgumentIndex === 0);
|
|
49008
|
+
return Boolean(isNodeOfType(callee.object, "Identifier") && callee.object.name === "Object" && methodName !== null && OBJECT_MUTATING_METHODS.has(methodName) && referenceArgumentIndex === 0);
|
|
49660
49009
|
}
|
|
49661
49010
|
if (!mayFollowCalleeHop || !isNodeOfType(callee, "Identifier")) return false;
|
|
49662
49011
|
const calleeSymbol = scopes.symbolFor(callee);
|
|
@@ -50386,7 +49735,7 @@ const walkServerFnChain = (outerNode) => {
|
|
|
50386
49735
|
};
|
|
50387
49736
|
if (!isNodeOfType(outerNode, "CallExpression")) return result;
|
|
50388
49737
|
if (!isNodeOfType(outerNode.callee, "MemberExpression")) return result;
|
|
50389
|
-
let currentNode =
|
|
49738
|
+
let currentNode = outerNode.callee.object;
|
|
50390
49739
|
while (isNodeOfType(currentNode, "CallExpression")) {
|
|
50391
49740
|
const calleeName = getCalleeName$2(currentNode);
|
|
50392
49741
|
if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) {
|
|
@@ -50397,7 +49746,7 @@ const walkServerFnChain = (outerNode) => {
|
|
|
50397
49746
|
}
|
|
50398
49747
|
}
|
|
50399
49748
|
if (calleeName && TANSTACK_INPUT_VALIDATOR_METHOD_NAMES.has(calleeName)) result.hasInputValidation = true;
|
|
50400
|
-
if (isNodeOfType(currentNode.callee, "MemberExpression")) currentNode =
|
|
49749
|
+
if (isNodeOfType(currentNode.callee, "MemberExpression")) currentNode = currentNode.callee.object;
|
|
50401
49750
|
else break;
|
|
50402
49751
|
}
|
|
50403
49752
|
return result;
|
|
@@ -51050,7 +50399,7 @@ const tanstackStartServerFnMethodOrder = defineRule({
|
|
|
51050
50399
|
while (isNodeOfType(currentNode, "CallExpression") && isNodeOfType(currentNode.callee, "MemberExpression")) {
|
|
51051
50400
|
const methodName = isNodeOfType(currentNode.callee.property, "Identifier") ? currentNode.callee.property.name : null;
|
|
51052
50401
|
if (methodName) methodNames.unshift(methodName);
|
|
51053
|
-
currentNode =
|
|
50402
|
+
currentNode = currentNode.callee.object;
|
|
51054
50403
|
}
|
|
51055
50404
|
if (isNodeOfType(currentNode, "CallExpression") && isNodeOfType(currentNode.callee, "Identifier")) {
|
|
51056
50405
|
if (!TANSTACK_SERVER_FN_NAMES.has(currentNode.callee.name)) return;
|
|
@@ -54132,18 +53481,6 @@ const reactDoctorRules = [
|
|
|
54132
53481
|
category: "Bugs"
|
|
54133
53482
|
}
|
|
54134
53483
|
},
|
|
54135
|
-
{
|
|
54136
|
-
key: "react-doctor/no-locale-format-in-render",
|
|
54137
|
-
id: "no-locale-format-in-render",
|
|
54138
|
-
source: "react-doctor",
|
|
54139
|
-
originallyExternal: false,
|
|
54140
|
-
rule: {
|
|
54141
|
-
...noLocaleFormatInRender,
|
|
54142
|
-
framework: "global",
|
|
54143
|
-
category: "Bugs",
|
|
54144
|
-
requires: [...new Set(["react", ...noLocaleFormatInRender.requires ?? []])]
|
|
54145
|
-
}
|
|
54146
|
-
},
|
|
54147
53484
|
{
|
|
54148
53485
|
key: "react-doctor/no-long-transition-duration",
|
|
54149
53486
|
id: "no-long-transition-duration",
|