oxlint-plugin-react-doctor 0.7.5-dev.3aef25a → 0.7.5-dev.654849

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.
Files changed (2) hide show
  1. package/dist/index.js +95 -308
  2. 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
- return resolveHandlerFunctionExpression(attribute.value.expression);
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
- const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
6231
- if (!onClick && !spreadOnClickExpression) return;
6232
- if (onClick && isPureEventBlockerHandler(onClick)) return;
6233
- if (onClick && isFocusForwardingHandler(onClick)) return;
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 (onClick && isBackdropDismissHandler(onClick)) return;
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) || spreadEventValues.has(handler.toLowerCase()))) return;
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
@@ -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) && !hasSplitLifecycleCleanup(callback, usage, context)) return usage;
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(callee, "MemberExpression") && isNodeOfType(calleeRoot, "Identifier") && getIdentifierBindingIdentity(analysis, calleeRoot) === null && PURE_HELPER_NAMESPACE_MEMBER_NAMES.get(calleeRoot.name)?.has(getStaticMemberName$1(callee) ?? "") === true;
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;
@@ -28483,31 +28284,20 @@ const noDocumentStartViewTransition = defineRule({
28483
28284
  } })
28484
28285
  });
28485
28286
  //#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
28287
  //#region src/plugin/rules/js-performance/no-document-write.ts
28496
28288
  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
28289
  const WRITE_METHODS = new Set(["write", "writeln"]);
28498
- const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
28499
28290
  const noDocumentWrite = defineRule({
28500
28291
  id: "no-document-write",
28501
28292
  title: "document.write/writeln",
28502
28293
  severity: "warn",
28503
28294
  recommendation: "Don't use `document.write()`/`document.writeln()`. Append DOM nodes or set `innerHTML`/`textContent` on a specific element instead.",
28504
28295
  create: (context) => ({ CallExpression(node) {
28505
- const callee = stripParenExpression(node.callee);
28506
- if (!isNodeOfType(callee, "MemberExpression")) return;
28296
+ const callee = node.callee;
28297
+ if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return;
28507
28298
  const receiver = stripParenExpression(callee.object);
28508
- if (!isNodeOfType(receiver, "Identifier") || !isGlobalDocumentReference(receiver, context)) return;
28509
- const methodName = getStaticPropertyName(callee);
28510
- if (methodName === null || !WRITE_METHODS.has(methodName)) return;
28299
+ if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "document") return;
28300
+ if (!isNodeOfType(callee.property, "Identifier") || !WRITE_METHODS.has(callee.property.name)) return;
28511
28301
  context.report({
28512
28302
  node,
28513
28303
  message: MESSAGE$24
@@ -29314,14 +29104,6 @@ const noEffectWithFreshDeps = defineRule({
29314
29104
  //#endregion
29315
29105
  //#region src/plugin/rules/security/no-eval.ts
29316
29106
  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
29107
  const noEval = defineRule({
29326
29108
  id: "no-eval",
29327
29109
  title: "eval() runs untrusted code strings",
@@ -29331,21 +29113,20 @@ const noEval = defineRule({
29331
29113
  if (SANDBOX_SURFACE_PATH_PATTERN.test(normalizeFilename(context.filename ?? ""))) return {};
29332
29114
  return {
29333
29115
  CallExpression(node) {
29334
- const executableGlobalName = getExecutableGlobalName(node.callee, context);
29335
- if (executableGlobalName === "eval") {
29116
+ if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "eval") {
29336
29117
  context.report({
29337
29118
  node,
29338
29119
  message: "eval() is a code-injection vulnerability: it runs any string as code."
29339
29120
  });
29340
29121
  return;
29341
29122
  }
29342
- if ((executableGlobalName === "setTimeout" || executableGlobalName === "setInterval") && isNodeOfType(node.arguments?.[0], "Literal") && typeof node.arguments[0].value === "string") context.report({
29123
+ 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
29124
  node,
29344
- message: `Passing a string to ${executableGlobalName}() is a code-injection vulnerability, since it runs that string as code.`
29125
+ message: `Passing a string to ${node.callee.name}() is a code-injection vulnerability, since it runs that string as code.`
29345
29126
  });
29346
29127
  },
29347
29128
  NewExpression(node) {
29348
- if (getExecutableGlobalName(node.callee, context) === "Function") {
29129
+ if (isNodeOfType(node.callee, "Identifier") && node.callee.name === "Function") {
29349
29130
  const onlyArgument = node.arguments.length === 1 ? node.arguments[0] : void 0;
29350
29131
  if (isNodeOfType(onlyArgument, "Literal") && typeof onlyArgument.value === "string" && onlyArgument.value.trim() === "return this") return;
29351
29132
  context.report({
@@ -54906,6 +54687,12 @@ const webhookSignatureRisk = defineRule({
54906
54687
  //#region src/plugin/rules/zod/utils/zod-ast.ts
54907
54688
  const ZOD_MODULE = "zod";
54908
54689
  const ZOD_MODULE_SOURCES = [ZOD_MODULE];
54690
+ const getStaticPropertyName = (member) => {
54691
+ const property = member.property;
54692
+ if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
54693
+ if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
54694
+ return null;
54695
+ };
54909
54696
  const importInfoCache = /* @__PURE__ */ new WeakMap();
54910
54697
  const getImportInfoForIdentifier = (identifier) => {
54911
54698
  if (importInfoCache.has(identifier)) return importInfoCache.get(identifier) ?? null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.5-dev.3aef25a",
3
+ "version": "0.7.5-dev.0654849",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",