oxlint-plugin-react-doctor 0.8.3-dev.3d7ea66 → 0.8.3-dev.76263ec

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 (3) hide show
  1. package/dist/index.d.ts +3875 -2963
  2. package/dist/index.js +1442 -212
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13065,7 +13065,7 @@ const getOutermostTarget = (node) => {
13065
13065
  }
13066
13066
  return current;
13067
13067
  };
13068
- const getExecutionOwner = (node) => {
13068
+ const getExecutionOwner$1 = (node) => {
13069
13069
  let current = node;
13070
13070
  while (current) {
13071
13071
  if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return current;
@@ -13207,7 +13207,7 @@ const getSymbolMutationInspector = (scopes) => {
13207
13207
  const eventsBySymbolId = /* @__PURE__ */ new Map();
13208
13208
  walkAst(scopes.rootScope.node, (node) => {
13209
13209
  if (isNodeOfType(node, "CallExpression")) {
13210
- const owner = getExecutionOwner(node);
13210
+ const owner = getExecutionOwner$1(node);
13211
13211
  const targetOwner = getLocalCallTarget(node);
13212
13212
  if (targetOwner && !isStaticallyUnreachable$1(node, owner)) calls.push({
13213
13213
  call: node,
@@ -13220,7 +13220,7 @@ const getSymbolMutationInspector = (scopes) => {
13220
13220
  if (propertyNames === void 0) return;
13221
13221
  const symbol = resolveConstIdentifierAlias(node, scopes);
13222
13222
  if (!symbol) return;
13223
- const owner = getExecutionOwner(node);
13223
+ const owner = getExecutionOwner$1(node);
13224
13224
  if (isStaticallyUnreachable$1(node, owner)) return;
13225
13225
  const events = eventsBySymbolId.get(symbol.id) ?? [];
13226
13226
  events.push({
@@ -13231,7 +13231,7 @@ const getSymbolMutationInspector = (scopes) => {
13231
13231
  eventsBySymbolId.set(symbol.id, events);
13232
13232
  });
13233
13233
  const getInvokedOwnersBefore = (checkpoint) => {
13234
- const checkpointOwner = getExecutionOwner(checkpoint);
13234
+ const checkpointOwner = getExecutionOwner$1(checkpoint);
13235
13235
  const checkpointStartIndex = getNodeStartIndex(checkpoint);
13236
13236
  const invokedOwners = /* @__PURE__ */ new Set();
13237
13237
  const visitOwner = (owner, cutoffIndex) => {
@@ -13328,14 +13328,14 @@ const getSymbolMutationInspector = (scopes) => {
13328
13328
  return false;
13329
13329
  };
13330
13330
  const isExecutionOrderAmbiguous = (usageNode) => {
13331
- const usageOwner = getExecutionOwner(usageNode);
13331
+ const usageOwner = getExecutionOwner$1(usageNode);
13332
13332
  if (isNodeOfType(usageOwner, "Program")) return false;
13333
13333
  const reachingProgramCalls = calls.filter((call) => isNodeOfType(call.owner, "Program") && canOwnerReach(call.targetOwner, usageOwner));
13334
13334
  if (reachingProgramCalls.length === 0) return false;
13335
13335
  return reachingProgramCalls.length !== 1 || reachingProgramCalls[0]?.targetOwner !== usageOwner;
13336
13336
  };
13337
13337
  const isMutationOrderAmbiguous = (symbol, usageNode, relevantPropertyName) => {
13338
- const usageOwner = getExecutionOwner(usageNode);
13338
+ const usageOwner = getExecutionOwner$1(usageNode);
13339
13339
  const usageStartIndex = getNodeStartIndex(usageNode);
13340
13340
  return (eventsBySymbolId.get(symbol.id) ?? []).some((event) => {
13341
13341
  if (relevantPropertyName !== null && event.propertyNames !== null && !event.propertyNames.has(relevantPropertyName)) return false;
@@ -13370,7 +13370,7 @@ const getSymbolMutationInspector = (scopes) => {
13370
13370
  visitOwner(operation.call.targetOwner, Number.POSITIVE_INFINITY, nextActiveOwners, isConditionalPath || isConditionallyExecuted(operation.call.call, operation.call.owner));
13371
13371
  }
13372
13372
  };
13373
- const usageOwner = getExecutionOwner(usageNode);
13373
+ const usageOwner = getExecutionOwner$1(usageNode);
13374
13374
  if (!isNodeOfType(usageOwner, "Program")) visitOwner(scopes.rootScope.node, getProgramCutoffIndex(usageOwner), /* @__PURE__ */ new Set(), false);
13375
13375
  visitOwner(usageOwner, getNodeStartIndex(usageNode), /* @__PURE__ */ new Set(), false);
13376
13376
  return mutationEvents;
@@ -13379,7 +13379,7 @@ const getSymbolMutationInspector = (scopes) => {
13379
13379
  const events = eventsBySymbolId.get(symbol.id);
13380
13380
  if (!events) return false;
13381
13381
  const usageStartIndex = getNodeStartIndex(usageNode);
13382
- const usageOwner = getExecutionOwner(usageNode);
13382
+ const usageOwner = getExecutionOwner$1(usageNode);
13383
13383
  const invokedOwners = getInvokedOwnersBefore(usageNode);
13384
13384
  return events.some((event) => {
13385
13385
  if (relevantPropertyName !== null && event.propertyNames !== null && !event.propertyNames.has(relevantPropertyName)) return false;
@@ -24018,22 +24018,26 @@ const isInsideTryStatement = (node, options) => {
24018
24018
  return false;
24019
24019
  };
24020
24020
  //#endregion
24021
- //#region src/plugin/utils/is-node-conditionally-executed.ts
24022
- const isNodeConditionallyExecuted = (node, boundary) => {
24021
+ //#region src/plugin/utils/get-conditional-execution-regions.ts
24022
+ const getConditionalExecutionRegions = (node, boundary) => {
24023
+ const regions = /* @__PURE__ */ new Set();
24023
24024
  let child = node;
24024
24025
  let parent = child.parent ?? null;
24025
24026
  while (parent && parent !== boundary) {
24026
- if (isNodeOfType(parent, "IfStatement") && parent.test !== child) return true;
24027
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
24028
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
24029
- if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
24030
- if (isNodeOfType(parent, "SwitchCase")) return true;
24027
+ if (isNodeOfType(parent, "IfStatement") && parent.test !== child) regions.add(child);
24028
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) regions.add(child);
24029
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) regions.add(child);
24030
+ if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) regions.add(child);
24031
+ if (isNodeOfType(parent, "SwitchCase")) regions.add(parent);
24031
24032
  child = parent;
24032
24033
  parent = child.parent ?? null;
24033
24034
  }
24034
- return false;
24035
+ return regions;
24035
24036
  };
24036
24037
  //#endregion
24038
+ //#region src/plugin/utils/is-node-conditionally-executed.ts
24039
+ const isNodeConditionallyExecuted = (node, boundary) => getConditionalExecutionRegions(node, boundary).size > 0;
24040
+ //#endregion
24037
24041
  //#region src/plugin/utils/is-non-source-filename.ts
24038
24042
  const NON_SOURCE_FILENAME_MARKERS = [
24039
24043
  "/dist/",
@@ -91330,6 +91334,35 @@ const getModuleNamespaceSource = (expression, scopes, visitedSymbolIds = /* @__P
91330
91334
  return getModuleNamespaceSource(symbol.initializer, scopes, visitedSymbolIds);
91331
91335
  };
91332
91336
  //#endregion
91337
+ //#region src/plugin/rules/r3f/utils/r3f-public-modules.ts
91338
+ const R3F_PUBLIC_MODULES = new Set([
91339
+ "@react-three/fiber",
91340
+ "@react-three/fiber/legacy",
91341
+ "@react-three/fiber/native",
91342
+ "@react-three/fiber/webgpu",
91343
+ "react-three-fiber"
91344
+ ]);
91345
+ //#endregion
91346
+ //#region src/plugin/rules/r3f/utils/has-r3f-runtime-import.ts
91347
+ const isR3fRuntimeModule = (moduleSource) => R3F_PUBLIC_MODULES.has(moduleSource) || moduleSource.startsWith("@react-three/");
91348
+ const hasR3fRuntimeImport = (program, scopes) => program.body.some((statement) => {
91349
+ if (isNodeOfType(statement, "ImportDeclaration") && !isTypeOnlyImport(statement) && typeof statement.source.value === "string") return isR3fRuntimeModule(statement.source.value);
91350
+ if (isNodeOfType(statement, "TSImportEqualsDeclaration")) {
91351
+ const moduleSource = getModuleNamespaceSource(statement.id, scopes);
91352
+ return moduleSource !== null && isR3fRuntimeModule(moduleSource);
91353
+ }
91354
+ if (isNodeOfType(statement, "ExpressionStatement")) {
91355
+ const moduleSource = getGlobalRequireModuleSource(statement.expression, scopes);
91356
+ return moduleSource !== null && isR3fRuntimeModule(moduleSource);
91357
+ }
91358
+ if (!isNodeOfType(statement, "VariableDeclaration")) return false;
91359
+ return statement.declarations.some((declaration) => {
91360
+ if (!declaration.init) return false;
91361
+ const moduleSource = getGlobalRequireModuleSource(declaration.init, scopes);
91362
+ return moduleSource !== null && isR3fRuntimeModule(moduleSource);
91363
+ });
91364
+ });
91365
+ //#endregion
91333
91366
  //#region src/plugin/rules/r3f/utils/get-api-reference-provenance.ts
91334
91367
  const getApiReferenceProvenance = (reference, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
91335
91368
  const candidate = stripParenExpression(reference);
@@ -91385,35 +91418,6 @@ const getApiReferenceProvenance = (reference, scopes, visitedSymbolIds = /* @__P
91385
91418
  return getApiReferenceProvenance(symbol.initializer, scopes, visitedSymbolIds);
91386
91419
  };
91387
91420
  //#endregion
91388
- //#region src/plugin/rules/r3f/utils/r3f-public-modules.ts
91389
- const R3F_PUBLIC_MODULES = new Set([
91390
- "@react-three/fiber",
91391
- "@react-three/fiber/legacy",
91392
- "@react-three/fiber/native",
91393
- "@react-three/fiber/webgpu",
91394
- "react-three-fiber"
91395
- ]);
91396
- //#endregion
91397
- //#region src/plugin/rules/r3f/utils/has-r3f-runtime-import.ts
91398
- const isR3fRuntimeModule = (moduleSource) => R3F_PUBLIC_MODULES.has(moduleSource) || moduleSource.startsWith("@react-three/");
91399
- const hasR3fRuntimeImport = (program, scopes) => program.body.some((statement) => {
91400
- if (isNodeOfType(statement, "ImportDeclaration") && !isTypeOnlyImport(statement) && typeof statement.source.value === "string") return isR3fRuntimeModule(statement.source.value);
91401
- if (isNodeOfType(statement, "TSImportEqualsDeclaration")) {
91402
- const moduleSource = getModuleNamespaceSource(statement.id, scopes);
91403
- return moduleSource !== null && isR3fRuntimeModule(moduleSource);
91404
- }
91405
- if (isNodeOfType(statement, "ExpressionStatement")) {
91406
- const moduleSource = getGlobalRequireModuleSource(statement.expression, scopes);
91407
- return moduleSource !== null && isR3fRuntimeModule(moduleSource);
91408
- }
91409
- if (!isNodeOfType(statement, "VariableDeclaration")) return false;
91410
- return statement.declarations.some((declaration) => {
91411
- if (!declaration.init) return false;
91412
- const moduleSource = getGlobalRequireModuleSource(declaration.init, scopes);
91413
- return moduleSource !== null && isR3fRuntimeModule(moduleSource);
91414
- });
91415
- });
91416
- //#endregion
91417
91421
  //#region src/plugin/rules/r3f/utils/is-r3f-canvas.ts
91418
91422
  const isR3fCanvas = (node, context) => {
91419
91423
  const provenance = getApiReferenceProvenance(node.name, context.scopes);
@@ -91501,6 +91505,43 @@ const resolveLocalReactCallback = (expression, scopes) => {
91501
91505
  return resolveExactLocalFunction(wrappedCallback, scopes);
91502
91506
  };
91503
91507
  //#endregion
91508
+ //#region src/plugin/rules/r3f/utils/resolve-raw-device-pixel-ratio.ts
91509
+ const resolveRawDevicePixelRatio = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
91510
+ const candidate = stripParenExpression(expression);
91511
+ if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "+") return resolveRawDevicePixelRatio(candidate.argument, scopes, visitedSymbolIds);
91512
+ if (isNodeOfType(candidate, "BinaryExpression")) {
91513
+ const rawLeft = resolveRawDevicePixelRatio(candidate.left, scopes, new Set(visitedSymbolIds));
91514
+ const rawRight = resolveRawDevicePixelRatio(candidate.right, scopes, new Set(visitedSymbolIds));
91515
+ if (rawLeft && !rawRight) {
91516
+ const rightOperand = stripParenExpression(candidate.right);
91517
+ if (isNodeOfType(rightOperand, "Literal") && typeof rightOperand.value === "number" && Number.isFinite(rightOperand.value) && (candidate.operator === "+" || candidate.operator === "-" || (candidate.operator === "*" || candidate.operator === "/" || candidate.operator === "**") && rightOperand.value > 0)) return rawLeft;
91518
+ }
91519
+ if (rawRight && !rawLeft) {
91520
+ const leftOperand = stripParenExpression(candidate.left);
91521
+ if (isNodeOfType(leftOperand, "Literal") && typeof leftOperand.value === "number" && Number.isFinite(leftOperand.value) && (candidate.operator === "+" || candidate.operator === "*" && leftOperand.value > 0)) return rawRight;
91522
+ }
91523
+ return null;
91524
+ }
91525
+ if (isNodeOfType(candidate, "ArrayExpression") && candidate.elements.length === 2) {
91526
+ const upperBound = candidate.elements[1];
91527
+ return upperBound && !isNodeOfType(upperBound, "SpreadElement") ? resolveRawDevicePixelRatio(upperBound, scopes, new Set(visitedSymbolIds)) : null;
91528
+ }
91529
+ if (isNodeOfType(candidate, "MemberExpression")) {
91530
+ const receiver = stripParenExpression(candidate.object);
91531
+ return getStaticPropertyName(candidate) === "devicePixelRatio" && isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver) ? candidate : null;
91532
+ }
91533
+ if (!isNodeOfType(candidate, "Identifier")) return null;
91534
+ const symbol = scopes.symbolFor(candidate);
91535
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
91536
+ visitedSymbolIds.add(symbol.id);
91537
+ if (getDestructuredBindingPropertyName(symbol.bindingIdentifier) === "devicePixelRatio") {
91538
+ const initializer = stripParenExpression(symbol.initializer);
91539
+ if (isNodeOfType(initializer, "Identifier") && (initializer.name === "window" || initializer.name === "globalThis") && scopes.isGlobalReference(initializer)) return candidate;
91540
+ }
91541
+ if (symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
91542
+ return resolveRawDevicePixelRatio(symbol.initializer, scopes, visitedSymbolIds);
91543
+ };
91544
+ //#endregion
91504
91545
  //#region src/plugin/rules/r3f/utils/walk-function-execution.ts
91505
91546
  const isUseTransitionCall = (expression, scopes) => {
91506
91547
  const candidate = stripParenExpression(expression);
@@ -91575,43 +91616,6 @@ const walkFunctionExecution = (functionNode, scopes, visitor) => {
91575
91616
  };
91576
91617
  //#endregion
91577
91618
  //#region src/plugin/rules/r3f/r3f-cap-device-pixel-ratio.ts
91578
- const THREE_RENDERER_CONSTRUCTOR_NAMES = new Set(["WebGLRenderer", "WebGPURenderer"]);
91579
- const isThreeModuleSource$4 = (moduleSource) => moduleSource === "three" || moduleSource.startsWith("three/") || moduleSource === "three-stdlib";
91580
- const resolveRawDevicePixelRatio = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
91581
- const candidate = stripParenExpression(expression);
91582
- if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "+") return resolveRawDevicePixelRatio(candidate.argument, context, visitedSymbolIds);
91583
- if (isNodeOfType(candidate, "BinaryExpression")) {
91584
- const rawLeft = resolveRawDevicePixelRatio(candidate.left, context, new Set(visitedSymbolIds));
91585
- const rawRight = resolveRawDevicePixelRatio(candidate.right, context, new Set(visitedSymbolIds));
91586
- if (rawLeft && !rawRight) {
91587
- const rightOperand = stripParenExpression(candidate.right);
91588
- if (isNodeOfType(rightOperand, "Literal") && typeof rightOperand.value === "number" && Number.isFinite(rightOperand.value) && (candidate.operator === "+" || candidate.operator === "-" || (candidate.operator === "*" || candidate.operator === "/" || candidate.operator === "**") && rightOperand.value > 0)) return rawLeft;
91589
- }
91590
- if (rawRight && !rawLeft) {
91591
- const leftOperand = stripParenExpression(candidate.left);
91592
- if (isNodeOfType(leftOperand, "Literal") && typeof leftOperand.value === "number" && Number.isFinite(leftOperand.value) && (candidate.operator === "+" || candidate.operator === "*" && leftOperand.value > 0)) return rawRight;
91593
- }
91594
- return null;
91595
- }
91596
- if (isNodeOfType(candidate, "ArrayExpression") && candidate.elements.length === 2) {
91597
- const upperBound = candidate.elements[1];
91598
- return upperBound && !isNodeOfType(upperBound, "SpreadElement") ? resolveRawDevicePixelRatio(upperBound, context, new Set(visitedSymbolIds)) : null;
91599
- }
91600
- if (isNodeOfType(candidate, "MemberExpression")) {
91601
- const receiver = stripParenExpression(candidate.object);
91602
- return getStaticPropertyName(candidate) === "devicePixelRatio" && isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && context.scopes.isGlobalReference(receiver) ? candidate : null;
91603
- }
91604
- if (!isNodeOfType(candidate, "Identifier")) return null;
91605
- const symbol = context.scopes.symbolFor(candidate);
91606
- if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
91607
- visitedSymbolIds.add(symbol.id);
91608
- if (getDestructuredBindingPropertyName(symbol.bindingIdentifier) === "devicePixelRatio") {
91609
- const initializer = stripParenExpression(symbol.initializer);
91610
- if (isNodeOfType(initializer, "Identifier") && (initializer.name === "window" || initializer.name === "globalThis") && context.scopes.isGlobalReference(initializer)) return candidate;
91611
- }
91612
- if (symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
91613
- return resolveRawDevicePixelRatio(symbol.initializer, context, visitedSymbolIds);
91614
- };
91615
91619
  const getExplicitObjectPropertyValue = (expression, propertyName) => {
91616
91620
  const candidate = stripParenExpression(expression);
91617
91621
  if (!isNodeOfType(candidate, "ObjectExpression")) return null;
@@ -91644,18 +91648,6 @@ const isR3fRootReceiver = (expression, context, visitedSymbolIds = /* @__PURE__
91644
91648
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
91645
91649
  return isR3fRootReceiver(symbol.initializer, context, visitedSymbolIds);
91646
91650
  };
91647
- const isThreeRendererReceiver = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
91648
- const candidate = stripParenExpression(expression);
91649
- if (isNodeOfType(candidate, "NewExpression")) {
91650
- const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
91651
- return Boolean(provenance && isThreeModuleSource$4(provenance.moduleSource) && THREE_RENDERER_CONSTRUCTOR_NAMES.has(provenance.apiName));
91652
- }
91653
- if (!isNodeOfType(candidate, "Identifier")) return false;
91654
- const symbol = context.scopes.symbolFor(candidate);
91655
- if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
91656
- visitedSymbolIds.add(symbol.id);
91657
- return isThreeRendererReceiver(symbol.initializer, context, visitedSymbolIds);
91658
- };
91659
91651
  const useThreeSelectsSetDpr = (call, context) => {
91660
91652
  if (!isR3fApiCall(call, "useThree", context.scopes)) return false;
91661
91653
  const selectorExpression = call.arguments[0];
@@ -91687,7 +91679,7 @@ const r3fCapDevicePixelRatio = defineRule({
91687
91679
  create: (context) => {
91688
91680
  let importsReactThreeFiber = false;
91689
91681
  const reportRawDpr = (expression) => {
91690
- const rawDpr = resolveRawDevicePixelRatio(expression, context);
91682
+ const rawDpr = resolveRawDevicePixelRatio(expression, context.scopes);
91691
91683
  if (!rawDpr) return;
91692
91684
  context.report({
91693
91685
  node: rawDpr,
@@ -91716,10 +91708,6 @@ const r3fCapDevicePixelRatio = defineRule({
91716
91708
  if (dprValue) reportRawDpr(dprValue);
91717
91709
  return;
91718
91710
  }
91719
- if (methodName === "setPixelRatio" && isThreeRendererReceiver(node.callee.object, context)) {
91720
- reportRawDpr(firstArgument);
91721
- return;
91722
- }
91723
91711
  },
91724
91712
  Identifier(node) {
91725
91713
  const parent = node.parent;
@@ -91733,6 +91721,14 @@ const r3fCapDevicePixelRatio = defineRule({
91733
91721
  });
91734
91722
  //#endregion
91735
91723
  //#region src/plugin/rules/r3f/constants.ts
91724
+ const THREE_INTERPOLATION_FACTOR_ARGUMENT_BY_METHOD = new Map([
91725
+ ["lerp", 1],
91726
+ ["lerpColors", 2],
91727
+ ["lerpHSL", 1],
91728
+ ["lerpVectors", 2],
91729
+ ["slerp", 1],
91730
+ ["slerpQuaternions", 2]
91731
+ ]);
91736
91732
  const THREE_POSTPROCESSING_PASS_DISPOSAL_RELEASES = new Map([
91737
91733
  ["RenderPixelatedPass", 147],
91738
91734
  ["OutputPass", 153],
@@ -92011,7 +92007,7 @@ const THREE_OBJECT_MEMBER_PROPERTIES = new Set([
92011
92007
  "rotation",
92012
92008
  "scale"
92013
92009
  ]);
92014
- const hasThreeObjectProvenance = (expression, callback, managedRefSymbolIds, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92010
+ const hasThreeObjectProvenance$1 = (expression, callback, managedRefSymbolIds, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92015
92011
  const firstCallbackParameter = isFunctionLike$1(callback) ? callback.params[0] : null;
92016
92012
  const callbackParameter = isNodeOfType(firstCallbackParameter, "AssignmentPattern") ? firstCallbackParameter.left : firstCallbackParameter;
92017
92013
  let current = stripParenExpression(expression);
@@ -92032,7 +92028,7 @@ const hasThreeObjectProvenance = (expression, callback, managedRefSymbolIds, con
92032
92028
  const symbol = context.scopes.symbolFor(current);
92033
92029
  if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
92034
92030
  visitedSymbolIds.add(symbol.id);
92035
- return hasThreeObjectProvenance(symbol.initializer, callback, managedRefSymbolIds, context, visitedSymbolIds);
92031
+ return hasThreeObjectProvenance$1(symbol.initializer, callback, managedRefSymbolIds, context, visitedSymbolIds);
92036
92032
  };
92037
92033
  const r3fNoCloneInUseFrame = defineRule({
92038
92034
  id: "r3f-no-clone-in-use-frame",
@@ -92049,7 +92045,7 @@ const r3fNoCloneInUseFrame = defineRule({
92049
92045
  const callback = resolveR3fCallback(node, "useFrame", context.scopes);
92050
92046
  if (!callback) return;
92051
92047
  walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
92052
- if (isConditionallyExecuted || !isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance(candidate.callee.object, callback, managedRefSymbolIds, context)) return;
92048
+ if (isConditionallyExecuted || !isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance$1(candidate.callee.object, callback, managedRefSymbolIds, context)) return;
92053
92049
  context.report({
92054
92050
  node: candidate,
92055
92051
  message: "This clone allocates a new Three.js object every executed frame. Reuse a scratch object or clone once outside useFrame"
@@ -92720,6 +92716,9 @@ const r3fNoFreshUseThreeSelector = defineRule({
92720
92716
  } })
92721
92717
  });
92722
92718
  //#endregion
92719
+ //#region src/plugin/rules/r3f/utils/is-three-module-source.ts
92720
+ const isThreeModuleSource$1 = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
92721
+ //#endregion
92723
92722
  //#region src/plugin/rules/r3f/r3f-no-imperative-attach-of-managed-ref.ts
92724
92723
  const IMPERATIVE_ATTACH_METHOD_NAMES = new Set(["add", "attach"]);
92725
92724
  const THREE_OBJECT3D_CONSTRUCTOR_NAMES = new Set([
@@ -92748,12 +92747,11 @@ const THREE_OBJECT3D_CONSTRUCTOR_NAMES = new Set([
92748
92747
  "SpotLight",
92749
92748
  "Sprite"
92750
92749
  ]);
92751
- const isThreeModuleSource$3 = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
92752
92750
  const hasThreeObject3DProvenance = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92753
92751
  const candidate = stripParenExpression(expression);
92754
92752
  if (isNodeOfType(candidate, "NewExpression")) {
92755
92753
  const provenance = getApiReferenceProvenance(candidate.callee, scopes);
92756
- return Boolean(provenance && isThreeModuleSource$3(provenance.moduleSource) && THREE_OBJECT3D_CONSTRUCTOR_NAMES.has(provenance.apiName));
92754
+ return Boolean(provenance && isThreeModuleSource$1(provenance.moduleSource) && THREE_OBJECT3D_CONSTRUCTOR_NAMES.has(provenance.apiName));
92757
92755
  }
92758
92756
  if (!isNodeOfType(candidate, "Identifier")) return false;
92759
92757
  const symbol = scopes.symbolFor(candidate);
@@ -92918,15 +92916,15 @@ const GEOMETRY_OWNER_CONSTRUCTORS = new Set([
92918
92916
  "SkinnedMesh"
92919
92917
  ]);
92920
92918
  const MATERIAL_OWNER_CONSTRUCTORS = new Set([...GEOMETRY_OWNER_CONSTRUCTORS, "Sprite"]);
92921
- const isThreeModuleSource$2 = (source) => typeof source === "string" && (source === "three" || source.startsWith("three/") || source === "three-stdlib");
92922
- const getThreeConstructorName = (constructorExpression, scopes) => {
92919
+ const isThreeModuleSource = (source) => typeof source === "string" && (source === "three" || source.startsWith("three/") || source === "three-stdlib");
92920
+ const getThreeConstructorName$1 = (constructorExpression, scopes) => {
92923
92921
  const provenance = getApiReferenceProvenance(stripParenExpression(constructorExpression), scopes);
92924
- return provenance && isThreeModuleSource$2(provenance.moduleSource) ? provenance.apiName : null;
92922
+ return provenance && isThreeModuleSource(provenance.moduleSource) ? provenance.apiName : null;
92925
92923
  };
92926
92924
  const hasThreeResourceOwnerProvenance = (expression, ownerConstructors, scopes, visitedSymbolIds) => {
92927
92925
  const candidate = stripParenExpression(expression);
92928
92926
  if (isNodeOfType(candidate, "NewExpression")) {
92929
- const constructorName = getThreeConstructorName(candidate.callee, scopes);
92927
+ const constructorName = getThreeConstructorName$1(candidate.callee, scopes);
92930
92928
  return Boolean(constructorName && ownerConstructors.has(constructorName));
92931
92929
  }
92932
92930
  if (isNodeOfType(candidate, "Identifier")) {
@@ -92940,7 +92938,7 @@ const hasThreeResourceOwnerProvenance = (expression, ownerConstructors, scopes,
92940
92938
  const getResourceMethods = (constructorSuffix) => constructorSuffix === "Geometry" ? GEOMETRY_RESOURCE_METHODS : MATERIAL_RESOURCE_METHODS;
92941
92939
  const hasThreeResourceProvenance = (expression, constructorSuffix, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92942
92940
  const candidate = stripParenExpression(expression);
92943
- if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
92941
+ if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName$1(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
92944
92942
  if (isNodeOfType(candidate, "Identifier")) {
92945
92943
  const symbol = scopes.symbolFor(candidate);
92946
92944
  if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
@@ -92980,7 +92978,7 @@ const hasProvenIndexedThreeGeometry = (expression, scopes, visitedSymbolIds = /*
92980
92978
  };
92981
92979
  const hasFreshThreeResource = (expression, constructorSuffix, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92982
92980
  const candidate = stripParenExpression(expression);
92983
- if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
92981
+ if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName$1(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
92984
92982
  if (isNodeOfType(candidate, "Identifier")) {
92985
92983
  const symbol = scopes.symbolFor(candidate);
92986
92984
  if (symbol?.kind !== "const" || symbol.scope.kind === "module" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
@@ -93599,32 +93597,39 @@ const isGlobalBrowserFunctionCall = (call, functionName, scopes) => {
93599
93597
  return isNodeOfType(receiver, "Identifier") && (receiver.name === "globalThis" || receiver.name === "window") && scopes.isGlobalReference(receiver);
93600
93598
  };
93601
93599
  //#endregion
93602
- //#region src/plugin/rules/r3f/r3f-no-recursive-raf-with-use-frame.ts
93603
- const EFFECT_HOOK_NAMES$3 = new Set([
93604
- "useEffect",
93605
- "useInsertionEffect",
93606
- "useLayoutEffect"
93607
- ]);
93600
+ //#region src/plugin/utils/resolve-recursive-animation-frame-callback.ts
93608
93601
  const getAnimationFrameCallback = (call, scopes) => {
93609
93602
  const callbackArgument = call.arguments[0];
93610
- if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
93611
- return resolveExactLocalFunction(callbackArgument, scopes);
93603
+ return callbackArgument && !isNodeOfType(callbackArgument, "SpreadElement") ? resolveExactLocalFunction(callbackArgument, scopes) : null;
93612
93604
  };
93613
- const callbackDirectlySchedulesItself = (callback, scopes) => {
93605
+ const callbackSchedulesItself = (callback, scopes) => {
93614
93606
  let doesScheduleItself = false;
93615
93607
  walkAst(callback, (candidate) => {
93616
93608
  if (doesScheduleItself || candidate !== callback && isFunctionLike$1(candidate)) return false;
93617
- if (!isNodeOfType(candidate, "CallExpression") || !isGlobalBrowserFunctionCall(candidate, "requestAnimationFrame", scopes)) return;
93618
- doesScheduleItself = getAnimationFrameCallback(candidate, scopes) === callback;
93609
+ if (isNodeOfType(candidate, "CallExpression") && isGlobalBrowserFunctionCall(candidate, "requestAnimationFrame", scopes) && getAnimationFrameCallback(candidate, scopes) === callback) {
93610
+ doesScheduleItself = true;
93611
+ return false;
93612
+ }
93619
93613
  });
93620
93614
  return doesScheduleItself;
93621
93615
  };
93616
+ const resolveRecursiveAnimationFrameCallback = (call, scopes) => {
93617
+ if (!isGlobalBrowserFunctionCall(call, "requestAnimationFrame", scopes)) return null;
93618
+ const callback = getAnimationFrameCallback(call, scopes);
93619
+ return callback && callbackSchedulesItself(callback, scopes) ? callback : null;
93620
+ };
93621
+ //#endregion
93622
+ //#region src/plugin/rules/r3f/r3f-no-recursive-raf-with-use-frame.ts
93623
+ const EFFECT_HOOK_NAMES$3 = new Set([
93624
+ "useEffect",
93625
+ "useInsertionEffect",
93626
+ "useLayoutEffect"
93627
+ ]);
93622
93628
  const collectRecursiveAnimationFrameStarts = (executedFunction, scopes) => {
93623
93629
  const starts = /* @__PURE__ */ new Set();
93624
93630
  walkFunctionExecution(executedFunction, scopes, (candidate) => {
93625
93631
  if (!isNodeOfType(candidate, "CallExpression") || !isGlobalBrowserFunctionCall(candidate, "requestAnimationFrame", scopes)) return;
93626
- const callback = getAnimationFrameCallback(candidate, scopes);
93627
- if (callback && callbackDirectlySchedulesItself(callback, scopes)) starts.add(candidate);
93632
+ if (resolveRecursiveAnimationFrameCallback(candidate, scopes)) starts.add(candidate);
93628
93633
  });
93629
93634
  return starts;
93630
93635
  };
@@ -94128,7 +94133,7 @@ const r3fNoStateInPointerMove = defineRule({
94128
94133
  }
94129
94134
  });
94130
94135
  //#endregion
94131
- //#region src/plugin/rules/r3f/r3f-no-sync-readback-in-use-frame.ts
94136
+ //#region src/plugin/utils/is-cpu-typed-array.ts
94132
94137
  const CPU_TYPED_ARRAY_CONSTRUCTORS = new Set([
94133
94138
  "BigInt64Array",
94134
94139
  "BigUint64Array",
@@ -94142,44 +94147,61 @@ const CPU_TYPED_ARRAY_CONSTRUCTORS = new Set([
94142
94147
  "Uint16Array",
94143
94148
  "Uint32Array"
94144
94149
  ]);
94145
- const CANVAS_2D_CONTEXT_NAMES = new Set(["2d"]);
94150
+ const isCpuTypedArray = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94151
+ const candidate = stripParenExpression(expression);
94152
+ if (isNodeOfType(candidate, "Identifier")) {
94153
+ const symbol = scopes.symbolFor(candidate);
94154
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
94155
+ visitedSymbolIds.add(symbol.id);
94156
+ return isCpuTypedArray(symbol.initializer, scopes, visitedSymbolIds);
94157
+ }
94158
+ if (!isNodeOfType(candidate, "NewExpression")) return false;
94159
+ const callee = stripParenExpression(candidate.callee);
94160
+ return isNodeOfType(callee, "Identifier") && CPU_TYPED_ARRAY_CONSTRUCTORS.has(callee.name) && scopes.isGlobalReference(callee);
94161
+ };
94162
+ //#endregion
94163
+ //#region src/plugin/utils/is-webgl-context-reference.ts
94146
94164
  const WEBGL_CONTEXT_NAMES = new Set([
94147
94165
  "experimental-webgl",
94148
94166
  "webgl",
94149
94167
  "webgl2"
94150
94168
  ]);
94151
- const isContextFromGetContext = (expression, contextNames, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94169
+ const isWebglContextReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94152
94170
  const candidate = stripParenExpression(expression);
94153
94171
  if (isNodeOfType(candidate, "Identifier")) {
94154
94172
  const symbol = scopes.symbolFor(candidate);
94155
94173
  if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
94156
94174
  visitedSymbolIds.add(symbol.id);
94157
- return isContextFromGetContext(symbol.initializer, contextNames, scopes, visitedSymbolIds);
94175
+ return isWebglContextReference(symbol.initializer, scopes, visitedSymbolIds);
94158
94176
  }
94159
94177
  if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "getContext") return false;
94160
94178
  const contextName = candidate.arguments[0];
94161
- if (!contextName || isNodeOfType(contextName, "SpreadElement")) return false;
94162
- const staticContextName = stripParenExpression(contextName);
94163
- return Boolean(isNodeOfType(staticContextName, "Literal") && typeof staticContextName.value === "string" && contextNames.has(staticContextName.value));
94179
+ const staticContextName = contextName && !isNodeOfType(contextName, "SpreadElement") ? stripParenExpression(contextName) : null;
94180
+ return Boolean(staticContextName && isNodeOfType(staticContextName, "Literal") && typeof staticContextName.value === "string" && WEBGL_CONTEXT_NAMES.has(staticContextName.value));
94164
94181
  };
94165
- const isCpuTypedArray = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94182
+ //#endregion
94183
+ //#region src/plugin/rules/r3f/r3f-no-sync-readback-in-use-frame.ts
94184
+ const CANVAS_2D_CONTEXT_NAMES = new Set(["2d"]);
94185
+ const isContextFromGetContext = (expression, contextNames, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94166
94186
  const candidate = stripParenExpression(expression);
94167
94187
  if (isNodeOfType(candidate, "Identifier")) {
94168
94188
  const symbol = scopes.symbolFor(candidate);
94169
94189
  if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
94170
94190
  visitedSymbolIds.add(symbol.id);
94171
- return isCpuTypedArray(symbol.initializer, scopes, visitedSymbolIds);
94191
+ return isContextFromGetContext(symbol.initializer, contextNames, scopes, visitedSymbolIds);
94172
94192
  }
94173
- if (!isNodeOfType(candidate, "NewExpression")) return false;
94174
- const callee = stripParenExpression(candidate.callee);
94175
- return isNodeOfType(callee, "Identifier") && CPU_TYPED_ARRAY_CONSTRUCTORS.has(callee.name) && scopes.isGlobalReference(callee);
94193
+ if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "getContext") return false;
94194
+ const contextName = candidate.arguments[0];
94195
+ if (!contextName || isNodeOfType(contextName, "SpreadElement")) return false;
94196
+ const staticContextName = stripParenExpression(contextName);
94197
+ return Boolean(isNodeOfType(staticContextName, "Literal") && typeof staticContextName.value === "string" && contextNames.has(staticContextName.value));
94176
94198
  };
94177
94199
  const getReadbackKind = (node, callback, context) => {
94178
94200
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
94179
94201
  const methodName = getStaticPropertyName(node.callee);
94180
94202
  if (methodName === "readRenderTargetPixels" && (isR3fCallbackStateProperty(node.callee.object, callback, "gl", context.scopes) || isR3fCallbackStateProperty(node.callee.object, callback, "renderer", context.scopes))) return "three";
94181
94203
  if (methodName === "getImageData" && isContextFromGetContext(node.callee.object, CANVAS_2D_CONTEXT_NAMES, context.scopes)) return "canvas";
94182
- if (methodName === "readPixels" && isContextFromGetContext(node.callee.object, WEBGL_CONTEXT_NAMES, context.scopes)) {
94204
+ if (methodName === "readPixels" && isWebglContextReference(node.callee.object, context.scopes)) {
94183
94205
  const destination = node.arguments[6];
94184
94206
  return destination && !isNodeOfType(destination, "SpreadElement") && isCpuTypedArray(destination, context.scopes) ? "webgl" : null;
94185
94207
  }
@@ -94374,6 +94396,36 @@ const r3fPreferUseLoader = defineRule({
94374
94396
  }
94375
94397
  });
94376
94398
  //#endregion
94399
+ //#region src/plugin/rules/r3f/utils/resolve-static-number.ts
94400
+ const resolveStaticNumber = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94401
+ const candidate = stripParenExpression(expression);
94402
+ if (isNodeOfType(candidate, "Literal") && typeof candidate.value === "number") return Number.isFinite(candidate.value) ? candidate.value : null;
94403
+ if (isNodeOfType(candidate, "Identifier")) {
94404
+ const symbol = scopes.symbolFor(candidate);
94405
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
94406
+ visitedSymbolIds.add(symbol.id);
94407
+ return resolveStaticNumber(symbol.initializer, scopes, visitedSymbolIds);
94408
+ }
94409
+ if (isNodeOfType(candidate, "UnaryExpression")) {
94410
+ const argument = resolveStaticNumber(candidate.argument, scopes, visitedSymbolIds);
94411
+ if (argument === null) return null;
94412
+ if (candidate.operator === "+") return argument;
94413
+ if (candidate.operator === "-") return -argument;
94414
+ return null;
94415
+ }
94416
+ if (!isNodeOfType(candidate, "BinaryExpression")) return null;
94417
+ const left = resolveStaticNumber(candidate.left, scopes, new Set(visitedSymbolIds));
94418
+ const right = resolveStaticNumber(candidate.right, scopes, new Set(visitedSymbolIds));
94419
+ if (left === null || right === null) return null;
94420
+ let result = null;
94421
+ if (candidate.operator === "+") result = left + right;
94422
+ if (candidate.operator === "-") result = left - right;
94423
+ if (candidate.operator === "*") result = left * right;
94424
+ if (candidate.operator === "/") result = left / right;
94425
+ if (candidate.operator === "**") result = left ** right;
94426
+ return result !== null && Number.isFinite(result) ? result : null;
94427
+ };
94428
+ //#endregion
94377
94429
  //#region src/plugin/rules/r3f/r3f-require-frame-delta.ts
94378
94430
  const TRANSFORM_PROPERTIES = new Set([
94379
94431
  "position",
@@ -94436,34 +94488,6 @@ const expressionReferencesDelta = (expression, callback, context, visitedSymbolI
94436
94488
  });
94437
94489
  return referencesDelta;
94438
94490
  };
94439
- const resolveStaticNumber = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94440
- const candidate = stripParenExpression(expression);
94441
- if (isNodeOfType(candidate, "Literal") && typeof candidate.value === "number") return Number.isFinite(candidate.value) ? candidate.value : null;
94442
- if (isNodeOfType(candidate, "Identifier")) {
94443
- const symbol = context.scopes.symbolFor(candidate);
94444
- if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
94445
- visitedSymbolIds.add(symbol.id);
94446
- return resolveStaticNumber(symbol.initializer, context, visitedSymbolIds);
94447
- }
94448
- if (isNodeOfType(candidate, "UnaryExpression")) {
94449
- const argument = resolveStaticNumber(candidate.argument, context, visitedSymbolIds);
94450
- if (argument === null) return null;
94451
- if (candidate.operator === "+") return argument;
94452
- if (candidate.operator === "-") return -argument;
94453
- return null;
94454
- }
94455
- if (!isNodeOfType(candidate, "BinaryExpression")) return null;
94456
- const left = resolveStaticNumber(candidate.left, context, new Set(visitedSymbolIds));
94457
- const right = resolveStaticNumber(candidate.right, context, new Set(visitedSymbolIds));
94458
- if (left === null || right === null) return null;
94459
- let result = null;
94460
- if (candidate.operator === "+") result = left + right;
94461
- if (candidate.operator === "-") result = left - right;
94462
- if (candidate.operator === "*") result = left * right;
94463
- if (candidate.operator === "/") result = left / right;
94464
- if (candidate.operator === "**") result = left ** right;
94465
- return result !== null && Number.isFinite(result) ? result : null;
94466
- };
94467
94491
  const isThreeMathUtils = (expression, context) => {
94468
94492
  return getApiReferenceModuleSource(expression, "MathUtils", context.scopes) === "three";
94469
94493
  };
@@ -94523,7 +94547,7 @@ const isConditionallyExecutedOnlyByReactRefAvailability = (node, callback, conte
94523
94547
  }
94524
94548
  return didFindRefAvailabilityCondition;
94525
94549
  };
94526
- const getFixedInterpolationFactor = (node, callback, managedRefSymbolIds, context) => {
94550
+ const getFixedInterpolationFactor$1 = (node, callback, managedRefSymbolIds, context) => {
94527
94551
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
94528
94552
  const methodName = getStaticPropertyName(node.callee);
94529
94553
  let factorArgumentIndex;
@@ -94532,7 +94556,7 @@ const getFixedInterpolationFactor = (node, callback, managedRefSymbolIds, contex
94532
94556
  if (factorArgumentIndex === void 0) return null;
94533
94557
  const factor = node.arguments[factorArgumentIndex];
94534
94558
  if (!factor || isNodeOfType(factor, "SpreadElement")) return null;
94535
- const staticFactor = resolveStaticNumber(factor, context);
94559
+ const staticFactor = resolveStaticNumber(factor, context.scopes);
94536
94560
  return staticFactor !== null && staticFactor > 0 && staticFactor < 1 ? factor : null;
94537
94561
  };
94538
94562
  const getRotationOwner = (expression) => {
@@ -94606,7 +94630,7 @@ const r3fRequireFrameDelta = defineRule({
94606
94630
  return;
94607
94631
  }
94608
94632
  if (!isNodeOfType(candidate, "CallExpression")) return;
94609
- const factor = getFixedInterpolationFactor(candidate, callback, managedRefSymbolIds, context);
94633
+ const factor = getFixedInterpolationFactor$1(candidate, callback, managedRefSymbolIds, context);
94610
94634
  if (!factor || expressionReferencesDelta(factor, callback, context) || isConditionallyExecuted && !isBehindReactRefAvailabilityGuard) return;
94611
94635
  context.report({
94612
94636
  node: factor,
@@ -95646,20 +95670,30 @@ const ownedResourceHasMethodCall = (analysis, methodName, scopes, matchesCall =
95646
95670
  return false;
95647
95671
  };
95648
95672
  //#endregion
95673
+ //#region src/plugin/rules/r3f/utils/create-owned-three-resource-cleanup-visitors.ts
95674
+ const createOwnedThreeResourceCleanupVisitors = ({ analysisOptions, constructorNameSuffix, context, message }) => ({ NewExpression(node) {
95675
+ const provenance = getApiReferenceProvenance(node.callee, context.scopes);
95676
+ if (!provenance || !isThreeModuleSource$1(provenance.moduleSource) || !provenance.apiName.endsWith(constructorNameSuffix)) return;
95677
+ const ownership = analyzeOwnedLifecycleResource(node, context, analysisOptions);
95678
+ if (!ownership || ownership.hasUnknownOwnershipTransfer) return;
95679
+ const allocationFunction = findEnclosingFunction$1(node);
95680
+ if (allocationFunction && ownedResourceHasMethodCall(ownership, "dispose", context.scopes, (call) => call.range[0] > node.range[1] && findEnclosingFunction$1(call) === allocationFunction && !isNodeConditionallyExecuted(call, allocationFunction))) return;
95681
+ const cleanup = analyzeOwnedLifecycleCleanup(ownership, context, (cleanupFunction) => functionInvokesOwnedResourceMethod(cleanupFunction, ownership, "dispose", context.scopes));
95682
+ if (cleanup.isProven || cleanup.isUnknown) return;
95683
+ context.report({
95684
+ node,
95685
+ message
95686
+ });
95687
+ } });
95688
+ //#endregion
95649
95689
  //#region src/plugin/rules/r3f/r3f-require-owned-texture-cleanup.ts
95650
- const OWNED_TEXTURE_CONSTRUCTOR_NAMES = new Set([
95651
- "CanvasTexture",
95652
- "DataTexture",
95653
- "Texture",
95654
- "VideoTexture"
95655
- ]);
95656
95690
  const TEXTURE_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
95657
95691
  const isMaterialTexturePropertyName = (propertyName) => propertyName === "map" || propertyName === "matcap" || Boolean(propertyName?.endsWith("Map"));
95658
95692
  const isThreeMaterialAllocation = (expression, context) => {
95659
95693
  const candidate = stripParenExpression(expression);
95660
95694
  if (!isNodeOfType(candidate, "NewExpression")) return false;
95661
95695
  const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
95662
- return Boolean(provenance?.moduleSource === "three" && provenance.apiName.endsWith("Material"));
95696
+ return Boolean(provenance && isThreeModuleSource$1(provenance.moduleSource) && provenance.apiName.endsWith("Material"));
95663
95697
  };
95664
95698
  const expressionResolvesToThreeMaterial = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
95665
95699
  const candidate = stripParenExpression(expression);
@@ -95722,27 +95756,21 @@ const r3fRequireOwnedTextureCleanup = defineRule({
95722
95756
  category: "Performance",
95723
95757
  severity: "warn",
95724
95758
  recommendation: "Dispose locally constructed textures in a React effect cleanup",
95725
- create: (context) => ({ NewExpression(node) {
95726
- const provenance = getApiReferenceProvenance(node.callee, context.scopes);
95727
- if (provenance?.moduleSource !== "three" || !OWNED_TEXTURE_CONSTRUCTOR_NAMES.has(provenance.apiName)) return;
95728
- const ownership = analyzeOwnedLifecycleResource(node, context, {
95759
+ create: (context) => createOwnedThreeResourceCleanupVisitors({
95760
+ analysisOptions: {
95729
95761
  borrowedArgumentMethodNames: TEXTURE_BORROWING_METHOD_NAMES,
95730
95762
  isBorrowedReference: (reference) => isBorrowedByThreeMaterial(reference, context),
95731
95763
  retainsOwnershipInJsx: true
95732
- });
95733
- if (!ownership || ownership.hasUnknownOwnershipTransfer) return;
95734
- const cleanup = analyzeOwnedLifecycleCleanup(ownership, context, (cleanupFunction) => functionInvokesOwnedResourceMethod(cleanupFunction, ownership, "dispose", context.scopes));
95735
- if (cleanup.isProven || cleanup.isUnknown) return;
95736
- context.report({
95737
- node,
95738
- message: "This locally constructed Three.js texture owns GPU resources but has no provable React cleanup. Dispose it when the owning component or hook releases it"
95739
- });
95740
- } })
95764
+ },
95765
+ constructorNameSuffix: "Texture",
95766
+ context,
95767
+ message: "This locally constructed Three.js texture owns GPU resources but has no provable React cleanup. Dispose it when the owning component or hook releases it"
95768
+ })
95741
95769
  });
95742
95770
  //#endregion
95743
95771
  //#region src/plugin/rules/r3f/r3f-require-projection-matrix-update.ts
95744
95772
  const CAMERA_HOST_NAMES = new Set(["orthographicCamera", "perspectiveCamera"]);
95745
- const PROJECTION_PROPERTY_NAMES = new Set([
95773
+ const PROJECTION_PROPERTY_NAMES$1 = new Set([
95746
95774
  "aspect",
95747
95775
  "bottom",
95748
95776
  "far",
@@ -95805,7 +95833,7 @@ const hasManagedCameraRefProvenance = (expression, managedCameraRefSymbolIds, co
95805
95833
  const hasR3fCameraProvenance = (expression, frameCallbacks, managedCameraRefSymbolIds, context) => hasStableRootBinding(expression, context.scopes) && (hasUseThreeCameraProvenance(expression, context) || hasFrameCameraProvenance(expression, frameCallbacks, context) || hasManagedCameraRefProvenance(expression, managedCameraRefSymbolIds, context));
95806
95834
  const getProjectionMutationReceiver = (node) => {
95807
95835
  const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? stripParenExpression(node.left) : isNodeOfType(node, "UpdateExpression") ? stripParenExpression(node.argument) : null;
95808
- return mutationTarget && isNodeOfType(mutationTarget, "MemberExpression") && PROJECTION_PROPERTY_NAMES.has(getStaticPropertyName(mutationTarget) ?? "") ? mutationTarget.object : null;
95836
+ return mutationTarget && isNodeOfType(mutationTarget, "MemberExpression") && PROJECTION_PROPERTY_NAMES$1.has(getStaticPropertyName(mutationTarget) ?? "") ? mutationTarget.object : null;
95809
95837
  };
95810
95838
  const getOnlyCallExpression = (statement) => {
95811
95839
  if (isNodeOfType(statement, "BlockStatement")) return statement.body.length === 1 ? getOnlyCallExpression(statement.body[0]) : null;
@@ -96229,14 +96257,16 @@ const r3fWebgpuNoGlState = defineRule({
96229
96257
  })
96230
96258
  });
96231
96259
  //#endregion
96232
- //#region src/plugin/rules/r3f/r3f-webgpu-no-js-uniform-branch.ts
96233
- const WEBGPU_GRAPH_HOOKS = new Set([
96234
- "useLocalNodes",
96235
- "useNodes",
96236
- "usePostProcessing",
96237
- "useRenderPipeline"
96238
- ]);
96239
- const WEBGPU_TWO_CALLBACK_HOOKS = new Set(["usePostProcessing", "useRenderPipeline"]);
96260
+ //#region src/plugin/rules/r3f/utils/get-control-flow-test.ts
96261
+ const getControlFlowTest = (node) => {
96262
+ if (isNodeOfType(node, "IfStatement") || isNodeOfType(node, "WhileStatement") || isNodeOfType(node, "DoWhileStatement") || isNodeOfType(node, "ConditionalExpression")) return node.test;
96263
+ if (isNodeOfType(node, "SwitchStatement")) return node.discriminant;
96264
+ if (isNodeOfType(node, "ForStatement")) return node.test;
96265
+ if (isNodeOfType(node, "LogicalExpression")) return node.left;
96266
+ return null;
96267
+ };
96268
+ //#endregion
96269
+ //#region src/plugin/rules/r3f/utils/resolves-to-tsl-uniform.ts
96240
96270
  const TSL_UNIFORM_MODULES = new Set(["three/tsl", "three/webgpu"]);
96241
96271
  const resolvesToTslUniform = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
96242
96272
  const candidate = stripParenExpression(expression);
@@ -96247,6 +96277,15 @@ const resolvesToTslUniform = (expression, scopes, visitedSymbolIds = /* @__PURE_
96247
96277
  visitedSymbolIds.add(symbol.id);
96248
96278
  return resolvesToTslUniform(symbol.initializer, scopes, visitedSymbolIds);
96249
96279
  };
96280
+ //#endregion
96281
+ //#region src/plugin/rules/r3f/r3f-webgpu-no-js-uniform-branch.ts
96282
+ const WEBGPU_GRAPH_HOOKS = new Set([
96283
+ "useLocalNodes",
96284
+ "useNodes",
96285
+ "usePostProcessing",
96286
+ "useRenderPipeline"
96287
+ ]);
96288
+ const WEBGPU_TWO_CALLBACK_HOOKS = new Set(["usePostProcessing", "useRenderPipeline"]);
96250
96289
  const isUniformValueMember = (expression, callback, scopes) => {
96251
96290
  const candidate = stripParenExpression(expression);
96252
96291
  if (!isNodeOfType(candidate, "MemberExpression") || getStaticPropertyName(candidate) !== "value") return false;
@@ -96277,13 +96316,6 @@ const expressionReferencesUniformValue = (expression, callback, scopes, visitedS
96277
96316
  });
96278
96317
  return didFindUniformValue;
96279
96318
  };
96280
- const getControlFlowTest = (node) => {
96281
- if (isNodeOfType(node, "IfStatement") || isNodeOfType(node, "WhileStatement") || isNodeOfType(node, "DoWhileStatement") || isNodeOfType(node, "ConditionalExpression")) return node.test;
96282
- if (isNodeOfType(node, "SwitchStatement")) return node.discriminant;
96283
- if (isNodeOfType(node, "ForStatement")) return node.test;
96284
- if (isNodeOfType(node, "LogicalExpression")) return node.left;
96285
- return null;
96286
- };
96287
96319
  const getWebgpuGraphHookName = (node, context) => {
96288
96320
  for (const hookName of WEBGPU_GRAPH_HOOKS) if (isApiCallFromModules(node, hookName, R3F_WEBGPU_MODULES, context.scopes)) return hookName;
96289
96321
  return null;
@@ -113807,6 +113839,363 @@ const tenantStaticProxyRisk = defineRule({
113807
113839
  })
113808
113840
  });
113809
113841
  //#endregion
113842
+ //#region src/plugin/rules/r3f/utils/get-three-constructor-name.ts
113843
+ const getThreeConstructorName = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
113844
+ const candidate = stripParenExpression(expression);
113845
+ if (isNodeOfType(candidate, "NewExpression")) {
113846
+ const provenance = getApiReferenceProvenance(candidate.callee, scopes);
113847
+ return provenance && isThreeModuleSource$1(provenance.moduleSource) ? provenance.apiName : null;
113848
+ }
113849
+ if (!isNodeOfType(candidate, "Identifier")) return null;
113850
+ const symbol = scopes.symbolFor(candidate);
113851
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
113852
+ visitedSymbolIds.add(symbol.id);
113853
+ return getThreeConstructorName(symbol.initializer, scopes, visitedSymbolIds);
113854
+ };
113855
+ //#endregion
113856
+ //#region src/plugin/rules/r3f/utils/is-three-renderer-reference.ts
113857
+ const THREE_RENDERER_CONSTRUCTOR_NAMES = new Set(["WebGLRenderer", "WebGPURenderer"]);
113858
+ const isThreeRendererReference = (expression, scopes) => THREE_RENDERER_CONSTRUCTOR_NAMES.has(getThreeConstructorName(expression, scopes) ?? "");
113859
+ //#endregion
113860
+ //#region src/plugin/rules/r3f/three-cap-device-pixel-ratio.ts
113861
+ const threeCapDevicePixelRatio = defineRule({
113862
+ id: "three-cap-device-pixel-ratio",
113863
+ title: "Unbounded Three.js device pixel ratio",
113864
+ category: "Performance",
113865
+ severity: "warn",
113866
+ recommendation: "Cap renderer pixel ratio, commonly at 2, so high-density displays do not multiply GPU work without a bound",
113867
+ create: (context) => ({ CallExpression(node) {
113868
+ if (!isNodeOfType(node.callee, "MemberExpression") || getStaticPropertyName(node.callee) !== "setPixelRatio" || !isThreeRendererReference(node.callee.object, context.scopes)) return;
113869
+ const pixelRatio = node.arguments[0];
113870
+ if (!pixelRatio || isNodeOfType(pixelRatio, "SpreadElement")) return;
113871
+ const rawPixelRatio = resolveRawDevicePixelRatio(pixelRatio, context.scopes);
113872
+ if (!rawPixelRatio) return;
113873
+ context.report({
113874
+ node: rawPixelRatio,
113875
+ message: "This renderer uses the device's raw pixel ratio without a cap. Bound the ratio to limit the rendered pixel count on high-density displays"
113876
+ });
113877
+ } })
113878
+ });
113879
+ //#endregion
113880
+ //#region src/plugin/rules/r3f/three-limit-shadowed-point-lights.ts
113881
+ const getExecutionOwner = (node, program) => findEnclosingFunction$1(node) ?? program;
113882
+ const threeLimitShadowedPointLights = defineRule({
113883
+ id: "three-limit-shadowed-point-lights",
113884
+ title: "Too many shadow-casting Three.js point lights",
113885
+ category: "Performance",
113886
+ severity: "warn",
113887
+ recommendation: "Keep at most two shadow-casting point lights in one scene, or use cheaper directional, spot, baked, or fake shadows",
113888
+ create: (context) => {
113889
+ const sceneLightFacts = [];
113890
+ const shadowedPointLights = [];
113891
+ let program = null;
113892
+ return {
113893
+ Program(node) {
113894
+ program = node;
113895
+ },
113896
+ CallExpression(node) {
113897
+ if (!program || !isNodeOfType(node.callee, "MemberExpression") || getStaticPropertyName(node.callee) !== "add" || getThreeConstructorName(node.callee.object, context.scopes) !== "Scene") return;
113898
+ const owner = getExecutionOwner(node, program);
113899
+ if (isNodeConditionallyExecuted(node, owner)) return;
113900
+ const sceneKey = resolveExpressionKey$1(node.callee.object, context);
113901
+ if (!sceneKey) return;
113902
+ for (const light of node.arguments) {
113903
+ if (isNodeOfType(light, "SpreadElement") || getThreeConstructorName(light, context.scopes) !== "PointLight") continue;
113904
+ const lightKey = resolveExpressionKey$1(light, context);
113905
+ if (lightKey) sceneLightFacts.push({
113906
+ lightKey,
113907
+ owner,
113908
+ sceneKey
113909
+ });
113910
+ }
113911
+ },
113912
+ AssignmentExpression(node) {
113913
+ const assignedValue = stripParenExpression(node.right);
113914
+ if (!program || node.operator !== "=" || !isNodeOfType(node.left, "MemberExpression") || getStaticPropertyName(node.left) !== "castShadow" || getThreeConstructorName(node.left.object, context.scopes) !== "PointLight" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== true) return;
113915
+ const owner = getExecutionOwner(node, program);
113916
+ if (isNodeConditionallyExecuted(node, owner)) return;
113917
+ const lightKey = resolveExpressionKey$1(node.left.object, context);
113918
+ if (lightKey) shadowedPointLights.push({
113919
+ lightKey,
113920
+ node,
113921
+ owner
113922
+ });
113923
+ },
113924
+ "Program:exit"() {
113925
+ const countByOwnerAndScene = /* @__PURE__ */ new Map();
113926
+ const countedLights = /* @__PURE__ */ new Map();
113927
+ for (const shadowedLight of shadowedPointLights) {
113928
+ const matchingScene = sceneLightFacts.find((sceneLight) => sceneLight.owner === shadowedLight.owner && sceneLight.lightKey === shadowedLight.lightKey);
113929
+ if (!matchingScene) continue;
113930
+ const ownerCounts = countByOwnerAndScene.get(shadowedLight.owner) ?? /* @__PURE__ */ new Map();
113931
+ const ownerLights = countedLights.get(shadowedLight.owner) ?? /* @__PURE__ */ new Map();
113932
+ countByOwnerAndScene.set(shadowedLight.owner, ownerCounts);
113933
+ countedLights.set(shadowedLight.owner, ownerLights);
113934
+ const sceneLights = ownerLights.get(matchingScene.sceneKey) ?? /* @__PURE__ */ new Set();
113935
+ ownerLights.set(matchingScene.sceneKey, sceneLights);
113936
+ if (sceneLights.has(shadowedLight.lightKey)) continue;
113937
+ sceneLights.add(shadowedLight.lightKey);
113938
+ const nextCount = (ownerCounts.get(matchingScene.sceneKey) ?? 0) + 1;
113939
+ ownerCounts.set(matchingScene.sceneKey, nextCount);
113940
+ if (nextCount <= 2) continue;
113941
+ context.report({
113942
+ node: shadowedLight.node,
113943
+ message: "This is the third or later shadow-casting point light added to the same scene. Each point-light shadow renders six cube faces, multiplying shadow passes"
113944
+ });
113945
+ }
113946
+ }
113947
+ };
113948
+ }
113949
+ });
113950
+ //#endregion
113951
+ //#region src/plugin/rules/r3f/utils/has-three-object-provenance.ts
113952
+ const hasThreeObjectProvenance = (expression, scopes) => {
113953
+ let candidate = stripParenExpression(expression);
113954
+ while (isNodeOfType(candidate, "MemberExpression")) candidate = stripParenExpression(candidate.object);
113955
+ return getThreeConstructorName(candidate, scopes) !== null;
113956
+ };
113957
+ //#endregion
113958
+ //#region src/plugin/rules/r3f/utils/resolve-three-pointer-move-callback.ts
113959
+ const callbackExecutesThreeWork = (callback, context) => {
113960
+ let executesThreeWork = false;
113961
+ walkFunctionExecution(callback, context.scopes, (candidate) => {
113962
+ if (executesThreeWork) return;
113963
+ if (isNodeOfType(candidate, "NewExpression")) {
113964
+ const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
113965
+ executesThreeWork = Boolean(provenance && isThreeModuleSource$1(provenance.moduleSource));
113966
+ return;
113967
+ }
113968
+ if (isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression")) {
113969
+ executesThreeWork = hasThreeObjectProvenance(candidate.callee.object, context.scopes);
113970
+ return;
113971
+ }
113972
+ const target = isNodeOfType(candidate, "AssignmentExpression") ? stripParenExpression(candidate.left) : isNodeOfType(candidate, "UpdateExpression") ? stripParenExpression(candidate.argument) : null;
113973
+ executesThreeWork = Boolean(target && isNodeOfType(target, "MemberExpression") && hasThreeObjectProvenance(target.object, context.scopes));
113974
+ });
113975
+ return executesThreeWork;
113976
+ };
113977
+ const getPointerMoveListenerCallback = (node, context) => {
113978
+ if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "MemberExpression") || getStaticPropertyName(node.callee) !== "addEventListener") return null;
113979
+ const eventName = node.arguments[0];
113980
+ const callback = node.arguments[1];
113981
+ const listenerTarget = stripParenExpression(node.callee.object);
113982
+ if (!eventName || isNodeOfType(eventName, "SpreadElement") || !isNodeOfType(eventName, "Literal") || eventName.value !== "pointermove" || !callback || isNodeOfType(callback, "SpreadElement") || !isNodeOfType(listenerTarget, "MemberExpression") || getStaticPropertyName(listenerTarget) !== "domElement" || !isThreeRendererReference(listenerTarget.object, context.scopes)) return null;
113983
+ return resolveLocalReactCallback(callback, context.scopes);
113984
+ };
113985
+ const resolveThreePointerMoveCallback = (node, context) => {
113986
+ if (isNodeOfType(node, "CallExpression")) return getPointerMoveListenerCallback(node, context);
113987
+ if (!isNodeOfType(node, "JSXOpeningElement") || resolveJsxElementType(node) !== "canvas") return null;
113988
+ const attribute = getAuthoritativeJsxAttribute(node.attributes, "onPointerMove");
113989
+ if (!attribute?.value || !isNodeOfType(attribute.value, "JSXExpressionContainer") || isNodeOfType(attribute.value.expression, "JSXEmptyExpression")) return null;
113990
+ const callback = resolveLocalReactCallback(attribute.value.expression, context.scopes);
113991
+ return callback && callbackExecutesThreeWork(callback, context) ? callback : null;
113992
+ };
113993
+ //#endregion
113994
+ //#region src/plugin/rules/r3f/three-no-allocation-in-pointer-move.ts
113995
+ const threeNoAllocationInPointerMove = defineRule({
113996
+ id: "three-no-allocation-in-pointer-move",
113997
+ title: "Three.js allocation inside pointer-move handler",
113998
+ severity: "warn",
113999
+ recommendation: "Reuse vectors, raycasters, and other Three.js objects while handling continuous pointer movement",
114000
+ create: (context) => {
114001
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
114002
+ const analyzeCallback = (node) => {
114003
+ const callback = resolveThreePointerMoveCallback(node, context);
114004
+ if (!callback || analyzedCallbacks.has(callback)) return;
114005
+ analyzedCallbacks.add(callback);
114006
+ walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
114007
+ if (isConditionallyExecuted) return;
114008
+ if (isNodeOfType(candidate, "NewExpression")) {
114009
+ const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
114010
+ if (!provenance || !isThreeModuleSource$1(provenance.moduleSource)) return;
114011
+ context.report({
114012
+ node: candidate,
114013
+ message: "This Three.js constructor allocates on every pointer movement. Reuse an object created outside the handler"
114014
+ });
114015
+ return;
114016
+ }
114017
+ if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance(candidate.callee.object, context.scopes)) return;
114018
+ context.report({
114019
+ node: candidate,
114020
+ message: "This clone allocates a Three.js object on every pointer movement. Copy into a reusable object instead"
114021
+ });
114022
+ });
114023
+ };
114024
+ return {
114025
+ CallExpression(node) {
114026
+ analyzeCallback(node);
114027
+ },
114028
+ JSXOpeningElement(node) {
114029
+ analyzeCallback(node);
114030
+ }
114031
+ };
114032
+ }
114033
+ });
114034
+ //#endregion
114035
+ //#region src/plugin/rules/r3f/utils/resolve-three-animation-loop-callback.ts
114036
+ const callbackRendersWithThree = (callback, scopes) => {
114037
+ let doesRenderWithThree = false;
114038
+ walkFunctionExecution(callback, scopes, (candidate) => {
114039
+ if (!doesRenderWithThree && isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression") && THREE_RENDER_METHOD_NAMES.has(getStaticPropertyName(candidate.callee) ?? "") && isThreeRendererReference(candidate.callee.object, scopes)) doesRenderWithThree = true;
114040
+ });
114041
+ return doesRenderWithThree;
114042
+ };
114043
+ const resolveThreeAnimationLoopCallback = (call, scopes) => {
114044
+ if (isNodeOfType(call.callee, "MemberExpression") && getStaticPropertyName(call.callee) === "setAnimationLoop" && isThreeRendererReference(call.callee.object, scopes)) {
114045
+ const callbackArgument = call.arguments[0];
114046
+ return callbackArgument && !isNodeOfType(callbackArgument, "SpreadElement") ? resolveLocalReactCallback(callbackArgument, scopes) : null;
114047
+ }
114048
+ const callback = resolveRecursiveAnimationFrameCallback(call, scopes);
114049
+ return callback && callbackRendersWithThree(callback, scopes) ? callback : null;
114050
+ };
114051
+ //#endregion
114052
+ //#region src/plugin/rules/r3f/three-no-async-animation-loop.ts
114053
+ const threeNoAsyncAnimationLoop = defineRule({
114054
+ id: "three-no-async-animation-loop",
114055
+ title: "Async Three.js animation callback",
114056
+ category: "Correctness",
114057
+ severity: "warn",
114058
+ recommendation: "Keep animation callbacks synchronous; start asynchronous work outside the loop and consume completed state during frames",
114059
+ create: (context) => {
114060
+ const reportedCallbacks = /* @__PURE__ */ new Set();
114061
+ return { CallExpression(node) {
114062
+ const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
114063
+ if (!isFunctionLike$1(callback) || !callback.async || reportedCallbacks.has(callback)) return;
114064
+ reportedCallbacks.add(callback);
114065
+ context.report({
114066
+ node: callback,
114067
+ message: "The animation scheduler ignores this Promise, so rejected work can become unhandled and awaited work can overlap across frames. Keep the callback synchronous"
114068
+ });
114069
+ } };
114070
+ }
114071
+ });
114072
+ //#endregion
114073
+ //#region src/plugin/rules/r3f/three-no-clone-in-animation-loop.ts
114074
+ const threeNoCloneInAnimationLoop = defineRule({
114075
+ id: "three-no-clone-in-animation-loop",
114076
+ title: "Three.js clone inside animation loop",
114077
+ severity: "warn",
114078
+ recommendation: "Clone once before the animation loop or copy values into a reusable scratch object during frames",
114079
+ create: (context) => {
114080
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
114081
+ return { CallExpression(node) {
114082
+ const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
114083
+ if (!callback || analyzedCallbacks.has(callback)) return;
114084
+ analyzedCallbacks.add(callback);
114085
+ walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
114086
+ if (isConditionallyExecuted || !isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance(candidate.callee.object, context.scopes)) return;
114087
+ context.report({
114088
+ node: candidate,
114089
+ message: "This clone allocates a new Three.js object every executed frame. Copy into a reusable object or clone once outside the animation loop"
114090
+ });
114091
+ });
114092
+ } };
114093
+ }
114094
+ });
114095
+ //#endregion
114096
+ //#region src/plugin/rules/r3f/three-no-new-in-animation-loop.ts
114097
+ const threeNoNewInAnimationLoop = defineRule({
114098
+ id: "three-no-new-in-animation-loop",
114099
+ title: "Allocation inside Three.js animation loop",
114100
+ severity: "warn",
114101
+ recommendation: "Allocate reusable objects before the animation loop and mutate them in place during each frame",
114102
+ create: (context) => {
114103
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
114104
+ return { CallExpression(node) {
114105
+ const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
114106
+ if (!callback || analyzedCallbacks.has(callback)) return;
114107
+ analyzedCallbacks.add(callback);
114108
+ walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
114109
+ if (candidate.type !== "NewExpression" || isConditionallyExecuted) return;
114110
+ context.report({
114111
+ node: candidate,
114112
+ message: "This constructor allocates a new object every executed frame. Reuse an object allocated outside the Three.js animation loop"
114113
+ });
114114
+ });
114115
+ } };
114116
+ }
114117
+ });
114118
+ //#endregion
114119
+ //#region src/plugin/utils/function-is-referenced-as-jsx-element.ts
114120
+ const functionIsReferencedAsJsxElement = (functionNode, scopes) => getFunctionBindingSymbols(functionNode, scopes).some((symbol) => symbol.references.some((reference) => {
114121
+ const referenceNode = reference.identifier;
114122
+ const parentNode = referenceNode.parent;
114123
+ return Boolean(isNodeOfType(referenceNode, "JSXIdentifier") && parentNode && isNodeOfType(parentNode, "JSXOpeningElement") && parentNode.name === referenceNode);
114124
+ }));
114125
+ //#endregion
114126
+ //#region src/plugin/rules/r3f/three-no-object-construction-in-render.ts
114127
+ const threeNoObjectConstructionInRender = defineRule({
114128
+ id: "three-no-object-construction-in-render",
114129
+ title: "Three.js object constructed during React render",
114130
+ severity: "warn",
114131
+ recommendation: "Construct mutable Three.js objects in a stable initializer, effect, event, or module scope instead of recreating them during React render",
114132
+ create: (context) => ({ NewExpression(node) {
114133
+ const provenance = getApiReferenceProvenance(node.callee, context.scopes);
114134
+ const renderOwner = findRenderPhaseComponentOrHook(node, context.scopes);
114135
+ if (!provenance || !isThreeModuleSource$1(provenance.moduleSource) || !renderOwner || isInsideStableReactInitializer(node, context.scopes)) return;
114136
+ const renderOwnerName = componentOrHookDisplayNameForFunction(renderOwner);
114137
+ if (!renderOwnerName || !isReactHookName(renderOwnerName) && !functionHasReactComponentEvidence(renderOwner, context.scopes, context.cfg) && !functionIsReferencedAsJsxElement(renderOwner, context.scopes)) return;
114138
+ context.report({
114139
+ node,
114140
+ message: `new ${provenance.apiName}() creates a fresh mutable Three.js object during this render. Move it to useMemo, a lazy useState initializer, an initialized-once ref, or module scope`
114141
+ });
114142
+ } })
114143
+ });
114144
+ //#endregion
114145
+ //#region src/plugin/rules/r3f/three-no-state-in-animation-loop.ts
114146
+ const threeNoStateInAnimationLoop = defineRule({
114147
+ id: "three-no-state-in-animation-loop",
114148
+ title: "React state update inside Three.js animation loop",
114149
+ severity: "warn",
114150
+ recommendation: "Mutate Three.js objects or refs during frames; reserve React state for guarded, infrequent transitions",
114151
+ create: (context) => {
114152
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
114153
+ return { CallExpression(node) {
114154
+ const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
114155
+ if (!callback || analyzedCallbacks.has(callback)) return;
114156
+ analyzedCallbacks.add(callback);
114157
+ walkFunctionExecution(callback, context.scopes, (candidate) => {
114158
+ if (!isNodeOfType(candidate, "CallExpression") || !resolveStateSetterBinding(candidate.callee, context.scopes) || isGuardedStateTransition(candidate, callback, context.scopes)) return;
114159
+ context.report({
114160
+ node: candidate,
114161
+ message: "This React state update can schedule a component render every frame. Mutate a Three.js object or ref, or guard an infrequent state transition"
114162
+ });
114163
+ });
114164
+ } };
114165
+ }
114166
+ });
114167
+ //#endregion
114168
+ //#region src/plugin/rules/r3f/three-no-state-in-pointer-move.ts
114169
+ const threeNoStateInPointerMove = defineRule({
114170
+ id: "three-no-state-in-pointer-move",
114171
+ title: "React state update inside Three.js pointer-move handler",
114172
+ severity: "warn",
114173
+ recommendation: "Keep continuous pointer previews in Three.js objects or refs and publish React state when the interaction commits",
114174
+ create: (context) => {
114175
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
114176
+ const analyzeCallback = (node) => {
114177
+ const callback = resolveThreePointerMoveCallback(node, context);
114178
+ if (!callback || analyzedCallbacks.has(callback)) return;
114179
+ analyzedCallbacks.add(callback);
114180
+ walkFunctionExecution(callback, context.scopes, (candidate) => {
114181
+ if (!isNodeOfType(candidate, "CallExpression") || !resolveStateSetterBinding(candidate.callee, context.scopes) || isGuardedStateTransition(candidate, callback, context.scopes)) return;
114182
+ context.report({
114183
+ node: candidate,
114184
+ message: "This React state update can render on every pointer movement. Mutate a Three.js object or ref and publish state on pointer-up"
114185
+ });
114186
+ });
114187
+ };
114188
+ return {
114189
+ CallExpression(node) {
114190
+ analyzeCallback(node);
114191
+ },
114192
+ JSXOpeningElement(node) {
114193
+ analyzeCallback(node);
114194
+ }
114195
+ };
114196
+ }
114197
+ });
114198
+ //#endregion
113810
114199
  //#region src/plugin/rules/r3f/three-require-animation-mixer-cleanup.ts
113811
114200
  const MIXER_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
113812
114201
  const FINE_GRAINED_UNCACHE_METHOD_NAMES = ["uncacheAction", "uncacheClip"];
@@ -113914,6 +114303,247 @@ const threeRequireControlsCleanup = defineRule({
113914
114303
  } })
113915
114304
  });
113916
114305
  //#endregion
114306
+ //#region src/plugin/rules/r3f/three-require-frame-delta.ts
114307
+ const TRANSFORM_PROPERTY_NAMES = new Set([
114308
+ "position",
114309
+ "quaternion",
114310
+ "rotation",
114311
+ "scale"
114312
+ ]);
114313
+ const isThreeTransformMember = (expression, context) => {
114314
+ let candidate = stripParenExpression(expression);
114315
+ let hasTransformProperty = false;
114316
+ while (isNodeOfType(candidate, "MemberExpression")) {
114317
+ if (TRANSFORM_PROPERTY_NAMES.has(getStaticPropertyName(candidate) ?? "")) hasTransformProperty = true;
114318
+ candidate = stripParenExpression(candidate.object);
114319
+ }
114320
+ return hasTransformProperty && hasThreeObjectProvenance(expression, context.scopes);
114321
+ };
114322
+ const expressionUsesThreeClockDelta = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
114323
+ let usesDelta = false;
114324
+ walkAst(expression, (candidate) => {
114325
+ if (isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression") && getStaticPropertyName(candidate.callee) === "getDelta" && getThreeConstructorName(candidate.callee.object, context.scopes) === "Clock") {
114326
+ usesDelta = true;
114327
+ return false;
114328
+ }
114329
+ if (!isNodeOfType(candidate, "Identifier")) return;
114330
+ const symbol = context.scopes.symbolFor(candidate);
114331
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return;
114332
+ visitedSymbolIds.add(symbol.id);
114333
+ if (expressionUsesThreeClockDelta(symbol.initializer, context, visitedSymbolIds)) {
114334
+ usesDelta = true;
114335
+ return false;
114336
+ }
114337
+ });
114338
+ return usesDelta;
114339
+ };
114340
+ const getFixedInterpolationFactor = (node, context) => {
114341
+ if (!isNodeOfType(node.callee, "MemberExpression")) return null;
114342
+ const methodName = getStaticPropertyName(node.callee);
114343
+ let factorArgumentIndex;
114344
+ if (methodName === "lerp" && getApiReferenceModuleSource(node.callee.object, "MathUtils", context.scopes) === "three") factorArgumentIndex = 2;
114345
+ else if (methodName && hasThreeObjectProvenance(node.callee.object, context.scopes)) factorArgumentIndex = THREE_INTERPOLATION_FACTOR_ARGUMENT_BY_METHOD.get(methodName);
114346
+ if (factorArgumentIndex === void 0) return null;
114347
+ const factor = node.arguments[factorArgumentIndex];
114348
+ if (!factor || isNodeOfType(factor, "SpreadElement")) return null;
114349
+ const staticFactor = resolveStaticNumber(factor, context.scopes);
114350
+ return staticFactor !== null && staticFactor > 0 && staticFactor < 1 ? factor : null;
114351
+ };
114352
+ const threeRequireFrameDelta = defineRule({
114353
+ id: "three-require-frame-delta",
114354
+ title: "Frame-rate-dependent Three.js animation",
114355
+ category: "Correctness",
114356
+ severity: "warn",
114357
+ recommendation: "Scale incremental transforms and interpolation by Clock.getDelta(), use delta-aware damping, or assign from absolute animation time",
114358
+ create: (context) => {
114359
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
114360
+ return { CallExpression(node) {
114361
+ const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
114362
+ if (!callback || analyzedCallbacks.has(callback)) return;
114363
+ analyzedCallbacks.add(callback);
114364
+ walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
114365
+ if (isConditionallyExecuted) return;
114366
+ if (isNodeOfType(candidate, "UpdateExpression") && isThreeTransformMember(candidate.argument, context)) {
114367
+ context.report({
114368
+ node: candidate,
114369
+ message: "This transform changes by a fixed amount per frame, so animation speed depends on refresh rate. Use a Three.js Clock delta instead of an update operator"
114370
+ });
114371
+ return;
114372
+ }
114373
+ if (isNodeOfType(candidate, "AssignmentExpression") && (candidate.operator === "+=" || candidate.operator === "-=") && isThreeTransformMember(candidate.left, context) && !expressionUsesThreeClockDelta(candidate.right, context)) {
114374
+ context.report({
114375
+ node: candidate,
114376
+ message: "This transform changes by a fixed amount per frame, so animation speed depends on refresh rate. Multiply the increment by Clock.getDelta()"
114377
+ });
114378
+ return;
114379
+ }
114380
+ if (!isNodeOfType(candidate, "CallExpression")) return;
114381
+ const factor = getFixedInterpolationFactor(candidate, context);
114382
+ if (!factor || expressionUsesThreeClockDelta(factor, context)) return;
114383
+ context.report({
114384
+ node: factor,
114385
+ message: "This fixed interpolation factor converges once per frame, so its speed changes with refresh rate. Derive the factor from Clock.getDelta() or use delta-aware damping"
114386
+ });
114387
+ });
114388
+ } };
114389
+ }
114390
+ });
114391
+ //#endregion
114392
+ //#region src/plugin/rules/r3f/three-require-instanced-buffer-update.ts
114393
+ const getInstancedBufferMutation = (node, context) => {
114394
+ const callee = stripParenExpression(node.callee);
114395
+ if (!isNodeOfType(callee, "MemberExpression")) return null;
114396
+ const methodName = getStaticPropertyName(callee);
114397
+ if (methodName !== "setMatrixAt" && methodName !== "setColorAt") return null;
114398
+ if (getThreeConstructorName(callee.object, context.scopes) !== "InstancedMesh") return null;
114399
+ const receiverKey = resolveExpressionKey$1(callee.object, context);
114400
+ if (!receiverKey) return null;
114401
+ return {
114402
+ bufferPropertyName: methodName === "setMatrixAt" ? "instanceMatrix" : "instanceColor",
114403
+ methodName,
114404
+ node,
114405
+ receiverKey
114406
+ };
114407
+ };
114408
+ const getInstancedBufferCompletion = (node, context) => {
114409
+ const assignedValue = stripParenExpression(node.right);
114410
+ const needsUpdateMember = stripParenExpression(node.left);
114411
+ if (node.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== true || !isNodeOfType(needsUpdateMember, "MemberExpression") || getStaticPropertyName(needsUpdateMember) !== "needsUpdate") return null;
114412
+ const bufferMember = stripParenExpression(needsUpdateMember.object);
114413
+ if (!isNodeOfType(bufferMember, "MemberExpression")) return null;
114414
+ const bufferPropertyName = getStaticPropertyName(bufferMember);
114415
+ if (bufferPropertyName !== "instanceMatrix" && bufferPropertyName !== "instanceColor") return null;
114416
+ if (getThreeConstructorName(bufferMember.object, context.scopes) !== "InstancedMesh") return null;
114417
+ const receiverKey = resolveExpressionKey$1(bufferMember.object, context);
114418
+ return receiverKey ? {
114419
+ bufferPropertyName,
114420
+ node,
114421
+ receiverKey
114422
+ } : null;
114423
+ };
114424
+ const getOpaqueInstancedBufferCompletions = (node, context) => {
114425
+ if (!isImportedOrStableParameterCall(node, context.scopes)) return [];
114426
+ const completions = [];
114427
+ for (const argument of node.arguments) {
114428
+ if (isNodeOfType(argument, "SpreadElement")) continue;
114429
+ const candidate = stripParenExpression(argument);
114430
+ if (isNodeOfType(candidate, "MemberExpression")) {
114431
+ const bufferPropertyName = getStaticPropertyName(candidate);
114432
+ if ((bufferPropertyName === "instanceMatrix" || bufferPropertyName === "instanceColor") && getThreeConstructorName(candidate.object, context.scopes) === "InstancedMesh") {
114433
+ const receiverKey = resolveExpressionKey$1(candidate.object, context);
114434
+ if (receiverKey) completions.push({
114435
+ bufferPropertyName,
114436
+ node,
114437
+ receiverKey
114438
+ });
114439
+ continue;
114440
+ }
114441
+ }
114442
+ if (getThreeConstructorName(candidate, context.scopes) !== "InstancedMesh") continue;
114443
+ const receiverKey = resolveExpressionKey$1(candidate, context);
114444
+ if (!receiverKey) continue;
114445
+ completions.push({
114446
+ bufferPropertyName: "instanceMatrix",
114447
+ node,
114448
+ receiverKey
114449
+ });
114450
+ completions.push({
114451
+ bufferPropertyName: "instanceColor",
114452
+ node,
114453
+ receiverKey
114454
+ });
114455
+ }
114456
+ return completions;
114457
+ };
114458
+ const completionCoversMutation = (mutation, completions, program, context) => {
114459
+ const owner = context.cfg.enclosingFunction(mutation.node);
114460
+ const matchingCompletions = completions.filter((completion) => completion.receiverKey === mutation.receiverKey && completion.bufferPropertyName === mutation.bufferPropertyName && context.cfg.enclosingFunction(completion.node) === owner);
114461
+ if (owner) return doNodesCoverEveryPathAfterNode(mutation.node, matchingCompletions.map((completion) => completion.node), context);
114462
+ const mutationStart = getRangeStart(mutation.node);
114463
+ return matchingCompletions.some((completion) => {
114464
+ const completionStart = getRangeStart(completion.node);
114465
+ return mutationStart !== null && completionStart !== null && completionStart > mutationStart && !isNodeConditionallyExecuted(completion.node, program);
114466
+ });
114467
+ };
114468
+ const threeRequireInstancedBufferUpdate = defineRule({
114469
+ id: "three-require-instanced-buffer-update",
114470
+ title: "Three.js instanced mesh buffer is not marked for upload",
114471
+ category: "Correctness",
114472
+ severity: "error",
114473
+ recommendation: "After setMatrixAt or setColorAt, set the matching instance buffer's needsUpdate flag to true",
114474
+ create: (context) => {
114475
+ const mutations = [];
114476
+ const completions = [];
114477
+ let program = null;
114478
+ return {
114479
+ Program(node) {
114480
+ program = node;
114481
+ },
114482
+ AssignmentExpression(node) {
114483
+ const completion = getInstancedBufferCompletion(node, context);
114484
+ if (completion) completions.push(completion);
114485
+ },
114486
+ CallExpression(node) {
114487
+ const mutation = getInstancedBufferMutation(node, context);
114488
+ if (mutation) {
114489
+ mutations.push(mutation);
114490
+ return;
114491
+ }
114492
+ completions.push(...getOpaqueInstancedBufferCompletions(node, context));
114493
+ },
114494
+ "Program:exit"() {
114495
+ if (!program) return;
114496
+ for (const mutation of mutations) {
114497
+ if (completionCoversMutation(mutation, completions, program, context)) continue;
114498
+ context.report({
114499
+ node: mutation.node,
114500
+ message: `After ${mutation.methodName}, set ${mutation.bufferPropertyName}.needsUpdate to true so Three.js uploads the changed instance data`
114501
+ });
114502
+ }
114503
+ }
114504
+ };
114505
+ }
114506
+ });
114507
+ //#endregion
114508
+ //#region src/plugin/rules/r3f/three-require-owned-geometry-cleanup.ts
114509
+ const threeRequireOwnedGeometryCleanup = defineRule({
114510
+ id: "three-require-owned-geometry-cleanup",
114511
+ title: "Locally owned Three.js geometry is not disposed",
114512
+ category: "Performance",
114513
+ severity: "warn",
114514
+ recommendation: "Dispose locally constructed geometries when their React owner releases them",
114515
+ create: (context) => createOwnedThreeResourceCleanupVisitors({
114516
+ constructorNameSuffix: "Geometry",
114517
+ context,
114518
+ message: "This locally constructed Three.js geometry owns GPU buffers but has no provable React cleanup. Dispose it when the owning component or hook releases it"
114519
+ })
114520
+ });
114521
+ //#endregion
114522
+ //#region src/plugin/rules/r3f/three-require-owned-material-cleanup.ts
114523
+ const threeRequireOwnedMaterialCleanup = defineRule({
114524
+ id: "three-require-owned-material-cleanup",
114525
+ title: "Locally owned Three.js material is not disposed",
114526
+ category: "Performance",
114527
+ severity: "warn",
114528
+ recommendation: "Dispose locally constructed materials when their React owner releases them",
114529
+ create: (context) => createOwnedThreeResourceCleanupVisitors({
114530
+ constructorNameSuffix: "Material",
114531
+ context,
114532
+ message: "This locally constructed Three.js material owns a GPU shader program but has no provable React cleanup. Dispose it when the owning component or hook releases it"
114533
+ })
114534
+ });
114535
+ //#endregion
114536
+ //#region src/plugin/rules/r3f/three-require-owned-texture-cleanup.ts
114537
+ const threeRequireOwnedTextureCleanup = defineRule({
114538
+ id: "three-require-owned-texture-cleanup",
114539
+ title: "Locally owned Three.js texture is not disposed",
114540
+ category: "Performance",
114541
+ severity: "warn",
114542
+ disabledWhen: ["r3f"],
114543
+ recommendation: "Dispose locally constructed textures when their React owner releases them",
114544
+ create: r3fRequireOwnedTextureCleanup.create
114545
+ });
114546
+ //#endregion
113917
114547
  //#region src/plugin/rules/r3f/three-require-postprocessing-cleanup.ts
113918
114548
  const POSTPROCESSING_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
113919
114549
  const THREE_COMPOSER_BORROWING_METHOD_NAMES = new Set(["addPass", "insertPass"]);
@@ -114033,6 +114663,110 @@ const threeRequirePostprocessingCleanup = defineRule({
114033
114663
  } })
114034
114664
  });
114035
114665
  //#endregion
114666
+ //#region src/plugin/rules/r3f/three-require-projection-matrix-update.ts
114667
+ const CAMERA_CONSTRUCTOR_NAMES = new Set(["OrthographicCamera", "PerspectiveCamera"]);
114668
+ const PROJECTION_PROPERTY_NAMES = new Set([
114669
+ "aspect",
114670
+ "bottom",
114671
+ "far",
114672
+ "filmGauge",
114673
+ "filmOffset",
114674
+ "fov",
114675
+ "left",
114676
+ "near",
114677
+ "right",
114678
+ "top",
114679
+ "zoom"
114680
+ ]);
114681
+ const getProjectionMutation = (node, context) => {
114682
+ const target = isNodeOfType(node, "AssignmentExpression") ? stripParenExpression(node.left) : isNodeOfType(node, "UpdateExpression") ? stripParenExpression(node.argument) : null;
114683
+ if (!target || !isNodeOfType(target, "MemberExpression") || !PROJECTION_PROPERTY_NAMES.has(getStaticPropertyName(target) ?? "") || !CAMERA_CONSTRUCTOR_NAMES.has(getThreeConstructorName(target.object, context.scopes) ?? "")) return null;
114684
+ const receiverKey = resolveExpressionKey$1(target.object, context);
114685
+ return receiverKey ? {
114686
+ node,
114687
+ receiverKey
114688
+ } : null;
114689
+ };
114690
+ const getProjectionUpdate = (node, context) => {
114691
+ const callee = stripParenExpression(node.callee);
114692
+ if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyName(callee) !== "updateProjectionMatrix" || !CAMERA_CONSTRUCTOR_NAMES.has(getThreeConstructorName(callee.object, context.scopes) ?? "")) return null;
114693
+ const receiverKey = resolveExpressionKey$1(callee.object, context);
114694
+ return receiverKey ? {
114695
+ node,
114696
+ receiverKey
114697
+ } : null;
114698
+ };
114699
+ const getOpaqueProjectionUpdates = (node, context) => {
114700
+ if (!isImportedOrStableParameterCall(node, context.scopes)) return [];
114701
+ const updates = [];
114702
+ for (const argument of node.arguments) {
114703
+ if (isNodeOfType(argument, "SpreadElement") || !CAMERA_CONSTRUCTOR_NAMES.has(getThreeConstructorName(argument, context.scopes) ?? "")) continue;
114704
+ const receiverKey = resolveExpressionKey$1(argument, context);
114705
+ if (receiverKey) updates.push({
114706
+ node,
114707
+ receiverKey
114708
+ });
114709
+ }
114710
+ return updates;
114711
+ };
114712
+ const moduleUpdateCoversMutation = (mutation, update, program) => {
114713
+ const mutationStart = getRangeStart(mutation.node);
114714
+ const updateStart = getRangeStart(update.node);
114715
+ if (mutationStart === null || updateStart === null || updateStart <= mutationStart) return false;
114716
+ if (!isNodeConditionallyExecuted(update.node, program)) return true;
114717
+ const mutationRegions = getConditionalExecutionRegions(mutation.node, program);
114718
+ return [...getConditionalExecutionRegions(update.node, program)].every((region) => mutationRegions.has(region));
114719
+ };
114720
+ const updateCoversMutation = (mutation, updates, program, context) => {
114721
+ const owner = context.cfg.enclosingFunction(mutation.node);
114722
+ const matchingUpdates = updates.filter((update) => update.receiverKey === mutation.receiverKey && context.cfg.enclosingFunction(update.node) === owner);
114723
+ if (owner) return doNodesCoverEveryPathAfterNode(mutation.node, matchingUpdates.map((update) => update.node), context);
114724
+ return matchingUpdates.some((update) => moduleUpdateCoversMutation(mutation, update, program));
114725
+ };
114726
+ const threeRequireProjectionMatrixUpdate = defineRule({
114727
+ id: "three-require-projection-matrix-update",
114728
+ title: "Missing Three.js camera projection-matrix update",
114729
+ category: "Correctness",
114730
+ severity: "error",
114731
+ recommendation: "Call camera.updateProjectionMatrix() after changing projection properties so Three.js renders the new frustum",
114732
+ create: (context) => {
114733
+ const mutations = [];
114734
+ const updates = [];
114735
+ let program = null;
114736
+ return {
114737
+ Program(node) {
114738
+ program = node;
114739
+ },
114740
+ AssignmentExpression(node) {
114741
+ const mutation = getProjectionMutation(node, context);
114742
+ if (mutation) mutations.push(mutation);
114743
+ },
114744
+ UpdateExpression(node) {
114745
+ const mutation = getProjectionMutation(node, context);
114746
+ if (mutation) mutations.push(mutation);
114747
+ },
114748
+ CallExpression(node) {
114749
+ const update = getProjectionUpdate(node, context);
114750
+ if (update) {
114751
+ updates.push(update);
114752
+ return;
114753
+ }
114754
+ updates.push(...getOpaqueProjectionUpdates(node, context));
114755
+ },
114756
+ "Program:exit"() {
114757
+ if (!program) return;
114758
+ for (const mutation of mutations) {
114759
+ if (updateCoversMutation(mutation, updates, program, context)) continue;
114760
+ context.report({
114761
+ node: mutation.node,
114762
+ message: "This camera projection property changes without a later updateProjectionMatrix() call on every path, so Three.js can keep rendering a stale projection matrix"
114763
+ });
114764
+ }
114765
+ }
114766
+ };
114767
+ }
114768
+ });
114769
+ //#endregion
114036
114770
  //#region src/plugin/rules/r3f/three-require-render-target-cleanup.ts
114037
114771
  const RENDER_TARGET_CONSTRUCTORS = new Set([
114038
114772
  "RenderTarget",
@@ -114045,7 +114779,6 @@ const RENDER_TARGET_BORROWING_METHODS = new Set([
114045
114779
  "setRenderTarget",
114046
114780
  "setRenderTargetTextures"
114047
114781
  ]);
114048
- const isThreeModuleSource$1 = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
114049
114782
  const threeRequireRenderTargetCleanup = defineRule({
114050
114783
  id: "three-require-render-target-cleanup",
114051
114784
  title: "Undisposed Three.js render target",
@@ -114070,7 +114803,6 @@ const threeRequireRenderTargetCleanup = defineRule({
114070
114803
  //#endregion
114071
114804
  //#region src/plugin/rules/r3f/three-require-renderer-cleanup.ts
114072
114805
  const RENDERER_CONSTRUCTORS = new Set(["WebGLRenderer", "WebGPURenderer"]);
114073
- const isThreeModuleSource = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
114074
114806
  const isNullArgument = (call) => {
114075
114807
  const argument = call.arguments[0];
114076
114808
  return Boolean(isNodeOfType(argument, "Literal") && argument.value === null);
@@ -114199,7 +114931,7 @@ const threeRequireRendererCleanup = defineRule({
114199
114931
  recommendation: "Dispose component-owned renderers and stop their animation loop or animation frame in matching React cleanup",
114200
114932
  create: (context) => ({ NewExpression(node) {
114201
114933
  const provenance = getApiReferenceProvenance(node.callee, context.scopes);
114202
- if (!provenance || !RENDERER_CONSTRUCTORS.has(provenance.apiName) || !isThreeModuleSource(provenance.moduleSource)) return;
114934
+ if (!provenance || !RENDERER_CONSTRUCTORS.has(provenance.apiName) || !isThreeModuleSource$1(provenance.moduleSource)) return;
114203
114935
  const analysis = analyzeOwnedLifecycleResource(node, context, { isBorrowedReference: (reference) => isRetainedByUnusedLocalReactRef(reference, context.scopes) });
114204
114936
  if (!analysis || analysis.hasUnknownOwnershipTransfer || isRendererSuppliedToR3fCanvas(analysis, context)) return;
114205
114937
  const disposeCleanup = analyzeOwnedLifecycleCleanup(analysis, context, (cleanupFunction) => functionInvokesOwnedResourceMethod(cleanupFunction, analysis, "dispose", context.scopes));
@@ -114221,6 +114953,124 @@ const threeRequireRendererCleanup = defineRule({
114221
114953
  } })
114222
114954
  });
114223
114955
  //#endregion
114956
+ //#region src/plugin/rules/r3f/three-tsl-no-js-uniform-branch.ts
114957
+ const TSL_MODULES = new Set(["three/tsl", "three/webgpu"]);
114958
+ const expressionReferencesTslUniformValue = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
114959
+ let referencesUniformValue = false;
114960
+ walkAst(expression, (candidate) => {
114961
+ if (referencesUniformValue) return false;
114962
+ if (isNodeOfType(candidate, "MemberExpression") && getStaticPropertyName(candidate) === "value" && resolvesToTslUniform(candidate.object, scopes)) {
114963
+ referencesUniformValue = true;
114964
+ return false;
114965
+ }
114966
+ if (!isNodeOfType(candidate, "Identifier")) return;
114967
+ const symbol = scopes.symbolFor(candidate);
114968
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return;
114969
+ visitedSymbolIds.add(symbol.id);
114970
+ if (expressionReferencesTslUniformValue(symbol.initializer, scopes, visitedSymbolIds)) {
114971
+ referencesUniformValue = true;
114972
+ return false;
114973
+ }
114974
+ });
114975
+ return referencesUniformValue;
114976
+ };
114977
+ const threeTslNoJsUniformBranch = defineRule({
114978
+ id: "three-tsl-no-js-uniform-branch",
114979
+ title: "JavaScript branch reads a TSL uniform value",
114980
+ category: "Correctness",
114981
+ severity: "warn",
114982
+ recommendation: "Express uniform-dependent shader control flow with TSL If, select, or Loop nodes so it runs on the GPU",
114983
+ create: (context) => ({ CallExpression(node) {
114984
+ if (!isApiCallFromModules(node, "Fn", TSL_MODULES, context.scopes)) return;
114985
+ const callbackArgument = node.arguments[0];
114986
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return;
114987
+ const callback = resolveExactLocalFunction(callbackArgument, context.scopes);
114988
+ if (!isFunctionLike$1(callback)) return;
114989
+ const reportedTests = /* @__PURE__ */ new Set();
114990
+ walkFunctionExecution(callback, context.scopes, (candidate) => {
114991
+ const controlFlowTest = getControlFlowTest(candidate);
114992
+ if (!controlFlowTest || !expressionReferencesTslUniformValue(controlFlowTest, context.scopes)) return;
114993
+ for (const reportedTest of reportedTests) if (isAstDescendant(controlFlowTest, reportedTest)) return;
114994
+ reportedTests.add(controlFlowTest);
114995
+ context.report({
114996
+ node: controlFlowTest,
114997
+ message: "This JavaScript branch reads a TSL uniform while the shader graph is built, so later uniform changes cannot change the branch. Use TSL control flow"
114998
+ });
114999
+ });
115000
+ } })
115001
+ });
115002
+ //#endregion
115003
+ //#region src/plugin/rules/r3f/utils/program-constructs-three-webgpu-renderer.ts
115004
+ const programConstructsThreeWebgpuRenderer = (program, scopes) => {
115005
+ let doesConstructRenderer = false;
115006
+ walkAst(program, (candidate) => {
115007
+ if (!doesConstructRenderer && isNodeOfType(candidate, "NewExpression") && getThreeConstructorName(candidate, scopes) === "WebGPURenderer") {
115008
+ doesConstructRenderer = true;
115009
+ return false;
115010
+ }
115011
+ });
115012
+ return doesConstructRenderer;
115013
+ };
115014
+ //#endregion
115015
+ //#region src/plugin/rules/r3f/three-webgpu-no-legacy-effect-composer.ts
115016
+ const threeWebgpuNoLegacyEffectComposer = defineRule({
115017
+ id: "three-webgpu-no-legacy-effect-composer",
115018
+ title: "Legacy EffectComposer used with WebGPURenderer",
115019
+ category: "Correctness",
115020
+ severity: "error",
115021
+ recommendation: "Use WebGPURenderer's node-based post-processing pipeline instead of the legacy WebGL EffectComposer",
115022
+ create: (context) => {
115023
+ let constructsWebgpuRenderer = false;
115024
+ return {
115025
+ Program(node) {
115026
+ constructsWebgpuRenderer = programConstructsThreeWebgpuRenderer(node, context.scopes);
115027
+ },
115028
+ NewExpression(node) {
115029
+ if (!constructsWebgpuRenderer || getThreeConstructorName(node, context.scopes) !== "EffectComposer") return;
115030
+ context.report({
115031
+ node,
115032
+ message: "Legacy EffectComposer does not support Three.js WebGPURenderer. Build post-processing with the renderer's node-based pipeline"
115033
+ });
115034
+ }
115035
+ };
115036
+ }
115037
+ });
115038
+ //#endregion
115039
+ //#region src/plugin/rules/r3f/three-webgpu-no-legacy-material-api.ts
115040
+ const LEGACY_SHADER_MATERIAL_NAMES = new Set(["RawShaderMaterial", "ShaderMaterial"]);
115041
+ const threeWebgpuNoLegacyMaterialApi = defineRule({
115042
+ id: "three-webgpu-no-legacy-material-api",
115043
+ title: "Legacy material API used with WebGPURenderer",
115044
+ category: "Correctness",
115045
+ severity: "error",
115046
+ recommendation: "Use Three.js node materials and TSL for custom shaders rendered by WebGPURenderer",
115047
+ create: (context) => {
115048
+ let constructsWebgpuRenderer = false;
115049
+ return {
115050
+ Program(node) {
115051
+ constructsWebgpuRenderer = programConstructsThreeWebgpuRenderer(node, context.scopes);
115052
+ },
115053
+ NewExpression(node) {
115054
+ const constructorName = getThreeConstructorName(node, context.scopes);
115055
+ if (!constructsWebgpuRenderer || !constructorName) return;
115056
+ if (!LEGACY_SHADER_MATERIAL_NAMES.has(constructorName)) return;
115057
+ context.report({
115058
+ node,
115059
+ message: "ShaderMaterial and RawShaderMaterial are not supported by Three.js WebGPURenderer. Build this shader with a node material and TSL"
115060
+ });
115061
+ },
115062
+ AssignmentExpression(node) {
115063
+ if (!constructsWebgpuRenderer || !isNodeOfType(node.left, "MemberExpression") || getStaticPropertyName(node.left) !== "onBeforeCompile") return;
115064
+ if (!getThreeConstructorName(node.left.object, context.scopes)?.endsWith("Material")) return;
115065
+ context.report({
115066
+ node,
115067
+ message: "onBeforeCompile patches WebGL shader source and is not supported by Three.js WebGPURenderer. Use a node material and TSL"
115068
+ });
115069
+ }
115070
+ };
115071
+ }
115072
+ });
115073
+ //#endregion
114224
115074
  //#region src/plugin/rules/security-scan/unsafe-json-in-html.ts
114225
115075
  const SINK_JSON_STRINGIFY_PATTERNS = [/dangerouslySetInnerHTML\s*=\s*\{\{\s*__html\s*:[\s\S]{0,300}?\bJSON\.stringify\s*\(/gi, /<script\b[^>]*>(?:(?!<\/script>)[\s\S]){0,300}?\bJSON\.stringify\s*\(/gi];
114226
115076
  const INLINE_SCRIPT_PATTERN_INDEX = 1;
@@ -114978,6 +115828,44 @@ const webAnimationOffsetsValid = defineRule({
114978
115828
  } })
114979
115829
  });
114980
115830
  //#endregion
115831
+ //#region src/plugin/rules/webgl/webgl-no-sync-readback-in-animation-loop.ts
115832
+ const getBlockingReadbackKind = (call, context) => {
115833
+ if (!isNodeOfType(call.callee, "MemberExpression")) return null;
115834
+ const methodName = getStaticPropertyName(call.callee);
115835
+ if (methodName === "readRenderTargetPixels" && isThreeRendererReference(call.callee.object, context.scopes)) return "three";
115836
+ if (!isWebglContextReference(call.callee.object, context.scopes)) return null;
115837
+ if (methodName === "finish") return "finish";
115838
+ let destinationArgumentIndex = null;
115839
+ if (methodName === "readPixels") destinationArgumentIndex = 6;
115840
+ else if (methodName === "getBufferSubData") destinationArgumentIndex = 2;
115841
+ if (destinationArgumentIndex === null) return null;
115842
+ const destination = call.arguments[destinationArgumentIndex];
115843
+ return destination && !isNodeOfType(destination, "SpreadElement") && isCpuTypedArray(destination, context.scopes) ? "raw" : null;
115844
+ };
115845
+ const webglNoSyncReadbackInAnimationLoop = defineRule({
115846
+ id: "webgl-no-sync-readback-in-animation-loop",
115847
+ title: "Synchronous GPU readback inside animation loop",
115848
+ severity: "warn",
115849
+ recommendation: "Move GPU readback to a discrete or asynchronous path and reuse the latest completed result during frames",
115850
+ create: (context) => {
115851
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
115852
+ return { CallExpression(node) {
115853
+ const callback = resolveThreeAnimationLoopCallback(node, context.scopes) ?? resolveRecursiveAnimationFrameCallback(node, context.scopes);
115854
+ if (!callback || analyzedCallbacks.has(callback)) return;
115855
+ analyzedCallbacks.add(callback);
115856
+ walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
115857
+ if (!isNodeOfType(candidate, "CallExpression") || isConditionallyExecuted) return;
115858
+ const readbackKind = getBlockingReadbackKind(candidate, context);
115859
+ if (!readbackKind) return;
115860
+ context.report({
115861
+ node: candidate,
115862
+ message: readbackKind === "finish" ? "finish blocks the calling thread until queued GPU work completes. Synchronize outside the animation loop" : "Synchronous GPU readback can stall the frame until prior GPU work completes. Use an asynchronous or event-driven readback path"
115863
+ });
115864
+ });
115865
+ } };
115866
+ }
115867
+ });
115868
+ //#endregion
114981
115869
  //#region src/plugin/rules/security-scan/webhook-signature-risk.ts
114982
115870
  const WEBHOOK_HANDLER_PATTERN = /(?:^|\/)[^/]*webhook[^/]*\/|(?:^|\/)[^/]*webhook[^/]*\.[cm]?[jt]s$|\bwebhook\b/i;
114983
115871
  const WEBHOOK_ENTRYPOINT_PATTERN = /\b(?:export\s+(?:async\s+)?function\s+POST|export\s+const\s+(?:POST|handler|webhook)|webhookHandler|webhookRoute)\b/i;
@@ -126797,6 +127685,171 @@ const reactDoctorRules = [
126797
127685
  tags: [...new Set(["security-scan", ...tenantStaticProxyRisk.tags ?? []])]
126798
127686
  }
126799
127687
  },
127688
+ {
127689
+ key: "react-doctor/three-cap-device-pixel-ratio",
127690
+ id: "three-cap-device-pixel-ratio",
127691
+ source: "react-doctor",
127692
+ originallyExternal: false,
127693
+ rule: {
127694
+ ...threeCapDevicePixelRatio,
127695
+ framework: "global",
127696
+ category: "Performance",
127697
+ tags: [...new Set([
127698
+ "three",
127699
+ "webgl",
127700
+ ...threeCapDevicePixelRatio.tags ?? []
127701
+ ])],
127702
+ requires: [...new Set(["three", ...threeCapDevicePixelRatio.requires ?? []])]
127703
+ }
127704
+ },
127705
+ {
127706
+ key: "react-doctor/three-limit-shadowed-point-lights",
127707
+ id: "three-limit-shadowed-point-lights",
127708
+ source: "react-doctor",
127709
+ originallyExternal: false,
127710
+ rule: {
127711
+ ...threeLimitShadowedPointLights,
127712
+ framework: "global",
127713
+ category: "Performance",
127714
+ tags: [...new Set([
127715
+ "three",
127716
+ "webgl",
127717
+ ...threeLimitShadowedPointLights.tags ?? []
127718
+ ])],
127719
+ requires: [...new Set(["three", ...threeLimitShadowedPointLights.requires ?? []])]
127720
+ }
127721
+ },
127722
+ {
127723
+ key: "react-doctor/three-no-allocation-in-pointer-move",
127724
+ id: "three-no-allocation-in-pointer-move",
127725
+ source: "react-doctor",
127726
+ originallyExternal: false,
127727
+ rule: {
127728
+ ...threeNoAllocationInPointerMove,
127729
+ framework: "global",
127730
+ category: "Performance",
127731
+ tags: [...new Set([
127732
+ "three",
127733
+ "webgl",
127734
+ ...threeNoAllocationInPointerMove.tags ?? []
127735
+ ])],
127736
+ requires: [...new Set(["three", ...threeNoAllocationInPointerMove.requires ?? []])]
127737
+ }
127738
+ },
127739
+ {
127740
+ key: "react-doctor/three-no-async-animation-loop",
127741
+ id: "three-no-async-animation-loop",
127742
+ source: "react-doctor",
127743
+ originallyExternal: false,
127744
+ rule: {
127745
+ ...threeNoAsyncAnimationLoop,
127746
+ framework: "global",
127747
+ category: "Bugs",
127748
+ tags: [...new Set([
127749
+ "three",
127750
+ "webgl",
127751
+ ...threeNoAsyncAnimationLoop.tags ?? []
127752
+ ])],
127753
+ requires: [...new Set(["three", ...threeNoAsyncAnimationLoop.requires ?? []])]
127754
+ }
127755
+ },
127756
+ {
127757
+ key: "react-doctor/three-no-clone-in-animation-loop",
127758
+ id: "three-no-clone-in-animation-loop",
127759
+ source: "react-doctor",
127760
+ originallyExternal: false,
127761
+ rule: {
127762
+ ...threeNoCloneInAnimationLoop,
127763
+ framework: "global",
127764
+ category: "Performance",
127765
+ tags: [...new Set([
127766
+ "three",
127767
+ "webgl",
127768
+ ...threeNoCloneInAnimationLoop.tags ?? []
127769
+ ])],
127770
+ requires: [...new Set(["three", ...threeNoCloneInAnimationLoop.requires ?? []])]
127771
+ }
127772
+ },
127773
+ {
127774
+ key: "react-doctor/three-no-new-in-animation-loop",
127775
+ id: "three-no-new-in-animation-loop",
127776
+ source: "react-doctor",
127777
+ originallyExternal: false,
127778
+ rule: {
127779
+ ...threeNoNewInAnimationLoop,
127780
+ framework: "global",
127781
+ category: "Performance",
127782
+ tags: [...new Set([
127783
+ "three",
127784
+ "webgl",
127785
+ ...threeNoNewInAnimationLoop.tags ?? []
127786
+ ])],
127787
+ requires: [...new Set(["three", ...threeNoNewInAnimationLoop.requires ?? []])]
127788
+ }
127789
+ },
127790
+ {
127791
+ key: "react-doctor/three-no-object-construction-in-render",
127792
+ id: "three-no-object-construction-in-render",
127793
+ source: "react-doctor",
127794
+ originallyExternal: false,
127795
+ rule: {
127796
+ ...threeNoObjectConstructionInRender,
127797
+ framework: "global",
127798
+ category: "Performance",
127799
+ tags: [...new Set([
127800
+ "three",
127801
+ "webgl",
127802
+ ...threeNoObjectConstructionInRender.tags ?? []
127803
+ ])],
127804
+ requires: [...new Set([
127805
+ "react",
127806
+ "three",
127807
+ ...threeNoObjectConstructionInRender.requires ?? []
127808
+ ])]
127809
+ }
127810
+ },
127811
+ {
127812
+ key: "react-doctor/three-no-state-in-animation-loop",
127813
+ id: "three-no-state-in-animation-loop",
127814
+ source: "react-doctor",
127815
+ originallyExternal: false,
127816
+ rule: {
127817
+ ...threeNoStateInAnimationLoop,
127818
+ framework: "global",
127819
+ category: "Performance",
127820
+ tags: [...new Set([
127821
+ "three",
127822
+ "webgl",
127823
+ ...threeNoStateInAnimationLoop.tags ?? []
127824
+ ])],
127825
+ requires: [...new Set([
127826
+ "react",
127827
+ "three",
127828
+ ...threeNoStateInAnimationLoop.requires ?? []
127829
+ ])]
127830
+ }
127831
+ },
127832
+ {
127833
+ key: "react-doctor/three-no-state-in-pointer-move",
127834
+ id: "three-no-state-in-pointer-move",
127835
+ source: "react-doctor",
127836
+ originallyExternal: false,
127837
+ rule: {
127838
+ ...threeNoStateInPointerMove,
127839
+ framework: "global",
127840
+ category: "Performance",
127841
+ tags: [...new Set([
127842
+ "three",
127843
+ "webgl",
127844
+ ...threeNoStateInPointerMove.tags ?? []
127845
+ ])],
127846
+ requires: [...new Set([
127847
+ "react",
127848
+ "three",
127849
+ ...threeNoStateInPointerMove.requires ?? []
127850
+ ])]
127851
+ }
127852
+ },
126800
127853
  {
126801
127854
  key: "react-doctor/three-require-animation-mixer-cleanup",
126802
127855
  id: "three-require-animation-mixer-cleanup",
@@ -126807,7 +127860,7 @@ const reactDoctorRules = [
126807
127860
  framework: "global",
126808
127861
  category: "Bugs",
126809
127862
  tags: [...new Set([
126810
- "r3f",
127863
+ "three",
126811
127864
  "webgl",
126812
127865
  ...threeRequireAnimationMixerCleanup.tags ?? []
126813
127866
  ])],
@@ -126828,7 +127881,7 @@ const reactDoctorRules = [
126828
127881
  framework: "global",
126829
127882
  category: "Bugs",
126830
127883
  tags: [...new Set([
126831
- "r3f",
127884
+ "three",
126832
127885
  "webgl",
126833
127886
  ...threeRequireControlsCleanup.tags ?? []
126834
127887
  ])],
@@ -126839,6 +127892,103 @@ const reactDoctorRules = [
126839
127892
  ])]
126840
127893
  }
126841
127894
  },
127895
+ {
127896
+ key: "react-doctor/three-require-frame-delta",
127897
+ id: "three-require-frame-delta",
127898
+ source: "react-doctor",
127899
+ originallyExternal: false,
127900
+ rule: {
127901
+ ...threeRequireFrameDelta,
127902
+ framework: "global",
127903
+ category: "Bugs",
127904
+ tags: [...new Set([
127905
+ "three",
127906
+ "webgl",
127907
+ ...threeRequireFrameDelta.tags ?? []
127908
+ ])],
127909
+ requires: [...new Set(["three", ...threeRequireFrameDelta.requires ?? []])]
127910
+ }
127911
+ },
127912
+ {
127913
+ key: "react-doctor/three-require-instanced-buffer-update",
127914
+ id: "three-require-instanced-buffer-update",
127915
+ source: "react-doctor",
127916
+ originallyExternal: false,
127917
+ rule: {
127918
+ ...threeRequireInstancedBufferUpdate,
127919
+ framework: "global",
127920
+ category: "Bugs",
127921
+ tags: [...new Set([
127922
+ "three",
127923
+ "webgl",
127924
+ ...threeRequireInstancedBufferUpdate.tags ?? []
127925
+ ])],
127926
+ requires: [...new Set(["three", ...threeRequireInstancedBufferUpdate.requires ?? []])]
127927
+ }
127928
+ },
127929
+ {
127930
+ key: "react-doctor/three-require-owned-geometry-cleanup",
127931
+ id: "three-require-owned-geometry-cleanup",
127932
+ source: "react-doctor",
127933
+ originallyExternal: false,
127934
+ rule: {
127935
+ ...threeRequireOwnedGeometryCleanup,
127936
+ framework: "global",
127937
+ category: "Performance",
127938
+ tags: [...new Set([
127939
+ "three",
127940
+ "webgl",
127941
+ ...threeRequireOwnedGeometryCleanup.tags ?? []
127942
+ ])],
127943
+ requires: [...new Set([
127944
+ "react",
127945
+ "three",
127946
+ ...threeRequireOwnedGeometryCleanup.requires ?? []
127947
+ ])]
127948
+ }
127949
+ },
127950
+ {
127951
+ key: "react-doctor/three-require-owned-material-cleanup",
127952
+ id: "three-require-owned-material-cleanup",
127953
+ source: "react-doctor",
127954
+ originallyExternal: false,
127955
+ rule: {
127956
+ ...threeRequireOwnedMaterialCleanup,
127957
+ framework: "global",
127958
+ category: "Performance",
127959
+ tags: [...new Set([
127960
+ "three",
127961
+ "webgl",
127962
+ ...threeRequireOwnedMaterialCleanup.tags ?? []
127963
+ ])],
127964
+ requires: [...new Set([
127965
+ "react",
127966
+ "three",
127967
+ ...threeRequireOwnedMaterialCleanup.requires ?? []
127968
+ ])]
127969
+ }
127970
+ },
127971
+ {
127972
+ key: "react-doctor/three-require-owned-texture-cleanup",
127973
+ id: "three-require-owned-texture-cleanup",
127974
+ source: "react-doctor",
127975
+ originallyExternal: false,
127976
+ rule: {
127977
+ ...threeRequireOwnedTextureCleanup,
127978
+ framework: "global",
127979
+ category: "Performance",
127980
+ tags: [...new Set([
127981
+ "three",
127982
+ "webgl",
127983
+ ...threeRequireOwnedTextureCleanup.tags ?? []
127984
+ ])],
127985
+ requires: [...new Set([
127986
+ "react",
127987
+ "three",
127988
+ ...threeRequireOwnedTextureCleanup.requires ?? []
127989
+ ])]
127990
+ }
127991
+ },
126842
127992
  {
126843
127993
  key: "react-doctor/three-require-postprocessing-cleanup",
126844
127994
  id: "three-require-postprocessing-cleanup",
@@ -126849,7 +127999,7 @@ const reactDoctorRules = [
126849
127999
  framework: "global",
126850
128000
  category: "Bugs",
126851
128001
  tags: [...new Set([
126852
- "r3f",
128002
+ "three",
126853
128003
  "webgl",
126854
128004
  ...threeRequirePostprocessingCleanup.tags ?? []
126855
128005
  ])],
@@ -126860,6 +128010,23 @@ const reactDoctorRules = [
126860
128010
  ])]
126861
128011
  }
126862
128012
  },
128013
+ {
128014
+ key: "react-doctor/three-require-projection-matrix-update",
128015
+ id: "three-require-projection-matrix-update",
128016
+ source: "react-doctor",
128017
+ originallyExternal: false,
128018
+ rule: {
128019
+ ...threeRequireProjectionMatrixUpdate,
128020
+ framework: "global",
128021
+ category: "Bugs",
128022
+ tags: [...new Set([
128023
+ "three",
128024
+ "webgl",
128025
+ ...threeRequireProjectionMatrixUpdate.tags ?? []
128026
+ ])],
128027
+ requires: [...new Set(["three", ...threeRequireProjectionMatrixUpdate.requires ?? []])]
128028
+ }
128029
+ },
126863
128030
  {
126864
128031
  key: "react-doctor/three-require-render-target-cleanup",
126865
128032
  id: "three-require-render-target-cleanup",
@@ -126870,7 +128037,7 @@ const reactDoctorRules = [
126870
128037
  framework: "global",
126871
128038
  category: "Bugs",
126872
128039
  tags: [...new Set([
126873
- "r3f",
128040
+ "three",
126874
128041
  "webgl",
126875
128042
  ...threeRequireRenderTargetCleanup.tags ?? []
126876
128043
  ])],
@@ -126891,7 +128058,7 @@ const reactDoctorRules = [
126891
128058
  framework: "global",
126892
128059
  category: "Bugs",
126893
128060
  tags: [...new Set([
126894
- "r3f",
128061
+ "three",
126895
128062
  "webgl",
126896
128063
  ...threeRequireRendererCleanup.tags ?? []
126897
128064
  ])],
@@ -126902,6 +128069,57 @@ const reactDoctorRules = [
126902
128069
  ])]
126903
128070
  }
126904
128071
  },
128072
+ {
128073
+ key: "react-doctor/three-tsl-no-js-uniform-branch",
128074
+ id: "three-tsl-no-js-uniform-branch",
128075
+ source: "react-doctor",
128076
+ originallyExternal: false,
128077
+ rule: {
128078
+ ...threeTslNoJsUniformBranch,
128079
+ framework: "global",
128080
+ category: "Bugs",
128081
+ tags: [...new Set([
128082
+ "three",
128083
+ "webgl",
128084
+ ...threeTslNoJsUniformBranch.tags ?? []
128085
+ ])],
128086
+ requires: [...new Set(["three", ...threeTslNoJsUniformBranch.requires ?? []])]
128087
+ }
128088
+ },
128089
+ {
128090
+ key: "react-doctor/three-webgpu-no-legacy-effect-composer",
128091
+ id: "three-webgpu-no-legacy-effect-composer",
128092
+ source: "react-doctor",
128093
+ originallyExternal: false,
128094
+ rule: {
128095
+ ...threeWebgpuNoLegacyEffectComposer,
128096
+ framework: "global",
128097
+ category: "Bugs",
128098
+ tags: [...new Set([
128099
+ "three",
128100
+ "webgl",
128101
+ ...threeWebgpuNoLegacyEffectComposer.tags ?? []
128102
+ ])],
128103
+ requires: [...new Set(["three", ...threeWebgpuNoLegacyEffectComposer.requires ?? []])]
128104
+ }
128105
+ },
128106
+ {
128107
+ key: "react-doctor/three-webgpu-no-legacy-material-api",
128108
+ id: "three-webgpu-no-legacy-material-api",
128109
+ source: "react-doctor",
128110
+ originallyExternal: false,
128111
+ rule: {
128112
+ ...threeWebgpuNoLegacyMaterialApi,
128113
+ framework: "global",
128114
+ category: "Bugs",
128115
+ tags: [...new Set([
128116
+ "three",
128117
+ "webgl",
128118
+ ...threeWebgpuNoLegacyMaterialApi.tags ?? []
128119
+ ])],
128120
+ requires: [...new Set(["three", ...threeWebgpuNoLegacyMaterialApi.requires ?? []])]
128121
+ }
128122
+ },
126905
128123
  {
126906
128124
  key: "react-doctor/unsafe-json-in-html",
126907
128125
  id: "unsafe-json-in-html",
@@ -127007,6 +128225,18 @@ const reactDoctorRules = [
127007
128225
  category: "Bugs"
127008
128226
  }
127009
128227
  },
128228
+ {
128229
+ key: "react-doctor/webgl-no-sync-readback-in-animation-loop",
128230
+ id: "webgl-no-sync-readback-in-animation-loop",
128231
+ source: "react-doctor",
128232
+ originallyExternal: false,
128233
+ rule: {
128234
+ ...webglNoSyncReadbackInAnimationLoop,
128235
+ framework: "global",
128236
+ category: "Performance",
128237
+ tags: [...new Set(["webgl", ...webglNoSyncReadbackInAnimationLoop.tags ?? []])]
128238
+ }
128239
+ },
127010
128240
  {
127011
128241
  key: "react-doctor/webhook-signature-risk",
127012
128242
  id: "webhook-signature-risk",