oxlint-plugin-react-doctor 0.7.9-dev.9aa98b9 → 0.7.9-dev.b30fb80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +236 -50
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6205,6 +6205,23 @@ const asyncDeferAwait = defineRule({
|
|
|
6205
6205
|
}
|
|
6206
6206
|
});
|
|
6207
6207
|
//#endregion
|
|
6208
|
+
//#region src/plugin/utils/expression-reads-pattern-binding.ts
|
|
6209
|
+
const expressionReadsPatternBinding = (expression, patterns, scopes) => {
|
|
6210
|
+
const bindingSymbolIds = /* @__PURE__ */ new Set();
|
|
6211
|
+
for (const pattern of patterns) walkAst(pattern, (child) => {
|
|
6212
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
6213
|
+
const symbol = scopes.symbolFor(child);
|
|
6214
|
+
if (symbol?.bindingIdentifier === child) bindingSymbolIds.add(symbol.id);
|
|
6215
|
+
});
|
|
6216
|
+
let didReadBinding = false;
|
|
6217
|
+
walkAst(expression, (child) => {
|
|
6218
|
+
if (didReadBinding || !isNodeOfType(child, "Identifier")) return;
|
|
6219
|
+
const reference = scopes.referenceFor(child);
|
|
6220
|
+
if (reference?.flag !== "write" && reference?.resolvedSymbol && bindingSymbolIds.has(reference.resolvedSymbol.id)) didReadBinding = true;
|
|
6221
|
+
});
|
|
6222
|
+
return didReadBinding;
|
|
6223
|
+
};
|
|
6224
|
+
//#endregion
|
|
6208
6225
|
//#region src/plugin/utils/get-callee-identifier-trail.ts
|
|
6209
6226
|
const getCalleeIdentifierTrail = (call) => {
|
|
6210
6227
|
let entry = call;
|
|
@@ -6310,16 +6327,12 @@ const isInsideTransactionCallback = (node) => {
|
|
|
6310
6327
|
return false;
|
|
6311
6328
|
};
|
|
6312
6329
|
const reportIfIndependent = (statements, context) => {
|
|
6313
|
-
const
|
|
6330
|
+
const declaredPatterns = [];
|
|
6314
6331
|
for (const statement of statements) {
|
|
6315
6332
|
const awaitArgument = getAwaitedCall(statement);
|
|
6316
6333
|
if (!awaitArgument) continue;
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
if (isNodeOfType(child, "Identifier") && declaredNames.has(child.name)) referencesEarlierResult = true;
|
|
6320
|
-
});
|
|
6321
|
-
if (referencesEarlierResult) return;
|
|
6322
|
-
if (isNodeOfType(statement, "VariableDeclaration") && statement.declarations[0]?.id) collectPatternNames(statement.declarations[0].id, declaredNames);
|
|
6334
|
+
if (expressionReadsPatternBinding(awaitArgument, declaredPatterns, context.scopes)) return;
|
|
6335
|
+
if (isNodeOfType(statement, "VariableDeclaration") && statement.declarations[0]?.id) declaredPatterns.push(statement.declarations[0].id);
|
|
6323
6336
|
}
|
|
6324
6337
|
context.report({
|
|
6325
6338
|
node: statements[0],
|
|
@@ -8463,10 +8476,21 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
|
|
|
8463
8476
|
};
|
|
8464
8477
|
const isReactApiCall = (node, apiNames, scopes, options = {}) => {
|
|
8465
8478
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
8466
|
-
|
|
8479
|
+
return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
|
|
8480
|
+
};
|
|
8481
|
+
const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
|
|
8482
|
+
const callee = stripParenExpression(rawCallee);
|
|
8483
|
+
if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
|
|
8467
8484
|
if (isNodeOfType(callee, "Identifier")) {
|
|
8468
8485
|
if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
|
|
8469
8486
|
if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
|
|
8487
|
+
if (options.resolveConditionalAliases) {
|
|
8488
|
+
const symbol = scopes.symbolFor(callee);
|
|
8489
|
+
if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
|
|
8490
|
+
visitedSymbolIds.add(symbol.id);
|
|
8491
|
+
return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
|
|
8492
|
+
}
|
|
8493
|
+
}
|
|
8470
8494
|
return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
|
|
8471
8495
|
}
|
|
8472
8496
|
if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
|
|
@@ -12323,6 +12347,10 @@ const isConstAliasOfGlobalArrayFrom = (node, scopes) => {
|
|
|
12323
12347
|
};
|
|
12324
12348
|
const executesDuringRender = (functionNode, scopes) => {
|
|
12325
12349
|
const parent = functionNode.parent;
|
|
12350
|
+
if (isNodeOfType(parent, "NewExpression")) {
|
|
12351
|
+
const callee = stripParenExpression(parent.callee);
|
|
12352
|
+
return Boolean(scopes && parent.arguments?.[0] === functionNode && isNodeOfType(callee, "Identifier") && callee.name === "Promise" && scopes.isGlobalReference(callee));
|
|
12353
|
+
}
|
|
12326
12354
|
if (!isNodeOfType(parent, "CallExpression")) return false;
|
|
12327
12355
|
if (parent.callee === functionNode) return true;
|
|
12328
12356
|
if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] === functionNode) return true;
|
|
@@ -29812,18 +29840,143 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
|
|
|
29812
29840
|
"%"
|
|
29813
29841
|
]);
|
|
29814
29842
|
const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
|
|
29815
|
-
const
|
|
29843
|
+
const UNKNOWN_STATIC_KEY_BRANCH_VALUE = {
|
|
29844
|
+
isNullish: null,
|
|
29845
|
+
isTruthy: null
|
|
29846
|
+
};
|
|
29847
|
+
const mergeStaticKeyBranchValues = (firstValue, secondValue) => ({
|
|
29848
|
+
isNullish: firstValue.isNullish === secondValue.isNullish ? firstValue.isNullish : null,
|
|
29849
|
+
isTruthy: firstValue.isTruthy === secondValue.isTruthy ? firstValue.isTruthy : null
|
|
29850
|
+
});
|
|
29851
|
+
const readStaticKeyBranchValue = (expression, depth) => {
|
|
29852
|
+
if (depth > TYPE_RESOLUTION_DEPTH_LIMIT) return null;
|
|
29853
|
+
const node = stripParenExpression(expression);
|
|
29854
|
+
if (isNodeOfType(node, "Literal")) return {
|
|
29855
|
+
isNullish: node.value === null,
|
|
29856
|
+
isTruthy: Boolean(node.value)
|
|
29857
|
+
};
|
|
29858
|
+
if (isNodeOfType(node, "ArrayExpression") || isNodeOfType(node, "ObjectExpression") || isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ClassExpression") || isNodeOfType(node, "NewExpression")) return {
|
|
29859
|
+
isNullish: false,
|
|
29860
|
+
isTruthy: true
|
|
29861
|
+
};
|
|
29862
|
+
if (isNodeOfType(node, "TemplateLiteral")) return {
|
|
29863
|
+
isNullish: false,
|
|
29864
|
+
isTruthy: (node.quasis ?? []).some((quasi) => typeof quasi.value?.cooked === "string" && quasi.value.cooked.length > 0) ? true : null
|
|
29865
|
+
};
|
|
29866
|
+
if (isNodeOfType(node, "BinaryExpression")) return {
|
|
29867
|
+
isNullish: false,
|
|
29868
|
+
isTruthy: null
|
|
29869
|
+
};
|
|
29870
|
+
if (isNodeOfType(node, "Identifier")) {
|
|
29871
|
+
const binding = findVariableInitializer(node, node.name);
|
|
29872
|
+
if (!binding) {
|
|
29873
|
+
if (node.name === "undefined") return {
|
|
29874
|
+
isNullish: true,
|
|
29875
|
+
isTruthy: false
|
|
29876
|
+
};
|
|
29877
|
+
if (node.name === "NaN") return {
|
|
29878
|
+
isNullish: false,
|
|
29879
|
+
isTruthy: false
|
|
29880
|
+
};
|
|
29881
|
+
if (node.name === "Infinity") return {
|
|
29882
|
+
isNullish: false,
|
|
29883
|
+
isTruthy: true
|
|
29884
|
+
};
|
|
29885
|
+
return null;
|
|
29886
|
+
}
|
|
29887
|
+
if (!isConstDeclaredBinding(binding) || !binding.initializer) return null;
|
|
29888
|
+
const declarator = binding.bindingIdentifier.parent;
|
|
29889
|
+
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.id !== binding.bindingIdentifier || declarator.init !== binding.initializer) return null;
|
|
29890
|
+
return readStaticKeyBranchValue(binding.initializer, depth + 1);
|
|
29891
|
+
}
|
|
29892
|
+
if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier")) {
|
|
29893
|
+
if (!Boolean(findVariableInitializer(node.callee, node.callee.name)) && STRING_COERCION_FUNCTIONS.has(node.callee.name)) return {
|
|
29894
|
+
isNullish: false,
|
|
29895
|
+
isTruthy: null
|
|
29896
|
+
};
|
|
29897
|
+
}
|
|
29898
|
+
if (isNodeOfType(node, "UnaryExpression")) {
|
|
29899
|
+
if (node.operator === "void") return {
|
|
29900
|
+
isNullish: true,
|
|
29901
|
+
isTruthy: false
|
|
29902
|
+
};
|
|
29903
|
+
if (node.operator === "typeof") return {
|
|
29904
|
+
isNullish: false,
|
|
29905
|
+
isTruthy: true
|
|
29906
|
+
};
|
|
29907
|
+
if (node.operator === "+" || node.operator === "-" || node.operator === "~") return {
|
|
29908
|
+
isNullish: false,
|
|
29909
|
+
isTruthy: null
|
|
29910
|
+
};
|
|
29911
|
+
if (node.operator !== "!") return null;
|
|
29912
|
+
const argumentValue = readStaticKeyBranchValue(node.argument, depth + 1);
|
|
29913
|
+
if (!argumentValue || argumentValue.isTruthy === null) return null;
|
|
29914
|
+
return {
|
|
29915
|
+
isNullish: false,
|
|
29916
|
+
isTruthy: !argumentValue.isTruthy
|
|
29917
|
+
};
|
|
29918
|
+
}
|
|
29919
|
+
if (isNodeOfType(node, "SequenceExpression")) {
|
|
29920
|
+
const finalExpression = node.expressions.at(-1);
|
|
29921
|
+
return finalExpression ? readStaticKeyBranchValue(finalExpression, depth + 1) : null;
|
|
29922
|
+
}
|
|
29923
|
+
if (isNodeOfType(node, "LogicalExpression")) {
|
|
29924
|
+
const leftValue = readStaticKeyBranchValue(node.left, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE;
|
|
29925
|
+
if (node.operator === "&&" && leftValue.isTruthy !== null) return leftValue.isTruthy ? readStaticKeyBranchValue(node.right, depth + 1) : leftValue;
|
|
29926
|
+
if (node.operator === "||" && leftValue.isTruthy !== null) return leftValue.isTruthy ? leftValue : readStaticKeyBranchValue(node.right, depth + 1);
|
|
29927
|
+
if (node.operator === "??" && leftValue.isNullish !== null) return leftValue.isNullish ? readStaticKeyBranchValue(node.right, depth + 1) : leftValue;
|
|
29928
|
+
const rightValue = readStaticKeyBranchValue(node.right, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE;
|
|
29929
|
+
if (node.operator === "||") return {
|
|
29930
|
+
isNullish: rightValue.isNullish === false ? false : null,
|
|
29931
|
+
isTruthy: rightValue.isTruthy === true ? true : null
|
|
29932
|
+
};
|
|
29933
|
+
if (node.operator === "&&") return {
|
|
29934
|
+
isNullish: leftValue.isNullish === false && rightValue.isNullish === false ? false : null,
|
|
29935
|
+
isTruthy: rightValue.isTruthy === false ? false : null
|
|
29936
|
+
};
|
|
29937
|
+
return {
|
|
29938
|
+
isNullish: rightValue.isNullish === false ? false : null,
|
|
29939
|
+
isTruthy: leftValue.isTruthy !== null && leftValue.isTruthy === rightValue.isTruthy ? leftValue.isTruthy : null
|
|
29940
|
+
};
|
|
29941
|
+
}
|
|
29942
|
+
if (isNodeOfType(node, "ConditionalExpression")) {
|
|
29943
|
+
const testValue = readStaticKeyBranchValue(node.test, depth + 1);
|
|
29944
|
+
if (testValue?.isTruthy !== null && testValue?.isTruthy !== void 0) return readStaticKeyBranchValue(testValue.isTruthy ? node.consequent : node.alternate, depth + 1);
|
|
29945
|
+
return mergeStaticKeyBranchValues(readStaticKeyBranchValue(node.consequent, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE, readStaticKeyBranchValue(node.alternate, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE);
|
|
29946
|
+
}
|
|
29947
|
+
return null;
|
|
29948
|
+
};
|
|
29949
|
+
const extractCandidateIdentifiers = (expression, depth = 0) => {
|
|
29950
|
+
if (depth > TYPE_RESOLUTION_DEPTH_LIMIT) return [];
|
|
29816
29951
|
const node = stripParenExpression(expression);
|
|
29817
29952
|
if (isNodeOfType(node, "Identifier")) return [node];
|
|
29818
|
-
if (isNodeOfType(node, "UnaryExpression") && (node.operator === "-" || node.operator === "+" || node.operator === "~") &&
|
|
29953
|
+
if (isNodeOfType(node, "UnaryExpression") && (node.operator === "-" || node.operator === "+" || node.operator === "~") && node.argument) return extractCandidateIdentifiers(node.argument, depth + 1);
|
|
29819
29954
|
if (isNodeOfType(node, "TemplateLiteral")) {
|
|
29820
29955
|
const identifiers = [];
|
|
29821
|
-
for (const templateExpression of node.expressions ?? [])
|
|
29956
|
+
for (const templateExpression of node.expressions ?? []) identifiers.push(...extractCandidateIdentifiers(templateExpression, depth + 1));
|
|
29822
29957
|
return identifiers;
|
|
29823
29958
|
}
|
|
29959
|
+
if (isNodeOfType(node, "LogicalExpression")) {
|
|
29960
|
+
const leftValue = readStaticKeyBranchValue(node.left, 0);
|
|
29961
|
+
if (leftValue) {
|
|
29962
|
+
if (node.operator === "&&" && leftValue.isTruthy !== null) return extractCandidateIdentifiers(leftValue.isTruthy ? node.right : node.left, depth + 1);
|
|
29963
|
+
if (node.operator === "||" && leftValue.isTruthy !== null) return extractCandidateIdentifiers(leftValue.isTruthy ? node.left : node.right, depth + 1);
|
|
29964
|
+
if (node.operator === "??" && leftValue.isNullish !== null) return extractCandidateIdentifiers(leftValue.isNullish ? node.right : node.left, depth + 1);
|
|
29965
|
+
}
|
|
29966
|
+
return [...extractCandidateIdentifiers(node.left, depth + 1), ...extractCandidateIdentifiers(node.right, depth + 1)];
|
|
29967
|
+
}
|
|
29968
|
+
if (isNodeOfType(node, "ConditionalExpression")) {
|
|
29969
|
+
const testValue = readStaticKeyBranchValue(node.test, 0);
|
|
29970
|
+
if (testValue && testValue.isTruthy !== null) return extractCandidateIdentifiers(testValue.isTruthy ? node.consequent : node.alternate, depth + 1);
|
|
29971
|
+
return [...extractCandidateIdentifiers(node.consequent, depth + 1), ...extractCandidateIdentifiers(node.alternate, depth + 1)];
|
|
29972
|
+
}
|
|
29973
|
+
if (isNodeOfType(node, "SequenceExpression")) {
|
|
29974
|
+
const finalExpression = node.expressions.at(-1);
|
|
29975
|
+
return finalExpression ? extractCandidateIdentifiers(finalExpression, depth + 1) : [];
|
|
29976
|
+
}
|
|
29824
29977
|
if (isNodeOfType(node, "CallExpression")) {
|
|
29825
|
-
if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.
|
|
29826
|
-
if (isNodeOfType(node.callee, "Identifier") && STRING_COERCION_FUNCTIONS.has(node.callee.name) &&
|
|
29978
|
+
if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "toString") return extractCandidateIdentifiers(node.callee.object, depth + 1);
|
|
29979
|
+
if (isNodeOfType(node.callee, "Identifier") && STRING_COERCION_FUNCTIONS.has(node.callee.name) && !findVariableInitializer(node.callee, node.callee.name) && node.arguments?.[0]) return extractCandidateIdentifiers(node.arguments[0], depth + 1);
|
|
29827
29980
|
return [];
|
|
29828
29981
|
}
|
|
29829
29982
|
if (isNodeOfType(node, "BinaryExpression")) {
|
|
@@ -31976,6 +32129,7 @@ const createStateTriggerReachability = ({ analysis, context, effectFunction }) =
|
|
|
31976
32129
|
};
|
|
31977
32130
|
//#endregion
|
|
31978
32131
|
//#region src/plugin/rules/state-and-effects/no-chain-state-updates.ts
|
|
32132
|
+
const APPLICABLE_EFFECT_HOOK_NAMES = new Set(["useEffect", "useLayoutEffect"]);
|
|
31979
32133
|
const getUseStateDeclarator = (ref) => (ref.resolved?.defs ?? []).map((def) => def.node).find((node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "ArrayPattern")) ?? null;
|
|
31980
32134
|
const isDeclaredWithin = (node, container) => {
|
|
31981
32135
|
let walker = node;
|
|
@@ -32027,7 +32181,12 @@ const noChainStateUpdates = defineRule({
|
|
|
32027
32181
|
tags: ["test-noise"],
|
|
32028
32182
|
recommendation: "Set all the related state together in the event handler that starts it, instead of having one useEffect react to a state change and set more state. See https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations",
|
|
32029
32183
|
create: (context) => ({ CallExpression(node) {
|
|
32030
|
-
if (!
|
|
32184
|
+
if (!isReactApiCall(node, APPLICABLE_EFFECT_HOOK_NAMES, context.scopes, {
|
|
32185
|
+
allowGlobalReactNamespace: true,
|
|
32186
|
+
allowUnboundBareCalls: true,
|
|
32187
|
+
resolveConditionalAliases: true,
|
|
32188
|
+
resolveNamedAliases: true
|
|
32189
|
+
})) return;
|
|
32031
32190
|
const analysis = getProgramAnalysis(node);
|
|
32032
32191
|
if (!analysis) return;
|
|
32033
32192
|
if (hasCleanup(analysis, node)) return;
|
|
@@ -42005,7 +42164,7 @@ const getRefBindingProvenance = (analysis, receiver, isReactUseRefCall) => {
|
|
|
42005
42164
|
variables
|
|
42006
42165
|
};
|
|
42007
42166
|
};
|
|
42008
|
-
const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactUseRefCall) => {
|
|
42167
|
+
const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactUseRefCall, isReactUseEffectCall) => {
|
|
42009
42168
|
const callee = stripParenExpression(callExpression.callee);
|
|
42010
42169
|
if (!isNodeOfType(callee, "MemberExpression") || getStaticMemberPropertyName(callee) !== "current") return null;
|
|
42011
42170
|
const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(callee.object), isReactUseRefCall);
|
|
@@ -42013,7 +42172,9 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
42013
42172
|
const { refCall, variables } = bindingProvenance;
|
|
42014
42173
|
const componentFunction = findEnclosingFunction$1(effectCall);
|
|
42015
42174
|
if (!componentFunction || !isFunctionLike$1(componentFunction) || !isComponentFunction$1(componentFunction) || !isNodeOfType(componentFunction.body, "BlockStatement")) return null;
|
|
42016
|
-
|
|
42175
|
+
const notificationEffectStatement = getDirectComponentBodyStatement(effectCall, componentFunction.body);
|
|
42176
|
+
const notificationEffectRoot = findTransparentExpressionRoot(effectCall);
|
|
42177
|
+
if (!notificationEffectStatement || !isNodeOfType(notificationEffectStatement, "ExpressionStatement") || notificationEffectRoot.parent !== notificationEffectStatement || !isReactUseEffectCall(effectCall)) return null;
|
|
42017
42178
|
const callbackPropNames = /* @__PURE__ */ new Set();
|
|
42018
42179
|
const initializer = refCall.arguments?.[0];
|
|
42019
42180
|
const initializerCallbackName = initializer ? getParentCallbackPropName(analysis, initializer) : null;
|
|
@@ -42031,9 +42192,20 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
42031
42192
|
const assignment = getRefMemberAssignment(identifier);
|
|
42032
42193
|
if (!assignment) continue;
|
|
42033
42194
|
const assignmentStatement = findTransparentExpressionRoot(assignment).parent;
|
|
42034
|
-
if (assignment.operator !== "=" || !assignmentStatement || !isNodeOfType(assignmentStatement, "ExpressionStatement")
|
|
42195
|
+
if (assignment.operator !== "=" || !assignmentStatement || !isNodeOfType(assignmentStatement, "ExpressionStatement")) return null;
|
|
42035
42196
|
const assignedCallbackName = getParentCallbackPropName(analysis, assignment.right);
|
|
42036
42197
|
if (!assignedCallbackName) return null;
|
|
42198
|
+
if (assignmentStatement.parent !== componentFunction.body) {
|
|
42199
|
+
if (!initializerCallbackName || assignedCallbackName !== initializerCallbackName) return null;
|
|
42200
|
+
const assignmentEffectBody = assignmentStatement.parent;
|
|
42201
|
+
if (!assignmentEffectBody || !isNodeOfType(assignmentEffectBody, "BlockStatement")) return null;
|
|
42202
|
+
const assignmentEffectFunction = assignmentEffectBody.parent;
|
|
42203
|
+
if (!assignmentEffectFunction || !isFunctionLike$1(assignmentEffectFunction) || assignmentEffectFunction.body !== assignmentEffectBody) return null;
|
|
42204
|
+
const assignmentEffectCall = findTransparentExpressionRoot(assignmentEffectFunction).parent;
|
|
42205
|
+
if (!assignmentEffectCall || !isNodeOfType(assignmentEffectCall, "CallExpression") || !isReactUseEffectCall(assignmentEffectCall) || getEffectFn(analysis, assignmentEffectCall) !== assignmentEffectFunction) return null;
|
|
42206
|
+
const assignmentEffectStatement = getDirectComponentBodyStatement(assignmentEffectCall, componentFunction.body);
|
|
42207
|
+
if (!assignmentEffectStatement || !isNodeOfType(assignmentEffectStatement, "ExpressionStatement") || assignmentEffectCall.parent !== assignmentEffectStatement || assignmentEffectStatement.range[0] >= notificationEffectStatement.range[0]) return null;
|
|
42208
|
+
}
|
|
42037
42209
|
callbackPropNames.add(assignedCallbackName);
|
|
42038
42210
|
}
|
|
42039
42211
|
return callbackPropNames.size > 0 ? { callbackPropNames } : null;
|
|
@@ -42246,6 +42418,10 @@ const noPassDataToParent = defineRule({
|
|
|
42246
42418
|
allowGlobalReactNamespace: true,
|
|
42247
42419
|
allowUnboundBareCalls: true
|
|
42248
42420
|
});
|
|
42421
|
+
const isReactUseEffectCall = (node) => isReactApiCall(node, "useEffect", context.scopes, {
|
|
42422
|
+
allowGlobalReactNamespace: true,
|
|
42423
|
+
allowUnboundBareCalls: true
|
|
42424
|
+
});
|
|
42249
42425
|
return { CallExpression(node) {
|
|
42250
42426
|
if (!isUseEffect(node)) return;
|
|
42251
42427
|
const analysis = getProgramAnalysis(node);
|
|
@@ -42258,7 +42434,7 @@ const noPassDataToParent = defineRule({
|
|
|
42258
42434
|
for (const ref of effectFnRefs) {
|
|
42259
42435
|
const callExpr = getCallExpr(ref);
|
|
42260
42436
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
|
|
42261
|
-
const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall);
|
|
42437
|
+
const callbackRefProvenance = getCallbackRefProvenance(analysis, node, callExpr, isReactUseRefCall, isReactUseEffectCall);
|
|
42262
42438
|
if (isRefCall(analysis, ref) && !callbackRefProvenance) continue;
|
|
42263
42439
|
if (!isSynchronous(ref.identifier, effectFn)) continue;
|
|
42264
42440
|
const calleeNode = unwrapChainExpression(callExpr.callee);
|
|
@@ -43820,9 +43996,9 @@ const isInsideComponentContext = (node) => {
|
|
|
43820
43996
|
}
|
|
43821
43997
|
return false;
|
|
43822
43998
|
};
|
|
43823
|
-
const
|
|
43824
|
-
if (isFunctionLike$1(node)) return node
|
|
43825
|
-
if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init
|
|
43999
|
+
const getFunctionFromDeclaration = (node) => {
|
|
44000
|
+
if (isFunctionLike$1(node)) return node;
|
|
44001
|
+
if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init;
|
|
43826
44002
|
return null;
|
|
43827
44003
|
};
|
|
43828
44004
|
const isHookCallee = (callee) => {
|
|
@@ -43830,23 +44006,43 @@ const isHookCallee = (callee) => {
|
|
|
43830
44006
|
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && isUppercaseName(callee.object.name) && isNodeOfType(callee.property, "Identifier")) return isReactHookName(callee.property.name);
|
|
43831
44007
|
return false;
|
|
43832
44008
|
};
|
|
43833
|
-
const
|
|
43834
|
-
|
|
43835
|
-
|
|
43836
|
-
|
|
43837
|
-
|
|
43838
|
-
if (
|
|
43839
|
-
if (
|
|
44009
|
+
const containsReachableHookCall = (functionNode, rootFunction, context, visitedFunctions) => {
|
|
44010
|
+
if (!isFunctionLike$1(functionNode) || visitedFunctions.has(functionNode)) return false;
|
|
44011
|
+
visitedFunctions.add(functionNode);
|
|
44012
|
+
let didFindReachableHook = false;
|
|
44013
|
+
walkAst(functionNode.body, (child) => {
|
|
44014
|
+
if (didFindReachableHook) return false;
|
|
44015
|
+
if (isFunctionLike$1(child) && !executesDuringRender(child, context.scopes)) return false;
|
|
44016
|
+
if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
|
|
44017
|
+
if (isNodeOfType(child, "CallExpression")) {
|
|
44018
|
+
if (isHookCallee(child.callee)) {
|
|
44019
|
+
didFindReachableHook = true;
|
|
44020
|
+
return false;
|
|
44021
|
+
}
|
|
44022
|
+
const calledFunction = resolveExactLocalFunction(child.callee, context.scopes);
|
|
44023
|
+
if (calledFunction && isAstDescendant(calledFunction, rootFunction) && containsReachableHookCall(calledFunction, rootFunction, context, visitedFunctions)) {
|
|
44024
|
+
didFindReachableHook = true;
|
|
44025
|
+
return false;
|
|
44026
|
+
}
|
|
44027
|
+
}
|
|
44028
|
+
for (const callArgument of child.arguments ?? []) {
|
|
44029
|
+
if (!executesDuringRender(callArgument, context.scopes)) continue;
|
|
44030
|
+
const callbackFunction = resolveExactLocalFunction(callArgument, context.scopes);
|
|
44031
|
+
if (callbackFunction && isAstDescendant(callbackFunction, rootFunction) && containsReachableHookCall(callbackFunction, rootFunction, context, visitedFunctions)) {
|
|
44032
|
+
didFindReachableHook = true;
|
|
44033
|
+
return false;
|
|
44034
|
+
}
|
|
44035
|
+
}
|
|
43840
44036
|
});
|
|
43841
|
-
return
|
|
44037
|
+
return didFindReachableHook;
|
|
43842
44038
|
};
|
|
43843
|
-
const isHookCallingRenderHelper = (symbol) => {
|
|
44039
|
+
const isHookCallingRenderHelper = (symbol, context) => {
|
|
43844
44040
|
if (!symbol) return false;
|
|
43845
44041
|
const declaration = symbol.declarationNode;
|
|
43846
44042
|
if (!isNodeOfType(declaration, "FunctionDeclaration") && !isNodeOfType(declaration, "VariableDeclarator")) return false;
|
|
43847
|
-
const
|
|
43848
|
-
if (!
|
|
43849
|
-
return
|
|
44043
|
+
const functionNode = getFunctionFromDeclaration(declaration);
|
|
44044
|
+
if (!functionNode) return false;
|
|
44045
|
+
return containsReachableHookCall(functionNode, functionNode, context, /* @__PURE__ */ new Set());
|
|
43850
44046
|
};
|
|
43851
44047
|
const noRenderInRender = defineRule({
|
|
43852
44048
|
id: "no-render-in-render",
|
|
@@ -43861,7 +44057,7 @@ const noRenderInRender = defineRule({
|
|
|
43861
44057
|
const calleeName = expression.callee.name;
|
|
43862
44058
|
if (!RENDER_FUNCTION_PATTERN.test(calleeName)) return;
|
|
43863
44059
|
if (!isInsideComponentContext(node)) return;
|
|
43864
|
-
if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee))) return;
|
|
44060
|
+
if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee), context)) return;
|
|
43865
44061
|
context.report({
|
|
43866
44062
|
node: expression,
|
|
43867
44063
|
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.`
|
|
@@ -62844,29 +63040,19 @@ const serverNoMutableModuleState = defineRule({
|
|
|
62844
63040
|
});
|
|
62845
63041
|
//#endregion
|
|
62846
63042
|
//#region src/plugin/rules/server/server-sequential-independent-await.ts
|
|
62847
|
-
const collectDeclaredNames = (declaration) => {
|
|
62848
|
-
const names = /* @__PURE__ */ new Set();
|
|
62849
|
-
if (!isNodeOfType(declaration, "VariableDeclaration")) return names;
|
|
62850
|
-
for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
|
|
62851
|
-
return names;
|
|
62852
|
-
};
|
|
62853
63043
|
const declarationStartsWithAwait = (declaration) => {
|
|
62854
63044
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
62855
63045
|
for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator.init, "AwaitExpression")) return true;
|
|
62856
63046
|
return false;
|
|
62857
63047
|
};
|
|
62858
|
-
const
|
|
62859
|
-
if (
|
|
63048
|
+
const declarationReadsAnyPatternBinding = (declaration, patterns, context) => {
|
|
63049
|
+
if (patterns.length === 0) return false;
|
|
62860
63050
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
62861
|
-
let didRead = false;
|
|
62862
63051
|
for (const declarator of declaration.declarations ?? []) {
|
|
62863
63052
|
if (!declarator.init) continue;
|
|
62864
|
-
|
|
62865
|
-
if (didRead) return;
|
|
62866
|
-
if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
|
|
62867
|
-
});
|
|
63053
|
+
if (expressionReadsPatternBinding(declarator.init, patterns, context.scopes)) return true;
|
|
62868
63054
|
}
|
|
62869
|
-
return
|
|
63055
|
+
return false;
|
|
62870
63056
|
};
|
|
62871
63057
|
const GATE_LEADING_VERBS = new Set([
|
|
62872
63058
|
"require",
|
|
@@ -62940,11 +63126,11 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
62940
63126
|
const currentStatement = statements[statementIndex];
|
|
62941
63127
|
if (!isNodeOfType(currentStatement, "VariableDeclaration")) continue;
|
|
62942
63128
|
if (!declarationStartsWithAwait(currentStatement)) continue;
|
|
62943
|
-
const
|
|
63129
|
+
const declaredPatterns = currentStatement.declarations.map((declarator) => declarator.id);
|
|
62944
63130
|
const nextStatement = statements[statementIndex + 1];
|
|
62945
63131
|
if (!isNodeOfType(nextStatement, "VariableDeclaration")) continue;
|
|
62946
63132
|
if (!declarationStartsWithAwait(nextStatement)) continue;
|
|
62947
|
-
if (
|
|
63133
|
+
if (declarationReadsAnyPatternBinding(nextStatement, declaredPatterns, context)) continue;
|
|
62948
63134
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
62949
63135
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
62950
63136
|
if (declarationAwaitsGate(currentStatement, context)) continue;
|