oxlint-plugin-react-doctor 0.9.1-dev.6904ff1 → 0.9.1-dev.846c2df

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 +4145 -3137
  2. package/dist/index.js +2051 -12
  3. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -4,6 +4,8 @@ import { parseSync, visitorKeys } from "oxc-parser";
4
4
  import * as fs from "node:fs";
5
5
  import { readFileSync } from "node:fs";
6
6
  import { analyze } from "eslint-scope";
7
+ import { visit } from "@shaderfrog/glsl-parser/ast/index.js";
8
+ import { parse } from "@shaderfrog/glsl-parser/index.js";
7
9
  //#region src/plugin/utils/is-node-of-type.ts
8
10
  const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
9
11
  //#endregion
@@ -22875,10 +22877,10 @@ const hasBroaderDeclaredDependency = (declaredKey, declaredKeys) => {
22875
22877
  for (const otherDeclaredKey of declaredKeys) if (otherDeclaredKey !== declaredKey && declaredKey.startsWith(`${otherDeclaredKey}.`)) return true;
22876
22878
  return false;
22877
22879
  };
22878
- const getMemberRootIdentifier$1 = (node) => {
22880
+ const getMemberRootIdentifier$2 = (node) => {
22879
22881
  const stripped = unwrapExpression$3(node);
22880
22882
  if (isNodeOfType(stripped, "Identifier")) return stripped;
22881
- if (isNodeOfType(stripped, "MemberExpression")) return getMemberRootIdentifier$1(stripped.object);
22883
+ if (isNodeOfType(stripped, "MemberExpression")) return getMemberRootIdentifier$2(stripped.object);
22882
22884
  return null;
22883
22885
  };
22884
22886
  const hasComputedMemberExpression = (node) => {
@@ -22915,7 +22917,7 @@ const resolveIdentitySourceKeysFromExpression = (expression, scopes, visitedSymb
22915
22917
  if (isNodeOfType(stripped, "MemberExpression")) {
22916
22918
  if (hasComputedMemberExpression(stripped)) return null;
22917
22919
  const sourceKey = stringifyMemberChain(stripped);
22918
- const rootIdentifier = getMemberRootIdentifier$1(stripped);
22920
+ const rootIdentifier = getMemberRootIdentifier$2(stripped);
22919
22921
  const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
22920
22922
  if (!sourceKey || !rootSymbol) return null;
22921
22923
  if (isOutsideAllFunctions(rootSymbol)) return /* @__PURE__ */ new Set();
@@ -23009,7 +23011,7 @@ const resolveDerivedExpressionSourceKeys = (expression, scopes, visitedSymbolIds
23009
23011
  if (isNodeOfType(candidate, "MemberExpression")) {
23010
23012
  if (hasComputedMemberExpression(candidate)) return null;
23011
23013
  const sourceKey = stringifyMemberChain(candidate);
23012
- const rootIdentifier = getMemberRootIdentifier$1(candidate);
23014
+ const rootIdentifier = getMemberRootIdentifier$2(candidate);
23013
23015
  const rootSymbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
23014
23016
  if (!sourceKey || !rootSymbol) return null;
23015
23017
  if (isOutsideAllFunctions(rootSymbol) || symbolHasStableValue(rootSymbol, scopes)) return /* @__PURE__ */ new Set();
@@ -23096,14 +23098,14 @@ const isUseCallbackResultDep = (node, scopes) => {
23096
23098
  return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && getHookName(initializer.callee, scopes) === "useCallback");
23097
23099
  };
23098
23100
  const isExtraReactiveDepAllowed = (node, scopes) => {
23099
- const rootIdentifier = getMemberRootIdentifier$1(node);
23101
+ const rootIdentifier = getMemberRootIdentifier$2(node);
23100
23102
  if (!rootIdentifier) return false;
23101
23103
  const symbol = scopes.symbolFor(rootIdentifier);
23102
23104
  if (!symbol || isOutsideAllFunctions(symbol)) return false;
23103
23105
  return !isUseCallbackResultDep(node, scopes);
23104
23106
  };
23105
23107
  const getRootSymbol = (node, scopes) => {
23106
- const rootIdentifier = getMemberRootIdentifier$1(node);
23108
+ const rootIdentifier = getMemberRootIdentifier$2(node);
23107
23109
  return rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
23108
23110
  };
23109
23111
  const getDeclaredDepSymbolSource = (node) => {
@@ -23392,7 +23394,7 @@ const findRefCurrentInCleanup = (callback, scopes) => {
23392
23394
  if (isNodeOfType(node, "MemberExpression")) {
23393
23395
  const candidateName = getRefCurrentNameFromMemberExpression(node);
23394
23396
  if (candidateName) {
23395
- const rootIdentifier = getMemberRootIdentifier$1(node);
23397
+ const rootIdentifier = getMemberRootIdentifier$2(node);
23396
23398
  const symbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
23397
23399
  const callbackScope = scopes.ownScopeFor(callback) ?? scopes.scopeFor(callback);
23398
23400
  if (!symbol || !isDescendantScope(symbol.scope, callbackScope)) {
@@ -23465,7 +23467,7 @@ const hasRefCurrentAssignment = (callback, refCurrentName) => {
23465
23467
  return didAssignRefCurrent;
23466
23468
  };
23467
23469
  const isOuterFunctionScopeDep = (node, callback, scopes) => {
23468
- const rootIdentifier = getMemberRootIdentifier$1(node);
23470
+ const rootIdentifier = getMemberRootIdentifier$2(node);
23469
23471
  if (!rootIdentifier) return false;
23470
23472
  const symbol = scopes.symbolFor(rootIdentifier);
23471
23473
  if (!symbol || isOutsideAllFunctions(symbol)) return false;
@@ -98683,6 +98685,20 @@ const THREE_INTERPOLATION_FACTOR_ARGUMENT_BY_METHOD = new Map([
98683
98685
  ["slerp", 1],
98684
98686
  ["slerpQuaternions", 2]
98685
98687
  ]);
98688
+ const GLSL_POSITION_COMPONENT_BIT_BY_ALIAS = new Map([
98689
+ ["x", 1],
98690
+ ["r", 1],
98691
+ ["s", 1],
98692
+ ["y", 2],
98693
+ ["g", 2],
98694
+ ["t", 2],
98695
+ ["z", 4],
98696
+ ["b", 4],
98697
+ ["p", 4],
98698
+ ["w", 8],
98699
+ ["a", 8],
98700
+ ["q", 8]
98701
+ ]);
98686
98702
  const THREE_POSTPROCESSING_PASS_DISPOSAL_RELEASES = new Map([
98687
98703
  ["RenderPixelatedPass", 147],
98688
98704
  ["OutputPass", 153],
@@ -98690,6 +98706,73 @@ const THREE_POSTPROCESSING_PASS_DISPOSAL_RELEASES = new Map([
98690
98706
  ["RenderTransitionPass", 164],
98691
98707
  ["FXAAPass", 177]
98692
98708
  ]);
98709
+ const THREE_RENDERER_MANAGED_SHADER_UNIFORM_NAMES = new Set([
98710
+ "ambientLightColor",
98711
+ "bindMatrix",
98712
+ "bindMatrixInverse",
98713
+ "boneTexture",
98714
+ "boneTextureSize",
98715
+ "cameraPosition",
98716
+ "clippingPlanes",
98717
+ "directionalLights",
98718
+ "directionalLightShadows",
98719
+ "directionalShadowMap",
98720
+ "directionalShadowMatrix",
98721
+ "fogColor",
98722
+ "fogDensity",
98723
+ "fogFar",
98724
+ "fogNear",
98725
+ "hemisphereLights",
98726
+ "isOrthographic",
98727
+ "lightProbe",
98728
+ "logDepthBufFC",
98729
+ "ltc_1",
98730
+ "ltc_2",
98731
+ "modelMatrix",
98732
+ "modelViewMatrix",
98733
+ "morphTargetBaseInfluence",
98734
+ "morphTargetInfluences",
98735
+ "morphTargetsTexture",
98736
+ "morphTargetsTextureSize",
98737
+ "normalMatrix",
98738
+ "pointLights",
98739
+ "pointLightShadows",
98740
+ "pointShadowMap",
98741
+ "pointShadowMatrix",
98742
+ "probesMax",
98743
+ "probesMin",
98744
+ "probesResolution",
98745
+ "probesSH",
98746
+ "projectionMatrix",
98747
+ "receiveShadow",
98748
+ "rectAreaLights",
98749
+ "spotLightMap",
98750
+ "spotLightMatrix",
98751
+ "spotLights",
98752
+ "spotLightShadows",
98753
+ "spotShadowMap",
98754
+ "toneMappingExposure",
98755
+ "transmissionSamplerMap",
98756
+ "transmissionSamplerSize",
98757
+ "viewMatrix"
98758
+ ]);
98759
+ const THREE_SHADER_MATERIAL_INJECTED_FRAGMENT_NAMES = new Set([
98760
+ "cameraPosition",
98761
+ "isOrthographic",
98762
+ "viewMatrix"
98763
+ ]);
98764
+ const THREE_SHADER_MATERIAL_INJECTED_VERTEX_NAMES = new Set([
98765
+ "cameraPosition",
98766
+ "isOrthographic",
98767
+ "modelMatrix",
98768
+ "modelViewMatrix",
98769
+ "normal",
98770
+ "normalMatrix",
98771
+ "position",
98772
+ "projectionMatrix",
98773
+ "uv",
98774
+ "viewMatrix"
98775
+ ]);
98693
98776
  //#endregion
98694
98777
  //#region src/plugin/rules/r3f/r3f-limit-shadowed-point-lights.ts
98695
98778
  const resolvesToTrue = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
@@ -121827,9 +121910,9 @@ const getJsxMemberRootIdentifier = (node) => {
121827
121910
  return null;
121828
121911
  };
121829
121912
  const getJsxMemberPropertyName = (node) => isNodeOfType(node.property, "JSXIdentifier") ? node.property.name : null;
121830
- const getMemberRootIdentifier = (node) => {
121913
+ const getMemberRootIdentifier$1 = (node) => {
121831
121914
  if (isNodeOfType(node.object, "Identifier")) return node.object;
121832
- if (isNodeOfType(node.object, "MemberExpression")) return getMemberRootIdentifier(node.object);
121915
+ if (isNodeOfType(node.object, "MemberExpression")) return getMemberRootIdentifier$1(node.object);
121833
121916
  return null;
121834
121917
  };
121835
121918
  const getMemberPropertyName = (node) => isNodeOfType(node.property, "Identifier") ? node.property.name : null;
@@ -121934,7 +122017,7 @@ const tanstackStartMissingScripts = defineRule({
121934
122017
  return didCollectAlias;
121935
122018
  }
121936
122019
  if (!isNodeOfType(node.init, "MemberExpression")) return false;
121937
- const rootIdentifier = getMemberRootIdentifier(node.init);
122020
+ const rootIdentifier = getMemberRootIdentifier$1(node.init);
121938
122021
  const rootDeclaration = rootIdentifier ? getBindingDeclaration(rootIdentifier, context) : null;
121939
122022
  const propertyName = getMemberPropertyName(node.init);
121940
122023
  if (!rootDeclaration || !tanstackRouterNamespaceDeclarations.has(rootDeclaration)) return false;
@@ -121968,7 +122051,7 @@ const tanstackStartMissingScripts = defineRule({
121968
122051
  return factoryDeclaration ? rootRouteFactoryDeclarations.has(factoryDeclaration) : TANSTACK_ROOT_ROUTE_FACTORY_NAMES.has(callee.name);
121969
122052
  }
121970
122053
  if (!isNodeOfType(callee, "MemberExpression")) return false;
121971
- const rootIdentifier = getMemberRootIdentifier(callee);
122054
+ const rootIdentifier = getMemberRootIdentifier$1(callee);
121972
122055
  const rootDeclaration = rootIdentifier ? getBindingDeclaration(rootIdentifier, context) : null;
121973
122056
  const propertyName = getMemberPropertyName(callee);
121974
122057
  return Boolean(rootDeclaration && tanstackRouterNamespaceDeclarations.has(rootDeclaration) && propertyName && TANSTACK_ROOT_ROUTE_FACTORY_NAMES.has(propertyName));
@@ -122791,6 +122874,47 @@ const threeNoCloneInAnimationLoop = defineRule({
122791
122874
  }
122792
122875
  });
122793
122876
  //#endregion
122877
+ //#region src/plugin/rules/r3f/utils/visit-three-animation-loop-enabled-property-writes.ts
122878
+ const visitThreeAnimationLoopEnabledPropertyWrites = (call, scopes, propertyNames, analyzedCallbacks, visitor) => {
122879
+ const callback = resolveThreeAnimationLoopCallback(call, scopes);
122880
+ if (!callback || analyzedCallbacks.has(callback)) return;
122881
+ analyzedCallbacks.add(callback);
122882
+ walkFunctionExecution(callback, scopes, (candidate, isConditionallyExecuted) => {
122883
+ if (isConditionallyExecuted || !isNodeOfType(candidate, "AssignmentExpression") || candidate.operator !== "=" || !isNodeOfType(candidate.left, "MemberExpression") || !isNodeOfType(candidate.right, "Literal") || candidate.right.value !== true) return;
122884
+ const propertyName = getStaticPropertyName(candidate.left);
122885
+ if (!propertyName || !propertyNames.has(propertyName)) return;
122886
+ const constructorName = getThreeConstructorName(candidate.left.object, scopes);
122887
+ if (!constructorName) return;
122888
+ visitor({
122889
+ constructorName,
122890
+ node: candidate,
122891
+ propertyName
122892
+ });
122893
+ });
122894
+ };
122895
+ //#endregion
122896
+ //#region src/plugin/rules/r3f/three-no-material-recompile-in-animation-loop.ts
122897
+ const MATERIAL_RECOMPILE_PROPERTY_NAMES = new Set(["needsUpdate"]);
122898
+ const threeNoMaterialRecompileInAnimationLoop = defineRule({
122899
+ id: "three-no-material-recompile-in-animation-loop",
122900
+ title: "Material recompiled inside animation loop",
122901
+ category: "Performance",
122902
+ severity: "error",
122903
+ recommendation: "Change material defines or features outside the animation loop and set needsUpdate only when the compiled program must change",
122904
+ create: (context) => {
122905
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
122906
+ return { CallExpression(node) {
122907
+ visitThreeAnimationLoopEnabledPropertyWrites(node, context.scopes, MATERIAL_RECOMPILE_PROPERTY_NAMES, analyzedCallbacks, ({ constructorName, node: assignment }) => {
122908
+ if (!constructorName.endsWith("Material")) return;
122909
+ context.report({
122910
+ node: assignment,
122911
+ message: "Setting material.needsUpdate every frame forces Three.js to reconsider and often rebuild the GPU program. Set it only after a shader-affecting material change"
122912
+ });
122913
+ });
122914
+ } };
122915
+ }
122916
+ });
122917
+ //#endregion
122794
122918
  //#region src/plugin/rules/r3f/three-no-new-in-animation-loop.ts
122795
122919
  const threeNoNewInAnimationLoop = defineRule({
122796
122920
  id: "three-no-new-in-animation-loop",
@@ -122840,6 +122964,28 @@ const threeNoObjectConstructionInRender = defineRule({
122840
122964
  } })
122841
122965
  });
122842
122966
  //#endregion
122967
+ //#region src/plugin/rules/r3f/three-no-redundant-uniforms-need-update.ts
122968
+ const UNIFORM_UPDATE_PROPERTY_NAMES = new Set(["uniformsNeedUpdate"]);
122969
+ const threeNoRedundantUniformsNeedUpdate = defineRule({
122970
+ id: "three-no-redundant-uniforms-need-update",
122971
+ title: "Redundant uniformsNeedUpdate inside animation loop",
122972
+ category: "Performance",
122973
+ severity: "warn",
122974
+ recommendation: "Mutate ShaderMaterial uniform values directly; Three.js uploads custom uniforms during rendering",
122975
+ create: (context) => {
122976
+ const analyzedCallbacks = /* @__PURE__ */ new Set();
122977
+ return { CallExpression(node) {
122978
+ visitThreeAnimationLoopEnabledPropertyWrites(node, context.scopes, UNIFORM_UPDATE_PROPERTY_NAMES, analyzedCallbacks, ({ constructorName, node: assignment }) => {
122979
+ if (constructorName !== "ShaderMaterial" && constructorName !== "RawShaderMaterial") return;
122980
+ context.report({
122981
+ node: assignment,
122982
+ message: "Shader material custom uniforms are refreshed during rendering, so setting uniformsNeedUpdate on every animation frame is redundant"
122983
+ });
122984
+ });
122985
+ } };
122986
+ }
122987
+ });
122988
+ //#endregion
122843
122989
  //#region src/plugin/rules/r3f/three-no-state-in-animation-loop.ts
122844
122990
  const threeNoStateInAnimationLoop = defineRule({
122845
122991
  id: "three-no-state-in-animation-loop",
@@ -122894,6 +123040,631 @@ const threeNoStateInPointerMove = defineRule({
122894
123040
  }
122895
123041
  });
122896
123042
  //#endregion
123043
+ //#region src/plugin/rules/r3f/three-on-before-compile-require-program-cache-key.ts
123044
+ const SHADER_PROGRAM_PROPERTY_NAMES = new Set([
123045
+ "defines",
123046
+ "fragmentShader",
123047
+ "vertexShader"
123048
+ ]);
123049
+ const MATERIAL_PROGRAM_OPTION_NAMES = ["customProgramCacheKey", "onBeforeCompile"];
123050
+ const isScopeWithin$1 = (candidateScope, ancestorScope) => {
123051
+ let currentScope = candidateScope;
123052
+ while (currentScope) {
123053
+ if (currentScope === ancestorScope) return true;
123054
+ currentScope = currentScope.parent;
123055
+ }
123056
+ return false;
123057
+ };
123058
+ const getMemberRootIdentifier = (expression) => {
123059
+ let candidate = expression;
123060
+ while (isNodeOfType(candidate, "MemberExpression")) candidate = candidate.object;
123061
+ return isNodeOfType(candidate, "Identifier") ? candidate : null;
123062
+ };
123063
+ const getShaderProgramPropertyName = (member, shaderParameterSymbol, context) => {
123064
+ const rootIdentifier = getMemberRootIdentifier(member);
123065
+ if (!rootIdentifier || context.scopes.symbolFor(rootIdentifier)?.id !== shaderParameterSymbol.id) return null;
123066
+ let candidate = member;
123067
+ let propertyName = null;
123068
+ while (isNodeOfType(candidate, "MemberExpression")) {
123069
+ propertyName = getStaticPropertyName(candidate);
123070
+ if (isNodeOfType(candidate.object, "Identifier")) break;
123071
+ candidate = candidate.object;
123072
+ }
123073
+ return propertyName && SHADER_PROGRAM_PROPERTY_NAMES.has(propertyName) ? propertyName : null;
123074
+ };
123075
+ const expressionDependsOnMutableCapture = (expression, callbackScope, context) => {
123076
+ const visitedLocalSymbolIds = /* @__PURE__ */ new Set();
123077
+ const inspectExpression = (expressionToInspect) => {
123078
+ let dependsOnMutableCapture = false;
123079
+ walkAst(expressionToInspect, (candidate) => {
123080
+ if (dependsOnMutableCapture || candidate !== expressionToInspect && isFunctionLike$1(candidate)) return false;
123081
+ if (isNodeOfType(candidate, "ThisExpression")) {
123082
+ dependsOnMutableCapture = true;
123083
+ return false;
123084
+ }
123085
+ if (isNodeOfType(candidate, "MemberExpression")) {
123086
+ const rootIdentifier = getMemberRootIdentifier(candidate);
123087
+ const symbol = rootIdentifier ? context.scopes.symbolFor(rootIdentifier) : null;
123088
+ if (symbol && !isScopeWithin$1(symbol.scope, callbackScope) && symbol.kind !== "import" && symbol.kind !== "function" && symbol.kind !== "class") {
123089
+ dependsOnMutableCapture = true;
123090
+ return false;
123091
+ }
123092
+ }
123093
+ if (!isNodeOfType(candidate, "Identifier")) return;
123094
+ const symbol = context.scopes.symbolFor(candidate);
123095
+ const hasMutableConstInitializer = symbol?.kind === "const" && Boolean(symbol.initializer && (isNodeOfType(symbol.initializer, "ArrayExpression") || isNodeOfType(symbol.initializer, "ObjectExpression") || isNodeOfType(symbol.initializer, "NewExpression")));
123096
+ if (symbol && !isScopeWithin$1(symbol.scope, callbackScope) && (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "parameter" || hasMutableConstInitializer)) {
123097
+ dependsOnMutableCapture = true;
123098
+ return false;
123099
+ }
123100
+ if (!symbol || !isScopeWithin$1(symbol.scope, callbackScope) || visitedLocalSymbolIds.has(symbol.id)) return;
123101
+ const initializer = getDirectUnreassignedInitializer(symbol);
123102
+ if (!initializer || isFunctionLike$1(initializer) || isNodeOfType(initializer, "ArrayExpression") || isNodeOfType(initializer, "ObjectExpression") || isNodeOfType(initializer, "NewExpression")) return;
123103
+ visitedLocalSymbolIds.add(symbol.id);
123104
+ if (inspectExpression(initializer)) {
123105
+ dependsOnMutableCapture = true;
123106
+ return false;
123107
+ }
123108
+ });
123109
+ return dependsOnMutableCapture;
123110
+ };
123111
+ return inspectExpression(expression);
123112
+ };
123113
+ const callbackHasVariantDependentPatch = (callback, context) => {
123114
+ const callbackScope = context.scopes.ownScopeFor(callback);
123115
+ if (!callbackScope || !isFunctionLike$1(callback)) return false;
123116
+ const shaderParameter = callback.params[0];
123117
+ if (!shaderParameter || !isNodeOfType(shaderParameter, "Identifier")) return false;
123118
+ const shaderParameterSymbol = context.scopes.symbolFor(shaderParameter);
123119
+ if (!shaderParameterSymbol) return false;
123120
+ const subtreeHasShaderProgramWrite = (subtree) => {
123121
+ let hasWrite = false;
123122
+ walkAst(subtree, (candidate) => {
123123
+ if (hasWrite || candidate !== subtree && isFunctionLike$1(candidate)) return false;
123124
+ if (isNodeOfType(candidate, "AssignmentExpression") && isNodeOfType(candidate.left, "MemberExpression") && getShaderProgramPropertyName(candidate.left, shaderParameterSymbol, context)) {
123125
+ hasWrite = true;
123126
+ return false;
123127
+ }
123128
+ });
123129
+ return hasWrite;
123130
+ };
123131
+ let hasVariantDependentPatch = false;
123132
+ walkAst(callback.body, (candidate) => {
123133
+ if (hasVariantDependentPatch || candidate !== callback.body && isFunctionLike$1(candidate)) return false;
123134
+ if (isNodeOfType(candidate, "AssignmentExpression") && isNodeOfType(candidate.left, "MemberExpression") && getShaderProgramPropertyName(candidate.left, shaderParameterSymbol, context) && expressionDependsOnMutableCapture(candidate.right, callbackScope, context)) {
123135
+ hasVariantDependentPatch = true;
123136
+ return false;
123137
+ }
123138
+ if (isNodeOfType(candidate, "IfStatement") && expressionDependsOnMutableCapture(candidate.test, callbackScope, context) && (subtreeHasShaderProgramWrite(candidate.consequent) || Boolean(candidate.alternate && subtreeHasShaderProgramWrite(candidate.alternate)))) {
123139
+ hasVariantDependentPatch = true;
123140
+ return false;
123141
+ }
123142
+ if (isNodeOfType(candidate, "ConditionalExpression") && expressionDependsOnMutableCapture(candidate.test, callbackScope, context) && (subtreeHasShaderProgramWrite(candidate.consequent) || subtreeHasShaderProgramWrite(candidate.alternate))) {
123143
+ hasVariantDependentPatch = true;
123144
+ return false;
123145
+ }
123146
+ if (isNodeOfType(candidate, "LogicalExpression") && expressionDependsOnMutableCapture(candidate.left, callbackScope, context) && subtreeHasShaderProgramWrite(candidate.right)) {
123147
+ hasVariantDependentPatch = true;
123148
+ return false;
123149
+ }
123150
+ if (isNodeOfType(candidate, "SwitchStatement") && (expressionDependsOnMutableCapture(candidate.discriminant, callbackScope, context) || candidate.cases.some((switchCase) => switchCase.test && expressionDependsOnMutableCapture(switchCase.test, callbackScope, context))) && candidate.cases.some((switchCase) => switchCase.consequent.some(subtreeHasShaderProgramWrite))) {
123151
+ hasVariantDependentPatch = true;
123152
+ return false;
123153
+ }
123154
+ if ((isNodeOfType(candidate, "WhileStatement") || isNodeOfType(candidate, "DoWhileStatement")) && expressionDependsOnMutableCapture(candidate.test, callbackScope, context) && subtreeHasShaderProgramWrite(candidate.body)) {
123155
+ hasVariantDependentPatch = true;
123156
+ return false;
123157
+ }
123158
+ if (isNodeOfType(candidate, "ForStatement") && candidate.test && expressionDependsOnMutableCapture(candidate.test, callbackScope, context) && subtreeHasShaderProgramWrite(candidate.body)) {
123159
+ hasVariantDependentPatch = true;
123160
+ return false;
123161
+ }
123162
+ if ((isNodeOfType(candidate, "ForInStatement") || isNodeOfType(candidate, "ForOfStatement")) && expressionDependsOnMutableCapture(candidate.right, callbackScope, context) && subtreeHasShaderProgramWrite(candidate.body)) {
123163
+ hasVariantDependentPatch = true;
123164
+ return false;
123165
+ }
123166
+ });
123167
+ return hasVariantDependentPatch;
123168
+ };
123169
+ const getStableMaterialSymbol = (expression, context) => {
123170
+ if (!isNodeOfType(expression, "Identifier")) return null;
123171
+ const symbol = context.scopes.symbolFor(expression);
123172
+ const initializer = symbol ? getDirectUnreassignedInitializer(symbol) : null;
123173
+ if (!symbol || !initializer || !getThreeConstructorName(initializer, context.scopes)?.endsWith("Material")) return null;
123174
+ return symbol;
123175
+ };
123176
+ const getConstructorMaterialSymbol = (node, context) => {
123177
+ const parent = node.parent;
123178
+ if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== node || !isNodeOfType(parent.id, "Identifier")) return null;
123179
+ const symbol = context.scopes.symbolFor(parent.id);
123180
+ return symbol && getDirectUnreassignedInitializer(symbol) === node ? symbol : null;
123181
+ };
123182
+ const isUsableProgramCacheKey = (expression, context) => {
123183
+ if (resolveExactLocalFunction(expression, context.scopes)) return true;
123184
+ let candidate = stripParenExpression(expression);
123185
+ if (isNodeOfType(candidate, "Identifier")) {
123186
+ if (candidate.name === "undefined" && context.scopes.isGlobalReference(candidate)) return false;
123187
+ const symbol = resolveConstIdentifierAlias(candidate, context.scopes);
123188
+ if (symbol?.kind !== "const" || !symbol.initializer) return true;
123189
+ candidate = stripParenExpression(symbol.initializer);
123190
+ }
123191
+ return !(isNodeOfType(candidate, "ArrayExpression") || isNodeOfType(candidate, "ClassExpression") || isNodeOfType(candidate, "Literal") || isNodeOfType(candidate, "ObjectExpression") || isNodeOfType(candidate, "TemplateLiteral") || isNodeOfType(candidate, "UnaryExpression"));
123192
+ };
123193
+ const threeOnBeforeCompileRequireProgramCacheKey = defineRule({
123194
+ id: "three-on-before-compile-require-program-cache-key",
123195
+ title: "onBeforeCompile variant lacks a program cache key",
123196
+ category: "Correctness",
123197
+ severity: "error",
123198
+ recommendation: "Return every mutable shader-patch variant from customProgramCacheKey on the same material",
123199
+ create: (context) => {
123200
+ const candidates = [];
123201
+ const materialSymbolsWithCacheKeys = /* @__PURE__ */ new Set();
123202
+ return {
123203
+ NewExpression(node) {
123204
+ if (!getThreeConstructorName(node, context.scopes)?.endsWith("Material")) return;
123205
+ const options = node.arguments[0];
123206
+ if (!options || isNodeOfType(options, "SpreadElement")) return;
123207
+ const optionsObject = resolveStableOptionsObject(options, MATERIAL_PROGRAM_OPTION_NAMES, context.scopes, node);
123208
+ if (!optionsObject) return;
123209
+ const properties = getEffectiveObjectPropertiesInInsertionOrder(optionsObject.properties);
123210
+ if (!properties) return;
123211
+ const cacheKeyProperty = properties.find((property) => getStaticPropertyKeyName(property, { allowComputedString: true }) === "customProgramCacheKey");
123212
+ const onBeforeCompileProperty = properties.find((property) => getStaticPropertyKeyName(property, { allowComputedString: true }) === "onBeforeCompile");
123213
+ const materialSymbol = getConstructorMaterialSymbol(node, context);
123214
+ const hasConstructorCacheKey = Boolean(cacheKeyProperty && isUsableProgramCacheKey(cacheKeyProperty.value, context));
123215
+ if (hasConstructorCacheKey && materialSymbol) materialSymbolsWithCacheKeys.add(materialSymbol.id);
123216
+ if (!onBeforeCompileProperty) return;
123217
+ const callback = resolveExactLocalFunction(onBeforeCompileProperty.value, context.scopes);
123218
+ if (!callback || !callbackHasVariantDependentPatch(callback, context)) return;
123219
+ candidates.push({
123220
+ hasConstructorCacheKey,
123221
+ materialSymbolId: materialSymbol?.id ?? null,
123222
+ node: onBeforeCompileProperty
123223
+ });
123224
+ },
123225
+ AssignmentExpression(node) {
123226
+ if (!isNodeOfType(node.left, "MemberExpression")) return;
123227
+ const propertyName = getStaticPropertyName(node.left);
123228
+ if (propertyName !== "onBeforeCompile" && propertyName !== "customProgramCacheKey") return;
123229
+ const materialSymbol = getStableMaterialSymbol(node.left.object, context);
123230
+ if (!materialSymbol) return;
123231
+ if (propertyName === "customProgramCacheKey") {
123232
+ if (isUsableProgramCacheKey(node.right, context)) materialSymbolsWithCacheKeys.add(materialSymbol.id);
123233
+ else materialSymbolsWithCacheKeys.delete(materialSymbol.id);
123234
+ return;
123235
+ }
123236
+ const callback = resolveExactLocalFunction(node.right, context.scopes);
123237
+ if (!callback || !callbackHasVariantDependentPatch(callback, context)) return;
123238
+ candidates.push({
123239
+ hasConstructorCacheKey: false,
123240
+ materialSymbolId: materialSymbol.id,
123241
+ node
123242
+ });
123243
+ },
123244
+ "Program:exit"() {
123245
+ for (const candidate of candidates) {
123246
+ if (candidate.materialSymbolId === null && candidate.hasConstructorCacheKey || candidate.materialSymbolId !== null && materialSymbolsWithCacheKeys.has(candidate.materialSymbolId)) continue;
123247
+ context.report({
123248
+ node: candidate.node,
123249
+ message: "This onBeforeCompile patch changes shader source from mutable captured state, but the material has no customProgramCacheKey for those program variants"
123250
+ });
123251
+ }
123252
+ }
123253
+ };
123254
+ }
123255
+ });
123256
+ //#endregion
123257
+ //#region src/plugin/rules/r3f/utils/get-glsl-type-specifier-name.ts
123258
+ const getGlslTypeSpecifierName = (specifier) => {
123259
+ const typeName = specifier.specifier;
123260
+ if (typeName.type === "keyword") return typeName.token;
123261
+ if (typeName.type === "identifier" || typeName.type === "type_name") return typeName.identifier;
123262
+ return null;
123263
+ };
123264
+ //#endregion
123265
+ //#region src/plugin/rules/r3f/utils/mask-glsl-comments.ts
123266
+ const GLSL_COMMENT_PATTERN = /\/\*[\s\S]*?(?:\*\/|$)|\/\/[^\r\n]*/g;
123267
+ const NON_LINE_BREAK_PATTERN = /[^\r\n]/g;
123268
+ const maskGlslComments = (source) => source.replace(GLSL_COMMENT_PATTERN, (comment) => comment.replace(NON_LINE_BREAK_PATTERN, " "));
123269
+ //#endregion
123270
+ //#region src/plugin/rules/r3f/utils/parse-glsl-shader-source.ts
123271
+ const GLSL_CONDITIONAL_DIRECTIVE_PATTERN = /^[ \t]*#[ \t]*(?:if|ifdef|ifndef|elif|else|endif)\b/m;
123272
+ const THREE_INCLUDE_DIRECTIVE_PATTERN = /^[ \t]*#[ \t]*include[ \t]+<[^>\r\n]+>[^\r\n]*/gm;
123273
+ const maskThreeIncludeDirectives = (source) => source.replace(THREE_INCLUDE_DIRECTIVE_PATTERN, (directive) => directive.replace(/[^\r\n]/g, " "));
123274
+ const parseGlslShaderSource = (source, stage) => {
123275
+ const sourceWithoutComments = maskGlslComments(source);
123276
+ if (GLSL_CONDITIONAL_DIRECTIVE_PATTERN.test(sourceWithoutComments)) return null;
123277
+ try {
123278
+ return parse(maskThreeIncludeDirectives(sourceWithoutComments), {
123279
+ includeLocation: true,
123280
+ quiet: true,
123281
+ stage
123282
+ });
123283
+ } catch {
123284
+ return null;
123285
+ }
123286
+ };
123287
+ //#endregion
123288
+ //#region src/plugin/rules/r3f/utils/resolve-static-shader-source.ts
123289
+ const shiftSegments = (source, offset) => source.segments.map((segment) => ({
123290
+ endOffset: segment.endOffset + offset,
123291
+ node: segment.node,
123292
+ startOffset: segment.startOffset + offset
123293
+ }));
123294
+ const combineStaticShaderSources = (sources, separator, fallbackNode) => {
123295
+ let text = "";
123296
+ const segments = [];
123297
+ sources.forEach((source, sourceIndex) => {
123298
+ if (sourceIndex > 0 && separator) {
123299
+ segments.push(...shiftSegments(separator, text.length));
123300
+ text += separator.text;
123301
+ }
123302
+ segments.push(...shiftSegments(source, text.length));
123303
+ text += source.text;
123304
+ });
123305
+ return {
123306
+ fallbackNode,
123307
+ segments,
123308
+ text
123309
+ };
123310
+ };
123311
+ const resolveStaticShaderSourceExpression = (expression, scopes, visitedSymbolIds) => {
123312
+ const candidate = stripParenExpression(expression);
123313
+ const directStaticString = getStaticStringExpression$1(candidate);
123314
+ if (directStaticString !== null) return {
123315
+ fallbackNode: candidate,
123316
+ segments: directStaticString.length === 0 ? [] : [{
123317
+ endOffset: directStaticString.length,
123318
+ node: candidate,
123319
+ startOffset: 0
123320
+ }],
123321
+ text: directStaticString
123322
+ };
123323
+ if (isNodeOfType(candidate, "Identifier")) {
123324
+ const symbol = scopes.symbolFor(candidate);
123325
+ if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
123326
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
123327
+ nextVisitedSymbolIds.add(symbol.id);
123328
+ return resolveStaticShaderSourceExpression(symbol.initializer, scopes, nextVisitedSymbolIds);
123329
+ }
123330
+ if (isNodeOfType(candidate, "BinaryExpression") && candidate.operator === "+") {
123331
+ const leftSource = resolveStaticShaderSourceExpression(candidate.left, scopes, new Set(visitedSymbolIds));
123332
+ const rightSource = resolveStaticShaderSourceExpression(candidate.right, scopes, new Set(visitedSymbolIds));
123333
+ return leftSource && rightSource ? combineStaticShaderSources([leftSource, rightSource], null, candidate) : null;
123334
+ }
123335
+ if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "join") return null;
123336
+ const arrayExpression = stripParenExpression(candidate.callee.object);
123337
+ if (!isNodeOfType(arrayExpression, "ArrayExpression") || candidate.arguments.length > 1) return null;
123338
+ const separatorArgument = candidate.arguments[0];
123339
+ let separator;
123340
+ if (!separatorArgument) separator = {
123341
+ fallbackNode: candidate,
123342
+ segments: [],
123343
+ text: ","
123344
+ };
123345
+ else if (isNodeOfType(separatorArgument, "SpreadElement")) separator = null;
123346
+ else separator = resolveStaticShaderSourceExpression(separatorArgument, scopes, new Set(visitedSymbolIds));
123347
+ if (!separator) return null;
123348
+ const elementSources = [];
123349
+ for (const element of arrayExpression.elements) {
123350
+ if (!element || isNodeOfType(element, "SpreadElement")) return null;
123351
+ const elementSource = resolveStaticShaderSourceExpression(element, scopes, new Set(visitedSymbolIds));
123352
+ if (!elementSource) return null;
123353
+ elementSources.push(elementSource);
123354
+ }
123355
+ return combineStaticShaderSources(elementSources, separator, candidate);
123356
+ };
123357
+ const resolveStaticShaderSource = (expression, scopes) => {
123358
+ const source = resolveStaticShaderSourceExpression(expression, scopes, /* @__PURE__ */ new Set());
123359
+ if (!source) return null;
123360
+ return {
123361
+ getOriginNodeAtOffset: (offset) => source.segments.find((segment) => offset >= segment.startOffset && offset < segment.endOffset)?.node ?? source.fallbackNode,
123362
+ text: source.text
123363
+ };
123364
+ };
123365
+ //#endregion
123366
+ //#region src/plugin/rules/r3f/utils/resolve-static-three-shader-material.ts
123367
+ const SHADER_MATERIAL_CONSTRUCTORS = new Set(["RawShaderMaterial", "ShaderMaterial"]);
123368
+ const STATIC_SHADER_MATERIAL_PROPERTY_NAMES = [
123369
+ "clipping",
123370
+ "fog",
123371
+ "fragmentShader",
123372
+ "glslVersion",
123373
+ "lights",
123374
+ "morphNormals",
123375
+ "morphTargets",
123376
+ "skinning",
123377
+ "transmission",
123378
+ "uniforms",
123379
+ "vertexShader"
123380
+ ];
123381
+ const resolveShaderStage = (expression, stage, context) => {
123382
+ if (!expression) return null;
123383
+ const source = resolveStaticShaderSource(expression, context.scopes);
123384
+ if (!source) return null;
123385
+ const program = parseGlslShaderSource(source.text, stage);
123386
+ return program ? {
123387
+ expression,
123388
+ program,
123389
+ source,
123390
+ stage
123391
+ } : null;
123392
+ };
123393
+ const resolveStaticThreeShaderMaterial = (node, context) => {
123394
+ const provenance = getApiReferenceProvenance(node.callee, context.scopes);
123395
+ if (!provenance || !isThreeModuleSource$1(provenance.moduleSource) || !SHADER_MATERIAL_CONSTRUCTORS.has(provenance.apiName)) return null;
123396
+ const options = node.arguments[0];
123397
+ if (!options || isNodeOfType(options, "SpreadElement")) return null;
123398
+ const optionsObject = resolveStableOptionsObject(options, STATIC_SHADER_MATERIAL_PROPERTY_NAMES, context.scopes, node);
123399
+ if (!optionsObject) return null;
123400
+ const unresolvedPropertyNames = new Set(STATIC_SHADER_MATERIAL_PROPERTY_NAMES);
123401
+ const properties = /* @__PURE__ */ new Map();
123402
+ for (let propertyIndex = optionsObject.properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
123403
+ const property = optionsObject.properties[propertyIndex];
123404
+ if (!property || isNodeOfType(property, "SpreadElement")) return null;
123405
+ if (!isNodeOfType(property, "Property")) continue;
123406
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
123407
+ if (!propertyName) return null;
123408
+ if (!unresolvedPropertyNames.has(propertyName)) continue;
123409
+ unresolvedPropertyNames.delete(propertyName);
123410
+ if (property.kind === "init" && !property.method) properties.set(propertyName, property.value);
123411
+ }
123412
+ const fragmentShaderExpression = properties.get("fragmentShader");
123413
+ const vertexShaderExpression = properties.get("vertexShader");
123414
+ return {
123415
+ constructorName: provenance.apiName,
123416
+ constructorNode: node,
123417
+ fragmentShader: resolveShaderStage(fragmentShaderExpression, "fragment", context),
123418
+ optionsObject,
123419
+ properties,
123420
+ vertexShader: resolveShaderStage(vertexShaderExpression, "vertex", context)
123421
+ };
123422
+ };
123423
+ //#endregion
123424
+ //#region src/plugin/rules/r3f/three-raw-shader-require-fragment-float-precision.ts
123425
+ const FLOAT_TYPE_NAME_PATTERN = /^(?:float|vec[234]|mat[234](?:x[234])?)$/;
123426
+ const PRECISION_QUALIFIER_NAMES$1 = new Set([
123427
+ "highp",
123428
+ "lowp",
123429
+ "mediump"
123430
+ ]);
123431
+ const hasPrecisionQualifier = (qualifiers) => Boolean(qualifiers?.some((qualifier) => qualifier.type === "keyword" && PRECISION_QUALIFIER_NAMES$1.has(qualifier.token)));
123432
+ const threeRawShaderRequireFragmentFloatPrecision = defineRule({
123433
+ id: "three-raw-shader-require-fragment-float-precision",
123434
+ title: "Raw fragment shader lacks float precision",
123435
+ category: "Correctness",
123436
+ severity: "error",
123437
+ recommendation: "Declare a default fragment float precision or qualify every floating-point declaration",
123438
+ create: (context) => ({ NewExpression(node) {
123439
+ const material = resolveStaticThreeShaderMaterial(node, context);
123440
+ if (material?.constructorName !== "RawShaderMaterial" || !material.fragmentShader) return;
123441
+ let hasDefaultFloatPrecision = false;
123442
+ const unqualifiedFloatNodes = [];
123443
+ visit(material.fragmentShader.program, {
123444
+ precision: { enter: ({ node: precision }) => {
123445
+ if (getGlslTypeSpecifierName(precision.specifier) === "float") hasDefaultFloatPrecision = true;
123446
+ } },
123447
+ fully_specified_type: { enter: ({ node: specifiedType }) => {
123448
+ const typeName = getGlslTypeSpecifierName(specifiedType.specifier);
123449
+ if (unqualifiedFloatNodes.length === 0 && !hasDefaultFloatPrecision && typeName && FLOAT_TYPE_NAME_PATTERN.test(typeName) && !hasPrecisionQualifier(specifiedType.qualifiers)) unqualifiedFloatNodes.push(specifiedType);
123450
+ } },
123451
+ parameter_declaration: { enter: ({ node: parameter }) => {
123452
+ const typeName = getGlslTypeSpecifierName(parameter.specifier);
123453
+ if (unqualifiedFloatNodes.length === 0 && !hasDefaultFloatPrecision && typeName && FLOAT_TYPE_NAME_PATTERN.test(typeName) && !hasPrecisionQualifier(parameter.qualifier)) unqualifiedFloatNodes.push(parameter);
123454
+ } }
123455
+ });
123456
+ const firstUnqualifiedFloatNode = unqualifiedFloatNodes[0];
123457
+ if (!firstUnqualifiedFloatNode) return;
123458
+ context.report({
123459
+ node: material.fragmentShader.source.getOriginNodeAtOffset(firstUnqualifiedFloatNode.location?.start.offset ?? 0),
123460
+ message: "RawShaderMaterial does not receive Three.js precision declarations, and fragment GLSL has no default float precision for this unqualified declaration"
123461
+ });
123462
+ } })
123463
+ });
123464
+ //#endregion
123465
+ //#region src/plugin/rules/r3f/utils/get-glsl-numeric-constant.ts
123466
+ const getGlslNumericConstant = (node) => {
123467
+ let value = null;
123468
+ if (node.type === "float_constant") value = Number(node.token.replace(/(?:lf|LF|f|F)$/, ""));
123469
+ else if (node.type === "double_constant") value = Number(node.token.replace(/(?:lf|LF)$/, ""));
123470
+ else if (node.type === "uint_constant") value = Number(node.token.replace(/[uU]$/, ""));
123471
+ else if (node.type === "int_constant") value = Number(node.token);
123472
+ else if (node.type === "group") value = getGlslNumericConstant(node.expression);
123473
+ else if (node.type === "unary" && (node.operator.literal === "+" || node.operator.literal === "-")) {
123474
+ const operand = getGlslNumericConstant(node.expression);
123475
+ if (operand !== null) value = node.operator.literal === "-" ? -operand : operand;
123476
+ }
123477
+ return value !== null && Number.isFinite(value) ? value : null;
123478
+ };
123479
+ //#endregion
123480
+ //#region src/plugin/rules/r3f/utils/collect-glsl-global-declarations.ts
123481
+ const GLSL_INTERPOLATION_QUALIFIER_NAMES = new Set([
123482
+ "centroid",
123483
+ "flat",
123484
+ "noperspective",
123485
+ "sample",
123486
+ "smooth"
123487
+ ]);
123488
+ const GLSL_PRIMARY_INTERPOLATION_QUALIFIER_NAMES = new Set([
123489
+ "flat",
123490
+ "noperspective",
123491
+ "smooth"
123492
+ ]);
123493
+ const getArraySize = (declaration, typeQuantifiers) => {
123494
+ const quantifiers = [...Array.isArray(typeQuantifiers) ? typeQuantifiers : [], ...Array.isArray(declaration.quantifier) ? declaration.quantifier : []];
123495
+ if (quantifiers.length === 0) return null;
123496
+ if (quantifiers.length !== 1) return void 0;
123497
+ return getGlslNumericConstant(quantifiers[0].expression) ?? void 0;
123498
+ };
123499
+ const getQualifierName = (qualifier) => qualifier.type === "keyword" ? qualifier.token : null;
123500
+ const getInterpolation = (qualifiers) => {
123501
+ const interpolationQualifiers = [...qualifiers].filter((qualifier) => GLSL_INTERPOLATION_QUALIFIER_NAMES.has(qualifier));
123502
+ if (!interpolationQualifiers.some((qualifier) => GLSL_PRIMARY_INTERPOLATION_QUALIFIER_NAMES.has(qualifier))) interpolationQualifiers.push("smooth");
123503
+ return interpolationQualifiers.sort().join(" ");
123504
+ };
123505
+ const collectGlslGlobalDeclarations = (program) => {
123506
+ const declarations = [];
123507
+ const globalBindings = program.scopes[0]?.bindings;
123508
+ for (const statement of program.program) {
123509
+ if (statement.type !== "declaration_statement" || statement.declaration.type !== "declarator_list") continue;
123510
+ const declaratorList = statement.declaration;
123511
+ const typeName = getGlslTypeSpecifierName(declaratorList.specified_type.specifier);
123512
+ if (!typeName) continue;
123513
+ const qualifierNodes = declaratorList.specified_type.qualifiers ?? [];
123514
+ const qualifiers = /* @__PURE__ */ new Set();
123515
+ let hasLayoutQualifier = false;
123516
+ for (const qualifier of qualifierNodes) {
123517
+ if (qualifier.type === "layout_qualifier") {
123518
+ hasLayoutQualifier = true;
123519
+ continue;
123520
+ }
123521
+ const qualifierName = getQualifierName(qualifier);
123522
+ if (qualifierName) qualifiers.add(qualifierName);
123523
+ }
123524
+ for (const declaration of declaratorList.declarations) {
123525
+ const name = declaration.identifier.identifier;
123526
+ const binding = globalBindings?.[name];
123527
+ declarations.push({
123528
+ arraySize: getArraySize(declaration, declaratorList.specified_type.specifier.quantifier),
123529
+ hasLayoutQualifier,
123530
+ interpolation: getInterpolation(qualifiers),
123531
+ isStaticallyUsed: Boolean(binding?.references.some((reference) => reference !== binding.declaration)),
123532
+ name,
123533
+ node: declaration,
123534
+ qualifiers,
123535
+ typeName
123536
+ });
123537
+ }
123538
+ }
123539
+ return declarations;
123540
+ };
123541
+ //#endregion
123542
+ //#region src/plugin/rules/r3f/utils/get-glsl-function-call-name.ts
123543
+ const getGlslFunctionCallName = (node) => {
123544
+ if (node.identifier.type === "identifier") return node.identifier.identifier;
123545
+ if (node.identifier.type !== "type_specifier") return null;
123546
+ const specifier = node.identifier.specifier;
123547
+ if (specifier.type === "keyword") return specifier.token;
123548
+ return specifier.type === "identifier" || specifier.type === "type_name" ? specifier.identifier : null;
123549
+ };
123550
+ //#endregion
123551
+ //#region src/plugin/rules/r3f/utils/has-glsl-function-declaration.ts
123552
+ const hasGlslFunctionDeclaration = (program, functionName) => {
123553
+ const overloads = program.scopes[0]?.functions[functionName];
123554
+ return Boolean(overloads && Object.values(overloads).some((overload) => Boolean(overload.declaration)));
123555
+ };
123556
+ //#endregion
123557
+ //#region src/plugin/rules/r3f/utils/has-glsl-function-like-macro.ts
123558
+ const hasGlslFunctionLikeMacro = (source, functionName) => new RegExp(`^[ \\t]*#[ \\t]*define[ \\t]+${functionName}(?:[ \\t]|\\()`, "m").test(maskGlslComments(source));
123559
+ //#endregion
123560
+ //#region src/plugin/rules/r3f/three-raw-shader-require-glsl3-version.ts
123561
+ const GLSL3_ONLY_FUNCTION_NAMES = new Set([
123562
+ "acosh",
123563
+ "asinh",
123564
+ "atanh",
123565
+ "bitCount",
123566
+ "bitfieldExtract",
123567
+ "bitfieldInsert",
123568
+ "bitfieldReverse",
123569
+ "cosh",
123570
+ "determinant",
123571
+ "findLSB",
123572
+ "findMSB",
123573
+ "floatBitsToInt",
123574
+ "floatBitsToUint",
123575
+ "frexp",
123576
+ "imulExtended",
123577
+ "intBitsToFloat",
123578
+ "interpolateAtCentroid",
123579
+ "interpolateAtOffset",
123580
+ "interpolateAtSample",
123581
+ "inverse",
123582
+ "isinf",
123583
+ "isnan",
123584
+ "ldexp",
123585
+ "modf",
123586
+ "outerProduct",
123587
+ "packHalf2x16",
123588
+ "packSnorm2x16",
123589
+ "packUnorm2x16",
123590
+ "round",
123591
+ "roundEven",
123592
+ "sinh",
123593
+ "tanh",
123594
+ "texelFetch",
123595
+ "texelFetchOffset",
123596
+ "texture",
123597
+ "textureGrad",
123598
+ "textureGradOffset",
123599
+ "textureLod",
123600
+ "textureLodOffset",
123601
+ "textureOffset",
123602
+ "textureProj",
123603
+ "textureProjGrad",
123604
+ "textureProjGradOffset",
123605
+ "textureProjLod",
123606
+ "textureProjLodOffset",
123607
+ "textureProjOffset",
123608
+ "textureSize",
123609
+ "transpose",
123610
+ "trunc",
123611
+ "uaddCarry",
123612
+ "uintBitsToFloat",
123613
+ "umulExtended",
123614
+ "unpackHalf2x16",
123615
+ "unpackSnorm2x16",
123616
+ "unpackUnorm2x16",
123617
+ "usubBorrow"
123618
+ ]);
123619
+ const GLSL3_ONLY_TYPE_NAME_PATTERN = /^(?:uint|uvec[234]|mat[234]x[234]|sampler3D|sampler2DArray|[iu]sampler(?:2D|3D|Cube|2DArray))$/;
123620
+ const GLSL3_ONLY_CONSTRUCTOR_NAME_PATTERN = /^(?:uint|uvec[234]|mat[234]x[234])$/;
123621
+ const shaderUsesGlsl3Syntax = (shader) => {
123622
+ if (collectGlslGlobalDeclarations(shader.program).some((declaration) => declaration.hasLayoutQualifier || declaration.qualifiers.has("in") || declaration.qualifiers.has("out") || GLSL3_ONLY_TYPE_NAME_PATTERN.test(declaration.typeName))) return true;
123623
+ let usesGlsl3Syntax = false;
123624
+ visit(shader.program, {
123625
+ binary: { enter: ({ node }) => {
123626
+ if (node.operator.literal === "<<" || node.operator.literal === ">>" || node.operator.literal === "&" || node.operator.literal === "|" || node.operator.literal === "^") usesGlsl3Syntax = true;
123627
+ } },
123628
+ assignment: { enter: ({ node }) => {
123629
+ const operatorLiteral = String(node.operator.literal);
123630
+ if (operatorLiteral === "<<=" || operatorLiteral === ">>=" || operatorLiteral === "&=" || operatorLiteral === "|=" || operatorLiteral === "^=") usesGlsl3Syntax = true;
123631
+ } },
123632
+ function_call: { enter: ({ node }) => {
123633
+ const functionName = getGlslFunctionCallName(node);
123634
+ if (functionName && (GLSL3_ONLY_FUNCTION_NAMES.has(functionName) || GLSL3_ONLY_CONSTRUCTOR_NAME_PATTERN.test(functionName)) && !hasGlslFunctionLikeMacro(shader.source.text, functionName) && !hasGlslFunctionDeclaration(shader.program, functionName)) usesGlsl3Syntax = true;
123635
+ } },
123636
+ switch_statement: { enter: () => {
123637
+ usesGlsl3Syntax = true;
123638
+ } }
123639
+ });
123640
+ return usesGlsl3Syntax;
123641
+ };
123642
+ const getGlsl3Configuration = (expression, context) => {
123643
+ if (!expression) return false;
123644
+ if (isNodeOfType(expression, "Literal")) return expression.value === "300 es";
123645
+ const provenance = getApiReferenceProvenance(expression, context.scopes);
123646
+ if (!provenance || !isThreeModuleSource$1(provenance.moduleSource)) return null;
123647
+ return provenance.apiName === "GLSL3";
123648
+ };
123649
+ const threeRawShaderRequireGlsl3Version = defineRule({
123650
+ id: "three-raw-shader-require-glsl3-version",
123651
+ title: "Raw shader uses GLSL 3 syntax without GLSL3",
123652
+ category: "Correctness",
123653
+ severity: "error",
123654
+ recommendation: "Set glslVersion to THREE.GLSL3 when RawShaderMaterial uses GLSL 3 syntax",
123655
+ create: (context) => ({ NewExpression(node) {
123656
+ const material = resolveStaticThreeShaderMaterial(node, context);
123657
+ if (material?.constructorName !== "RawShaderMaterial") return;
123658
+ if (getGlsl3Configuration(material.properties.get("glslVersion"), context) !== false) return;
123659
+ const incompatibleShader = [material.vertexShader, material.fragmentShader].find((shader) => Boolean(shader && shaderUsesGlsl3Syntax(shader)));
123660
+ if (!incompatibleShader) return;
123661
+ context.report({
123662
+ node: incompatibleShader.expression,
123663
+ message: "This RawShaderMaterial uses GLSL 3-only syntax but does not set glslVersion to THREE.GLSL3, so Three.js compiles it as GLSL 1"
123664
+ });
123665
+ } })
123666
+ });
123667
+ //#endregion
122897
123668
  //#region src/plugin/rules/r3f/three-require-animation-mixer-cleanup.ts
122898
123669
  const MIXER_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
122899
123670
  const FINE_GRAINED_UNCACHE_METHOD_NAMES = ["uncacheAction", "uncacheClip"];
@@ -123651,6 +124422,917 @@ const threeRequireRendererCleanup = defineRule({
123651
124422
  } })
123652
124423
  });
123653
124424
  //#endregion
124425
+ //#region src/plugin/rules/r3f/three-shader-no-constant-out-of-bounds-index.ts
124426
+ const getTypeIndexElementCounts = (typeName) => {
124427
+ const vectorMatch = /^[biud]?vec([234])$/.exec(typeName);
124428
+ if (vectorMatch) return [Number(vectorMatch[1])];
124429
+ const floatingPointMatrixMatch = /^mat([234])(?:x([234]))?$/.exec(typeName);
124430
+ return floatingPointMatrixMatch ? [Number(floatingPointMatrixMatch[1]), Number(floatingPointMatrixMatch[2] ?? floatingPointMatrixMatch[1])] : [];
124431
+ };
124432
+ const getDeclarationIndexElementCounts = (declaration) => {
124433
+ const typeElementCounts = getTypeIndexElementCounts(declaration.typeName);
124434
+ if (declaration.arraySize === null) return typeElementCounts;
124435
+ return [typeof declaration.arraySize === "number" ? declaration.arraySize : null, ...typeElementCounts];
124436
+ };
124437
+ const getIndexQuantifiers = (postfix) => {
124438
+ const quantifiers = [];
124439
+ let currentPostfix = postfix;
124440
+ while (currentPostfix.type === "postfix") {
124441
+ if (currentPostfix.expression.type !== "quantifier") return quantifiers.length > 0 ? quantifiers : null;
124442
+ quantifiers.push(currentPostfix.expression);
124443
+ currentPostfix = currentPostfix.postfix;
124444
+ }
124445
+ if (currentPostfix.type === "quantifier") quantifiers.push(currentPostfix);
124446
+ return quantifiers.length > 0 ? quantifiers : null;
124447
+ };
124448
+ const checkShader$10 = (shader, context) => {
124449
+ const declarationsByName = new Map(collectGlslGlobalDeclarations(shader.program).map((declaration) => [declaration.name, declaration]));
124450
+ const globalBindings = shader.program.scopes[0]?.bindings;
124451
+ visit(shader.program, { postfix: { enter: ({ node }) => {
124452
+ if (node.expression.type !== "identifier") return;
124453
+ const quantifiers = getIndexQuantifiers(node.postfix);
124454
+ if (!quantifiers) return;
124455
+ const declaration = declarationsByName.get(node.expression.identifier);
124456
+ const binding = globalBindings?.[node.expression.identifier];
124457
+ if (!declaration || !binding?.references.includes(node.expression)) return;
124458
+ const elementCounts = getDeclarationIndexElementCounts(declaration);
124459
+ for (const [quantifierIndex, quantifier] of quantifiers.entries()) {
124460
+ const elementCount = elementCounts[quantifierIndex];
124461
+ const index = getGlslNumericConstant(quantifier.expression);
124462
+ if (elementCount === null || elementCount === void 0 || index === null || !Number.isInteger(index) || index >= 0 && index < elementCount) continue;
124463
+ context.report({
124464
+ node: shader.source.getOriginNodeAtOffset(quantifier.location?.start.offset ?? 0),
124465
+ message: `The constant index ${index} is outside ${declaration.name}'s valid range 0–${elementCount - 1}, which is a GLSL compile error or undefined access`
124466
+ });
124467
+ }
124468
+ } } });
124469
+ };
124470
+ const threeShaderNoConstantOutOfBoundsIndex = defineRule({
124471
+ id: "three-shader-no-constant-out-of-bounds-index",
124472
+ title: "Shader uses a constant out-of-bounds index",
124473
+ category: "Correctness",
124474
+ severity: "error",
124475
+ recommendation: "Keep constant array, vector, and matrix indices within their declared bounds",
124476
+ create: (context) => ({ NewExpression(node) {
124477
+ const material = resolveStaticThreeShaderMaterial(node, context);
124478
+ if (!material) return;
124479
+ if (material.fragmentShader) checkShader$10(material.fragmentShader, context);
124480
+ if (material.vertexShader) checkShader$10(material.vertexShader, context);
124481
+ } })
124482
+ });
124483
+ //#endregion
124484
+ //#region src/plugin/rules/r3f/utils/does-glsl-expression-depend-on-fragment-input.ts
124485
+ const FRAGMENT_INPUT_BUILTIN_NAMES = new Set([
124486
+ "gl_FragCoord",
124487
+ "gl_FrontFacing",
124488
+ "gl_HelperInvocation",
124489
+ "gl_Layer",
124490
+ "gl_PointCoord",
124491
+ "gl_PrimitiveID",
124492
+ "gl_SampleID",
124493
+ "gl_SampleMaskIn",
124494
+ "gl_SamplePosition",
124495
+ "gl_ViewportIndex"
124496
+ ]);
124497
+ const doesGlslExpressionDependOnFragmentInput = (expression, fragmentInputReferences) => {
124498
+ let dependsOnFragmentInput = false;
124499
+ visit(expression, { identifier: { enter: (path) => {
124500
+ if (dependsOnFragmentInput || path.parent?.type === "field_selection" || path.parent?.type === "function_call" && path.key === "identifier" || path.parent?.type === "type_specifier") return;
124501
+ dependsOnFragmentInput = fragmentInputReferences.has(path.node) || FRAGMENT_INPUT_BUILTIN_NAMES.has(path.node.identifier);
124502
+ } } });
124503
+ return dependsOnFragmentInput;
124504
+ };
124505
+ //#endregion
124506
+ //#region src/plugin/rules/r3f/three-shader-no-derivatives-in-nonuniform-flow.ts
124507
+ const DERIVATIVE_FUNCTION_NAMES = new Set([
124508
+ "dFdx",
124509
+ "dFdy",
124510
+ "fwidth"
124511
+ ]);
124512
+ const IMPLICIT_DERIVATIVE_TEXTURE_FUNCTION_NAMES = new Set([
124513
+ "texture",
124514
+ "texture2D",
124515
+ "texture2DProj",
124516
+ "texture3D",
124517
+ "texture3DProj",
124518
+ "textureCube",
124519
+ "textureOffset",
124520
+ "textureProj",
124521
+ "textureProjOffset"
124522
+ ]);
124523
+ const getControllingExpressions = (path) => {
124524
+ const controllingExpressions = [];
124525
+ let currentPath = path;
124526
+ while (currentPath?.parentPath) {
124527
+ const parentPath = currentPath.parentPath;
124528
+ const parent = parentPath.node;
124529
+ if (parent.type === "function") break;
124530
+ const alternateNodes = parent.type === "if_statement" ? Reflect.get(parent, "else") : null;
124531
+ const isInsideAlternate = currentPath.key === "else" || Array.isArray(alternateNodes) && alternateNodes.includes(currentPath.node);
124532
+ if (parent.type === "if_statement" && (currentPath.key === "body" || isInsideAlternate)) controllingExpressions.push(parent.condition);
124533
+ if (parent.type === "while_statement" && currentPath.key === "body") controllingExpressions.push(parent.condition);
124534
+ if (parent.type === "do_statement" && currentPath.key === "body") controllingExpressions.push(parent.expression);
124535
+ if (parent.type === "for_statement" && currentPath.key === "body" && parent.condition) controllingExpressions.push(...[
124536
+ parent.init,
124537
+ parent.condition,
124538
+ parent.operation
124539
+ ].filter(Boolean));
124540
+ if (parent.type === "ternary" && (currentPath.key === "left" || currentPath.key === "right")) controllingExpressions.push(parent.expression);
124541
+ if (parent.type === "binary" && currentPath.key === "right" && (parent.operator.literal === "&&" || parent.operator.literal === "||")) controllingExpressions.push(parent.left);
124542
+ if (parent.type === "switch_statement" && currentPath.key === "cases") controllingExpressions.push(parent.expression);
124543
+ currentPath = parentPath;
124544
+ }
124545
+ return controllingExpressions;
124546
+ };
124547
+ const checkShader$9 = (shader, context) => {
124548
+ const globalBindings = shader.program.scopes[0]?.bindings;
124549
+ const fragmentInputReferences = /* @__PURE__ */ new Set();
124550
+ for (const declaration of collectGlslGlobalDeclarations(shader.program)) {
124551
+ if (!declaration.qualifiers.has("in") && !declaration.qualifiers.has("varying")) continue;
124552
+ const binding = globalBindings?.[declaration.name];
124553
+ if (!binding) continue;
124554
+ for (const reference of binding.references) if (reference !== binding.declaration) fragmentInputReferences.add(reference);
124555
+ }
124556
+ visit(shader.program, { function_call: { enter: (path) => {
124557
+ const functionName = getGlslFunctionCallName(path.node);
124558
+ if (!functionName || !DERIVATIVE_FUNCTION_NAMES.has(functionName) && !IMPLICIT_DERIVATIVE_TEXTURE_FUNCTION_NAMES.has(functionName) || hasGlslFunctionLikeMacro(shader.source.text, functionName) || hasGlslFunctionDeclaration(shader.program, functionName)) return;
124559
+ if (!getControllingExpressions(path).some((controllingExpression) => doesGlslExpressionDependOnFragmentInput(controllingExpression, fragmentInputReferences))) return;
124560
+ context.report({
124561
+ node: shader.source.getOriginNodeAtOffset(path.node.location?.start.offset ?? 0),
124562
+ message: `${functionName} executes only on fragment-input-dependent lanes, so implicit derivatives are undefined. Move it before the divergent branch or use explicit gradients`
124563
+ });
124564
+ } } });
124565
+ };
124566
+ const threeShaderNoDerivativesInNonuniformFlow = defineRule({
124567
+ id: "three-shader-no-derivatives-in-nonuniform-flow",
124568
+ title: "Shader derivative runs in non-uniform control flow",
124569
+ category: "Correctness",
124570
+ severity: "error",
124571
+ recommendation: "Evaluate derivatives and implicit-LOD texture samples before fragment-input-dependent branches",
124572
+ create: (context) => ({ NewExpression(node) {
124573
+ const material = resolveStaticThreeShaderMaterial(node, context);
124574
+ if (material?.fragmentShader) checkShader$9(material.fragmentShader, context);
124575
+ } })
124576
+ });
124577
+ //#endregion
124578
+ //#region src/plugin/rules/r3f/utils/get-glsl-function-call-arguments.ts
124579
+ const getGlslFunctionCallArguments = (node) => node.args.filter((argument) => argument.type !== "literal" || argument.literal !== ",");
124580
+ //#endregion
124581
+ //#region src/plugin/rules/r3f/three-shader-no-invalid-clamp-bounds.ts
124582
+ const checkShader$8 = (shader, context) => {
124583
+ if (hasGlslFunctionLikeMacro(shader.source.text, "clamp") || hasGlslFunctionDeclaration(shader.program, "clamp")) return;
124584
+ visit(shader.program, { function_call: { enter: ({ node }) => {
124585
+ if (getGlslFunctionCallName(node) !== "clamp") return;
124586
+ const argumentsWithoutSeparators = getGlslFunctionCallArguments(node);
124587
+ if (argumentsWithoutSeparators.length !== 3) return;
124588
+ const minimum = getGlslNumericConstant(argumentsWithoutSeparators[1]);
124589
+ const maximum = getGlslNumericConstant(argumentsWithoutSeparators[2]);
124590
+ if (minimum === null || maximum === null || minimum <= maximum) return;
124591
+ context.report({
124592
+ node: shader.source.getOriginNodeAtOffset(node.location?.start.offset ?? 0),
124593
+ message: `GLSL clamp requires minVal not to exceed maxVal, but this call uses ${minimum} and ${maximum}, so its result is undefined`
124594
+ });
124595
+ } } });
124596
+ };
124597
+ const threeShaderNoInvalidClampBounds = defineRule({
124598
+ id: "three-shader-no-invalid-clamp-bounds",
124599
+ title: "Shader uses invalid clamp bounds",
124600
+ category: "Correctness",
124601
+ severity: "error",
124602
+ recommendation: "Keep clamp minVal less than or equal to maxVal",
124603
+ create: (context) => ({ NewExpression(node) {
124604
+ const material = resolveStaticThreeShaderMaterial(node, context);
124605
+ if (!material) return;
124606
+ if (material.fragmentShader) checkShader$8(material.fragmentShader, context);
124607
+ if (material.vertexShader) checkShader$8(material.vertexShader, context);
124608
+ } })
124609
+ });
124610
+ //#endregion
124611
+ //#region src/plugin/rules/r3f/three-shader-no-invalid-constant-bit-operations.ts
124612
+ const getInvalidBitfieldMessage = (node, shader) => {
124613
+ const functionName = getGlslFunctionCallName(node);
124614
+ if (functionName !== "bitfieldExtract" && functionName !== "bitfieldInsert" || hasGlslFunctionLikeMacro(shader.source.text, functionName) || hasGlslFunctionDeclaration(shader.program, functionName)) return null;
124615
+ const callArguments = getGlslFunctionCallArguments(node);
124616
+ const offsetArgumentIndex = functionName === "bitfieldExtract" ? 1 : 2;
124617
+ const bitsArgumentIndex = offsetArgumentIndex + 1;
124618
+ const offset = callArguments[offsetArgumentIndex] ? getGlslNumericConstant(callArguments[offsetArgumentIndex]) : null;
124619
+ const bits = callArguments[bitsArgumentIndex] ? getGlslNumericConstant(callArguments[bitsArgumentIndex]) : null;
124620
+ if (offset === null || bits === null || offset >= 0 && bits >= 0 && offset + bits <= 32) return null;
124621
+ return `GLSL ${functionName} uses offset ${offset} and width ${bits}, outside a 32-bit integer`;
124622
+ };
124623
+ const reportInvalidShift = (right, locationOffset, shader, context) => {
124624
+ const shiftCount = getGlslNumericConstant(right);
124625
+ if (shiftCount === null || shiftCount >= 0 && shiftCount < 32) return;
124626
+ context.report({
124627
+ node: shader.source.getOriginNodeAtOffset(locationOffset ?? 0),
124628
+ message: `GLSL shift count ${shiftCount} is outside the valid range 0–31`
124629
+ });
124630
+ };
124631
+ const checkShader$7 = (shader, context) => {
124632
+ visit(shader.program, {
124633
+ binary: { enter: ({ node }) => {
124634
+ if (node.operator.literal !== "<<" && node.operator.literal !== ">>") return;
124635
+ reportInvalidShift(node.right, node.location?.start.offset, shader, context);
124636
+ } },
124637
+ assignment: { enter: ({ node }) => {
124638
+ const operatorLiteral = String(node.operator.literal);
124639
+ if (operatorLiteral !== "<<=" && operatorLiteral !== ">>=") return;
124640
+ reportInvalidShift(node.right, node.location?.start.offset, shader, context);
124641
+ } },
124642
+ function_call: { enter: ({ node }) => {
124643
+ const message = getInvalidBitfieldMessage(node, shader);
124644
+ if (!message) return;
124645
+ context.report({
124646
+ node: shader.source.getOriginNodeAtOffset(node.location?.start.offset ?? 0),
124647
+ message
124648
+ });
124649
+ } }
124650
+ });
124651
+ };
124652
+ const threeShaderNoInvalidConstantBitOperations = defineRule({
124653
+ id: "three-shader-no-invalid-constant-bit-operations",
124654
+ title: "Shader uses invalid constant bit operations",
124655
+ category: "Correctness",
124656
+ severity: "error",
124657
+ recommendation: "Keep shift counts and bitfield ranges within the integer bit width",
124658
+ create: (context) => ({ NewExpression(node) {
124659
+ const material = resolveStaticThreeShaderMaterial(node, context);
124660
+ if (!material) return;
124661
+ if (material.fragmentShader) checkShader$7(material.fragmentShader, context);
124662
+ if (material.vertexShader) checkShader$7(material.vertexShader, context);
124663
+ } })
124664
+ });
124665
+ //#endregion
124666
+ //#region src/plugin/rules/r3f/three-shader-no-invalid-constant-math.ts
124667
+ const INVALID_NONPOSITIVE_FUNCTIONS = new Set([
124668
+ "inversesqrt",
124669
+ "log",
124670
+ "log2"
124671
+ ]);
124672
+ const getInvalidFunctionCallMessage = (node, shader) => {
124673
+ const functionName = getGlslFunctionCallName(node);
124674
+ if (!functionName || hasGlslFunctionLikeMacro(shader.source.text, functionName) || hasGlslFunctionDeclaration(shader.program, functionName)) return null;
124675
+ const callArguments = getGlslFunctionCallArguments(node);
124676
+ const firstArgument = callArguments[0];
124677
+ if (!firstArgument) return null;
124678
+ const firstValue = getGlslNumericConstant(firstArgument);
124679
+ if (functionName === "asin" || functionName === "acos") return firstValue !== null && Math.abs(firstValue) > 1 ? `GLSL ${functionName} is undefined outside the range -1 to 1, but received ${firstValue}` : null;
124680
+ if (functionName === "atan" && callArguments.length === 2) {
124681
+ const secondValue = getGlslNumericConstant(callArguments[1]);
124682
+ return firstValue === 0 && secondValue === 0 ? "GLSL atan is undefined when both arguments are zero" : null;
124683
+ }
124684
+ if (functionName === "acosh") return firstValue !== null && firstValue < 1 ? `GLSL acosh is undefined below 1, but received ${firstValue}` : null;
124685
+ if (functionName === "atanh") return firstValue !== null && Math.abs(firstValue) >= 1 ? `GLSL atanh is undefined outside the range -1 to 1, but received ${firstValue}` : null;
124686
+ if (functionName === "ldexp" && callArguments.length === 2) {
124687
+ const exponent = getGlslNumericConstant(callArguments[1]);
124688
+ return exponent !== null && exponent > 128 ? `GLSL ldexp is undefined above exponent 128, but received ${exponent}` : null;
124689
+ }
124690
+ if (functionName === "pow") {
124691
+ if (firstValue !== null && firstValue < 0) return `GLSL pow is undefined for the negative base ${firstValue}`;
124692
+ const exponent = callArguments[1] ? getGlslNumericConstant(callArguments[1]) : null;
124693
+ return firstValue === 0 && exponent !== null && exponent <= 0 ? `GLSL pow is undefined for a zero base and the nonpositive exponent ${exponent}` : null;
124694
+ }
124695
+ if (functionName === "sqrt") return firstValue !== null && firstValue < 0 ? `GLSL sqrt is undefined for the negative argument ${firstValue}` : null;
124696
+ if (INVALID_NONPOSITIVE_FUNCTIONS.has(functionName)) return firstValue !== null && firstValue <= 0 ? `GLSL ${functionName} is undefined for the nonpositive argument ${firstValue}` : null;
124697
+ if (functionName !== "mod" || callArguments.length !== 2) return null;
124698
+ return getGlslNumericConstant(callArguments[1]) === 0 ? "GLSL mod is undefined with a zero divisor" : null;
124699
+ };
124700
+ const reportConstantZeroDivisor = (operator, right, locationOffset, shader, context) => {
124701
+ if (getGlslNumericConstant(right) !== 0) return;
124702
+ context.report({
124703
+ node: shader.source.getOriginNodeAtOffset(locationOffset ?? 0),
124704
+ message: `GLSL ${operator.startsWith("/") ? "division" : "remainder"} by a constant zero has undefined results`
124705
+ });
124706
+ };
124707
+ const checkShader$6 = (shader, context) => {
124708
+ visit(shader.program, {
124709
+ binary: { enter: ({ node }) => {
124710
+ if (node.operator.literal !== "/" && node.operator.literal !== "%") return;
124711
+ reportConstantZeroDivisor(node.operator.literal, node.right, node.location?.start.offset, shader, context);
124712
+ } },
124713
+ assignment: { enter: ({ node }) => {
124714
+ if (node.operator.literal !== "/=" && node.operator.literal !== "%=") return;
124715
+ reportConstantZeroDivisor(node.operator.literal, node.right, node.location?.start.offset, shader, context);
124716
+ } },
124717
+ function_call: { enter: ({ node }) => {
124718
+ const message = getInvalidFunctionCallMessage(node, shader);
124719
+ if (!message) return;
124720
+ context.report({
124721
+ node: shader.source.getOriginNodeAtOffset(node.location?.start.offset ?? 0),
124722
+ message
124723
+ });
124724
+ } }
124725
+ });
124726
+ };
124727
+ const threeShaderNoInvalidConstantMath = defineRule({
124728
+ id: "three-shader-no-invalid-constant-math",
124729
+ title: "Shader uses invalid constant math",
124730
+ category: "Correctness",
124731
+ severity: "error",
124732
+ recommendation: "Keep constant arguments within the defined GLSL builtin and operator domains",
124733
+ create: (context) => ({ NewExpression(node) {
124734
+ const material = resolveStaticThreeShaderMaterial(node, context);
124735
+ if (!material) return;
124736
+ if (material.fragmentShader) checkShader$6(material.fragmentShader, context);
124737
+ if (material.vertexShader) checkShader$6(material.vertexShader, context);
124738
+ } })
124739
+ });
124740
+ //#endregion
124741
+ //#region src/plugin/rules/r3f/three-shader-no-invalid-smoothstep-edges.ts
124742
+ const checkShaderSource = (shader, context) => {
124743
+ if (hasGlslFunctionLikeMacro(shader.source.text, "smoothstep") || hasGlslFunctionDeclaration(shader.program, "smoothstep")) return;
124744
+ visit(shader.program, { function_call: { enter: ({ node }) => {
124745
+ if (getGlslFunctionCallName(node) !== "smoothstep") return;
124746
+ const argumentsWithoutSeparators = getGlslFunctionCallArguments(node);
124747
+ if (argumentsWithoutSeparators.length !== 3) return;
124748
+ const firstEdge = getGlslNumericConstant(argumentsWithoutSeparators[0]);
124749
+ const secondEdge = getGlslNumericConstant(argumentsWithoutSeparators[1]);
124750
+ if (firstEdge === null || secondEdge === null || firstEdge < secondEdge) return;
124751
+ const shaderOffset = node.location?.start.offset ?? 0;
124752
+ context.report({
124753
+ node: shader.source.getOriginNodeAtOffset(shaderOffset),
124754
+ message: `GLSL smoothstep requires edge0 to be less than edge1, but this call uses ${firstEdge} and ${secondEdge}, so its result is undefined`
124755
+ });
124756
+ } } });
124757
+ };
124758
+ const threeShaderNoInvalidSmoothstepEdges = defineRule({
124759
+ id: "three-shader-no-invalid-smoothstep-edges",
124760
+ title: "Shader uses invalid smoothstep edges",
124761
+ category: "Correctness",
124762
+ severity: "error",
124763
+ recommendation: "Keep smoothstep edge0 below edge1 and invert the result when a descending transition is needed",
124764
+ create: (context) => ({ NewExpression(node) {
124765
+ const material = resolveStaticThreeShaderMaterial(node, context);
124766
+ if (!material) return;
124767
+ if (material.fragmentShader) checkShaderSource(material.fragmentShader, context);
124768
+ if (material.vertexShader) checkShaderSource(material.vertexShader, context);
124769
+ } })
124770
+ });
124771
+ //#endregion
124772
+ //#region src/plugin/rules/r3f/three-shader-no-inverse-of-uniform.ts
124773
+ const checkShader$5 = (shader, context) => {
124774
+ if (hasGlslFunctionLikeMacro(shader.source.text, "inverse") || hasGlslFunctionDeclaration(shader.program, "inverse")) return;
124775
+ const globalBindings = shader.program.scopes[0]?.bindings;
124776
+ const uniformReferences = new Set(collectGlslGlobalDeclarations(shader.program).filter((declaration) => declaration.qualifiers.has("uniform")).flatMap((declaration) => globalBindings?.[declaration.name]?.references ?? []));
124777
+ visit(shader.program, { function_call: { enter: ({ node }) => {
124778
+ if (getGlslFunctionCallName(node) !== "inverse") return;
124779
+ const callArguments = getGlslFunctionCallArguments(node);
124780
+ const matrix = callArguments[0];
124781
+ if (callArguments.length !== 1 || matrix?.type !== "identifier" || !uniformReferences.has(matrix)) return;
124782
+ context.report({
124783
+ node: shader.source.getOriginNodeAtOffset(node.location?.start.offset ?? 0),
124784
+ message: `The matrix ${matrix.identifier} is uniform across this draw, but inverse recomputes it for every shader invocation. Compute and bind the inverse matrix on the CPU`
124785
+ });
124786
+ } } });
124787
+ };
124788
+ const threeShaderNoInverseOfUniform = defineRule({
124789
+ id: "three-shader-no-inverse-of-uniform",
124790
+ title: "Shader inverts a uniform matrix per invocation",
124791
+ category: "Performance",
124792
+ severity: "warn",
124793
+ recommendation: "Compute uniform matrix inverses once on the CPU and upload the result",
124794
+ create: (context) => ({ NewExpression(node) {
124795
+ const material = resolveStaticThreeShaderMaterial(node, context);
124796
+ if (!material) return;
124797
+ if (material.fragmentShader) checkShader$5(material.fragmentShader, context);
124798
+ if (material.vertexShader) checkShader$5(material.vertexShader, context);
124799
+ } })
124800
+ });
124801
+ //#endregion
124802
+ //#region src/plugin/rules/r3f/three-shader-no-redeclared-builtins.ts
124803
+ const checkShader$4 = (shader, injectedNames, context) => {
124804
+ for (const declaration of collectGlslGlobalDeclarations(shader.program)) {
124805
+ if (!injectedNames.has(declaration.name)) continue;
124806
+ context.report({
124807
+ node: shader.source.getOriginNodeAtOffset(declaration.node.location?.start.offset ?? 0),
124808
+ message: `ShaderMaterial already injects ${declaration.name} into the ${shader.stage} shader, so this declaration collides with Three.js's generated prefix`
124809
+ });
124810
+ }
124811
+ };
124812
+ const threeShaderNoRedeclaredBuiltins = defineRule({
124813
+ id: "three-shader-no-redeclared-builtins",
124814
+ title: "ShaderMaterial redeclares a Three.js builtin",
124815
+ category: "Correctness",
124816
+ severity: "error",
124817
+ recommendation: "Use ShaderMaterial built-in attributes and uniforms without redeclaring them",
124818
+ create: (context) => ({ NewExpression(node) {
124819
+ const material = resolveStaticThreeShaderMaterial(node, context);
124820
+ if (material?.constructorName !== "ShaderMaterial") return;
124821
+ if (material.vertexShader) checkShader$4(material.vertexShader, THREE_SHADER_MATERIAL_INJECTED_VERTEX_NAMES, context);
124822
+ if (material.fragmentShader) checkShader$4(material.fragmentShader, THREE_SHADER_MATERIAL_INJECTED_FRAGMENT_NAMES, context);
124823
+ } })
124824
+ });
124825
+ //#endregion
124826
+ //#region src/plugin/rules/r3f/three-shader-no-redundant-frag-depth.ts
124827
+ const FRAGMENT_DEPTH_OUTPUT_NAMES = new Set(["gl_FragDepth", "gl_FragDepthEXT"]);
124828
+ const CONDITIONAL_NODE_TYPES = new Set([
124829
+ "do_statement",
124830
+ "for_statement",
124831
+ "if_statement",
124832
+ "switch_statement",
124833
+ "ternary",
124834
+ "while_statement"
124835
+ ]);
124836
+ const isDefaultFragmentDepthAssignment = (node) => {
124837
+ if (node.operator.literal !== "=" || node.left.type !== "identifier" || !FRAGMENT_DEPTH_OUTPUT_NAMES.has(node.left.identifier) || node.right.type !== "postfix" || node.right.expression.type !== "identifier" || node.right.expression.identifier !== "gl_FragCoord" || node.right.postfix.type !== "field_selection") return false;
124838
+ return Reflect.get(node.right.postfix.selection, "identifier") === "z";
124839
+ };
124840
+ const isUnconditionalMainAssignment = (path) => {
124841
+ let currentPath = path.parentPath;
124842
+ while (currentPath) {
124843
+ if (CONDITIONAL_NODE_TYPES.has(currentPath.node.type)) return false;
124844
+ if (currentPath.node.type === "binary") {
124845
+ const operator = currentPath.node.operator.literal;
124846
+ if (operator === "&&" || operator === "||") return false;
124847
+ }
124848
+ if (currentPath.node.type === "function") return currentPath.node.prototype.header.name.identifier === "main";
124849
+ currentPath = currentPath.parentPath;
124850
+ }
124851
+ return false;
124852
+ };
124853
+ const checkShader$3 = (shader, context) => {
124854
+ const depthAssignments = [];
124855
+ visit(shader.program, { assignment: { enter: (path) => {
124856
+ if (path.node.left.type === "identifier" && FRAGMENT_DEPTH_OUTPUT_NAMES.has(path.node.left.identifier)) depthAssignments.push(path);
124857
+ } } });
124858
+ if (depthAssignments.length !== 1 || !isDefaultFragmentDepthAssignment(depthAssignments[0].node) || !isUnconditionalMainAssignment(depthAssignments[0])) return;
124859
+ const assignment = depthAssignments[0].node;
124860
+ context.report({
124861
+ node: shader.source.getOriginNodeAtOffset(assignment.location?.start.offset ?? 0),
124862
+ message: "This shader unconditionally writes the fixed-function depth value back to gl_FragDepth. Remove the redundant write so early depth testing remains available"
124863
+ });
124864
+ };
124865
+ const threeShaderNoRedundantFragDepth = defineRule({
124866
+ id: "three-shader-no-redundant-frag-depth",
124867
+ title: "Shader redundantly writes fragment depth",
124868
+ category: "Performance",
124869
+ severity: "warn",
124870
+ recommendation: "Let fixed-function depth supply gl_FragCoord.z when depth is unchanged",
124871
+ create: (context) => ({ NewExpression(node) {
124872
+ const material = resolveStaticThreeShaderMaterial(node, context);
124873
+ if (material?.fragmentShader) checkShader$3(material.fragmentShader, context);
124874
+ } })
124875
+ });
124876
+ //#endregion
124877
+ //#region src/plugin/rules/r3f/three-shader-no-version-directive.ts
124878
+ const VERSION_DIRECTIVE_PATTERN = /^[ \t]*#[ \t]*version\b/m;
124879
+ const checkShader$2 = (shader, context) => {
124880
+ const versionDirectiveOffset = maskGlslComments(shader.source.text).search(VERSION_DIRECTIVE_PATTERN);
124881
+ if (versionDirectiveOffset < 0) return;
124882
+ context.report({
124883
+ node: shader.source.getOriginNodeAtOffset(versionDirectiveOffset),
124884
+ message: "Three.js prepends its own shader prefix before custom source, so an inline #version directive is not first and cannot compile. Set the material glslVersion property instead"
124885
+ });
124886
+ };
124887
+ const threeShaderNoVersionDirective = defineRule({
124888
+ id: "three-shader-no-version-directive",
124889
+ title: "Three shader source contains a version directive",
124890
+ category: "Correctness",
124891
+ severity: "error",
124892
+ recommendation: "Set glslVersion on the material instead of writing #version in shader source",
124893
+ create: (context) => ({ NewExpression(node) {
124894
+ const material = resolveStaticThreeShaderMaterial(node, context);
124895
+ if (!material) return;
124896
+ if (material.fragmentShader) checkShader$2(material.fragmentShader, context);
124897
+ if (material.vertexShader) checkShader$2(material.vertexShader, context);
124898
+ } })
124899
+ });
124900
+ //#endregion
124901
+ //#region src/plugin/rules/r3f/three-shader-prefer-small-integer-pow.ts
124902
+ const SMALL_INTEGER_POW_EXPONENTS = new Set([
124903
+ 2,
124904
+ 3,
124905
+ 4
124906
+ ]);
124907
+ const checkShader$1 = (shader, context) => {
124908
+ if (hasGlslFunctionLikeMacro(shader.source.text, "pow") || hasGlslFunctionDeclaration(shader.program, "pow")) return;
124909
+ visit(shader.program, { function_call: { enter: ({ node }) => {
124910
+ if (getGlslFunctionCallName(node) !== "pow") return;
124911
+ const callArguments = getGlslFunctionCallArguments(node);
124912
+ if (callArguments.length !== 2) return;
124913
+ const exponent = getGlslNumericConstant(callArguments[1]);
124914
+ if (exponent === null || !SMALL_INTEGER_POW_EXPONENTS.has(exponent)) return;
124915
+ context.report({
124916
+ node: shader.source.getOriginNodeAtOffset(node.location?.start.offset ?? 0),
124917
+ message: `This pow call has the small integer exponent ${exponent}. Explicit multiplication avoids a general exponentiation operation and makes the intended cost clear`
124918
+ });
124919
+ } } });
124920
+ };
124921
+ const threeShaderPreferSmallIntegerPow = defineRule({
124922
+ id: "three-shader-prefer-small-integer-pow",
124923
+ title: "Shader uses pow for a small integer exponent",
124924
+ category: "Performance",
124925
+ severity: "warn",
124926
+ recommendation: "Use explicit multiplication for powers of two, three, or four",
124927
+ create: (context) => ({ NewExpression(node) {
124928
+ const material = resolveStaticThreeShaderMaterial(node, context);
124929
+ if (!material) return;
124930
+ if (material.fragmentShader) checkShader$1(material.fragmentShader, context);
124931
+ if (material.vertexShader) checkShader$1(material.vertexShader, context);
124932
+ } })
124933
+ });
124934
+ //#endregion
124935
+ //#region src/plugin/rules/r3f/three-shader-prefer-squared-distance-comparison.ts
124936
+ const DISTANCE_FUNCTION_NAMES = new Set(["distance", "length"]);
124937
+ const ORDERING_OPERATORS = new Set([
124938
+ "<",
124939
+ "<=",
124940
+ ">",
124941
+ ">="
124942
+ ]);
124943
+ const getDistanceFunctionCall = (node) => {
124944
+ if (node.type === "group") return getDistanceFunctionCall(node.expression);
124945
+ return node.type === "function_call" && DISTANCE_FUNCTION_NAMES.has(getGlslFunctionCallName(node) ?? "") ? node : null;
124946
+ };
124947
+ const getDistanceComparison = (left, right) => {
124948
+ const leftCall = getDistanceFunctionCall(left);
124949
+ const rightThreshold = getGlslNumericConstant(right);
124950
+ if (leftCall && rightThreshold !== null && rightThreshold >= 0) return {
124951
+ functionCall: leftCall,
124952
+ threshold: rightThreshold
124953
+ };
124954
+ const rightCall = getDistanceFunctionCall(right);
124955
+ const leftThreshold = getGlslNumericConstant(left);
124956
+ return rightCall && leftThreshold !== null && leftThreshold >= 0 ? {
124957
+ functionCall: rightCall,
124958
+ threshold: leftThreshold
124959
+ } : null;
124960
+ };
124961
+ const checkShader = (shader, context) => {
124962
+ visit(shader.program, { binary: { enter: ({ node }) => {
124963
+ if (!ORDERING_OPERATORS.has(node.operator.literal)) return;
124964
+ const comparison = getDistanceComparison(node.left, node.right);
124965
+ if (!comparison) return;
124966
+ const functionName = getGlslFunctionCallName(comparison.functionCall);
124967
+ if (!functionName || hasGlslFunctionLikeMacro(shader.source.text, functionName) || hasGlslFunctionDeclaration(shader.program, functionName)) return;
124968
+ context.report({
124969
+ node: shader.source.getOriginNodeAtOffset(node.location?.start.offset ?? 0),
124970
+ message: `Compare squared ${functionName === "length" ? "length using dot(v, v)" : "distance using dot(a - b, a - b)"} against ${comparison.threshold * comparison.threshold} to avoid computing a square root`
124971
+ });
124972
+ } } });
124973
+ };
124974
+ const threeShaderPreferSquaredDistanceComparison = defineRule({
124975
+ id: "three-shader-prefer-squared-distance-comparison",
124976
+ title: "Shader compares a computed distance",
124977
+ category: "Performance",
124978
+ severity: "warn",
124979
+ recommendation: "Compare squared vector distance against a squared nonnegative threshold",
124980
+ create: (context) => ({ NewExpression(node) {
124981
+ const material = resolveStaticThreeShaderMaterial(node, context);
124982
+ if (!material) return;
124983
+ if (material.fragmentShader) checkShader(material.fragmentShader, context);
124984
+ if (material.vertexShader) checkShader(material.vertexShader, context);
124985
+ } })
124986
+ });
124987
+ //#endregion
124988
+ //#region src/plugin/rules/r3f/utils/has-glsl-array-dimension-mismatch.ts
124989
+ const hasGlslArrayDimensionMismatch = (firstArraySize, secondArraySize) => firstArraySize === null !== (secondArraySize === null) || typeof firstArraySize === "number" && typeof secondArraySize === "number" && firstArraySize !== secondArraySize;
124990
+ //#endregion
124991
+ //#region src/plugin/rules/r3f/three-shader-require-matching-uniforms.ts
124992
+ const PRECISION_QUALIFIER_NAMES = [
124993
+ "highp",
124994
+ "mediump",
124995
+ "lowp"
124996
+ ];
124997
+ const getPrecision = (declaration) => PRECISION_QUALIFIER_NAMES.find((precision) => declaration.qualifiers.has(precision)) ?? null;
124998
+ const getMismatch = (vertexUniform, fragmentUniform) => {
124999
+ if (vertexUniform.typeName !== fragmentUniform.typeName) return `type ${vertexUniform.typeName} in the vertex shader and ${fragmentUniform.typeName} in the fragment shader`;
125000
+ if (hasGlslArrayDimensionMismatch(vertexUniform.arraySize, fragmentUniform.arraySize)) return "different array dimensions";
125001
+ const vertexPrecision = getPrecision(vertexUniform);
125002
+ const fragmentPrecision = getPrecision(fragmentUniform);
125003
+ return vertexPrecision === null || fragmentPrecision === null || vertexPrecision === fragmentPrecision ? null : `precision ${vertexPrecision} in the vertex shader and ${fragmentPrecision} in the fragment shader`;
125004
+ };
125005
+ const getUsedUniforms = (declarations) => new Map(declarations.filter((declaration) => declaration.qualifiers.has("uniform") && declaration.isStaticallyUsed).map((declaration) => [declaration.name, declaration]));
125006
+ const threeShaderRequireMatchingUniforms = defineRule({
125007
+ id: "three-shader-require-matching-uniforms",
125008
+ title: "Shader stages declare incompatible uniforms",
125009
+ category: "Correctness",
125010
+ severity: "error",
125011
+ recommendation: "Use the same type, array dimensions, and precision for uniforms shared by both shader stages",
125012
+ create: (context) => ({ NewExpression(node) {
125013
+ const material = resolveStaticThreeShaderMaterial(node, context);
125014
+ if (!material?.vertexShader || !material.fragmentShader) return;
125015
+ const vertexUniforms = getUsedUniforms(collectGlslGlobalDeclarations(material.vertexShader.program));
125016
+ const fragmentUniforms = getUsedUniforms(collectGlslGlobalDeclarations(material.fragmentShader.program));
125017
+ for (const [uniformName, fragmentUniform] of fragmentUniforms) {
125018
+ const vertexUniform = vertexUniforms.get(uniformName);
125019
+ if (!vertexUniform) continue;
125020
+ const mismatch = getMismatch(vertexUniform, fragmentUniform);
125021
+ if (!mismatch) continue;
125022
+ context.report({
125023
+ node: material.fragmentShader.source.getOriginNodeAtOffset(fragmentUniform.node.location?.start.offset ?? 0),
125024
+ message: `Uniform ${uniformName} has ${mismatch}, so the shader stages cannot link consistently`
125025
+ });
125026
+ }
125027
+ } })
125028
+ });
125029
+ //#endregion
125030
+ //#region src/plugin/rules/r3f/three-shader-require-matching-varyings.ts
125031
+ const isVertexOutput = (declaration) => declaration.qualifiers.has("out") || declaration.qualifiers.has("varying");
125032
+ const isFragmentInput = (declaration) => declaration.qualifiers.has("in") || declaration.qualifiers.has("varying");
125033
+ const getInterfaceMismatch = (vertexOutput, fragmentInput) => {
125034
+ if (vertexOutput.typeName !== fragmentInput.typeName) return `type ${vertexOutput.typeName} in the vertex shader but ${fragmentInput.typeName} in the fragment shader`;
125035
+ if (hasGlslArrayDimensionMismatch(vertexOutput.arraySize, fragmentInput.arraySize)) return "different array dimensions";
125036
+ return vertexOutput.interpolation === fragmentInput.interpolation ? null : `interpolation ${vertexOutput.interpolation} in the vertex shader but ${fragmentInput.interpolation} in the fragment shader`;
125037
+ };
125038
+ const threeShaderRequireMatchingVaryings = defineRule({
125039
+ id: "three-shader-require-matching-varyings",
125040
+ title: "Shader stage interface does not match",
125041
+ category: "Correctness",
125042
+ severity: "error",
125043
+ recommendation: "Declare every statically used fragment input as a compatible vertex output with the same name",
125044
+ create: (context) => ({ NewExpression(node) {
125045
+ const material = resolveStaticThreeShaderMaterial(node, context);
125046
+ if (!material?.vertexShader || !material.fragmentShader) return;
125047
+ const vertexOutputs = new Map(collectGlslGlobalDeclarations(material.vertexShader.program).filter(isVertexOutput).map((declaration) => [declaration.name, declaration]));
125048
+ const fragmentInputs = collectGlslGlobalDeclarations(material.fragmentShader.program).filter((declaration) => isFragmentInput(declaration) && declaration.isStaticallyUsed && !declaration.name.startsWith("gl_") && !declaration.hasLayoutQualifier);
125049
+ for (const fragmentInput of fragmentInputs) {
125050
+ const vertexOutput = vertexOutputs.get(fragmentInput.name);
125051
+ const mismatch = vertexOutput ? getInterfaceMismatch(vertexOutput, fragmentInput) : "no matching vertex output";
125052
+ if (!mismatch) continue;
125053
+ context.report({
125054
+ node: material.fragmentShader.source.getOriginNodeAtOffset(fragmentInput.node.location?.start.offset ?? 0),
125055
+ message: `Fragment input ${fragmentInput.name} has ${mismatch}, so the shader program cannot link with a defined value`
125056
+ });
125057
+ }
125058
+ } })
125059
+ });
125060
+ //#endregion
125061
+ //#region src/plugin/rules/r3f/utils/does-glsl-main-write-position-on-all-paths.ts
125062
+ const getPositionSwizzleWriteMask = (selection) => {
125063
+ let writeMask = 0;
125064
+ for (const componentAlias of selection) {
125065
+ const componentBit = GLSL_POSITION_COMPONENT_BIT_BY_ALIAS.get(componentAlias);
125066
+ if (!componentBit) return 0;
125067
+ writeMask |= componentBit;
125068
+ }
125069
+ return writeMask;
125070
+ };
125071
+ const getExpressionPositionWriteMask = (expression) => {
125072
+ if (expression.type === "assignment" && expression.operator.literal === "=") {
125073
+ if (expression.left.type === "identifier" && expression.left.identifier === "gl_Position") return 15;
125074
+ if (expression.left.type === "postfix" && expression.left.expression.type === "identifier" && expression.left.expression.identifier === "gl_Position" && expression.left.postfix.type === "field_selection") {
125075
+ const selection = Reflect.get(expression.left.postfix.selection, "identifier");
125076
+ return typeof selection === "string" ? getPositionSwizzleWriteMask(selection) : 0;
125077
+ }
125078
+ }
125079
+ if (expression.type === "binary" && expression.operator.literal === ",") return getExpressionPositionWriteMask(expression.left) | getExpressionPositionWriteMask(expression.right);
125080
+ if (expression.type === "group") return getExpressionPositionWriteMask(expression.expression);
125081
+ if (expression.type === "ternary") return getExpressionPositionWriteMask(expression.left) & getExpressionPositionWriteMask(expression.right);
125082
+ return 0;
125083
+ };
125084
+ const expressionContainsPositionWrite = (expression) => {
125085
+ let containsPositionWrite = false;
125086
+ visit(expression, { assignment: { enter: ({ node }) => {
125087
+ if (getExpressionPositionWriteMask(node) !== 0) containsPositionWrite = true;
125088
+ } } });
125089
+ return containsPositionWrite;
125090
+ };
125091
+ const getBooleanConstant = (expression) => {
125092
+ if (expression.type !== "bool_constant") return null;
125093
+ return expression.token === "true";
125094
+ };
125095
+ const analyzeCompoundStatement = (statement, inputStates) => {
125096
+ let activeStates = inputStates;
125097
+ let hasUnwrittenReturn = false;
125098
+ for (const child of statement.statements) {
125099
+ if (activeStates.size === 0) break;
125100
+ const childResult = analyzeStatement(child, activeStates);
125101
+ if (!childResult.isSupported) return childResult;
125102
+ activeStates = childResult.activeStates;
125103
+ hasUnwrittenReturn ||= childResult.hasUnwrittenReturn;
125104
+ }
125105
+ return {
125106
+ activeStates,
125107
+ hasUnwrittenReturn,
125108
+ isSupported: true
125109
+ };
125110
+ };
125111
+ const analyzeStatement = (statement, inputStates) => {
125112
+ if (statement.type === "compound_statement") return analyzeCompoundStatement(statement, inputStates);
125113
+ if (statement.type === "expression_statement") {
125114
+ const expressionWriteMask = getExpressionPositionWriteMask(statement.expression);
125115
+ if (expressionWriteMask === 0 && expressionContainsPositionWrite(statement.expression)) return {
125116
+ activeStates: inputStates,
125117
+ hasUnwrittenReturn: false,
125118
+ isSupported: false
125119
+ };
125120
+ return {
125121
+ activeStates: expressionWriteMask === 0 ? inputStates : new Set([...inputStates].map((state) => state | expressionWriteMask)),
125122
+ hasUnwrittenReturn: false,
125123
+ isSupported: true
125124
+ };
125125
+ }
125126
+ if (statement.type === "return_statement") return {
125127
+ activeStates: /* @__PURE__ */ new Set(),
125128
+ hasUnwrittenReturn: [...inputStates].some((state) => state !== 15),
125129
+ isSupported: true
125130
+ };
125131
+ if (statement.type === "if_statement") {
125132
+ if (expressionContainsPositionWrite(statement.condition)) return {
125133
+ activeStates: inputStates,
125134
+ hasUnwrittenReturn: false,
125135
+ isSupported: false
125136
+ };
125137
+ const constantCondition = getBooleanConstant(statement.condition);
125138
+ const consequent = analyzeStatement(statement.body, inputStates);
125139
+ if (!consequent.isSupported) return consequent;
125140
+ const alternateNodes = Reflect.get(statement, "else");
125141
+ const alternateStatement = Array.isArray(alternateNodes) ? alternateNodes[alternateNodes.length - 1] : void 0;
125142
+ const alternate = alternateStatement ? analyzeStatement(alternateStatement, inputStates) : {
125143
+ activeStates: inputStates,
125144
+ hasUnwrittenReturn: false,
125145
+ isSupported: true
125146
+ };
125147
+ if (!alternate.isSupported) return alternate;
125148
+ if (constantCondition !== null) return constantCondition ? consequent : alternate;
125149
+ return {
125150
+ activeStates: new Set([...consequent.activeStates, ...alternate.activeStates]),
125151
+ hasUnwrittenReturn: consequent.hasUnwrittenReturn || alternate.hasUnwrittenReturn,
125152
+ isSupported: true
125153
+ };
125154
+ }
125155
+ if (statement.type === "for_statement" || statement.type === "while_statement") {
125156
+ const controlExpressions = statement.type === "for_statement" ? [
125157
+ Reflect.get(statement, "init"),
125158
+ Reflect.get(statement, "condition"),
125159
+ Reflect.get(statement, "operation")
125160
+ ] : [statement.condition];
125161
+ if (controlExpressions.some((expression) => !expression) || controlExpressions.some(expressionContainsPositionWrite)) return {
125162
+ activeStates: inputStates,
125163
+ hasUnwrittenReturn: false,
125164
+ isSupported: false
125165
+ };
125166
+ const bodyResult = analyzeStatement(statement.body, inputStates);
125167
+ return {
125168
+ activeStates: inputStates,
125169
+ hasUnwrittenReturn: bodyResult.hasUnwrittenReturn,
125170
+ isSupported: bodyResult.isSupported
125171
+ };
125172
+ }
125173
+ if (statement.type === "declaration_statement" && expressionContainsPositionWrite(statement.declaration)) return {
125174
+ activeStates: inputStates,
125175
+ hasUnwrittenReturn: false,
125176
+ isSupported: false
125177
+ };
125178
+ if (statement.type === "do_statement" || statement.type === "switch_statement" || statement.type === "break_statement" || statement.type === "continue_statement") return {
125179
+ activeStates: inputStates,
125180
+ hasUnwrittenReturn: false,
125181
+ isSupported: false
125182
+ };
125183
+ return {
125184
+ activeStates: inputStates,
125185
+ hasUnwrittenReturn: false,
125186
+ isSupported: true
125187
+ };
125188
+ };
125189
+ const callsUserDefinedFunction = (mainFunction, program) => {
125190
+ let callsFunction = false;
125191
+ visit(mainFunction.body, { function_call: { enter: ({ node }) => {
125192
+ const functionName = getGlslFunctionCallName(node);
125193
+ if (functionName && hasGlslFunctionDeclaration(program, functionName)) callsFunction = true;
125194
+ } } });
125195
+ return callsFunction;
125196
+ };
125197
+ const doesGlslMainWritePositionOnAllPaths = (program, source) => {
125198
+ const mainFunction = program.program.find((node) => node.type === "function" && node.prototype.header.name.identifier === "main") ?? null;
125199
+ if (!mainFunction || /^[ \t]*#[ \t]*define\b/m.test(maskGlslComments(source)) || callsUserDefinedFunction(mainFunction, program)) return {
125200
+ mainFunction,
125201
+ writesPositionOnAllPaths: null
125202
+ };
125203
+ const result = analyzeCompoundStatement(mainFunction.body, new Set([0]));
125204
+ if (!result.isSupported) return {
125205
+ mainFunction,
125206
+ writesPositionOnAllPaths: null
125207
+ };
125208
+ return {
125209
+ mainFunction,
125210
+ writesPositionOnAllPaths: !result.hasUnwrittenReturn && [...result.activeStates].every((state) => state === 15)
125211
+ };
125212
+ };
125213
+ //#endregion
125214
+ //#region src/plugin/rules/r3f/three-shader-require-position-on-all-paths.ts
125215
+ const threeShaderRequirePositionOnAllPaths = defineRule({
125216
+ id: "three-shader-require-position-on-all-paths",
125217
+ title: "Vertex shader leaves gl_Position undefined",
125218
+ category: "Correctness",
125219
+ severity: "error",
125220
+ recommendation: "Assign gl_Position before every return and on every path through main",
125221
+ create: (context) => ({ NewExpression(node) {
125222
+ const material = resolveStaticThreeShaderMaterial(node, context);
125223
+ if (!material?.vertexShader) return;
125224
+ const analysis = doesGlslMainWritePositionOnAllPaths(material.vertexShader.program, material.vertexShader.source.text);
125225
+ if (!analysis.mainFunction || analysis.writesPositionOnAllPaths !== false) return;
125226
+ context.report({
125227
+ node: material.vertexShader.source.getOriginNodeAtOffset(analysis.mainFunction.location?.start.offset ?? 0),
125228
+ message: "At least one path through vertex main returns or falls through without assigning gl_Position, leaving the clip-space position undefined"
125229
+ });
125230
+ } })
125231
+ });
125232
+ //#endregion
125233
+ //#region src/plugin/rules/r3f/utils/collect-authoritative-static-object-property-names.ts
125234
+ const collectAuthoritativeStaticObjectPropertyNames = (objectExpression) => {
125235
+ const propertyNames = /* @__PURE__ */ new Set();
125236
+ for (let propertyIndex = objectExpression.properties.length - 1; propertyIndex >= 0; propertyIndex -= 1) {
125237
+ const property = objectExpression.properties[propertyIndex];
125238
+ if (!property || isNodeOfType(property, "SpreadElement")) return null;
125239
+ if (!isNodeOfType(property, "Property")) continue;
125240
+ const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
125241
+ if (!propertyName) return null;
125242
+ propertyNames.add(propertyName);
125243
+ }
125244
+ return propertyNames;
125245
+ };
125246
+ //#endregion
125247
+ //#region src/plugin/rules/r3f/three-shader-require-uniform-bindings.ts
125248
+ const MANAGED_UNIFORM_FEATURE_BY_NAME = new Map([
125249
+ ["ambientLightColor", "lights"],
125250
+ ["bindMatrix", "skinning"],
125251
+ ["bindMatrixInverse", "skinning"],
125252
+ ["boneTexture", "skinning"],
125253
+ ["boneTextureSize", "skinning"],
125254
+ ["clippingPlanes", "clipping"],
125255
+ ["directionalLights", "lights"],
125256
+ ["directionalLightShadows", "lights"],
125257
+ ["directionalShadowMap", "lights"],
125258
+ ["directionalShadowMatrix", "lights"],
125259
+ ["fogColor", "fog"],
125260
+ ["fogDensity", "fog"],
125261
+ ["fogFar", "fog"],
125262
+ ["fogNear", "fog"],
125263
+ ["hemisphereLights", "lights"],
125264
+ ["lightProbe", "lights"],
125265
+ ["ltc_1", "lights"],
125266
+ ["ltc_2", "lights"],
125267
+ ["morphTargetBaseInfluence", "morphTargets"],
125268
+ ["morphTargetInfluences", "morphTargets"],
125269
+ ["morphTargetsTexture", "morphTargets"],
125270
+ ["morphTargetsTextureSize", "morphTargets"],
125271
+ ["pointLights", "lights"],
125272
+ ["pointLightShadows", "lights"],
125273
+ ["pointShadowMap", "lights"],
125274
+ ["pointShadowMatrix", "lights"],
125275
+ ["probesMax", "lights"],
125276
+ ["probesMin", "lights"],
125277
+ ["probesResolution", "lights"],
125278
+ ["probesSH", "lights"],
125279
+ ["receiveShadow", "lights"],
125280
+ ["rectAreaLights", "lights"],
125281
+ ["spotLightMap", "lights"],
125282
+ ["spotLightMatrix", "lights"],
125283
+ ["spotLights", "lights"],
125284
+ ["spotLightShadows", "lights"],
125285
+ ["spotShadowMap", "lights"],
125286
+ ["transmissionSamplerMap", "transmission"],
125287
+ ["transmissionSamplerSize", "transmission"]
125288
+ ]);
125289
+ const isRendererManagedUniform = (uniformName, material) => {
125290
+ if (material.constructorName !== "ShaderMaterial" || !THREE_RENDERER_MANAGED_SHADER_UNIFORM_NAMES.has(uniformName)) return false;
125291
+ const requiredFeature = MANAGED_UNIFORM_FEATURE_BY_NAME.get(uniformName);
125292
+ if (!requiredFeature) return true;
125293
+ const featureExpression = material.properties.get(requiredFeature);
125294
+ return featureExpression !== void 0 && readStaticBoolean(featureExpression) !== false;
125295
+ };
125296
+ const isCustomUniform = (declaration, material) => declaration.qualifiers.has("uniform") && declaration.isStaticallyUsed && !declaration.name.startsWith("gl_") && !isRendererManagedUniform(declaration.name, material);
125297
+ const threeShaderRequireUniformBindings = defineRule({
125298
+ id: "three-shader-require-uniform-bindings",
125299
+ title: "Shader uniform has no material binding",
125300
+ category: "Correctness",
125301
+ severity: "error",
125302
+ recommendation: "Add every custom GLSL uniform to the ShaderMaterial uniforms object with a compatible value",
125303
+ create: (context) => ({ NewExpression(node) {
125304
+ const material = resolveStaticThreeShaderMaterial(node, context);
125305
+ if (!material) return;
125306
+ const declarationsByName = /* @__PURE__ */ new Map();
125307
+ for (const shader of [material.vertexShader, material.fragmentShader]) {
125308
+ if (!shader) continue;
125309
+ for (const declaration of collectGlslGlobalDeclarations(shader.program)) if (isCustomUniform(declaration, material) && !declarationsByName.has(declaration.name)) declarationsByName.set(declaration.name, {
125310
+ declaration,
125311
+ shader
125312
+ });
125313
+ }
125314
+ if (declarationsByName.size === 0) return;
125315
+ const uniformNames = [...declarationsByName.keys()];
125316
+ const uniformsExpression = material.properties.get("uniforms");
125317
+ let boundUniformNames;
125318
+ if (!uniformsExpression) boundUniformNames = /* @__PURE__ */ new Set();
125319
+ else {
125320
+ const uniformsObject = resolveStableOptionsObject(uniformsExpression, uniformNames, context.scopes, material.constructorNode);
125321
+ if (!uniformsObject || !isNodeOfType(uniformsObject, "ObjectExpression")) return;
125322
+ const propertyNames = collectAuthoritativeStaticObjectPropertyNames(uniformsObject);
125323
+ if (!propertyNames) return;
125324
+ boundUniformNames = propertyNames;
125325
+ }
125326
+ for (const [uniformName, declarationSource] of declarationsByName) {
125327
+ if (boundUniformNames.has(uniformName)) continue;
125328
+ context.report({
125329
+ node: declarationSource.shader.source.getOriginNodeAtOffset(declarationSource.declaration.node.location?.start.offset ?? 0),
125330
+ message: `GLSL uniform ${uniformName} has no matching entry in this material's uniforms object, so it keeps its default value`
125331
+ });
125332
+ }
125333
+ } })
125334
+ });
125335
+ //#endregion
123654
125336
  //#region src/plugin/rules/r3f/three-tsl-no-js-uniform-branch.ts
123655
125337
  const TSL_MODULES = new Set(["three/tsl", "three/webgpu"]);
123656
125338
  const expressionReferencesTslUniformValue = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
@@ -136754,6 +138436,23 @@ const reactDoctorRules = [
136754
138436
  requires: [...new Set(["three", ...threeNoCloneInAnimationLoop.requires ?? []])]
136755
138437
  }
136756
138438
  },
138439
+ {
138440
+ key: "react-doctor/three-no-material-recompile-in-animation-loop",
138441
+ id: "three-no-material-recompile-in-animation-loop",
138442
+ source: "react-doctor",
138443
+ originallyExternal: false,
138444
+ rule: {
138445
+ ...threeNoMaterialRecompileInAnimationLoop,
138446
+ framework: "global",
138447
+ category: "Performance",
138448
+ tags: [...new Set([
138449
+ "three",
138450
+ "webgl",
138451
+ ...threeNoMaterialRecompileInAnimationLoop.tags ?? []
138452
+ ])],
138453
+ requires: [...new Set(["three", ...threeNoMaterialRecompileInAnimationLoop.requires ?? []])]
138454
+ }
138455
+ },
136757
138456
  {
136758
138457
  key: "react-doctor/three-no-new-in-animation-loop",
136759
138458
  id: "three-no-new-in-animation-loop",
@@ -136792,6 +138491,23 @@ const reactDoctorRules = [
136792
138491
  ])]
136793
138492
  }
136794
138493
  },
138494
+ {
138495
+ key: "react-doctor/three-no-redundant-uniforms-need-update",
138496
+ id: "three-no-redundant-uniforms-need-update",
138497
+ source: "react-doctor",
138498
+ originallyExternal: false,
138499
+ rule: {
138500
+ ...threeNoRedundantUniformsNeedUpdate,
138501
+ framework: "global",
138502
+ category: "Performance",
138503
+ tags: [...new Set([
138504
+ "three",
138505
+ "webgl",
138506
+ ...threeNoRedundantUniformsNeedUpdate.tags ?? []
138507
+ ])],
138508
+ requires: [...new Set(["three", ...threeNoRedundantUniformsNeedUpdate.requires ?? []])]
138509
+ }
138510
+ },
136795
138511
  {
136796
138512
  key: "react-doctor/three-no-state-in-animation-loop",
136797
138513
  id: "three-no-state-in-animation-loop",
@@ -136834,6 +138550,57 @@ const reactDoctorRules = [
136834
138550
  ])]
136835
138551
  }
136836
138552
  },
138553
+ {
138554
+ key: "react-doctor/three-on-before-compile-require-program-cache-key",
138555
+ id: "three-on-before-compile-require-program-cache-key",
138556
+ source: "react-doctor",
138557
+ originallyExternal: false,
138558
+ rule: {
138559
+ ...threeOnBeforeCompileRequireProgramCacheKey,
138560
+ framework: "global",
138561
+ category: "Bugs",
138562
+ tags: [...new Set([
138563
+ "three",
138564
+ "webgl",
138565
+ ...threeOnBeforeCompileRequireProgramCacheKey.tags ?? []
138566
+ ])],
138567
+ requires: [...new Set(["three", ...threeOnBeforeCompileRequireProgramCacheKey.requires ?? []])]
138568
+ }
138569
+ },
138570
+ {
138571
+ key: "react-doctor/three-raw-shader-require-fragment-float-precision",
138572
+ id: "three-raw-shader-require-fragment-float-precision",
138573
+ source: "react-doctor",
138574
+ originallyExternal: false,
138575
+ rule: {
138576
+ ...threeRawShaderRequireFragmentFloatPrecision,
138577
+ framework: "global",
138578
+ category: "Bugs",
138579
+ tags: [...new Set([
138580
+ "three",
138581
+ "webgl",
138582
+ ...threeRawShaderRequireFragmentFloatPrecision.tags ?? []
138583
+ ])],
138584
+ requires: [...new Set(["three", ...threeRawShaderRequireFragmentFloatPrecision.requires ?? []])]
138585
+ }
138586
+ },
138587
+ {
138588
+ key: "react-doctor/three-raw-shader-require-glsl3-version",
138589
+ id: "three-raw-shader-require-glsl3-version",
138590
+ source: "react-doctor",
138591
+ originallyExternal: false,
138592
+ rule: {
138593
+ ...threeRawShaderRequireGlsl3Version,
138594
+ framework: "global",
138595
+ category: "Bugs",
138596
+ tags: [...new Set([
138597
+ "three",
138598
+ "webgl",
138599
+ ...threeRawShaderRequireGlsl3Version.tags ?? []
138600
+ ])],
138601
+ requires: [...new Set(["three", ...threeRawShaderRequireGlsl3Version.requires ?? []])]
138602
+ }
138603
+ },
136837
138604
  {
136838
138605
  key: "react-doctor/three-require-animation-mixer-cleanup",
136839
138606
  id: "three-require-animation-mixer-cleanup",
@@ -137053,6 +138820,278 @@ const reactDoctorRules = [
137053
138820
  ])]
137054
138821
  }
137055
138822
  },
138823
+ {
138824
+ key: "react-doctor/three-shader-no-constant-out-of-bounds-index",
138825
+ id: "three-shader-no-constant-out-of-bounds-index",
138826
+ source: "react-doctor",
138827
+ originallyExternal: false,
138828
+ rule: {
138829
+ ...threeShaderNoConstantOutOfBoundsIndex,
138830
+ framework: "global",
138831
+ category: "Bugs",
138832
+ tags: [...new Set([
138833
+ "three",
138834
+ "webgl",
138835
+ ...threeShaderNoConstantOutOfBoundsIndex.tags ?? []
138836
+ ])],
138837
+ requires: [...new Set(["three", ...threeShaderNoConstantOutOfBoundsIndex.requires ?? []])]
138838
+ }
138839
+ },
138840
+ {
138841
+ key: "react-doctor/three-shader-no-derivatives-in-nonuniform-flow",
138842
+ id: "three-shader-no-derivatives-in-nonuniform-flow",
138843
+ source: "react-doctor",
138844
+ originallyExternal: false,
138845
+ rule: {
138846
+ ...threeShaderNoDerivativesInNonuniformFlow,
138847
+ framework: "global",
138848
+ category: "Bugs",
138849
+ tags: [...new Set([
138850
+ "three",
138851
+ "webgl",
138852
+ ...threeShaderNoDerivativesInNonuniformFlow.tags ?? []
138853
+ ])],
138854
+ requires: [...new Set(["three", ...threeShaderNoDerivativesInNonuniformFlow.requires ?? []])]
138855
+ }
138856
+ },
138857
+ {
138858
+ key: "react-doctor/three-shader-no-invalid-clamp-bounds",
138859
+ id: "three-shader-no-invalid-clamp-bounds",
138860
+ source: "react-doctor",
138861
+ originallyExternal: false,
138862
+ rule: {
138863
+ ...threeShaderNoInvalidClampBounds,
138864
+ framework: "global",
138865
+ category: "Bugs",
138866
+ tags: [...new Set([
138867
+ "three",
138868
+ "webgl",
138869
+ ...threeShaderNoInvalidClampBounds.tags ?? []
138870
+ ])],
138871
+ requires: [...new Set(["three", ...threeShaderNoInvalidClampBounds.requires ?? []])]
138872
+ }
138873
+ },
138874
+ {
138875
+ key: "react-doctor/three-shader-no-invalid-constant-bit-operations",
138876
+ id: "three-shader-no-invalid-constant-bit-operations",
138877
+ source: "react-doctor",
138878
+ originallyExternal: false,
138879
+ rule: {
138880
+ ...threeShaderNoInvalidConstantBitOperations,
138881
+ framework: "global",
138882
+ category: "Bugs",
138883
+ tags: [...new Set([
138884
+ "three",
138885
+ "webgl",
138886
+ ...threeShaderNoInvalidConstantBitOperations.tags ?? []
138887
+ ])],
138888
+ requires: [...new Set(["three", ...threeShaderNoInvalidConstantBitOperations.requires ?? []])]
138889
+ }
138890
+ },
138891
+ {
138892
+ key: "react-doctor/three-shader-no-invalid-constant-math",
138893
+ id: "three-shader-no-invalid-constant-math",
138894
+ source: "react-doctor",
138895
+ originallyExternal: false,
138896
+ rule: {
138897
+ ...threeShaderNoInvalidConstantMath,
138898
+ framework: "global",
138899
+ category: "Bugs",
138900
+ tags: [...new Set([
138901
+ "three",
138902
+ "webgl",
138903
+ ...threeShaderNoInvalidConstantMath.tags ?? []
138904
+ ])],
138905
+ requires: [...new Set(["three", ...threeShaderNoInvalidConstantMath.requires ?? []])]
138906
+ }
138907
+ },
138908
+ {
138909
+ key: "react-doctor/three-shader-no-invalid-smoothstep-edges",
138910
+ id: "three-shader-no-invalid-smoothstep-edges",
138911
+ source: "react-doctor",
138912
+ originallyExternal: false,
138913
+ rule: {
138914
+ ...threeShaderNoInvalidSmoothstepEdges,
138915
+ framework: "global",
138916
+ category: "Bugs",
138917
+ tags: [...new Set([
138918
+ "three",
138919
+ "webgl",
138920
+ ...threeShaderNoInvalidSmoothstepEdges.tags ?? []
138921
+ ])],
138922
+ requires: [...new Set(["three", ...threeShaderNoInvalidSmoothstepEdges.requires ?? []])]
138923
+ }
138924
+ },
138925
+ {
138926
+ key: "react-doctor/three-shader-no-inverse-of-uniform",
138927
+ id: "three-shader-no-inverse-of-uniform",
138928
+ source: "react-doctor",
138929
+ originallyExternal: false,
138930
+ rule: {
138931
+ ...threeShaderNoInverseOfUniform,
138932
+ framework: "global",
138933
+ category: "Performance",
138934
+ tags: [...new Set([
138935
+ "three",
138936
+ "webgl",
138937
+ ...threeShaderNoInverseOfUniform.tags ?? []
138938
+ ])],
138939
+ requires: [...new Set(["three", ...threeShaderNoInverseOfUniform.requires ?? []])]
138940
+ }
138941
+ },
138942
+ {
138943
+ key: "react-doctor/three-shader-no-redeclared-builtins",
138944
+ id: "three-shader-no-redeclared-builtins",
138945
+ source: "react-doctor",
138946
+ originallyExternal: false,
138947
+ rule: {
138948
+ ...threeShaderNoRedeclaredBuiltins,
138949
+ framework: "global",
138950
+ category: "Bugs",
138951
+ tags: [...new Set([
138952
+ "three",
138953
+ "webgl",
138954
+ ...threeShaderNoRedeclaredBuiltins.tags ?? []
138955
+ ])],
138956
+ requires: [...new Set(["three", ...threeShaderNoRedeclaredBuiltins.requires ?? []])]
138957
+ }
138958
+ },
138959
+ {
138960
+ key: "react-doctor/three-shader-no-redundant-frag-depth",
138961
+ id: "three-shader-no-redundant-frag-depth",
138962
+ source: "react-doctor",
138963
+ originallyExternal: false,
138964
+ rule: {
138965
+ ...threeShaderNoRedundantFragDepth,
138966
+ framework: "global",
138967
+ category: "Performance",
138968
+ tags: [...new Set([
138969
+ "three",
138970
+ "webgl",
138971
+ ...threeShaderNoRedundantFragDepth.tags ?? []
138972
+ ])],
138973
+ requires: [...new Set(["three", ...threeShaderNoRedundantFragDepth.requires ?? []])]
138974
+ }
138975
+ },
138976
+ {
138977
+ key: "react-doctor/three-shader-no-version-directive",
138978
+ id: "three-shader-no-version-directive",
138979
+ source: "react-doctor",
138980
+ originallyExternal: false,
138981
+ rule: {
138982
+ ...threeShaderNoVersionDirective,
138983
+ framework: "global",
138984
+ category: "Bugs",
138985
+ tags: [...new Set([
138986
+ "three",
138987
+ "webgl",
138988
+ ...threeShaderNoVersionDirective.tags ?? []
138989
+ ])],
138990
+ requires: [...new Set(["three", ...threeShaderNoVersionDirective.requires ?? []])]
138991
+ }
138992
+ },
138993
+ {
138994
+ key: "react-doctor/three-shader-prefer-small-integer-pow",
138995
+ id: "three-shader-prefer-small-integer-pow",
138996
+ source: "react-doctor",
138997
+ originallyExternal: false,
138998
+ rule: {
138999
+ ...threeShaderPreferSmallIntegerPow,
139000
+ framework: "global",
139001
+ category: "Performance",
139002
+ tags: [...new Set([
139003
+ "three",
139004
+ "webgl",
139005
+ ...threeShaderPreferSmallIntegerPow.tags ?? []
139006
+ ])],
139007
+ requires: [...new Set(["three", ...threeShaderPreferSmallIntegerPow.requires ?? []])]
139008
+ }
139009
+ },
139010
+ {
139011
+ key: "react-doctor/three-shader-prefer-squared-distance-comparison",
139012
+ id: "three-shader-prefer-squared-distance-comparison",
139013
+ source: "react-doctor",
139014
+ originallyExternal: false,
139015
+ rule: {
139016
+ ...threeShaderPreferSquaredDistanceComparison,
139017
+ framework: "global",
139018
+ category: "Performance",
139019
+ tags: [...new Set([
139020
+ "three",
139021
+ "webgl",
139022
+ ...threeShaderPreferSquaredDistanceComparison.tags ?? []
139023
+ ])],
139024
+ requires: [...new Set(["three", ...threeShaderPreferSquaredDistanceComparison.requires ?? []])]
139025
+ }
139026
+ },
139027
+ {
139028
+ key: "react-doctor/three-shader-require-matching-uniforms",
139029
+ id: "three-shader-require-matching-uniforms",
139030
+ source: "react-doctor",
139031
+ originallyExternal: false,
139032
+ rule: {
139033
+ ...threeShaderRequireMatchingUniforms,
139034
+ framework: "global",
139035
+ category: "Bugs",
139036
+ tags: [...new Set([
139037
+ "three",
139038
+ "webgl",
139039
+ ...threeShaderRequireMatchingUniforms.tags ?? []
139040
+ ])],
139041
+ requires: [...new Set(["three", ...threeShaderRequireMatchingUniforms.requires ?? []])]
139042
+ }
139043
+ },
139044
+ {
139045
+ key: "react-doctor/three-shader-require-matching-varyings",
139046
+ id: "three-shader-require-matching-varyings",
139047
+ source: "react-doctor",
139048
+ originallyExternal: false,
139049
+ rule: {
139050
+ ...threeShaderRequireMatchingVaryings,
139051
+ framework: "global",
139052
+ category: "Bugs",
139053
+ tags: [...new Set([
139054
+ "three",
139055
+ "webgl",
139056
+ ...threeShaderRequireMatchingVaryings.tags ?? []
139057
+ ])],
139058
+ requires: [...new Set(["three", ...threeShaderRequireMatchingVaryings.requires ?? []])]
139059
+ }
139060
+ },
139061
+ {
139062
+ key: "react-doctor/three-shader-require-position-on-all-paths",
139063
+ id: "three-shader-require-position-on-all-paths",
139064
+ source: "react-doctor",
139065
+ originallyExternal: false,
139066
+ rule: {
139067
+ ...threeShaderRequirePositionOnAllPaths,
139068
+ framework: "global",
139069
+ category: "Bugs",
139070
+ tags: [...new Set([
139071
+ "three",
139072
+ "webgl",
139073
+ ...threeShaderRequirePositionOnAllPaths.tags ?? []
139074
+ ])],
139075
+ requires: [...new Set(["three", ...threeShaderRequirePositionOnAllPaths.requires ?? []])]
139076
+ }
139077
+ },
139078
+ {
139079
+ key: "react-doctor/three-shader-require-uniform-bindings",
139080
+ id: "three-shader-require-uniform-bindings",
139081
+ source: "react-doctor",
139082
+ originallyExternal: false,
139083
+ rule: {
139084
+ ...threeShaderRequireUniformBindings,
139085
+ framework: "global",
139086
+ category: "Bugs",
139087
+ tags: [...new Set([
139088
+ "three",
139089
+ "webgl",
139090
+ ...threeShaderRequireUniformBindings.tags ?? []
139091
+ ])],
139092
+ requires: [...new Set(["three", ...threeShaderRequireUniformBindings.requires ?? []])]
139093
+ }
139094
+ },
137056
139095
  {
137057
139096
  key: "react-doctor/three-tsl-no-js-uniform-branch",
137058
139097
  id: "three-tsl-no-js-uniform-branch",