oxlint-plugin-react-doctor 0.7.9-dev.c7f61cd → 0.7.9-dev.c88a39a
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 +34 -225
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -579,22 +579,6 @@ const EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS = new Set([
|
|
|
579
579
|
"ResizeObserver",
|
|
580
580
|
"PerformanceObserver"
|
|
581
581
|
]);
|
|
582
|
-
const EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES = new Set([
|
|
583
|
-
"blur",
|
|
584
|
-
"focus",
|
|
585
|
-
"getBoundingClientRect",
|
|
586
|
-
"getClientRects",
|
|
587
|
-
"measure",
|
|
588
|
-
"measureInWindow",
|
|
589
|
-
"measureLayout",
|
|
590
|
-
"scroll",
|
|
591
|
-
"scrollBy",
|
|
592
|
-
"scrollIntoView",
|
|
593
|
-
"scrollTo",
|
|
594
|
-
"select",
|
|
595
|
-
"setRangeText",
|
|
596
|
-
"setSelectionRange"
|
|
597
|
-
]);
|
|
598
582
|
const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
|
|
599
583
|
//#endregion
|
|
600
584
|
//#region src/plugin/constants/react.ts
|
|
@@ -28485,11 +28469,7 @@ const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs
|
|
|
28485
28469
|
}));
|
|
28486
28470
|
//#endregion
|
|
28487
28471
|
//#region src/plugin/rules/state-and-effects/utils/effect/react.ts
|
|
28488
|
-
const
|
|
28489
|
-
"memo",
|
|
28490
|
-
"forwardRef",
|
|
28491
|
-
"observer"
|
|
28492
|
-
]);
|
|
28472
|
+
const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
28493
28473
|
const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
|
|
28494
28474
|
const isReactFunctionalComponent = (node) => {
|
|
28495
28475
|
if (!node) return false;
|
|
@@ -28511,7 +28491,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
28511
28491
|
const isWrappedInline = () => {
|
|
28512
28492
|
if (!isNodeOfType(init, "CallExpression")) return false;
|
|
28513
28493
|
if (!isNodeOfType(init.callee, "Identifier")) return false;
|
|
28514
|
-
if (
|
|
28494
|
+
if (KNOWN_PURE_HOC_NAMES.has(init.callee.name)) return false;
|
|
28515
28495
|
const firstArg = init.arguments?.[0];
|
|
28516
28496
|
if (!firstArg) return false;
|
|
28517
28497
|
return isNodeOfType(firstArg, "ArrowFunctionExpression") || isNodeOfType(firstArg, "FunctionExpression");
|
|
@@ -28531,7 +28511,7 @@ const isReactFunctionalHOC = (analysis, node) => {
|
|
|
28531
28511
|
if (!args.includes(refId)) continue;
|
|
28532
28512
|
const callee = parent.callee;
|
|
28533
28513
|
const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "CallExpression") && isNodeOfType(callee.callee, "Identifier") ? callee.callee.name : null;
|
|
28534
|
-
if (calleeName != null && !
|
|
28514
|
+
if (calleeName != null && !KNOWN_PURE_HOC_NAMES.has(calleeName)) return true;
|
|
28535
28515
|
}
|
|
28536
28516
|
return false;
|
|
28537
28517
|
};
|
|
@@ -32959,18 +32939,6 @@ const isProvenIntrinsicJsxElement = (openingElement, scopes) => {
|
|
|
32959
32939
|
return isIntrinsicValue(openingElement.name);
|
|
32960
32940
|
};
|
|
32961
32941
|
//#endregion
|
|
32962
|
-
//#region src/plugin/utils/is-inline-intrinsic-ref-callback.ts
|
|
32963
|
-
const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
|
|
32964
|
-
const functionExpression = findTransparentExpressionRoot(functionNode);
|
|
32965
|
-
if (!isFunctionLike$1(functionExpression) || functionExpression.async || functionExpression.generator) return false;
|
|
32966
|
-
const container = functionExpression.parent;
|
|
32967
|
-
if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
|
|
32968
|
-
const attribute = container.parent;
|
|
32969
|
-
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
32970
|
-
const openingElement = attribute.parent;
|
|
32971
|
-
return Boolean(openingElement && isNodeOfType(openingElement, "JSXOpeningElement") && isProvenIntrinsicJsxElement(openingElement, scopes));
|
|
32972
|
-
};
|
|
32973
|
-
//#endregion
|
|
32974
32942
|
//#region src/plugin/rules/react-builtins/is-safe-create-ref-callback-current-write.ts
|
|
32975
32943
|
const pathStartsWith$1 = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
|
|
32976
32944
|
const collectMemberExpression = (identifier) => {
|
|
@@ -32981,6 +32949,16 @@ const collectMemberExpression = (identifier) => {
|
|
|
32981
32949
|
}
|
|
32982
32950
|
return expression;
|
|
32983
32951
|
};
|
|
32952
|
+
const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
|
|
32953
|
+
const functionExpression = findTransparentExpressionRoot(functionNode);
|
|
32954
|
+
if (!isFunctionLike$1(functionExpression) || functionExpression.async || functionExpression.generator) return false;
|
|
32955
|
+
const container = functionExpression.parent;
|
|
32956
|
+
if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
|
|
32957
|
+
const attribute = container.parent;
|
|
32958
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
32959
|
+
const openingElement = attribute.parent;
|
|
32960
|
+
return Boolean(openingElement && isNodeOfType(openingElement, "JSXOpeningElement") && isProvenIntrinsicJsxElement(openingElement, scopes));
|
|
32961
|
+
};
|
|
32984
32962
|
const isSafeCreateRefCallbackCurrentWrite = (referenceNode, accessedPropertyPath, targetPropertyPath, scopes) => {
|
|
32985
32963
|
if (accessedPropertyPath.length !== targetPropertyPath.length + 1 || !pathStartsWith$1(accessedPropertyPath, targetPropertyPath) || accessedPropertyPath[targetPropertyPath.length] !== "current") return false;
|
|
32986
32964
|
const memberExpression = collectMemberExpression(referenceNode);
|
|
@@ -36087,15 +36065,6 @@ const canStateWriteReachReaderWork = (writeInfo, readerEffect, stateSymbolId, sc
|
|
|
36087
36065
|
return false;
|
|
36088
36066
|
};
|
|
36089
36067
|
const EMPTY_CLEANUP_NAME_SET = /* @__PURE__ */ new Set();
|
|
36090
|
-
const NON_CONTAMINATING_MAP_METHOD_NAMES = new Set([
|
|
36091
|
-
"clear",
|
|
36092
|
-
"delete",
|
|
36093
|
-
"entries",
|
|
36094
|
-
"get",
|
|
36095
|
-
"has",
|
|
36096
|
-
"keys",
|
|
36097
|
-
"values"
|
|
36098
|
-
]);
|
|
36099
36068
|
const isFunctionShapedReturn = (returnedValue, setterToStateName, isExplicitReturnStatement) => {
|
|
36100
36069
|
if (isNodeOfType(returnedValue, "ArrowFunctionExpression") || isNodeOfType(returnedValue, "FunctionExpression")) return true;
|
|
36101
36070
|
if (isNodeOfType(returnedValue, "CallExpression")) {
|
|
@@ -36146,135 +36115,27 @@ const callsOpaqueExternalSetter = (analysisFunctions, setterToStateName) => {
|
|
|
36146
36115
|
});
|
|
36147
36116
|
return didFindOpaqueSetterCall;
|
|
36148
36117
|
};
|
|
36149
|
-
const isReactRefCall = (expression, scopes) => isNodeOfType(expression, "CallExpression") && (isReactApiCall(expression, "useRef", scopes, {
|
|
36150
|
-
allowGlobalReactNamespace: true,
|
|
36151
|
-
allowUnboundBareCalls: true,
|
|
36152
|
-
resolveNamedAliases: true
|
|
36153
|
-
}) || isReactApiCall(expression, "createRef", scopes, {
|
|
36154
|
-
allowGlobalReactNamespace: true,
|
|
36155
|
-
allowUnboundBareCalls: true,
|
|
36156
|
-
resolveNamedAliases: true
|
|
36157
|
-
}));
|
|
36158
|
-
const getDirectReactRefSymbol = (rawExpression, scopes) => {
|
|
36159
|
-
const expression = stripParenExpression(rawExpression);
|
|
36160
|
-
if (!isNodeOfType(expression, "Identifier")) return null;
|
|
36161
|
-
const symbol = scopes.symbolFor(expression);
|
|
36162
|
-
if (!symbol) return null;
|
|
36163
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
36164
|
-
return initializer && isReactRefCall(stripParenExpression(initializer), scopes) ? symbol : null;
|
|
36165
|
-
};
|
|
36166
|
-
const isReactNativeJsxElement = (openingElement, scopes) => {
|
|
36167
|
-
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
|
|
36168
|
-
const symbol = scopes.symbolFor(openingElement.name);
|
|
36169
|
-
const importDeclaration = symbol?.declarationNode.parent;
|
|
36170
|
-
return Boolean(symbol?.kind === "import" && importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "react-native");
|
|
36171
|
-
};
|
|
36172
|
-
const isDirectHostJsxRef = (symbol, scopes) => {
|
|
36173
|
-
let hostRefCount = 0;
|
|
36174
|
-
for (const reference of symbol.references) {
|
|
36175
|
-
const expression = findTransparentExpressionRoot(reference.identifier);
|
|
36176
|
-
const container = expression.parent;
|
|
36177
|
-
if (isNodeOfType(container, "MemberExpression") && container.object === expression && getStaticPropertyName(container) === "current") continue;
|
|
36178
|
-
if (!container || !isNodeOfType(container, "JSXExpressionContainer") || container.expression !== expression) return false;
|
|
36179
|
-
const attribute = container.parent;
|
|
36180
|
-
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
36181
|
-
const openingElement = attribute.parent;
|
|
36182
|
-
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement") || !isProvenIntrinsicJsxElement(openingElement, scopes) && !isReactNativeJsxElement(openingElement, scopes)) return false;
|
|
36183
|
-
hostRefCount += 1;
|
|
36184
|
-
}
|
|
36185
|
-
return hostRefCount > 0;
|
|
36186
|
-
};
|
|
36187
|
-
const isIntrinsicRefCallbackParameter = (expression, scopes) => {
|
|
36188
|
-
const identifier = stripParenExpression(expression);
|
|
36189
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
36190
|
-
const callback = findEnclosingFunction$1(identifier);
|
|
36191
|
-
if (!callback || !isFunctionLike$1(callback) || !isInlineIntrinsicRefCallback(callback, scopes)) return false;
|
|
36192
|
-
const rawFirstParameter = callback.params?.[0];
|
|
36193
|
-
const firstParameter = isNodeOfType(rawFirstParameter, "AssignmentPattern") ? rawFirstParameter.left : rawFirstParameter;
|
|
36194
|
-
const symbol = scopes.symbolFor(identifier);
|
|
36195
|
-
return Boolean(firstParameter && symbol?.bindingIdentifier === firstParameter);
|
|
36196
|
-
};
|
|
36197
|
-
const getDirectReactRefCall = (symbol, scopes) => {
|
|
36198
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
36199
|
-
if (!initializer) return null;
|
|
36200
|
-
const expression = stripParenExpression(initializer);
|
|
36201
|
-
return isNodeOfType(expression, "CallExpression") && isReactRefCall(expression, scopes) ? expression : null;
|
|
36202
|
-
};
|
|
36203
|
-
const storesOnlyIntrinsicRefCallbackValues = (symbol, scopes) => {
|
|
36204
|
-
const initialValue = getDirectReactRefCall(symbol, scopes)?.arguments?.[0];
|
|
36205
|
-
if (!initialValue || !isNodeOfType(initialValue, "NewExpression") || !isNodeOfType(initialValue.callee, "Identifier") || initialValue.callee.name !== "Map" || !scopes.isGlobalReference(initialValue.callee) || initialValue.arguments.length !== 0) return false;
|
|
36206
|
-
let intrinsicValueWriteCount = 0;
|
|
36207
|
-
for (const reference of symbol.references) {
|
|
36208
|
-
const identifier = findTransparentExpressionRoot(reference.identifier);
|
|
36209
|
-
const currentMember = identifier.parent;
|
|
36210
|
-
if (!isNodeOfType(currentMember, "MemberExpression") || currentMember.object !== identifier || getStaticPropertyName(currentMember) !== "current") return false;
|
|
36211
|
-
const currentExpression = findTransparentExpressionRoot(currentMember);
|
|
36212
|
-
const methodMember = currentExpression.parent;
|
|
36213
|
-
if (!isNodeOfType(methodMember, "MemberExpression") || methodMember.object !== currentExpression) return false;
|
|
36214
|
-
const methodName = getStaticPropertyName(methodMember);
|
|
36215
|
-
if (methodName === "size") continue;
|
|
36216
|
-
const call = methodMember.parent;
|
|
36217
|
-
if (!isNodeOfType(call, "CallExpression") || call.callee !== methodMember) return false;
|
|
36218
|
-
if (methodName && NON_CONTAMINATING_MAP_METHOD_NAMES.has(methodName)) continue;
|
|
36219
|
-
if (methodName !== "set") return false;
|
|
36220
|
-
const storedValue = call.arguments[1];
|
|
36221
|
-
if (!storedValue || isNodeOfType(storedValue, "SpreadElement") || !isIntrinsicRefCallbackParameter(storedValue, scopes)) return false;
|
|
36222
|
-
intrinsicValueWriteCount += 1;
|
|
36223
|
-
}
|
|
36224
|
-
return intrinsicValueWriteCount > 0;
|
|
36225
|
-
};
|
|
36226
|
-
const isDerivedFromProvenDomRefCurrent = (rawExpression, scopes, didReadCollectionValue = false, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
36227
|
-
const expression = stripParenExpression(rawExpression);
|
|
36228
|
-
if (isNodeOfType(expression, "Identifier")) {
|
|
36229
|
-
const symbol = scopes.symbolFor(expression);
|
|
36230
|
-
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
36231
|
-
const initializer = getDirectUnreassignedInitializer(symbol);
|
|
36232
|
-
if (!initializer) return false;
|
|
36233
|
-
visitedSymbolIds.add(symbol.id);
|
|
36234
|
-
return isDerivedFromProvenDomRefCurrent(initializer, scopes, didReadCollectionValue, visitedSymbolIds);
|
|
36235
|
-
}
|
|
36236
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
36237
|
-
if (getStaticPropertyName(expression) === "current") {
|
|
36238
|
-
const symbol = getDirectReactRefSymbol(expression.object, scopes);
|
|
36239
|
-
return Boolean(symbol && (isDirectHostJsxRef(symbol, scopes) || didReadCollectionValue && storesOnlyIntrinsicRefCallbackValues(symbol, scopes)));
|
|
36240
|
-
}
|
|
36241
|
-
return isDerivedFromProvenDomRefCurrent(expression.object, scopes, didReadCollectionValue, visitedSymbolIds);
|
|
36242
|
-
}
|
|
36243
|
-
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
36244
|
-
const callee = stripParenExpression(expression.callee);
|
|
36245
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
36246
|
-
return isDerivedFromProvenDomRefCurrent(callee.object, scopes, didReadCollectionValue || getStaticPropertyName(callee) === "get", visitedSymbolIds);
|
|
36247
|
-
};
|
|
36248
|
-
const isCommittedDomSyncNode = (node, scopes) => {
|
|
36249
|
-
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
36250
|
-
const callee = stripParenExpression(node.callee);
|
|
36251
|
-
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
36252
|
-
const propertyName = getStaticPropertyName(callee);
|
|
36253
|
-
if (propertyName === null || !EXTERNAL_SYNC_DOM_MEMBER_METHOD_NAMES.has(propertyName)) return false;
|
|
36254
|
-
return isDerivedFromProvenDomRefCurrent(callee.object, scopes) || isProvenBrowserApiReceiver(callee.object, "dom-event-target", scopes);
|
|
36255
|
-
};
|
|
36256
36118
|
const isExternalSyncNode = (node) => {
|
|
36257
36119
|
if (isNodeOfType(node, "NewExpression")) return isNodeOfType(node.callee, "Identifier") && EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS.has(node.callee.name);
|
|
36258
36120
|
if (isNodeOfType(node, "AssignmentExpression")) return isNodeOfType(node.left, "MemberExpression") && isNodeOfType(node.left.property, "Identifier") && node.left.property.name === "current";
|
|
36259
36121
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
36260
36122
|
if (isNodeOfType(node.callee, "Identifier")) return EXTERNAL_SYNC_DIRECT_CALLEE_NAMES.has(node.callee.name);
|
|
36261
|
-
if (!isNodeOfType(node.callee, "MemberExpression")) return false;
|
|
36262
|
-
const propertyName =
|
|
36263
|
-
if (propertyName === null) return false;
|
|
36123
|
+
if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return false;
|
|
36124
|
+
const propertyName = node.callee.property.name;
|
|
36264
36125
|
if (EXTERNAL_SYNC_MEMBER_METHOD_NAMES.has(propertyName)) return true;
|
|
36265
36126
|
if (isBrowserStorageReceiver(node.callee.object)) return true;
|
|
36266
36127
|
if (!EXTERNAL_SYNC_AMBIGUOUS_HTTP_METHOD_NAMES.has(propertyName)) return false;
|
|
36267
36128
|
const receiverRootName = getRootIdentifierName(node.callee.object);
|
|
36268
36129
|
return receiverRootName !== null && EXTERNAL_SYNC_HTTP_CLIENT_RECEIVERS.has(receiverRootName);
|
|
36269
36130
|
};
|
|
36270
|
-
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName
|
|
36131
|
+
const isExternalSyncEffect = (effectCallback, analysisFunctions, setterToStateName) => {
|
|
36271
36132
|
if (!isFunctionLike$1(effectCallback)) return false;
|
|
36272
36133
|
if (!isNodeOfType(effectCallback.body, "BlockStatement")) {
|
|
36273
36134
|
if (isFunctionShapedReturn(effectCallback.body, setterToStateName, false)) return true;
|
|
36274
36135
|
} else for (const statement of effectCallback.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument && isFunctionShapedReturn(statement.argument, setterToStateName, true)) return true;
|
|
36275
36136
|
let didFindExternalCall = false;
|
|
36276
36137
|
visitSynchronousFunctionBodies(analysisFunctions, (child) => {
|
|
36277
|
-
if (isExternalSyncNode(child)
|
|
36138
|
+
if (isExternalSyncNode(child)) didFindExternalCall = true;
|
|
36278
36139
|
});
|
|
36279
36140
|
return didFindExternalCall;
|
|
36280
36141
|
};
|
|
@@ -36313,7 +36174,7 @@ const noEffectChain = defineRule({
|
|
|
36313
36174
|
depNames: collectDepIdentifierNames(effectCall),
|
|
36314
36175
|
stateWrites,
|
|
36315
36176
|
analysisFunctions,
|
|
36316
|
-
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName
|
|
36177
|
+
isExternalSync: isExternalSyncEffect(callback, analysisFunctions, setterToStateName) || callsStorageHookSetter(analysisFunctions, storageSetterNames) || writtenStateNames.size === 0 && callsOpaqueExternalSetter(analysisFunctions, setterToStateName)
|
|
36317
36178
|
});
|
|
36318
36179
|
}
|
|
36319
36180
|
if (effectInfos.length < 2) return;
|
|
@@ -39995,18 +39856,6 @@ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
|
39995
39856
|
return containsReactHookCall;
|
|
39996
39857
|
};
|
|
39997
39858
|
//#endregion
|
|
39998
|
-
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39999
|
-
const isNullExpression = (expression) => {
|
|
40000
|
-
const candidate = stripParenExpression(expression);
|
|
40001
|
-
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
40002
|
-
};
|
|
40003
|
-
const functionReturnsOnlyNull = (functionNode) => {
|
|
40004
|
-
if (!isFunctionLike$1(functionNode)) return false;
|
|
40005
|
-
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
40006
|
-
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
40007
|
-
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
40008
|
-
};
|
|
40009
|
-
//#endregion
|
|
40010
39859
|
//#region src/plugin/utils/function-returns-props-children.ts
|
|
40011
39860
|
const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
40012
39861
|
if (!isFunctionLike$1(functionNode) || functionNode.params.length === 0) return false;
|
|
@@ -40039,8 +39888,17 @@ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
|
|
|
40039
39888
|
}, controlFlow);
|
|
40040
39889
|
};
|
|
40041
39890
|
//#endregion
|
|
40042
|
-
//#region src/plugin/utils/function-
|
|
40043
|
-
const
|
|
39891
|
+
//#region src/plugin/utils/function-returns-only-null.ts
|
|
39892
|
+
const isNullExpression = (expression) => {
|
|
39893
|
+
const candidate = stripParenExpression(expression);
|
|
39894
|
+
return isNodeOfType(candidate, "Literal") && candidate.value === null;
|
|
39895
|
+
};
|
|
39896
|
+
const functionReturnsOnlyNull = (functionNode) => {
|
|
39897
|
+
if (!isFunctionLike$1(functionNode)) return false;
|
|
39898
|
+
if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
|
|
39899
|
+
const returnStatements = collectFunctionReturnStatements(functionNode);
|
|
39900
|
+
return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
|
|
39901
|
+
};
|
|
40044
39902
|
//#endregion
|
|
40045
39903
|
//#region src/plugin/utils/is-proven-styled-component-expression.ts
|
|
40046
39904
|
const findFactoryRoot = (node) => {
|
|
@@ -40073,16 +39931,17 @@ const isProvenStyledComponentExpression = (expression, scopes) => {
|
|
|
40073
39931
|
//#region src/plugin/utils/is-proven-react-component-symbol.ts
|
|
40074
39932
|
const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
40075
39933
|
const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
|
|
39934
|
+
const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
|
|
40076
39935
|
const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
40077
39936
|
const candidate = stripParenExpression(expression);
|
|
40078
|
-
if (isInlineFunctionExpression(candidate)) return
|
|
39937
|
+
if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
|
|
40079
39938
|
if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
|
|
40080
39939
|
if (isProvenStyledComponentExpression(candidate, scopes)) return true;
|
|
40081
39940
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
40082
39941
|
const symbol = scopes.symbolFor(candidate);
|
|
40083
39942
|
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
|
|
40084
39943
|
visitedSymbolIds.add(symbol.id);
|
|
40085
|
-
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return
|
|
39944
|
+
if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
|
|
40086
39945
|
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
|
|
40087
39946
|
return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
|
|
40088
39947
|
}
|
|
@@ -40109,7 +39968,7 @@ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentRefe
|
|
|
40109
39968
|
for (const candidateSymbol of candidateSymbols) {
|
|
40110
39969
|
if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
|
|
40111
39970
|
if (isComponentDeclaration(candidateSymbol.declarationNode)) {
|
|
40112
|
-
if (
|
|
39971
|
+
if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
|
|
40113
39972
|
continue;
|
|
40114
39973
|
}
|
|
40115
39974
|
const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
|
|
@@ -43895,53 +43754,6 @@ const noPropCallbackInEffect = defineRule({
|
|
|
43895
43754
|
});
|
|
43896
43755
|
//#endregion
|
|
43897
43756
|
//#region src/plugin/rules/state-and-effects/no-prop-callback-in-render.ts
|
|
43898
|
-
const functionBindingSymbols = (functionNode, scopes) => {
|
|
43899
|
-
let bindingIdentifier = null;
|
|
43900
|
-
if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) bindingIdentifier = functionNode.id;
|
|
43901
|
-
else {
|
|
43902
|
-
let bindingExpression = findTransparentExpressionRoot(functionNode);
|
|
43903
|
-
let parent = bindingExpression.parent;
|
|
43904
|
-
while (isNodeOfType(parent, "CallExpression") && parent.arguments[0] === bindingExpression) {
|
|
43905
|
-
const callee = parent.callee;
|
|
43906
|
-
const wrapperName = isNodeOfType(callee, "Identifier") ? callee.name : isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") ? callee.property.name : null;
|
|
43907
|
-
if (!isReactApiCall(parent, REACT_HOC_NAMES, scopes, {
|
|
43908
|
-
allowGlobalReactNamespace: true,
|
|
43909
|
-
resolveNamedAliases: true
|
|
43910
|
-
}) && (!wrapperName || REACT_HOC_NAMES.has(wrapperName) || !COMPONENT_HOC_WRAPPER_NAMES.has(wrapperName))) break;
|
|
43911
|
-
bindingExpression = findTransparentExpressionRoot(parent);
|
|
43912
|
-
parent = bindingExpression.parent;
|
|
43913
|
-
}
|
|
43914
|
-
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === bindingExpression && isNodeOfType(parent.id, "Identifier")) bindingIdentifier = parent.id;
|
|
43915
|
-
}
|
|
43916
|
-
if (!bindingIdentifier) return [];
|
|
43917
|
-
let scope = scopes.scopeFor(functionNode);
|
|
43918
|
-
while (scope) {
|
|
43919
|
-
const symbols = scope.symbols.filter((symbol) => symbol.bindingIdentifier === bindingIdentifier);
|
|
43920
|
-
if (symbols.length > 0) return symbols;
|
|
43921
|
-
scope = scope.parent;
|
|
43922
|
-
}
|
|
43923
|
-
return [];
|
|
43924
|
-
};
|
|
43925
|
-
const symbolHasReactComponentUse = (symbol, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
43926
|
-
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
43927
|
-
visitedSymbolIds.add(symbol.id);
|
|
43928
|
-
for (const reference of symbol.references) {
|
|
43929
|
-
const identifier = reference.identifier;
|
|
43930
|
-
if (hasSymbolWriteBefore(symbol, identifier, scopes)) continue;
|
|
43931
|
-
const parent = identifier.parent;
|
|
43932
|
-
if (isNodeOfType(parent, "JSXOpeningElement") && isNodeOfType(parent.name, "JSXIdentifier") && parent.name === identifier) return true;
|
|
43933
|
-
const expression = findTransparentExpressionRoot(identifier);
|
|
43934
|
-
const expressionParent = expression.parent;
|
|
43935
|
-
if (isNodeOfType(expressionParent, "CallExpression") && expressionParent.arguments[0] === expression && isReactApiCall(expressionParent, "createElement", scopes, { resolveNamedAliases: true })) return true;
|
|
43936
|
-
if (!isNodeOfType(expressionParent, "VariableDeclarator") || expressionParent.init !== expression || !isNodeOfType(expressionParent.id, "Identifier") || !isNodeOfType(expressionParent.parent, "VariableDeclaration") || expressionParent.parent.kind !== "const") continue;
|
|
43937
|
-
const aliasSymbol = scopes.symbolFor(expressionParent.id);
|
|
43938
|
-
if (aliasSymbol && symbolHasReactComponentUse(aliasSymbol, scopes, visitedSymbolIds)) return true;
|
|
43939
|
-
}
|
|
43940
|
-
return false;
|
|
43941
|
-
};
|
|
43942
|
-
const functionHasReactComponentUse = (functionNode, scopes) => {
|
|
43943
|
-
return functionBindingSymbols(functionNode, scopes).some((symbol) => symbolHasReactComponentUse(symbol, scopes));
|
|
43944
|
-
};
|
|
43945
43757
|
const isPreservedThroughConciseArrow = (callExpression, scopes) => {
|
|
43946
43758
|
let node = callExpression;
|
|
43947
43759
|
let parent = node.parent;
|
|
@@ -43988,10 +43800,7 @@ const noPropCallbackInRender = defineRule({
|
|
|
43988
43800
|
create: (context) => ({ CallExpression(node) {
|
|
43989
43801
|
if (!isResultDiscardedCall(node)) return;
|
|
43990
43802
|
if (isPreservedThroughConciseArrow(node, context.scopes)) return;
|
|
43991
|
-
|
|
43992
|
-
if (!renderPhaseOwner) return;
|
|
43993
|
-
const renderPhaseOwnerName = componentOrHookDisplayNameForFunction(renderPhaseOwner);
|
|
43994
|
-
if (!renderPhaseOwnerName || !isReactHookName(renderPhaseOwnerName) && !functionHasReactComponentEvidence(renderPhaseOwner, context.scopes, context.cfg) && !functionHasReactComponentUse(renderPhaseOwner, context.scopes)) return;
|
|
43803
|
+
if (!findRenderPhaseComponentOrHook(node, context.scopes)) return;
|
|
43995
43804
|
const analysis = getProgramAnalysis(node);
|
|
43996
43805
|
if (!analysis) return;
|
|
43997
43806
|
const callee = stripParenExpression(node.callee);
|