oxlint-plugin-react-doctor 0.7.5-dev.3aef25a → 0.7.5-dev.3afd146
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 +126 -351
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5934,21 +5934,6 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5934
5934
|
}
|
|
5935
5935
|
});
|
|
5936
5936
|
//#endregion
|
|
5937
|
-
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
5938
|
-
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
5939
|
-
if (!isNodeOfType(node, "Property")) return null;
|
|
5940
|
-
if (node.computed) {
|
|
5941
|
-
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
5942
|
-
return null;
|
|
5943
|
-
}
|
|
5944
|
-
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
5945
|
-
if (isNodeOfType(node.key, "Literal")) {
|
|
5946
|
-
if (typeof node.key.value === "string") return node.key.value;
|
|
5947
|
-
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
5948
|
-
}
|
|
5949
|
-
return null;
|
|
5950
|
-
};
|
|
5951
|
-
//#endregion
|
|
5952
5937
|
//#region src/plugin/utils/is-presentation-role.ts
|
|
5953
5938
|
const isPresentationRole = (openingElement) => {
|
|
5954
5939
|
const roleAttribute = hasJsxPropIgnoreCase(openingElement.attributes, "role");
|
|
@@ -5993,21 +5978,6 @@ const isPureEventBlockerHandler = (attribute) => {
|
|
|
5993
5978
|
return false;
|
|
5994
5979
|
};
|
|
5995
5980
|
//#endregion
|
|
5996
|
-
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
5997
|
-
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
5998
|
-
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
5999
|
-
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
6000
|
-
let symbol = scopes.symbolFor(identifier);
|
|
6001
|
-
while (symbol?.kind === "const") {
|
|
6002
|
-
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
6003
|
-
visitedSymbolIds.add(symbol.id);
|
|
6004
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
6005
|
-
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
6006
|
-
symbol = scopes.symbolFor(initializer);
|
|
6007
|
-
}
|
|
6008
|
-
return symbol;
|
|
6009
|
-
};
|
|
6010
|
-
//#endregion
|
|
6011
5981
|
//#region src/plugin/rules/a11y/click-events-have-key-events.ts
|
|
6012
5982
|
const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
|
|
6013
5983
|
const KEY_HANDLERS = [
|
|
@@ -6018,63 +5988,6 @@ const KEY_HANDLERS = [
|
|
|
6018
5988
|
"onKeyDownCapture",
|
|
6019
5989
|
"onKeyPressCapture"
|
|
6020
5990
|
];
|
|
6021
|
-
const CLICK_HANDLERS = ["onClick", "onClickCapture"];
|
|
6022
|
-
const TRANSPARENT_SPREAD_EVENT_NAMES = new Set([...CLICK_HANDLERS, ...KEY_HANDLERS].map((eventName) => eventName.toLowerCase()));
|
|
6023
|
-
const CONSERVATIVE_SPREAD_PROP_NAMES = new Set([
|
|
6024
|
-
"aria-hidden",
|
|
6025
|
-
"onmouseenter",
|
|
6026
|
-
"onmouseover",
|
|
6027
|
-
"role"
|
|
6028
|
-
]);
|
|
6029
|
-
const resolveSpreadObjectExpression = (expression, scopes) => {
|
|
6030
|
-
const innerExpression = stripParenExpression(expression);
|
|
6031
|
-
if (isNodeOfType(innerExpression, "ObjectExpression")) return innerExpression;
|
|
6032
|
-
if (!isNodeOfType(innerExpression, "Identifier")) return null;
|
|
6033
|
-
const symbol = resolveConstIdentifierAlias(innerExpression, scopes);
|
|
6034
|
-
if (symbol?.kind !== "const" || !symbol.initializer) return null;
|
|
6035
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
6036
|
-
return isNodeOfType(initializer, "ObjectExpression") ? initializer : null;
|
|
6037
|
-
};
|
|
6038
|
-
const collectTransparentSpreadEventNames = (expression, scopes, eventValues, visitedObjectExpressions) => {
|
|
6039
|
-
const objectExpression = resolveSpreadObjectExpression(expression, scopes);
|
|
6040
|
-
if (!objectExpression || visitedObjectExpressions.has(objectExpression)) return false;
|
|
6041
|
-
visitedObjectExpressions.add(objectExpression);
|
|
6042
|
-
let isTransparent = true;
|
|
6043
|
-
for (const property of objectExpression.properties) {
|
|
6044
|
-
if (isNodeOfType(property, "SpreadElement")) {
|
|
6045
|
-
if (!collectTransparentSpreadEventNames(property.argument, scopes, eventValues, visitedObjectExpressions)) {
|
|
6046
|
-
isTransparent = false;
|
|
6047
|
-
break;
|
|
6048
|
-
}
|
|
6049
|
-
continue;
|
|
6050
|
-
}
|
|
6051
|
-
if (!isNodeOfType(property, "Property")) {
|
|
6052
|
-
isTransparent = false;
|
|
6053
|
-
break;
|
|
6054
|
-
}
|
|
6055
|
-
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
6056
|
-
if (!propertyName) {
|
|
6057
|
-
isTransparent = false;
|
|
6058
|
-
break;
|
|
6059
|
-
}
|
|
6060
|
-
const normalizedPropertyName = propertyName.toLowerCase();
|
|
6061
|
-
if (CONSERVATIVE_SPREAD_PROP_NAMES.has(normalizedPropertyName)) {
|
|
6062
|
-
isTransparent = false;
|
|
6063
|
-
break;
|
|
6064
|
-
}
|
|
6065
|
-
if (TRANSPARENT_SPREAD_EVENT_NAMES.has(normalizedPropertyName)) eventValues.set(normalizedPropertyName, property.value);
|
|
6066
|
-
}
|
|
6067
|
-
visitedObjectExpressions.delete(objectExpression);
|
|
6068
|
-
return isTransparent;
|
|
6069
|
-
};
|
|
6070
|
-
const getTransparentSpreadEventValues = (attributes, scopes) => {
|
|
6071
|
-
const eventValues = /* @__PURE__ */ new Map();
|
|
6072
|
-
for (const attribute of attributes) {
|
|
6073
|
-
if (!isNodeOfType(attribute, "JSXSpreadAttribute")) continue;
|
|
6074
|
-
if (!collectTransparentSpreadEventNames(attribute.argument, scopes, eventValues, /* @__PURE__ */ new Set())) return null;
|
|
6075
|
-
}
|
|
6076
|
-
return eventValues;
|
|
6077
|
-
};
|
|
6078
5991
|
const FOCUSLESS_CONTAINER_TAGS = new Set([
|
|
6079
5992
|
"tr",
|
|
6080
5993
|
"td",
|
|
@@ -6122,10 +6035,7 @@ const isFocusForwardingFunctionBody = (body) => {
|
|
|
6122
6035
|
};
|
|
6123
6036
|
const resolveHandlerFunction = (attribute) => {
|
|
6124
6037
|
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
6125
|
-
|
|
6126
|
-
};
|
|
6127
|
-
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
6128
|
-
let expression = handlerExpression;
|
|
6038
|
+
let expression = attribute.value.expression;
|
|
6129
6039
|
if (isNodeOfType(expression, "Identifier")) {
|
|
6130
6040
|
const binding = findVariableInitializer(expression, expression.name);
|
|
6131
6041
|
if (!binding?.initializer) return null;
|
|
@@ -6224,22 +6134,18 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
6224
6134
|
if (!HTML_TAGS.has(tag)) return;
|
|
6225
6135
|
if (tag === "label") return;
|
|
6226
6136
|
if (!FOCUSLESS_CONTAINER_TAGS.has(tag) && isInteractiveElement(tag, node)) return;
|
|
6227
|
-
const spreadEventValues = getTransparentSpreadEventValues(node.attributes, context.scopes);
|
|
6228
|
-
if (!spreadEventValues) return;
|
|
6229
6137
|
const onClick = hasJsxPropIgnoreCase(node.attributes, "onClick") ?? hasJsxPropIgnoreCase(node.attributes, "onClickCapture");
|
|
6230
|
-
|
|
6231
|
-
if (
|
|
6232
|
-
if (
|
|
6233
|
-
if (
|
|
6234
|
-
const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
|
|
6235
|
-
if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
|
|
6138
|
+
if (!onClick) return;
|
|
6139
|
+
if (isPureEventBlockerHandler(onClick)) return;
|
|
6140
|
+
if (isFocusForwardingHandler(onClick)) return;
|
|
6141
|
+
if (hasJsxSpreadAttribute$1(node.attributes)) return;
|
|
6236
6142
|
if (hasCompositeItemRole(node)) return;
|
|
6237
6143
|
if (isHoverSelectionListItem(tag, node)) return;
|
|
6238
|
-
if (
|
|
6144
|
+
if (isBackdropDismissHandler(onClick)) return;
|
|
6239
6145
|
if (containsKeyboardActivatableDescendant(node.parent)) return;
|
|
6240
6146
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
6241
6147
|
if (isPresentationRole(node)) return;
|
|
6242
|
-
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler)
|
|
6148
|
+
if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler))) return;
|
|
6243
6149
|
context.report({
|
|
6244
6150
|
node: node.name,
|
|
6245
6151
|
message: MESSAGE$57
|
|
@@ -7712,7 +7618,7 @@ const isCreateClassLikeCall = (node) => {
|
|
|
7712
7618
|
if (isNodeOfType(callee, "MemberExpression")) return getStaticMemberName$2(callee) === "createClass";
|
|
7713
7619
|
return false;
|
|
7714
7620
|
};
|
|
7715
|
-
const isCreateContextCall
|
|
7621
|
+
const isCreateContextCall = (node) => {
|
|
7716
7622
|
if (!isNodeOfType(node, "CallExpression")) return false;
|
|
7717
7623
|
const callee = node.callee;
|
|
7718
7624
|
if (isNodeOfType(callee, "Identifier")) return callee.name === "createContext";
|
|
@@ -7889,7 +7795,7 @@ const displayName = defineRule({
|
|
|
7889
7795
|
}
|
|
7890
7796
|
},
|
|
7891
7797
|
CallExpression(node) {
|
|
7892
|
-
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall
|
|
7798
|
+
if (settings.checkContextObjects && isReactVersionAtLeast$1(settings.reactVersion, 16, 3) && isCreateContextCall(node)) {
|
|
7893
7799
|
const assignedName = getAssignedName(node);
|
|
7894
7800
|
if (assignedName) {
|
|
7895
7801
|
const programRoot = findProgramRoot(node);
|
|
@@ -7936,6 +7842,21 @@ const displayName = defineRule({
|
|
|
7936
7842
|
}
|
|
7937
7843
|
});
|
|
7938
7844
|
//#endregion
|
|
7845
|
+
//#region src/plugin/utils/get-static-property-key-name.ts
|
|
7846
|
+
const getStaticPropertyKeyName = (node, options = {}) => {
|
|
7847
|
+
if (!isNodeOfType(node, "Property")) return null;
|
|
7848
|
+
if (node.computed) {
|
|
7849
|
+
if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
|
|
7850
|
+
return null;
|
|
7851
|
+
}
|
|
7852
|
+
if (isNodeOfType(node.key, "Identifier")) return node.key.name;
|
|
7853
|
+
if (isNodeOfType(node.key, "Literal")) {
|
|
7854
|
+
if (typeof node.key.value === "string") return node.key.value;
|
|
7855
|
+
if (options.stringifyNonStringLiterals) return String(node.key.value);
|
|
7856
|
+
}
|
|
7857
|
+
return null;
|
|
7858
|
+
};
|
|
7859
|
+
//#endregion
|
|
7939
7860
|
//#region src/plugin/utils/read-static-boolean.ts
|
|
7940
7861
|
const readStaticBoolean = (node) => {
|
|
7941
7862
|
if (!node) return null;
|
|
@@ -7944,6 +7865,21 @@ const readStaticBoolean = (node) => {
|
|
|
7944
7865
|
return unwrappedNode.value;
|
|
7945
7866
|
};
|
|
7946
7867
|
//#endregion
|
|
7868
|
+
//#region src/plugin/utils/resolve-const-identifier-alias.ts
|
|
7869
|
+
const resolveConstIdentifierAlias = (identifier, scopes) => {
|
|
7870
|
+
if (!isNodeOfType(identifier, "Identifier")) return null;
|
|
7871
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
7872
|
+
let symbol = scopes.symbolFor(identifier);
|
|
7873
|
+
while (symbol?.kind === "const") {
|
|
7874
|
+
if (visitedSymbolIds.has(symbol.id) || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
7875
|
+
visitedSymbolIds.add(symbol.id);
|
|
7876
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
7877
|
+
if (!isNodeOfType(initializer, "Identifier")) return symbol;
|
|
7878
|
+
symbol = scopes.symbolFor(initializer);
|
|
7879
|
+
}
|
|
7880
|
+
return symbol;
|
|
7881
|
+
};
|
|
7882
|
+
//#endregion
|
|
7947
7883
|
//#region src/plugin/utils/is-react-api-call.ts
|
|
7948
7884
|
const includesApiName = (apiNames, apiName) => typeof apiNames === "string" ? apiNames === apiName : apiNames.has(apiName);
|
|
7949
7885
|
const isImportedFromReact = (symbol) => {
|
|
@@ -9070,30 +9006,6 @@ const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, conte
|
|
|
9070
9006
|
}
|
|
9071
9007
|
return matchingBlocks.size > 0;
|
|
9072
9008
|
};
|
|
9073
|
-
const doMatchingNodesCoverEveryPathFromFunctionEntry = (owner, matchingNodes, context) => {
|
|
9074
|
-
const functionCfg = context.cfg.cfgFor(owner);
|
|
9075
|
-
if (!functionCfg) return false;
|
|
9076
|
-
const matchingBlocks = new Set(matchingNodes.flatMap((matchingNode) => {
|
|
9077
|
-
if (context.cfg.enclosingFunction(matchingNode) !== owner) return [];
|
|
9078
|
-
const matchingBlock = functionCfg.blockOf(matchingNode);
|
|
9079
|
-
return matchingBlock ? [matchingBlock] : [];
|
|
9080
|
-
}));
|
|
9081
|
-
if (matchingBlocks.size === 0) return false;
|
|
9082
|
-
const visitedBlocks = new Set([functionCfg.entry]);
|
|
9083
|
-
const pendingBlocks = [functionCfg.entry];
|
|
9084
|
-
while (pendingBlocks.length > 0) {
|
|
9085
|
-
const currentBlock = pendingBlocks.pop();
|
|
9086
|
-
if (!currentBlock) break;
|
|
9087
|
-
if (matchingBlocks.has(currentBlock)) continue;
|
|
9088
|
-
for (const edge of currentBlock.successors) {
|
|
9089
|
-
if (edge.to === functionCfg.exit) return false;
|
|
9090
|
-
if (visitedBlocks.has(edge.to)) continue;
|
|
9091
|
-
visitedBlocks.add(edge.to);
|
|
9092
|
-
pendingBlocks.push(edge.to);
|
|
9093
|
-
}
|
|
9094
|
-
}
|
|
9095
|
-
return true;
|
|
9096
|
-
};
|
|
9097
9009
|
const removeSynchronouslyReleasedUsages = (callback, usages, context) => {
|
|
9098
9010
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return usages;
|
|
9099
9011
|
if (!isNodeOfType(callback.body, "BlockStatement")) return usages;
|
|
@@ -9294,83 +9206,6 @@ const doesCleanupFunctionReleaseUsage = (cleanupFunction, usage, context, visite
|
|
|
9294
9206
|
});
|
|
9295
9207
|
return didCleanupFunctionMatch;
|
|
9296
9208
|
};
|
|
9297
|
-
const doesBoundCleanupReleaseUsage = (expression, usage, context) => {
|
|
9298
|
-
const callExpression = stripParenExpression(expression);
|
|
9299
|
-
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
9300
|
-
const bindCallee = stripParenExpression(callExpression.callee);
|
|
9301
|
-
if (!isNodeOfType(bindCallee, "MemberExpression") || bindCallee.computed || !isNodeOfType(bindCallee.property, "Identifier") || bindCallee.property.name !== "bind") return false;
|
|
9302
|
-
const releaseMember = stripParenExpression(bindCallee.object);
|
|
9303
|
-
if (!isNodeOfType(releaseMember, "MemberExpression") || releaseMember.computed || !isNodeOfType(releaseMember.property, "Identifier")) return false;
|
|
9304
|
-
const releaseReceiverKey = resolveExpressionKey$1(releaseMember.object, context);
|
|
9305
|
-
if (releaseReceiverKey === null || releaseReceiverKey !== resolveExpressionKey$1(callExpression.arguments?.[0], context)) return false;
|
|
9306
|
-
const releaseVerbName = releaseMember.property.name;
|
|
9307
|
-
if (usage.kind === "socket") return usage.handleKey === releaseReceiverKey && (SOCKET_RELEASE_VERB_NAMES.has(releaseVerbName) || UNIVERSAL_RELEASE_VERB_NAMES.has(releaseVerbName));
|
|
9308
|
-
return usage.kind === "subscribe" && usage.handleKey === releaseReceiverKey && (releaseVerbName === "unsubscribe" || releaseVerbName === "unsub" || releaseVerbName === "close" || releaseVerbName === "unwatch" || releaseVerbName === "unlisten" || BOUND_RESOURCE_RELEASE_METHOD_NAMES.has(releaseVerbName));
|
|
9309
|
-
};
|
|
9310
|
-
const callbackReturnsCleanupForUsage = (callback, usage, context) => {
|
|
9311
|
-
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
9312
|
-
const doesReturnedValueReleaseUsage = (returnedValue) => {
|
|
9313
|
-
if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) return true;
|
|
9314
|
-
const cleanupFunction = resolveStableValue(returnedValue, context);
|
|
9315
|
-
if (cleanupFunction && doesBoundCleanupReleaseUsage(cleanupFunction, usage, context)) return true;
|
|
9316
|
-
return Boolean(cleanupFunction && isFunctionLike$1(cleanupFunction) && doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context));
|
|
9317
|
-
};
|
|
9318
|
-
if (!isNodeOfType(callback.body, "BlockStatement")) return doesReturnedValueReleaseUsage(stripParenExpression(callback.body));
|
|
9319
|
-
const matchingCleanupReturns = [];
|
|
9320
|
-
walkInsideStatementBlocks(callback.body, (child) => {
|
|
9321
|
-
if (isNodeOfType(child, "ReturnStatement") && child.argument && doesReturnedValueReleaseUsage(stripParenExpression(child.argument))) matchingCleanupReturns.push(child);
|
|
9322
|
-
});
|
|
9323
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingCleanupReturns, context);
|
|
9324
|
-
};
|
|
9325
|
-
const hasRerunReleaseBeforeUsage = (callback, usage, context) => {
|
|
9326
|
-
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression") || !isNodeOfType(callback.body, "BlockStatement")) return false;
|
|
9327
|
-
const functionCfg = context.cfg.cfgFor(callback);
|
|
9328
|
-
const usageBlock = functionCfg?.blockOf(usage.node);
|
|
9329
|
-
const usageStart = getRangeStart(usage.node);
|
|
9330
|
-
if (!functionCfg || !usageBlock || usageStart === null) return false;
|
|
9331
|
-
const findHandleGuard = (releaseCall) => {
|
|
9332
|
-
if (usage.handleKey === null) return null;
|
|
9333
|
-
let ancestor = releaseCall.parent;
|
|
9334
|
-
while (ancestor && ancestor !== callback.body) {
|
|
9335
|
-
if (isNodeOfType(ancestor, "IfStatement")) return ancestor.alternate === null && resolveExpressionKey$1(ancestor.test, context) === usage.handleKey && getRangeStart(ancestor) !== null && (getRangeStart(ancestor) ?? usageStart) < usageStart ? ancestor : null;
|
|
9336
|
-
ancestor = ancestor.parent;
|
|
9337
|
-
}
|
|
9338
|
-
return null;
|
|
9339
|
-
};
|
|
9340
|
-
const matchingReleaseAnchors = [];
|
|
9341
|
-
walkInsideStatementBlocks(callback.body, (child) => {
|
|
9342
|
-
if (!isNodeOfType(child, "CallExpression")) return;
|
|
9343
|
-
const releaseStart = getRangeStart(child);
|
|
9344
|
-
const handleGuard = findHandleGuard(child);
|
|
9345
|
-
if (releaseStart === null || releaseStart >= usageStart || functionCfg.blockOf(child) !== usageBlock && !handleGuard) return;
|
|
9346
|
-
if (doesReleaseCallMatchUsage(child, usage, context)) {
|
|
9347
|
-
matchingReleaseAnchors.push(handleGuard ?? child);
|
|
9348
|
-
return;
|
|
9349
|
-
}
|
|
9350
|
-
const helperFunction = resolveStableValue(child.callee, context);
|
|
9351
|
-
if (helperFunction && isFunctionLike$1(helperFunction) && doesCleanupFunctionReleaseUsage(helperFunction, usage, context)) matchingReleaseAnchors.push(handleGuard ?? child);
|
|
9352
|
-
});
|
|
9353
|
-
return doMatchingNodesCoverEveryPathFromFunctionEntry(callback, matchingReleaseAnchors, context);
|
|
9354
|
-
};
|
|
9355
|
-
const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
|
|
9356
|
-
const componentFunction = findEnclosingFunction(callback);
|
|
9357
|
-
if (!componentFunction || !isNodeOfType(componentFunction, "ArrowFunctionExpression") && !isNodeOfType(componentFunction, "FunctionExpression") && !isNodeOfType(componentFunction, "FunctionDeclaration")) return false;
|
|
9358
|
-
let didFindUnmountCleanup = false;
|
|
9359
|
-
walkAst(componentFunction.body, (child) => {
|
|
9360
|
-
if (didFindUnmountCleanup) return false;
|
|
9361
|
-
if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction(child) !== componentFunction) return;
|
|
9362
|
-
if (!isHookCall$2(child, CLEANUP_EFFECT_HOOK_NAMES)) return;
|
|
9363
|
-
const dependencyList = child.arguments?.[1];
|
|
9364
|
-
if (!isNodeOfType(dependencyList, "ArrayExpression") || dependencyList.elements.length > 0) return;
|
|
9365
|
-
const cleanupCallback = getEffectCallback(child);
|
|
9366
|
-
if (cleanupCallback && cleanupCallback !== callback && callbackReturnsCleanupForUsage(cleanupCallback, usage, context)) {
|
|
9367
|
-
didFindUnmountCleanup = true;
|
|
9368
|
-
return false;
|
|
9369
|
-
}
|
|
9370
|
-
});
|
|
9371
|
-
return didFindUnmountCleanup;
|
|
9372
|
-
};
|
|
9373
|
-
const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
|
|
9374
9209
|
const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
9375
9210
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
9376
9211
|
if (usage.kind === "subscribe" && findEnclosingFunction(usage.node) === callback && doesResourceResultEscape(usage.node, true) && isCleanupReturningSubscribeLikeCallExpression(usage.node)) return true;
|
|
@@ -9383,10 +9218,6 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
9383
9218
|
if (returnStart !== null && usageStart !== null && returnStart < usageStart) return;
|
|
9384
9219
|
const returnedValue = child.argument ? stripParenExpression(child.argument) : null;
|
|
9385
9220
|
if (!returnedValue) return;
|
|
9386
|
-
if (doesBoundCleanupReleaseUsage(returnedValue, usage, context)) {
|
|
9387
|
-
matchingCleanupReturns.push(child);
|
|
9388
|
-
return;
|
|
9389
|
-
}
|
|
9390
9221
|
if (usage.kind === "subscribe" && (returnedValue === usage.node || getRangeStart(returnedValue) !== null && getRangeStart(returnedValue) === getRangeStart(usage.node)) && isCleanupReturningSubscribeLikeCallExpression(returnedValue)) {
|
|
9391
9222
|
matchingCleanupReturns.push(child);
|
|
9392
9223
|
return;
|
|
@@ -9402,17 +9233,13 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
|
|
|
9402
9233
|
if (!context.scopes.symbolFor(returnedValue)?.initializer) return;
|
|
9403
9234
|
}
|
|
9404
9235
|
const cleanupFunction = resolveStableValue(returnedValue, context);
|
|
9405
|
-
if (cleanupFunction && doesBoundCleanupReleaseUsage(cleanupFunction, usage, context)) {
|
|
9406
|
-
matchingCleanupReturns.push(child);
|
|
9407
|
-
return;
|
|
9408
|
-
}
|
|
9409
9236
|
if (!cleanupFunction || !isFunctionLike$1(cleanupFunction)) return;
|
|
9410
9237
|
if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
|
|
9411
9238
|
});
|
|
9412
9239
|
return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
|
|
9413
9240
|
};
|
|
9414
9241
|
const findFirstUsageWithoutCleanup = (callback, usages, context) => {
|
|
9415
|
-
for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)
|
|
9242
|
+
for (const usage of usages) if (!effectHasCleanupForUsage(callback, usage, context)) return usage;
|
|
9416
9243
|
return null;
|
|
9417
9244
|
};
|
|
9418
9245
|
const isLocalAbortControllerExpression = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
@@ -23217,73 +23044,61 @@ const PURE_GLOBAL_CALLEE_NAMES = new Set([
|
|
|
23217
23044
|
"Array",
|
|
23218
23045
|
"BigInt",
|
|
23219
23046
|
"Boolean",
|
|
23220
|
-
"encodeURIComponent",
|
|
23221
23047
|
"Number",
|
|
23222
23048
|
"Object",
|
|
23223
23049
|
"String",
|
|
23224
23050
|
"parseFloat",
|
|
23225
|
-
"parseInt"
|
|
23226
|
-
"structuredClone"
|
|
23227
|
-
]);
|
|
23228
|
-
const PURE_GLOBAL_CONSTRUCTOR_NAMES = new Set(["Date", "Set"]);
|
|
23229
|
-
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([
|
|
23230
|
-
["Array", new Set(["from"])],
|
|
23231
|
-
["JSON", new Set([
|
|
23232
|
-
"isRawJSON",
|
|
23233
|
-
"parse",
|
|
23234
|
-
"rawJSON",
|
|
23235
|
-
"stringify"
|
|
23236
|
-
])],
|
|
23237
|
-
["Math", new Set([
|
|
23238
|
-
"abs",
|
|
23239
|
-
"acos",
|
|
23240
|
-
"acosh",
|
|
23241
|
-
"asin",
|
|
23242
|
-
"asinh",
|
|
23243
|
-
"atan",
|
|
23244
|
-
"atan2",
|
|
23245
|
-
"atanh",
|
|
23246
|
-
"cbrt",
|
|
23247
|
-
"ceil",
|
|
23248
|
-
"clz32",
|
|
23249
|
-
"cos",
|
|
23250
|
-
"cosh",
|
|
23251
|
-
"exp",
|
|
23252
|
-
"floor",
|
|
23253
|
-
"fround",
|
|
23254
|
-
"hypot",
|
|
23255
|
-
"imul",
|
|
23256
|
-
"log",
|
|
23257
|
-
"log10",
|
|
23258
|
-
"log1p",
|
|
23259
|
-
"log2",
|
|
23260
|
-
"max",
|
|
23261
|
-
"min",
|
|
23262
|
-
"pow",
|
|
23263
|
-
"round",
|
|
23264
|
-
"sign",
|
|
23265
|
-
"sin",
|
|
23266
|
-
"sinh",
|
|
23267
|
-
"sqrt",
|
|
23268
|
-
"tan",
|
|
23269
|
-
"tanh",
|
|
23270
|
-
"trunc"
|
|
23271
|
-
])],
|
|
23272
|
-
["Object", new Set(["assign"])]
|
|
23051
|
+
"parseInt"
|
|
23273
23052
|
]);
|
|
23053
|
+
const PURE_GLOBAL_NAMESPACE_NAMES = new Set(["JSON", "Math"]);
|
|
23054
|
+
const PURE_HELPER_NAMESPACE_MEMBER_NAMES = new Map([["JSON", new Set([
|
|
23055
|
+
"isRawJSON",
|
|
23056
|
+
"parse",
|
|
23057
|
+
"rawJSON",
|
|
23058
|
+
"stringify"
|
|
23059
|
+
])], ["Math", new Set([
|
|
23060
|
+
"abs",
|
|
23061
|
+
"acos",
|
|
23062
|
+
"acosh",
|
|
23063
|
+
"asin",
|
|
23064
|
+
"asinh",
|
|
23065
|
+
"atan",
|
|
23066
|
+
"atan2",
|
|
23067
|
+
"atanh",
|
|
23068
|
+
"cbrt",
|
|
23069
|
+
"ceil",
|
|
23070
|
+
"clz32",
|
|
23071
|
+
"cos",
|
|
23072
|
+
"cosh",
|
|
23073
|
+
"exp",
|
|
23074
|
+
"floor",
|
|
23075
|
+
"fround",
|
|
23076
|
+
"hypot",
|
|
23077
|
+
"imul",
|
|
23078
|
+
"log",
|
|
23079
|
+
"log10",
|
|
23080
|
+
"log1p",
|
|
23081
|
+
"log2",
|
|
23082
|
+
"max",
|
|
23083
|
+
"min",
|
|
23084
|
+
"pow",
|
|
23085
|
+
"round",
|
|
23086
|
+
"sign",
|
|
23087
|
+
"sin",
|
|
23088
|
+
"sinh",
|
|
23089
|
+
"sqrt",
|
|
23090
|
+
"tan",
|
|
23091
|
+
"tanh",
|
|
23092
|
+
"trunc"
|
|
23093
|
+
])]]);
|
|
23274
23094
|
const PURE_MEMBER_TRANSFORM_NAMES = new Set([
|
|
23275
23095
|
"concat",
|
|
23276
23096
|
"filter",
|
|
23277
|
-
"flatMap",
|
|
23278
23097
|
"join",
|
|
23279
23098
|
"map",
|
|
23280
|
-
"reduce",
|
|
23281
|
-
"replace",
|
|
23282
|
-
"slice",
|
|
23283
23099
|
"split",
|
|
23284
23100
|
"toLowerCase",
|
|
23285
23101
|
"toString",
|
|
23286
|
-
"toSorted",
|
|
23287
23102
|
"toUpperCase",
|
|
23288
23103
|
"trim"
|
|
23289
23104
|
]);
|
|
@@ -23665,11 +23480,6 @@ const analyzeHelperExpression = (expression, environment, usedParameterIndices)
|
|
|
23665
23480
|
const callbackSummary = analyzeHelperStatements(node.body.body ?? [], callbackEnvironment, usedParameterIndices, analyzeHelperExpression);
|
|
23666
23481
|
return callbackSummary.isValid && !callbackSummary.canContinue;
|
|
23667
23482
|
}
|
|
23668
|
-
if (isNodeOfType(node, "NewExpression")) {
|
|
23669
|
-
const callee = stripParenExpression(node.callee);
|
|
23670
|
-
if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || environment.parameterIndices.has(callee.name) || environment.recursiveNames.has(callee.name) || environment.shadowedGlobalNames.has(callee.name)) return false;
|
|
23671
|
-
return (node.arguments ?? []).every((argument) => analyzeHelperExpression(argument, environment, usedParameterIndices));
|
|
23672
|
-
}
|
|
23673
23483
|
if (isNodeOfType(node, "CallExpression")) {
|
|
23674
23484
|
const callee = stripParenExpression(node.callee);
|
|
23675
23485
|
const calleeRoot = getMemberRoot(callee);
|
|
@@ -23872,7 +23682,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23872
23682
|
}
|
|
23873
23683
|
const callee = stripParenExpression(node.callee);
|
|
23874
23684
|
const calleeRoot = getMemberRoot(callee);
|
|
23875
|
-
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(
|
|
23685
|
+
const isPureGlobalCall = isNodeOfType(callee, "Identifier") && PURE_GLOBAL_CALLEE_NAMES.has(callee.name) && getIdentifierBindingIdentity(analysis, callee) === null || isNodeOfType(calleeRoot, "Identifier") && PURE_GLOBAL_NAMESPACE_NAMES.has(calleeRoot.name) && getIdentifierBindingIdentity(analysis, calleeRoot) === null;
|
|
23876
23686
|
const isPureMemberTransform = isNodeOfType(callee, "MemberExpression") && PURE_MEMBER_TRANSFORM_NAMES.has(getStaticMemberName$1(callee) ?? "");
|
|
23877
23687
|
if (isPureGlobalCall || isPureMemberTransform) {
|
|
23878
23688
|
if (isPureMemberTransform && isNodeOfType(callee, "MemberExpression")) mergeEvidence(evidence, collectValueEvidence(analysis, callee.object, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
@@ -23925,15 +23735,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23925
23735
|
}
|
|
23926
23736
|
return evidence;
|
|
23927
23737
|
}
|
|
23928
|
-
if (isNodeOfType(node, "NewExpression")) {
|
|
23929
|
-
const callee = stripParenExpression(node.callee);
|
|
23930
|
-
if (!isNodeOfType(callee, "Identifier") || !PURE_GLOBAL_CONSTRUCTOR_NAMES.has(callee.name) || getIdentifierBindingIdentity(analysis, callee) !== null) {
|
|
23931
|
-
evidence.hasUnknownSource = true;
|
|
23932
|
-
return evidence;
|
|
23933
|
-
}
|
|
23934
|
-
for (const argument of node.arguments ?? []) mergeEvidence(evidence, collectValueEvidence(analysis, argument, frame, remainingCallFrames, new Set(visitedBindings)));
|
|
23935
|
-
return evidence;
|
|
23936
|
-
}
|
|
23937
23738
|
if (isFunctionLike$1(node) || isNodeOfType(node, "AwaitExpression")) {
|
|
23938
23739
|
evidence.hasUnknownSource = true;
|
|
23939
23740
|
return evidence;
|
|
@@ -26476,19 +26277,6 @@ const noCloneElement = defineRule({
|
|
|
26476
26277
|
} })
|
|
26477
26278
|
});
|
|
26478
26279
|
//#endregion
|
|
26479
|
-
//#region src/plugin/utils/get-call-method-name.ts
|
|
26480
|
-
/**
|
|
26481
|
-
* Returns the static method name of a call's callee when it's a
|
|
26482
|
-
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
26483
|
-
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
26484
|
-
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
26485
|
-
* method-call shape against an allow/block list.
|
|
26486
|
-
*/
|
|
26487
|
-
const getCallMethodName = (callee) => {
|
|
26488
|
-
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
26489
|
-
return null;
|
|
26490
|
-
};
|
|
26491
|
-
//#endregion
|
|
26492
26280
|
//#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
|
|
26493
26281
|
const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
|
|
26494
26282
|
const CONTEXT_MODULES = [
|
|
@@ -26496,35 +26284,23 @@ const CONTEXT_MODULES = [
|
|
|
26496
26284
|
"use-context-selector",
|
|
26497
26285
|
"react-tracked"
|
|
26498
26286
|
];
|
|
26499
|
-
const
|
|
26500
|
-
if (symbol?.kind !== "import") return null;
|
|
26501
|
-
const importDeclaration = symbol.declarationNode.parent;
|
|
26502
|
-
if (!importDeclaration || !isNodeOfType(importDeclaration, "ImportDeclaration") || typeof importDeclaration.source.value !== "string" || !CONTEXT_MODULES.includes(importDeclaration.source.value)) return null;
|
|
26503
|
-
return importDeclaration.source.value;
|
|
26504
|
-
};
|
|
26505
|
-
const isSupportedNamespaceSymbol = (symbol) => getSupportedContextImportSource(symbol) !== null && Boolean(symbol && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
|
|
26506
|
-
const isDestructuredCreateContextBinding = (identifier, scopes) => {
|
|
26507
|
-
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
26508
|
-
const symbol = scopes.symbolFor(identifier);
|
|
26509
|
-
if (symbol?.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "ObjectPattern")) return false;
|
|
26510
|
-
const property = symbol.bindingIdentifier.parent;
|
|
26511
|
-
if (!property || !isNodeOfType(property, "Property") || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "createContext") return false;
|
|
26512
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
26513
|
-
return isNodeOfType(initializer, "Identifier") && isSupportedNamespaceSymbol(resolveConstIdentifierAlias(initializer, scopes));
|
|
26514
|
-
};
|
|
26515
|
-
const isCreateContextCall = (node, scopes) => {
|
|
26516
|
-
const callee = stripParenExpression(node.callee);
|
|
26287
|
+
const isCreateContextCallee = (callee) => {
|
|
26517
26288
|
if (isNodeOfType(callee, "Identifier")) {
|
|
26518
|
-
|
|
26519
|
-
|
|
26520
|
-
return getSupportedContextImportSource(symbol) !== null && Boolean(symbol && getImportedName(symbol.declarationNode) === "createContext");
|
|
26289
|
+
const binding = getImportBindingForName(callee, callee.name);
|
|
26290
|
+
return binding !== null && binding.exportedName === "createContext" && CONTEXT_MODULES.includes(binding.source);
|
|
26521
26291
|
}
|
|
26522
|
-
if (
|
|
26523
|
-
|
|
26524
|
-
|
|
26525
|
-
|
|
26526
|
-
|
|
26527
|
-
|
|
26292
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed) {
|
|
26293
|
+
const namespaceIdentifier = callee.object;
|
|
26294
|
+
const propertyIdentifier = callee.property;
|
|
26295
|
+
if (!isNodeOfType(namespaceIdentifier, "Identifier")) return false;
|
|
26296
|
+
if (!isNodeOfType(propertyIdentifier, "Identifier")) return false;
|
|
26297
|
+
if (propertyIdentifier.name !== "createContext") return false;
|
|
26298
|
+
const namespaceName = namespaceIdentifier.name;
|
|
26299
|
+
if (isCanonicalReactNamespaceName(namespaceName)) return true;
|
|
26300
|
+
const importSource = getImportSourceForName(namespaceIdentifier, namespaceName);
|
|
26301
|
+
return importSource !== null && CONTEXT_MODULES.includes(importSource);
|
|
26302
|
+
}
|
|
26303
|
+
return false;
|
|
26528
26304
|
};
|
|
26529
26305
|
const noCreateContextInRender = defineRule({
|
|
26530
26306
|
id: "no-create-context-in-render",
|
|
@@ -26533,7 +26309,7 @@ const noCreateContextInRender = defineRule({
|
|
|
26533
26309
|
category: "Correctness",
|
|
26534
26310
|
recommendation: "Move `createContext(...)` outside the component, to the top level of the file, so it stays the same on every render.",
|
|
26535
26311
|
create: (context) => ({ CallExpression(node) {
|
|
26536
|
-
if (!
|
|
26312
|
+
if (!isCreateContextCallee(node.callee)) return;
|
|
26537
26313
|
const componentOrHookName = enclosingComponentOrHookName(node);
|
|
26538
26314
|
if (!componentOrHookName) return;
|
|
26539
26315
|
context.report({
|
|
@@ -28483,31 +28259,20 @@ const noDocumentStartViewTransition = defineRule({
|
|
|
28483
28259
|
} })
|
|
28484
28260
|
});
|
|
28485
28261
|
//#endregion
|
|
28486
|
-
//#region src/plugin/utils/get-static-property-name.ts
|
|
28487
|
-
const getStaticPropertyName = (memberExpression) => {
|
|
28488
|
-
const property = memberExpression.property;
|
|
28489
|
-
if (!memberExpression.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
28490
|
-
if (memberExpression.computed && isNodeOfType(property, "Literal")) return typeof property.value === "string" ? property.value : null;
|
|
28491
|
-
if (memberExpression.computed && isNodeOfType(property, "TemplateLiteral") && property.expressions.length === 0) return property.quasis[0]?.value.cooked ?? property.quasis[0]?.value.raw ?? null;
|
|
28492
|
-
return null;
|
|
28493
|
-
};
|
|
28494
|
-
//#endregion
|
|
28495
28262
|
//#region src/plugin/rules/js-performance/no-document-write.ts
|
|
28496
28263
|
const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
|
|
28497
28264
|
const WRITE_METHODS = new Set(["write", "writeln"]);
|
|
28498
|
-
const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
|
|
28499
28265
|
const noDocumentWrite = defineRule({
|
|
28500
28266
|
id: "no-document-write",
|
|
28501
28267
|
title: "document.write/writeln",
|
|
28502
28268
|
severity: "warn",
|
|
28503
28269
|
recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
|
|
28504
28270
|
create: (context) => ({ CallExpression(node) {
|
|
28505
|
-
const callee =
|
|
28506
|
-
if (!isNodeOfType(callee, "MemberExpression")) return;
|
|
28271
|
+
const callee = node.callee;
|
|
28272
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
|
|
28507
28273
|
const receiver = stripParenExpression(callee.object);
|
|
28508
|
-
if (!isNodeOfType(receiver, "Identifier") ||
|
|
28509
|
-
|
|
28510
|
-
if (methodName === null || !WRITE_METHODS.has(methodName)) return;
|
|
28274
|
+
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
|
|
28275
|
+
if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
|
|
28511
28276
|
context.report({
|
|
28512
28277
|
node,
|
|
28513
28278
|
message: MESSAGE$24
|
|
@@ -29314,14 +29079,6 @@ const noEffectWithFreshDeps = defineRule({
|
|
|
29314
29079
|
//#endregion
|
|
29315
29080
|
//#region src/plugin/rules/security/no-eval.ts
|
|
29316
29081
|
const SANDBOX_SURFACE_PATH_PATTERN = /(?:^|[/-])sandbox(?:$|[/-])|(?:^|\/)[\w.]+-sandbox(?:\/|\.[cm]?[jt]sx?$)/i;
|
|
29317
|
-
const getExecutableGlobalName = (node, context) => {
|
|
29318
|
-
const expression = stripParenExpression(node);
|
|
29319
|
-
if (isNodeOfType(expression, "Identifier")) return context.scopes.isGlobalReference(expression) ? expression.name : null;
|
|
29320
|
-
if (!isNodeOfType(expression, "MemberExpression")) return null;
|
|
29321
|
-
const receiver = stripParenExpression(expression.object);
|
|
29322
|
-
if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "globalThis" || !context.scopes.isGlobalReference(receiver)) return null;
|
|
29323
|
-
return getStaticPropertyName(expression);
|
|
29324
|
-
};
|
|
29325
29082
|
const noEval = defineRule({
|
|
29326
29083
|
id: "no-eval",
|
|
29327
29084
|
title: "eval() runs untrusted code strings",
|
|
@@ -29331,21 +29088,20 @@ const noEval = defineRule({
|
|
|
29331
29088
|
if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
|
|
29332
29089
|
return {
|
|
29333
29090
|
CallExpression(node) {
|
|
29334
|
-
|
|
29335
|
-
if (executableGlobalName === "eval") {
|
|
29091
|
+
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
|
|
29336
29092
|
context.report({
|
|
29337
29093
|
node,
|
|
29338
29094
|
message: "eval() is a code-injection vulnerability: it runs any string as code."
|
|
29339
29095
|
});
|
|
29340
29096
|
return;
|
|
29341
29097
|
}
|
|
29342
|
-
if ((
|
|
29098
|
+
if (isNodeOfType(node.callee, "Identifier") && (node.callee.name === "setTimeout" || node.callee.name === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
|
|
29343
29099
|
node,
|
|
29344
|
-
message: `Passing a string to ${
|
|
29100
|
+
message: `Passing a string to ${node.callee.name}() is a code-injection vulnerability, since it runs that string as code.`
|
|
29345
29101
|
});
|
|
29346
29102
|
},
|
|
29347
29103
|
NewExpression(node) {
|
|
29348
|
-
if (
|
|
29104
|
+
if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "Function") {
|
|
29349
29105
|
const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
|
|
29350
29106
|
if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
|
|
29351
29107
|
context.report({
|
|
@@ -34818,6 +34574,19 @@ const DATA_SINK_METHOD_NAMES = new Set([
|
|
|
34818
34574
|
"deserialize"
|
|
34819
34575
|
]);
|
|
34820
34576
|
//#endregion
|
|
34577
|
+
//#region src/plugin/utils/get-call-method-name.ts
|
|
34578
|
+
/**
|
|
34579
|
+
* Returns the static method name of a call's callee when it's a
|
|
34580
|
+
* non-computed MemberExpression (`obj.method` → `"method"`), or
|
|
34581
|
+
* `null` otherwise. Used by `no-pass-data-to-parent` and
|
|
34582
|
+
* `no-pass-live-state-to-parent` to match the receiver-bound
|
|
34583
|
+
* method-call shape against an allow/block list.
|
|
34584
|
+
*/
|
|
34585
|
+
const getCallMethodName = (callee) => {
|
|
34586
|
+
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier")) return callee.property.name;
|
|
34587
|
+
return null;
|
|
34588
|
+
};
|
|
34589
|
+
//#endregion
|
|
34821
34590
|
//#region src/plugin/rules/state-and-effects/no-pass-data-to-parent.ts
|
|
34822
34591
|
const isUseStateIdentifier = (identifier) => {
|
|
34823
34592
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
@@ -54906,6 +54675,12 @@ const webhookSignatureRisk = defineRule({
|
|
|
54906
54675
|
//#region src/plugin/rules/zod/utils/zod-ast.ts
|
|
54907
54676
|
const ZOD_MODULE = "zod";
|
|
54908
54677
|
const ZOD_MODULE_SOURCES = [ZOD_MODULE];
|
|
54678
|
+
const getStaticPropertyName = (member) => {
|
|
54679
|
+
const property = member.property;
|
|
54680
|
+
if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
54681
|
+
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
54682
|
+
return null;
|
|
54683
|
+
};
|
|
54909
54684
|
const importInfoCache = /* @__PURE__ */ new WeakMap();
|
|
54910
54685
|
const getImportInfoForIdentifier = (identifier) => {
|
|
54911
54686
|
if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
|