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