oxlint-plugin-react-doctor 0.8.3-dev.01820bb → 0.8.3-dev.3d7ea66

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 +2963 -3875
  2. package/dist/index.js +212 -1442
  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$1 = (node) => {
13068
+ const getExecutionOwner = (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$1(node);
13210
+ const owner = getExecutionOwner(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$1(node);
13223
+ const owner = getExecutionOwner(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$1(checkpoint);
13234
+ const checkpointOwner = getExecutionOwner(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$1(usageNode);
13331
+ const usageOwner = getExecutionOwner(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$1(usageNode);
13338
+ const usageOwner = getExecutionOwner(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$1(usageNode);
13373
+ const usageOwner = getExecutionOwner(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$1(usageNode);
13382
+ const usageOwner = getExecutionOwner(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,26 +24018,22 @@ const isInsideTryStatement = (node, options) => {
24018
24018
  return false;
24019
24019
  };
24020
24020
  //#endregion
24021
- //#region src/plugin/utils/get-conditional-execution-regions.ts
24022
- const getConditionalExecutionRegions = (node, boundary) => {
24023
- const regions = /* @__PURE__ */ new Set();
24021
+ //#region src/plugin/utils/is-node-conditionally-executed.ts
24022
+ const isNodeConditionallyExecuted = (node, boundary) => {
24024
24023
  let child = node;
24025
24024
  let parent = child.parent ?? null;
24026
24025
  while (parent && parent !== boundary) {
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);
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;
24032
24031
  child = parent;
24033
24032
  parent = child.parent ?? null;
24034
24033
  }
24035
- return regions;
24034
+ return false;
24036
24035
  };
24037
24036
  //#endregion
24038
- //#region src/plugin/utils/is-node-conditionally-executed.ts
24039
- const isNodeConditionallyExecuted = (node, boundary) => getConditionalExecutionRegions(node, boundary).size > 0;
24040
- //#endregion
24041
24037
  //#region src/plugin/utils/is-non-source-filename.ts
24042
24038
  const NON_SOURCE_FILENAME_MARKERS = [
24043
24039
  "/dist/",
@@ -91334,35 +91330,6 @@ const getModuleNamespaceSource = (expression, scopes, visitedSymbolIds = /* @__P
91334
91330
  return getModuleNamespaceSource(symbol.initializer, scopes, visitedSymbolIds);
91335
91331
  };
91336
91332
  //#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
91366
91333
  //#region src/plugin/rules/r3f/utils/get-api-reference-provenance.ts
91367
91334
  const getApiReferenceProvenance = (reference, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
91368
91335
  const candidate = stripParenExpression(reference);
@@ -91418,6 +91385,35 @@ const getApiReferenceProvenance = (reference, scopes, visitedSymbolIds = /* @__P
91418
91385
  return getApiReferenceProvenance(symbol.initializer, scopes, visitedSymbolIds);
91419
91386
  };
91420
91387
  //#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
91421
91417
  //#region src/plugin/rules/r3f/utils/is-r3f-canvas.ts
91422
91418
  const isR3fCanvas = (node, context) => {
91423
91419
  const provenance = getApiReferenceProvenance(node.name, context.scopes);
@@ -91505,43 +91501,6 @@ const resolveLocalReactCallback = (expression, scopes) => {
91505
91501
  return resolveExactLocalFunction(wrappedCallback, scopes);
91506
91502
  };
91507
91503
  //#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
91545
91504
  //#region src/plugin/rules/r3f/utils/walk-function-execution.ts
91546
91505
  const isUseTransitionCall = (expression, scopes) => {
91547
91506
  const candidate = stripParenExpression(expression);
@@ -91616,6 +91575,43 @@ const walkFunctionExecution = (functionNode, scopes, visitor) => {
91616
91575
  };
91617
91576
  //#endregion
91618
91577
  //#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
+ };
91619
91615
  const getExplicitObjectPropertyValue = (expression, propertyName) => {
91620
91616
  const candidate = stripParenExpression(expression);
91621
91617
  if (!isNodeOfType(candidate, "ObjectExpression")) return null;
@@ -91648,6 +91644,18 @@ const isR3fRootReceiver = (expression, context, visitedSymbolIds = /* @__PURE__
91648
91644
  if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
91649
91645
  return isR3fRootReceiver(symbol.initializer, context, visitedSymbolIds);
91650
91646
  };
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
+ };
91651
91659
  const useThreeSelectsSetDpr = (call, context) => {
91652
91660
  if (!isR3fApiCall(call, "useThree", context.scopes)) return false;
91653
91661
  const selectorExpression = call.arguments[0];
@@ -91679,7 +91687,7 @@ const r3fCapDevicePixelRatio = defineRule({
91679
91687
  create: (context) => {
91680
91688
  let importsReactThreeFiber = false;
91681
91689
  const reportRawDpr = (expression) => {
91682
- const rawDpr = resolveRawDevicePixelRatio(expression, context.scopes);
91690
+ const rawDpr = resolveRawDevicePixelRatio(expression, context);
91683
91691
  if (!rawDpr) return;
91684
91692
  context.report({
91685
91693
  node: rawDpr,
@@ -91708,6 +91716,10 @@ const r3fCapDevicePixelRatio = defineRule({
91708
91716
  if (dprValue) reportRawDpr(dprValue);
91709
91717
  return;
91710
91718
  }
91719
+ if (methodName === "setPixelRatio" && isThreeRendererReceiver(node.callee.object, context)) {
91720
+ reportRawDpr(firstArgument);
91721
+ return;
91722
+ }
91711
91723
  },
91712
91724
  Identifier(node) {
91713
91725
  const parent = node.parent;
@@ -91721,14 +91733,6 @@ const r3fCapDevicePixelRatio = defineRule({
91721
91733
  });
91722
91734
  //#endregion
91723
91735
  //#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
- ]);
91732
91736
  const THREE_POSTPROCESSING_PASS_DISPOSAL_RELEASES = new Map([
91733
91737
  ["RenderPixelatedPass", 147],
91734
91738
  ["OutputPass", 153],
@@ -92007,7 +92011,7 @@ const THREE_OBJECT_MEMBER_PROPERTIES = new Set([
92007
92011
  "rotation",
92008
92012
  "scale"
92009
92013
  ]);
92010
- const hasThreeObjectProvenance$1 = (expression, callback, managedRefSymbolIds, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92014
+ const hasThreeObjectProvenance = (expression, callback, managedRefSymbolIds, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92011
92015
  const firstCallbackParameter = isFunctionLike$1(callback) ? callback.params[0] : null;
92012
92016
  const callbackParameter = isNodeOfType(firstCallbackParameter, "AssignmentPattern") ? firstCallbackParameter.left : firstCallbackParameter;
92013
92017
  let current = stripParenExpression(expression);
@@ -92028,7 +92032,7 @@ const hasThreeObjectProvenance$1 = (expression, callback, managedRefSymbolIds, c
92028
92032
  const symbol = context.scopes.symbolFor(current);
92029
92033
  if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
92030
92034
  visitedSymbolIds.add(symbol.id);
92031
- return hasThreeObjectProvenance$1(symbol.initializer, callback, managedRefSymbolIds, context, visitedSymbolIds);
92035
+ return hasThreeObjectProvenance(symbol.initializer, callback, managedRefSymbolIds, context, visitedSymbolIds);
92032
92036
  };
92033
92037
  const r3fNoCloneInUseFrame = defineRule({
92034
92038
  id: "r3f-no-clone-in-use-frame",
@@ -92045,7 +92049,7 @@ const r3fNoCloneInUseFrame = defineRule({
92045
92049
  const callback = resolveR3fCallback(node, "useFrame", context.scopes);
92046
92050
  if (!callback) return;
92047
92051
  walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
92048
- if (isConditionallyExecuted || !isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance$1(candidate.callee.object, callback, managedRefSymbolIds, context)) return;
92052
+ if (isConditionallyExecuted || !isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance(candidate.callee.object, callback, managedRefSymbolIds, context)) return;
92049
92053
  context.report({
92050
92054
  node: candidate,
92051
92055
  message: "This clone allocates a new Three.js object every executed frame. Reuse a scratch object or clone once outside useFrame"
@@ -92716,9 +92720,6 @@ const r3fNoFreshUseThreeSelector = defineRule({
92716
92720
  } })
92717
92721
  });
92718
92722
  //#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
92722
92723
  //#region src/plugin/rules/r3f/r3f-no-imperative-attach-of-managed-ref.ts
92723
92724
  const IMPERATIVE_ATTACH_METHOD_NAMES = new Set(["add", "attach"]);
92724
92725
  const THREE_OBJECT3D_CONSTRUCTOR_NAMES = new Set([
@@ -92747,11 +92748,12 @@ const THREE_OBJECT3D_CONSTRUCTOR_NAMES = new Set([
92747
92748
  "SpotLight",
92748
92749
  "Sprite"
92749
92750
  ]);
92751
+ const isThreeModuleSource$3 = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
92750
92752
  const hasThreeObject3DProvenance = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92751
92753
  const candidate = stripParenExpression(expression);
92752
92754
  if (isNodeOfType(candidate, "NewExpression")) {
92753
92755
  const provenance = getApiReferenceProvenance(candidate.callee, scopes);
92754
- return Boolean(provenance && isThreeModuleSource$1(provenance.moduleSource) && THREE_OBJECT3D_CONSTRUCTOR_NAMES.has(provenance.apiName));
92756
+ return Boolean(provenance && isThreeModuleSource$3(provenance.moduleSource) && THREE_OBJECT3D_CONSTRUCTOR_NAMES.has(provenance.apiName));
92755
92757
  }
92756
92758
  if (!isNodeOfType(candidate, "Identifier")) return false;
92757
92759
  const symbol = scopes.symbolFor(candidate);
@@ -92916,15 +92918,15 @@ const GEOMETRY_OWNER_CONSTRUCTORS = new Set([
92916
92918
  "SkinnedMesh"
92917
92919
  ]);
92918
92920
  const MATERIAL_OWNER_CONSTRUCTORS = new Set([...GEOMETRY_OWNER_CONSTRUCTORS, "Sprite"]);
92919
- const isThreeModuleSource = (source) => typeof source === "string" && (source === "three" || source.startsWith("three/") || source === "three-stdlib");
92920
- const getThreeConstructorName$1 = (constructorExpression, scopes) => {
92921
+ const isThreeModuleSource$2 = (source) => typeof source === "string" && (source === "three" || source.startsWith("three/") || source === "three-stdlib");
92922
+ const getThreeConstructorName = (constructorExpression, scopes) => {
92921
92923
  const provenance = getApiReferenceProvenance(stripParenExpression(constructorExpression), scopes);
92922
- return provenance && isThreeModuleSource(provenance.moduleSource) ? provenance.apiName : null;
92924
+ return provenance && isThreeModuleSource$2(provenance.moduleSource) ? provenance.apiName : null;
92923
92925
  };
92924
92926
  const hasThreeResourceOwnerProvenance = (expression, ownerConstructors, scopes, visitedSymbolIds) => {
92925
92927
  const candidate = stripParenExpression(expression);
92926
92928
  if (isNodeOfType(candidate, "NewExpression")) {
92927
- const constructorName = getThreeConstructorName$1(candidate.callee, scopes);
92929
+ const constructorName = getThreeConstructorName(candidate.callee, scopes);
92928
92930
  return Boolean(constructorName && ownerConstructors.has(constructorName));
92929
92931
  }
92930
92932
  if (isNodeOfType(candidate, "Identifier")) {
@@ -92938,7 +92940,7 @@ const hasThreeResourceOwnerProvenance = (expression, ownerConstructors, scopes,
92938
92940
  const getResourceMethods = (constructorSuffix) => constructorSuffix === "Geometry" ? GEOMETRY_RESOURCE_METHODS : MATERIAL_RESOURCE_METHODS;
92939
92941
  const hasThreeResourceProvenance = (expression, constructorSuffix, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92940
92942
  const candidate = stripParenExpression(expression);
92941
- if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName$1(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
92943
+ if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
92942
92944
  if (isNodeOfType(candidate, "Identifier")) {
92943
92945
  const symbol = scopes.symbolFor(candidate);
92944
92946
  if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
@@ -92978,7 +92980,7 @@ const hasProvenIndexedThreeGeometry = (expression, scopes, visitedSymbolIds = /*
92978
92980
  };
92979
92981
  const hasFreshThreeResource = (expression, constructorSuffix, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
92980
92982
  const candidate = stripParenExpression(expression);
92981
- if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName$1(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
92983
+ if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
92982
92984
  if (isNodeOfType(candidate, "Identifier")) {
92983
92985
  const symbol = scopes.symbolFor(candidate);
92984
92986
  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;
@@ -93597,39 +93599,32 @@ const isGlobalBrowserFunctionCall = (call, functionName, scopes) => {
93597
93599
  return isNodeOfType(receiver, "Identifier") && (receiver.name === "globalThis" || receiver.name === "window") && scopes.isGlobalReference(receiver);
93598
93600
  };
93599
93601
  //#endregion
93600
- //#region src/plugin/utils/resolve-recursive-animation-frame-callback.ts
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
+ ]);
93601
93608
  const getAnimationFrameCallback = (call, scopes) => {
93602
93609
  const callbackArgument = call.arguments[0];
93603
- return callbackArgument && !isNodeOfType(callbackArgument, "SpreadElement") ? resolveExactLocalFunction(callbackArgument, scopes) : null;
93610
+ if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return null;
93611
+ return resolveExactLocalFunction(callbackArgument, scopes);
93604
93612
  };
93605
- const callbackSchedulesItself = (callback, scopes) => {
93613
+ const callbackDirectlySchedulesItself = (callback, scopes) => {
93606
93614
  let doesScheduleItself = false;
93607
93615
  walkAst(callback, (candidate) => {
93608
93616
  if (doesScheduleItself || candidate !== callback && isFunctionLike$1(candidate)) return false;
93609
- if (isNodeOfType(candidate, "CallExpression") && isGlobalBrowserFunctionCall(candidate, "requestAnimationFrame", scopes) && getAnimationFrameCallback(candidate, scopes) === callback) {
93610
- doesScheduleItself = true;
93611
- return false;
93612
- }
93617
+ if (!isNodeOfType(candidate, "CallExpression") || !isGlobalBrowserFunctionCall(candidate, "requestAnimationFrame", scopes)) return;
93618
+ doesScheduleItself = getAnimationFrameCallback(candidate, scopes) === callback;
93613
93619
  });
93614
93620
  return doesScheduleItself;
93615
93621
  };
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
- ]);
93628
93622
  const collectRecursiveAnimationFrameStarts = (executedFunction, scopes) => {
93629
93623
  const starts = /* @__PURE__ */ new Set();
93630
93624
  walkFunctionExecution(executedFunction, scopes, (candidate) => {
93631
93625
  if (!isNodeOfType(candidate, "CallExpression") || !isGlobalBrowserFunctionCall(candidate, "requestAnimationFrame", scopes)) return;
93632
- if (resolveRecursiveAnimationFrameCallback(candidate, scopes)) starts.add(candidate);
93626
+ const callback = getAnimationFrameCallback(candidate, scopes);
93627
+ if (callback && callbackDirectlySchedulesItself(callback, scopes)) starts.add(candidate);
93633
93628
  });
93634
93629
  return starts;
93635
93630
  };
@@ -94133,7 +94128,7 @@ const r3fNoStateInPointerMove = defineRule({
94133
94128
  }
94134
94129
  });
94135
94130
  //#endregion
94136
- //#region src/plugin/utils/is-cpu-typed-array.ts
94131
+ //#region src/plugin/rules/r3f/r3f-no-sync-readback-in-use-frame.ts
94137
94132
  const CPU_TYPED_ARRAY_CONSTRUCTORS = new Set([
94138
94133
  "BigInt64Array",
94139
94134
  "BigUint64Array",
@@ -94147,61 +94142,44 @@ const CPU_TYPED_ARRAY_CONSTRUCTORS = new Set([
94147
94142
  "Uint16Array",
94148
94143
  "Uint32Array"
94149
94144
  ]);
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
94145
+ const CANVAS_2D_CONTEXT_NAMES = new Set(["2d"]);
94164
94146
  const WEBGL_CONTEXT_NAMES = new Set([
94165
94147
  "experimental-webgl",
94166
94148
  "webgl",
94167
94149
  "webgl2"
94168
94150
  ]);
94169
- const isWebglContextReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94151
+ const isContextFromGetContext = (expression, contextNames, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94170
94152
  const candidate = stripParenExpression(expression);
94171
94153
  if (isNodeOfType(candidate, "Identifier")) {
94172
94154
  const symbol = scopes.symbolFor(candidate);
94173
94155
  if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
94174
94156
  visitedSymbolIds.add(symbol.id);
94175
- return isWebglContextReference(symbol.initializer, scopes, visitedSymbolIds);
94157
+ return isContextFromGetContext(symbol.initializer, contextNames, scopes, visitedSymbolIds);
94176
94158
  }
94177
94159
  if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "getContext") return false;
94178
94160
  const contextName = candidate.arguments[0];
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));
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));
94181
94164
  };
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()) => {
94165
+ const isCpuTypedArray = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
94186
94166
  const candidate = stripParenExpression(expression);
94187
94167
  if (isNodeOfType(candidate, "Identifier")) {
94188
94168
  const symbol = scopes.symbolFor(candidate);
94189
94169
  if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
94190
94170
  visitedSymbolIds.add(symbol.id);
94191
- return isContextFromGetContext(symbol.initializer, contextNames, scopes, visitedSymbolIds);
94171
+ return isCpuTypedArray(symbol.initializer, scopes, visitedSymbolIds);
94192
94172
  }
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));
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);
94198
94176
  };
94199
94177
  const getReadbackKind = (node, callback, context) => {
94200
94178
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
94201
94179
  const methodName = getStaticPropertyName(node.callee);
94202
94180
  if (methodName === "readRenderTargetPixels" && (isR3fCallbackStateProperty(node.callee.object, callback, "gl", context.scopes) || isR3fCallbackStateProperty(node.callee.object, callback, "renderer", context.scopes))) return "three";
94203
94181
  if (methodName === "getImageData" && isContextFromGetContext(node.callee.object, CANVAS_2D_CONTEXT_NAMES, context.scopes)) return "canvas";
94204
- if (methodName === "readPixels" && isWebglContextReference(node.callee.object, context.scopes)) {
94182
+ if (methodName === "readPixels" && isContextFromGetContext(node.callee.object, WEBGL_CONTEXT_NAMES, context.scopes)) {
94205
94183
  const destination = node.arguments[6];
94206
94184
  return destination && !isNodeOfType(destination, "SpreadElement") && isCpuTypedArray(destination, context.scopes) ? "webgl" : null;
94207
94185
  }
@@ -94396,36 +94374,6 @@ const r3fPreferUseLoader = defineRule({
94396
94374
  }
94397
94375
  });
94398
94376
  //#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
94429
94377
  //#region src/plugin/rules/r3f/r3f-require-frame-delta.ts
94430
94378
  const TRANSFORM_PROPERTIES = new Set([
94431
94379
  "position",
@@ -94488,6 +94436,34 @@ const expressionReferencesDelta = (expression, callback, context, visitedSymbolI
94488
94436
  });
94489
94437
  return referencesDelta;
94490
94438
  };
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
+ };
94491
94467
  const isThreeMathUtils = (expression, context) => {
94492
94468
  return getApiReferenceModuleSource(expression, "MathUtils", context.scopes) === "three";
94493
94469
  };
@@ -94547,7 +94523,7 @@ const isConditionallyExecutedOnlyByReactRefAvailability = (node, callback, conte
94547
94523
  }
94548
94524
  return didFindRefAvailabilityCondition;
94549
94525
  };
94550
- const getFixedInterpolationFactor$1 = (node, callback, managedRefSymbolIds, context) => {
94526
+ const getFixedInterpolationFactor = (node, callback, managedRefSymbolIds, context) => {
94551
94527
  if (!isNodeOfType(node.callee, "MemberExpression")) return null;
94552
94528
  const methodName = getStaticPropertyName(node.callee);
94553
94529
  let factorArgumentIndex;
@@ -94556,7 +94532,7 @@ const getFixedInterpolationFactor$1 = (node, callback, managedRefSymbolIds, cont
94556
94532
  if (factorArgumentIndex === void 0) return null;
94557
94533
  const factor = node.arguments[factorArgumentIndex];
94558
94534
  if (!factor || isNodeOfType(factor, "SpreadElement")) return null;
94559
- const staticFactor = resolveStaticNumber(factor, context.scopes);
94535
+ const staticFactor = resolveStaticNumber(factor, context);
94560
94536
  return staticFactor !== null && staticFactor > 0 && staticFactor < 1 ? factor : null;
94561
94537
  };
94562
94538
  const getRotationOwner = (expression) => {
@@ -94630,7 +94606,7 @@ const r3fRequireFrameDelta = defineRule({
94630
94606
  return;
94631
94607
  }
94632
94608
  if (!isNodeOfType(candidate, "CallExpression")) return;
94633
- const factor = getFixedInterpolationFactor$1(candidate, callback, managedRefSymbolIds, context);
94609
+ const factor = getFixedInterpolationFactor(candidate, callback, managedRefSymbolIds, context);
94634
94610
  if (!factor || expressionReferencesDelta(factor, callback, context) || isConditionallyExecuted && !isBehindReactRefAvailabilityGuard) return;
94635
94611
  context.report({
94636
94612
  node: factor,
@@ -95670,30 +95646,20 @@ const ownedResourceHasMethodCall = (analysis, methodName, scopes, matchesCall =
95670
95646
  return false;
95671
95647
  };
95672
95648
  //#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
95689
95649
  //#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
+ ]);
95690
95656
  const TEXTURE_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
95691
95657
  const isMaterialTexturePropertyName = (propertyName) => propertyName === "map" || propertyName === "matcap" || Boolean(propertyName?.endsWith("Map"));
95692
95658
  const isThreeMaterialAllocation = (expression, context) => {
95693
95659
  const candidate = stripParenExpression(expression);
95694
95660
  if (!isNodeOfType(candidate, "NewExpression")) return false;
95695
95661
  const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
95696
- return Boolean(provenance && isThreeModuleSource$1(provenance.moduleSource) && provenance.apiName.endsWith("Material"));
95662
+ return Boolean(provenance?.moduleSource === "three" && provenance.apiName.endsWith("Material"));
95697
95663
  };
95698
95664
  const expressionResolvesToThreeMaterial = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
95699
95665
  const candidate = stripParenExpression(expression);
@@ -95756,21 +95722,27 @@ const r3fRequireOwnedTextureCleanup = defineRule({
95756
95722
  category: "Performance",
95757
95723
  severity: "warn",
95758
95724
  recommendation: "Dispose locally constructed textures in a React effect cleanup",
95759
- create: (context) => createOwnedThreeResourceCleanupVisitors({
95760
- analysisOptions: {
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, {
95761
95729
  borrowedArgumentMethodNames: TEXTURE_BORROWING_METHOD_NAMES,
95762
95730
  isBorrowedReference: (reference) => isBorrowedByThreeMaterial(reference, context),
95763
95731
  retainsOwnershipInJsx: true
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
- })
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
+ } })
95769
95741
  });
95770
95742
  //#endregion
95771
95743
  //#region src/plugin/rules/r3f/r3f-require-projection-matrix-update.ts
95772
95744
  const CAMERA_HOST_NAMES = new Set(["orthographicCamera", "perspectiveCamera"]);
95773
- const PROJECTION_PROPERTY_NAMES$1 = new Set([
95745
+ const PROJECTION_PROPERTY_NAMES = new Set([
95774
95746
  "aspect",
95775
95747
  "bottom",
95776
95748
  "far",
@@ -95833,7 +95805,7 @@ const hasManagedCameraRefProvenance = (expression, managedCameraRefSymbolIds, co
95833
95805
  const hasR3fCameraProvenance = (expression, frameCallbacks, managedCameraRefSymbolIds, context) => hasStableRootBinding(expression, context.scopes) && (hasUseThreeCameraProvenance(expression, context) || hasFrameCameraProvenance(expression, frameCallbacks, context) || hasManagedCameraRefProvenance(expression, managedCameraRefSymbolIds, context));
95834
95806
  const getProjectionMutationReceiver = (node) => {
95835
95807
  const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? stripParenExpression(node.left) : isNodeOfType(node, "UpdateExpression") ? stripParenExpression(node.argument) : null;
95836
- return mutationTarget && isNodeOfType(mutationTarget, "MemberExpression") && PROJECTION_PROPERTY_NAMES$1.has(getStaticPropertyName(mutationTarget) ?? "") ? mutationTarget.object : null;
95808
+ return mutationTarget && isNodeOfType(mutationTarget, "MemberExpression") && PROJECTION_PROPERTY_NAMES.has(getStaticPropertyName(mutationTarget) ?? "") ? mutationTarget.object : null;
95837
95809
  };
95838
95810
  const getOnlyCallExpression = (statement) => {
95839
95811
  if (isNodeOfType(statement, "BlockStatement")) return statement.body.length === 1 ? getOnlyCallExpression(statement.body[0]) : null;
@@ -96257,16 +96229,14 @@ const r3fWebgpuNoGlState = defineRule({
96257
96229
  })
96258
96230
  });
96259
96231
  //#endregion
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
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"]);
96270
96240
  const TSL_UNIFORM_MODULES = new Set(["three/tsl", "three/webgpu"]);
96271
96241
  const resolvesToTslUniform = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
96272
96242
  const candidate = stripParenExpression(expression);
@@ -96277,15 +96247,6 @@ const resolvesToTslUniform = (expression, scopes, visitedSymbolIds = /* @__PURE_
96277
96247
  visitedSymbolIds.add(symbol.id);
96278
96248
  return resolvesToTslUniform(symbol.initializer, scopes, visitedSymbolIds);
96279
96249
  };
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"]);
96289
96250
  const isUniformValueMember = (expression, callback, scopes) => {
96290
96251
  const candidate = stripParenExpression(expression);
96291
96252
  if (!isNodeOfType(candidate, "MemberExpression") || getStaticPropertyName(candidate) !== "value") return false;
@@ -96316,6 +96277,13 @@ const expressionReferencesUniformValue = (expression, callback, scopes, visitedS
96316
96277
  });
96317
96278
  return didFindUniformValue;
96318
96279
  };
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
+ };
96319
96287
  const getWebgpuGraphHookName = (node, context) => {
96320
96288
  for (const hookName of WEBGPU_GRAPH_HOOKS) if (isApiCallFromModules(node, hookName, R3F_WEBGPU_MODULES, context.scopes)) return hookName;
96321
96289
  return null;
@@ -113839,363 +113807,6 @@ const tenantStaticProxyRisk = defineRule({
113839
113807
  })
113840
113808
  });
113841
113809
  //#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
114199
113810
  //#region src/plugin/rules/r3f/three-require-animation-mixer-cleanup.ts
114200
113811
  const MIXER_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
114201
113812
  const FINE_GRAINED_UNCACHE_METHOD_NAMES = ["uncacheAction", "uncacheClip"];
@@ -114303,247 +113914,6 @@ const threeRequireControlsCleanup = defineRule({
114303
113914
  } })
114304
113915
  });
114305
113916
  //#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
114547
113917
  //#region src/plugin/rules/r3f/three-require-postprocessing-cleanup.ts
114548
113918
  const POSTPROCESSING_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
114549
113919
  const THREE_COMPOSER_BORROWING_METHOD_NAMES = new Set(["addPass", "insertPass"]);
@@ -114663,110 +114033,6 @@ const threeRequirePostprocessingCleanup = defineRule({
114663
114033
  } })
114664
114034
  });
114665
114035
  //#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
114770
114036
  //#region src/plugin/rules/r3f/three-require-render-target-cleanup.ts
114771
114037
  const RENDER_TARGET_CONSTRUCTORS = new Set([
114772
114038
  "RenderTarget",
@@ -114779,6 +114045,7 @@ const RENDER_TARGET_BORROWING_METHODS = new Set([
114779
114045
  "setRenderTarget",
114780
114046
  "setRenderTargetTextures"
114781
114047
  ]);
114048
+ const isThreeModuleSource$1 = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
114782
114049
  const threeRequireRenderTargetCleanup = defineRule({
114783
114050
  id: "three-require-render-target-cleanup",
114784
114051
  title: "Undisposed Three.js render target",
@@ -114803,6 +114070,7 @@ const threeRequireRenderTargetCleanup = defineRule({
114803
114070
  //#endregion
114804
114071
  //#region src/plugin/rules/r3f/three-require-renderer-cleanup.ts
114805
114072
  const RENDERER_CONSTRUCTORS = new Set(["WebGLRenderer", "WebGPURenderer"]);
114073
+ const isThreeModuleSource = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
114806
114074
  const isNullArgument = (call) => {
114807
114075
  const argument = call.arguments[0];
114808
114076
  return Boolean(isNodeOfType(argument, "Literal") && argument.value === null);
@@ -114931,7 +114199,7 @@ const threeRequireRendererCleanup = defineRule({
114931
114199
  recommendation: "Dispose component-owned renderers and stop their animation loop or animation frame in matching React cleanup",
114932
114200
  create: (context) => ({ NewExpression(node) {
114933
114201
  const provenance = getApiReferenceProvenance(node.callee, context.scopes);
114934
- if (!provenance || !RENDERER_CONSTRUCTORS.has(provenance.apiName) || !isThreeModuleSource$1(provenance.moduleSource)) return;
114202
+ if (!provenance || !RENDERER_CONSTRUCTORS.has(provenance.apiName) || !isThreeModuleSource(provenance.moduleSource)) return;
114935
114203
  const analysis = analyzeOwnedLifecycleResource(node, context, { isBorrowedReference: (reference) => isRetainedByUnusedLocalReactRef(reference, context.scopes) });
114936
114204
  if (!analysis || analysis.hasUnknownOwnershipTransfer || isRendererSuppliedToR3fCanvas(analysis, context)) return;
114937
114205
  const disposeCleanup = analyzeOwnedLifecycleCleanup(analysis, context, (cleanupFunction) => functionInvokesOwnedResourceMethod(cleanupFunction, analysis, "dispose", context.scopes));
@@ -114953,124 +114221,6 @@ const threeRequireRendererCleanup = defineRule({
114953
114221
  } })
114954
114222
  });
114955
114223
  //#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
115074
114224
  //#region src/plugin/rules/security-scan/unsafe-json-in-html.ts
115075
114225
  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];
115076
114226
  const INLINE_SCRIPT_PATTERN_INDEX = 1;
@@ -115828,44 +114978,6 @@ const webAnimationOffsetsValid = defineRule({
115828
114978
  } })
115829
114979
  });
115830
114980
  //#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
115869
114981
  //#region src/plugin/rules/security-scan/webhook-signature-risk.ts
115870
114982
  const WEBHOOK_HANDLER_PATTERN = /(?:^|\/)[^/]*webhook[^/]*\/|(?:^|\/)[^/]*webhook[^/]*\.[cm]?[jt]s$|\bwebhook\b/i;
115871
114983
  const WEBHOOK_ENTRYPOINT_PATTERN = /\b(?:export\s+(?:async\s+)?function\s+POST|export\s+const\s+(?:POST|handler|webhook)|webhookHandler|webhookRoute)\b/i;
@@ -127685,171 +126797,6 @@ const reactDoctorRules = [
127685
126797
  tags: [...new Set(["security-scan", ...tenantStaticProxyRisk.tags ?? []])]
127686
126798
  }
127687
126799
  },
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
- },
127853
126800
  {
127854
126801
  key: "react-doctor/three-require-animation-mixer-cleanup",
127855
126802
  id: "three-require-animation-mixer-cleanup",
@@ -127860,7 +126807,7 @@ const reactDoctorRules = [
127860
126807
  framework: "global",
127861
126808
  category: "Bugs",
127862
126809
  tags: [...new Set([
127863
- "three",
126810
+ "r3f",
127864
126811
  "webgl",
127865
126812
  ...threeRequireAnimationMixerCleanup.tags ?? []
127866
126813
  ])],
@@ -127881,7 +126828,7 @@ const reactDoctorRules = [
127881
126828
  framework: "global",
127882
126829
  category: "Bugs",
127883
126830
  tags: [...new Set([
127884
- "three",
126831
+ "r3f",
127885
126832
  "webgl",
127886
126833
  ...threeRequireControlsCleanup.tags ?? []
127887
126834
  ])],
@@ -127892,103 +126839,6 @@ const reactDoctorRules = [
127892
126839
  ])]
127893
126840
  }
127894
126841
  },
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
- },
127992
126842
  {
127993
126843
  key: "react-doctor/three-require-postprocessing-cleanup",
127994
126844
  id: "three-require-postprocessing-cleanup",
@@ -127999,7 +126849,7 @@ const reactDoctorRules = [
127999
126849
  framework: "global",
128000
126850
  category: "Bugs",
128001
126851
  tags: [...new Set([
128002
- "three",
126852
+ "r3f",
128003
126853
  "webgl",
128004
126854
  ...threeRequirePostprocessingCleanup.tags ?? []
128005
126855
  ])],
@@ -128010,23 +126860,6 @@ const reactDoctorRules = [
128010
126860
  ])]
128011
126861
  }
128012
126862
  },
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
- },
128030
126863
  {
128031
126864
  key: "react-doctor/three-require-render-target-cleanup",
128032
126865
  id: "three-require-render-target-cleanup",
@@ -128037,7 +126870,7 @@ const reactDoctorRules = [
128037
126870
  framework: "global",
128038
126871
  category: "Bugs",
128039
126872
  tags: [...new Set([
128040
- "three",
126873
+ "r3f",
128041
126874
  "webgl",
128042
126875
  ...threeRequireRenderTargetCleanup.tags ?? []
128043
126876
  ])],
@@ -128058,7 +126891,7 @@ const reactDoctorRules = [
128058
126891
  framework: "global",
128059
126892
  category: "Bugs",
128060
126893
  tags: [...new Set([
128061
- "three",
126894
+ "r3f",
128062
126895
  "webgl",
128063
126896
  ...threeRequireRendererCleanup.tags ?? []
128064
126897
  ])],
@@ -128069,57 +126902,6 @@ const reactDoctorRules = [
128069
126902
  ])]
128070
126903
  }
128071
126904
  },
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
- },
128123
126905
  {
128124
126906
  key: "react-doctor/unsafe-json-in-html",
128125
126907
  id: "unsafe-json-in-html",
@@ -128225,18 +127007,6 @@ const reactDoctorRules = [
128225
127007
  category: "Bugs"
128226
127008
  }
128227
127009
  },
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
- },
128240
127010
  {
128241
127011
  key: "react-doctor/webhook-signature-risk",
128242
127012
  id: "webhook-signature-risk",