oxlint-plugin-react-doctor 0.8.3-dev.3d7ea66 → 0.8.3-dev.416fdcc
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 +1512 -242
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8262,6 +8262,56 @@ const isNodeReachableWithinFunction = (node, context) => {
|
|
|
8262
8262
|
return false;
|
|
8263
8263
|
};
|
|
8264
8264
|
//#endregion
|
|
8265
|
+
//#region src/plugin/utils/is-proven-non-throwing-built-in-call.ts
|
|
8266
|
+
const NON_THROWING_CONSOLE_METHOD_NAMES = new Set([
|
|
8267
|
+
"debug",
|
|
8268
|
+
"error",
|
|
8269
|
+
"info",
|
|
8270
|
+
"log",
|
|
8271
|
+
"trace",
|
|
8272
|
+
"warn"
|
|
8273
|
+
]);
|
|
8274
|
+
const NON_THROWING_NUMBER_BINARY_OPERATORS = new Set([
|
|
8275
|
+
"+",
|
|
8276
|
+
"-",
|
|
8277
|
+
"*",
|
|
8278
|
+
"/",
|
|
8279
|
+
"%",
|
|
8280
|
+
"**"
|
|
8281
|
+
]);
|
|
8282
|
+
const isGlobalPerformanceNowCall = (callNode, scopes) => {
|
|
8283
|
+
const callee = stripParenExpression(callNode.callee);
|
|
8284
|
+
if (!isNodeOfType(callee, "MemberExpression") || callNode.arguments.length !== 0) return false;
|
|
8285
|
+
const receiver = stripParenExpression(callee.object);
|
|
8286
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === "performance" && scopes.isGlobalReference(receiver) && getStaticPropertyName(callee) === "now";
|
|
8287
|
+
};
|
|
8288
|
+
const isProvenNonThrowingNumberExpression = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
8289
|
+
const strippedExpression = stripParenExpression(expression);
|
|
8290
|
+
if (isNodeOfType(strippedExpression, "Literal")) return typeof strippedExpression.value === "number";
|
|
8291
|
+
if (isNodeOfType(strippedExpression, "CallExpression")) return isGlobalPerformanceNowCall(strippedExpression, scopes);
|
|
8292
|
+
if (isNodeOfType(strippedExpression, "Identifier")) {
|
|
8293
|
+
const symbol = scopes.symbolFor(strippedExpression);
|
|
8294
|
+
if (symbol?.kind !== "const" || !symbol.initializer || symbol.declarationNode.range[0] >= strippedExpression.range[0] || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
8295
|
+
visitedSymbolIds.add(symbol.id);
|
|
8296
|
+
return isProvenNonThrowingNumberExpression(symbol.initializer, scopes, visitedSymbolIds);
|
|
8297
|
+
}
|
|
8298
|
+
if (isNodeOfType(strippedExpression, "UnaryExpression")) return (strippedExpression.operator === "+" || strippedExpression.operator === "-") && isProvenNonThrowingNumberExpression(strippedExpression.argument, scopes, visitedSymbolIds);
|
|
8299
|
+
if (!isNodeOfType(strippedExpression, "BinaryExpression")) return false;
|
|
8300
|
+
if (!NON_THROWING_NUMBER_BINARY_OPERATORS.has(strippedExpression.operator)) return false;
|
|
8301
|
+
return isProvenNonThrowingNumberExpression(strippedExpression.left, scopes, new Set(visitedSymbolIds)) && isProvenNonThrowingNumberExpression(strippedExpression.right, scopes, new Set(visitedSymbolIds));
|
|
8302
|
+
};
|
|
8303
|
+
const isProvenNonThrowingBuiltInCall = (callNode, scopes) => {
|
|
8304
|
+
if (isGlobalPerformanceNowCall(callNode, scopes)) return true;
|
|
8305
|
+
const callee = stripParenExpression(callNode.callee);
|
|
8306
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
8307
|
+
const receiver = stripParenExpression(callee.object);
|
|
8308
|
+
if (!isNodeOfType(receiver, "Identifier") || !scopes.isGlobalReference(receiver)) return false;
|
|
8309
|
+
const methodName = getStaticPropertyName(callee);
|
|
8310
|
+
if (receiver.name === "console") return NON_THROWING_CONSOLE_METHOD_NAMES.has(methodName ?? "");
|
|
8311
|
+
const firstArgument = callNode.arguments[0];
|
|
8312
|
+
return Boolean(receiver.name === "Math" && methodName === "round" && callNode.arguments.length === 1 && firstArgument && isProvenNonThrowingNumberExpression(firstArgument, scopes));
|
|
8313
|
+
};
|
|
8314
|
+
//#endregion
|
|
8265
8315
|
//#region src/plugin/utils/serialize-reference-key.ts
|
|
8266
8316
|
const serializeReferenceKey = ({ node, scopes }) => {
|
|
8267
8317
|
const expression = stripParenExpression(node);
|
|
@@ -9039,11 +9089,10 @@ const functionHasPotentialSynchronousThrow = (functionNode, classBody, scopes, b
|
|
|
9039
9089
|
}
|
|
9040
9090
|
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
9041
9091
|
if (cleanupReleaseKeys(candidate, scopes, classBody).length > 0) return;
|
|
9092
|
+
if (isProvenNonThrowingBuiltInCall(candidate, scopes)) return;
|
|
9042
9093
|
const callee = stripParenExpression(candidate.callee);
|
|
9043
9094
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
9044
|
-
|
|
9045
|
-
if (isNodeOfType(receiver, "Identifier") && receiver.name === "console" && scopes.isGlobalReference(receiver)) return;
|
|
9046
|
-
if (isNodeOfType(receiver, "ThisExpression")) {
|
|
9095
|
+
if (isNodeOfType(stripParenExpression(callee.object), "ThisExpression")) {
|
|
9047
9096
|
const memberName = getStaticPropertyName(callee);
|
|
9048
9097
|
const memberFunction = memberName ? classMemberFunction(classBody, memberName, candidate) : null;
|
|
9049
9098
|
if (memberFunction && !functionHasPotentialSynchronousThrow(memberFunction, classBody, scopes, Number.POSITIVE_INFINITY, new Set(visitedFunctions))) return;
|
|
@@ -13065,7 +13114,7 @@ const getOutermostTarget = (node) => {
|
|
|
13065
13114
|
}
|
|
13066
13115
|
return current;
|
|
13067
13116
|
};
|
|
13068
|
-
const getExecutionOwner = (node) => {
|
|
13117
|
+
const getExecutionOwner$1 = (node) => {
|
|
13069
13118
|
let current = node;
|
|
13070
13119
|
while (current) {
|
|
13071
13120
|
if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return current;
|
|
@@ -13207,7 +13256,7 @@ const getSymbolMutationInspector = (scopes) => {
|
|
|
13207
13256
|
const eventsBySymbolId = /* @__PURE__ */ new Map();
|
|
13208
13257
|
walkAst(scopes.rootScope.node, (node) => {
|
|
13209
13258
|
if (isNodeOfType(node, "CallExpression")) {
|
|
13210
|
-
const owner = getExecutionOwner(node);
|
|
13259
|
+
const owner = getExecutionOwner$1(node);
|
|
13211
13260
|
const targetOwner = getLocalCallTarget(node);
|
|
13212
13261
|
if (targetOwner && !isStaticallyUnreachable$1(node, owner)) calls.push({
|
|
13213
13262
|
call: node,
|
|
@@ -13220,7 +13269,7 @@ const getSymbolMutationInspector = (scopes) => {
|
|
|
13220
13269
|
if (propertyNames === void 0) return;
|
|
13221
13270
|
const symbol = resolveConstIdentifierAlias(node, scopes);
|
|
13222
13271
|
if (!symbol) return;
|
|
13223
|
-
const owner = getExecutionOwner(node);
|
|
13272
|
+
const owner = getExecutionOwner$1(node);
|
|
13224
13273
|
if (isStaticallyUnreachable$1(node, owner)) return;
|
|
13225
13274
|
const events = eventsBySymbolId.get(symbol.id) ?? [];
|
|
13226
13275
|
events.push({
|
|
@@ -13231,7 +13280,7 @@ const getSymbolMutationInspector = (scopes) => {
|
|
|
13231
13280
|
eventsBySymbolId.set(symbol.id, events);
|
|
13232
13281
|
});
|
|
13233
13282
|
const getInvokedOwnersBefore = (checkpoint) => {
|
|
13234
|
-
const checkpointOwner = getExecutionOwner(checkpoint);
|
|
13283
|
+
const checkpointOwner = getExecutionOwner$1(checkpoint);
|
|
13235
13284
|
const checkpointStartIndex = getNodeStartIndex(checkpoint);
|
|
13236
13285
|
const invokedOwners = /* @__PURE__ */ new Set();
|
|
13237
13286
|
const visitOwner = (owner, cutoffIndex) => {
|
|
@@ -13328,14 +13377,14 @@ const getSymbolMutationInspector = (scopes) => {
|
|
|
13328
13377
|
return false;
|
|
13329
13378
|
};
|
|
13330
13379
|
const isExecutionOrderAmbiguous = (usageNode) => {
|
|
13331
|
-
const usageOwner = getExecutionOwner(usageNode);
|
|
13380
|
+
const usageOwner = getExecutionOwner$1(usageNode);
|
|
13332
13381
|
if (isNodeOfType(usageOwner, "Program")) return false;
|
|
13333
13382
|
const reachingProgramCalls = calls.filter((call) => isNodeOfType(call.owner, "Program") && canOwnerReach(call.targetOwner, usageOwner));
|
|
13334
13383
|
if (reachingProgramCalls.length === 0) return false;
|
|
13335
13384
|
return reachingProgramCalls.length !== 1 || reachingProgramCalls[0]?.targetOwner !== usageOwner;
|
|
13336
13385
|
};
|
|
13337
13386
|
const isMutationOrderAmbiguous = (symbol, usageNode, relevantPropertyName) => {
|
|
13338
|
-
const usageOwner = getExecutionOwner(usageNode);
|
|
13387
|
+
const usageOwner = getExecutionOwner$1(usageNode);
|
|
13339
13388
|
const usageStartIndex = getNodeStartIndex(usageNode);
|
|
13340
13389
|
return (eventsBySymbolId.get(symbol.id) ?? []).some((event) => {
|
|
13341
13390
|
if (relevantPropertyName !== null && event.propertyNames !== null && !event.propertyNames.has(relevantPropertyName)) return false;
|
|
@@ -13370,7 +13419,7 @@ const getSymbolMutationInspector = (scopes) => {
|
|
|
13370
13419
|
visitOwner(operation.call.targetOwner, Number.POSITIVE_INFINITY, nextActiveOwners, isConditionalPath || isConditionallyExecuted(operation.call.call, operation.call.owner));
|
|
13371
13420
|
}
|
|
13372
13421
|
};
|
|
13373
|
-
const usageOwner = getExecutionOwner(usageNode);
|
|
13422
|
+
const usageOwner = getExecutionOwner$1(usageNode);
|
|
13374
13423
|
if (!isNodeOfType(usageOwner, "Program")) visitOwner(scopes.rootScope.node, getProgramCutoffIndex(usageOwner), /* @__PURE__ */ new Set(), false);
|
|
13375
13424
|
visitOwner(usageOwner, getNodeStartIndex(usageNode), /* @__PURE__ */ new Set(), false);
|
|
13376
13425
|
return mutationEvents;
|
|
@@ -13379,7 +13428,7 @@ const getSymbolMutationInspector = (scopes) => {
|
|
|
13379
13428
|
const events = eventsBySymbolId.get(symbol.id);
|
|
13380
13429
|
if (!events) return false;
|
|
13381
13430
|
const usageStartIndex = getNodeStartIndex(usageNode);
|
|
13382
|
-
const usageOwner = getExecutionOwner(usageNode);
|
|
13431
|
+
const usageOwner = getExecutionOwner$1(usageNode);
|
|
13383
13432
|
const invokedOwners = getInvokedOwnersBefore(usageNode);
|
|
13384
13433
|
return events.some((event) => {
|
|
13385
13434
|
if (relevantPropertyName !== null && event.propertyNames !== null && !event.propertyNames.has(relevantPropertyName)) return false;
|
|
@@ -18015,7 +18064,7 @@ const hasPotentialInterruptionAfterGuard = (callback, guardState, usageNode, con
|
|
|
18015
18064
|
if (child !== callback.body && isFunctionLike$1(child)) return false;
|
|
18016
18065
|
const childStart = getRangeStart(child);
|
|
18017
18066
|
if (childStart === null || childStart <= guardStart || childStart >= usageStart) return;
|
|
18018
|
-
if (isNodeOfType(child, "
|
|
18067
|
+
if (isNodeOfType(child, "AwaitExpression") || isNodeOfType(child, "YieldExpression") || isNodeOfType(child, "CallExpression") && !isProvenNonThrowingBuiltInCall(child, context.scopes)) {
|
|
18019
18068
|
if (canNodeReachLaterNodeWithinFunction(child, usageNode, callback, context) || canInterruptionReachUsageThroughCatch(child, usageNode, callback, context)) {
|
|
18020
18069
|
hasPotentialInterruption = true;
|
|
18021
18070
|
return false;
|
|
@@ -18181,7 +18230,7 @@ const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) =>
|
|
|
18181
18230
|
for (const argument of usage.node.arguments ?? []) walkAst(argument, (argumentChild) => {
|
|
18182
18231
|
if (hasPotentialInterruption) return false;
|
|
18183
18232
|
if (isFunctionLike$1(argumentChild)) return false;
|
|
18184
|
-
if (isNodeOfType(argumentChild, "
|
|
18233
|
+
if (isNodeOfType(argumentChild, "AwaitExpression") || isNodeOfType(argumentChild, "YieldExpression") || isNodeOfType(argumentChild, "CallExpression") && !isProvenNonThrowingBuiltInCall(argumentChild, context.scopes)) {
|
|
18185
18234
|
hasPotentialInterruption = true;
|
|
18186
18235
|
return false;
|
|
18187
18236
|
}
|
|
@@ -24018,22 +24067,26 @@ const isInsideTryStatement = (node, options) => {
|
|
|
24018
24067
|
return false;
|
|
24019
24068
|
};
|
|
24020
24069
|
//#endregion
|
|
24021
|
-
//#region src/plugin/utils/
|
|
24022
|
-
const
|
|
24070
|
+
//#region src/plugin/utils/get-conditional-execution-regions.ts
|
|
24071
|
+
const getConditionalExecutionRegions = (node, boundary) => {
|
|
24072
|
+
const regions = /* @__PURE__ */ new Set();
|
|
24023
24073
|
let child = node;
|
|
24024
24074
|
let parent = child.parent ?? null;
|
|
24025
24075
|
while (parent && parent !== boundary) {
|
|
24026
|
-
if (isNodeOfType(parent, "IfStatement") && parent.test !== child)
|
|
24027
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child))
|
|
24028
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child)
|
|
24029
|
-
if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child)
|
|
24030
|
-
if (isNodeOfType(parent, "SwitchCase"))
|
|
24076
|
+
if (isNodeOfType(parent, "IfStatement") && parent.test !== child) regions.add(child);
|
|
24077
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) regions.add(child);
|
|
24078
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) regions.add(child);
|
|
24079
|
+
if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) regions.add(child);
|
|
24080
|
+
if (isNodeOfType(parent, "SwitchCase")) regions.add(parent);
|
|
24031
24081
|
child = parent;
|
|
24032
24082
|
parent = child.parent ?? null;
|
|
24033
24083
|
}
|
|
24034
|
-
return
|
|
24084
|
+
return regions;
|
|
24035
24085
|
};
|
|
24036
24086
|
//#endregion
|
|
24087
|
+
//#region src/plugin/utils/is-node-conditionally-executed.ts
|
|
24088
|
+
const isNodeConditionallyExecuted = (node, boundary) => getConditionalExecutionRegions(node, boundary).size > 0;
|
|
24089
|
+
//#endregion
|
|
24037
24090
|
//#region src/plugin/utils/is-non-source-filename.ts
|
|
24038
24091
|
const NON_SOURCE_FILENAME_MARKERS = [
|
|
24039
24092
|
"/dist/",
|
|
@@ -63405,10 +63458,9 @@ const chainCarriesRejectionHandler = (node, scopes) => {
|
|
|
63405
63458
|
}
|
|
63406
63459
|
if (isNodeOfType(child, "CallExpression")) {
|
|
63407
63460
|
const callee = stripParenExpression(child.callee);
|
|
63408
|
-
const
|
|
63409
|
-
const isConsoleCall = isNodeOfType(receiver, "Identifier") && receiver.name === "console" && (!scopes || scopes.isGlobalReference(receiver));
|
|
63461
|
+
const isKnownNonThrowingBuiltInCall = Boolean(scopes && isProvenNonThrowingBuiltInCall(child, scopes));
|
|
63410
63462
|
const localFunction = scopes && isNodeOfType(callee, "Identifier") ? resolveExactLocalFunction(callee, scopes) : null;
|
|
63411
|
-
if (!
|
|
63463
|
+
if (!isKnownNonThrowingBuiltInCall && !isPromiseResolveCall(child, scopes) && !chainCarriesRejectionHandler(child, scopes) && (!localFunction || !scopes || subtreeCanThrowSynchronously(localFunction, localFunction, scopes))) {
|
|
63412
63464
|
canReject = true;
|
|
63413
63465
|
return false;
|
|
63414
63466
|
}
|
|
@@ -63885,6 +63937,7 @@ const helperHasUnhandledSynchronousCall = (helper, depth, scopes, visitedFunctio
|
|
|
63885
63937
|
ancestor = ancestor.parent ?? null;
|
|
63886
63938
|
}
|
|
63887
63939
|
if (isInsideNonRethrowingTry(child, helper)) return;
|
|
63940
|
+
if (scopes && isProvenNonThrowingBuiltInCall(child, scopes)) return;
|
|
63888
63941
|
if (isPromiseResolveCall(child, scopes) || chainCarriesRejectionHandler(child, scopes) || isSyncArrayLiteralMethodCall(child, scopes) || isThunkActionDispatchCall(child) || isNeverRejectingPromiseCombinatorCall(child, depth, scopes)) return;
|
|
63889
63942
|
const callee = stripParenExpression(child.callee);
|
|
63890
63943
|
if (scopes && isNodeOfType(callee, "Identifier") && isReactHookResultReference(callee, STATE_HOOK_NAMES, 1, scopes)) return;
|
|
@@ -63894,7 +63947,6 @@ const helperHasUnhandledSynchronousCall = (helper, depth, scopes, visitedFunctio
|
|
|
63894
63947
|
}
|
|
63895
63948
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
63896
63949
|
const receiver = stripParenExpression(callee.object);
|
|
63897
|
-
if (isNodeOfType(receiver, "Identifier") && receiver.name === "console" && (!scopes || scopes.isGlobalReference(receiver))) return;
|
|
63898
63950
|
if (getStaticPropertyName(callee) === "push" && isNodeOfType(receiver, "Identifier")) {
|
|
63899
63951
|
const receiverSymbol = scopes?.symbolFor(receiver);
|
|
63900
63952
|
if (isNodeOfType(receiverSymbol?.initializer ? stripParenExpression(receiverSymbol.initializer) : null, "ArrayExpression") && receiverSymbol?.references.every((reference) => {
|
|
@@ -64364,16 +64416,20 @@ const areOnExclusiveBranches = (first, second, functionNode) => {
|
|
|
64364
64416
|
};
|
|
64365
64417
|
const REACT_SETTER_CALLEE_PATTERN = /^set[A-Z]/;
|
|
64366
64418
|
const isProvenNonThrowingSynchronousCall = (callNode, context) => {
|
|
64419
|
+
if (isProvenNonThrowingBuiltInCall(callNode, context.scopes)) return true;
|
|
64367
64420
|
const callee = stripParenExpression(callNode.callee);
|
|
64368
64421
|
if (isNodeOfType(callee, "Identifier")) {
|
|
64369
64422
|
if (isReactHookResultReference(callee, STATE_HOOK_NAMES, 1, context.scopes) || context.scopes.isGlobalReference(callee) && REACT_SETTER_CALLEE_PATTERN.test(callee.name)) return true;
|
|
64423
|
+
if (context.scopes.isGlobalReference(callee) && callee.name === "String") {
|
|
64424
|
+
const firstArgument = callNode.arguments[0];
|
|
64425
|
+
const strippedArgument = firstArgument ? stripParenExpression(firstArgument) : null;
|
|
64426
|
+
return Boolean(callNode.arguments.length === 1 && strippedArgument && isNodeOfType(strippedArgument, "Identifier") && context.scopes.symbolFor(strippedArgument)?.kind === "catch-clause-parameter");
|
|
64427
|
+
}
|
|
64370
64428
|
const localFunction = resolveExactLocalFunction(callee, context.scopes);
|
|
64371
64429
|
if (localFunction && isFunctionLike$1(localFunction) && !localFunction.async) return !subtreeCanThrowSynchronously(localFunction, localFunction, context.scopes) && !helperHasUnhandledSynchronousCall(localFunction, NEVER_REJECTING_ANALYSIS_MAX_DEPTH, context.scopes);
|
|
64372
64430
|
return false;
|
|
64373
64431
|
}
|
|
64374
|
-
|
|
64375
|
-
const receiver = stripParenExpression(callee.object);
|
|
64376
|
-
return Boolean(isNodeOfType(receiver, "Identifier") && receiver.name === "console" && context.scopes.isGlobalReference(receiver));
|
|
64432
|
+
return false;
|
|
64377
64433
|
};
|
|
64378
64434
|
const subtreeHasAbruptSynchronousOperation = (root, functionBoundary, context) => {
|
|
64379
64435
|
let canCompleteAbruptly = false;
|
|
@@ -73602,14 +73658,6 @@ const REJECTING_PROMISE_COMBINATOR_NAMES = new Set([
|
|
|
73602
73658
|
const MAX_INITIATOR_RESOLUTION_DEPTH = 3;
|
|
73603
73659
|
const STATE_DISPATCHER_HOOK_NAMES = new Set(["useState", "useReducer"]);
|
|
73604
73660
|
const REF_HOOK_NAMES = new Set(["useRef"]);
|
|
73605
|
-
const NON_REJECTING_CONSOLE_METHOD_NAMES = new Set([
|
|
73606
|
-
"debug",
|
|
73607
|
-
"error",
|
|
73608
|
-
"info",
|
|
73609
|
-
"log",
|
|
73610
|
-
"trace",
|
|
73611
|
-
"warn"
|
|
73612
|
-
]);
|
|
73613
73661
|
const MESSAGE$26 = "This promise chain runs in an effect, ends in a `.then` that sets state or mutates a ref, and has no `.catch` or enclosing try/catch, so a rejection leaves the state unset and surfaces as an unhandled rejection. Add a `.catch` handler on the chain (`.finally` does not count).";
|
|
73614
73662
|
const isKnownNonThenableHandlerReturn = (expression, context, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
|
|
73615
73663
|
const strippedExpression = stripParenExpression(expression);
|
|
@@ -73646,6 +73694,10 @@ const isKnownNonRejectingHandler = (argument, context) => {
|
|
|
73646
73694
|
return false;
|
|
73647
73695
|
}
|
|
73648
73696
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
73697
|
+
if (isProvenNonThrowingBuiltInCall(child, context.scopes)) {
|
|
73698
|
+
didFindKnownNonRejectingCall = true;
|
|
73699
|
+
return;
|
|
73700
|
+
}
|
|
73649
73701
|
const callee = stripParenExpression(child.callee);
|
|
73650
73702
|
if (isNodeOfType(callee, "Identifier") && isReactHookResultReference(callee, STATE_DISPATCHER_HOOK_NAMES, 1, context.scopes)) {
|
|
73651
73703
|
if (context.scopes.symbolFor(callee)?.references.every((reference) => reference.flag === "read")) {
|
|
@@ -73653,13 +73705,6 @@ const isKnownNonRejectingHandler = (argument, context) => {
|
|
|
73653
73705
|
return;
|
|
73654
73706
|
}
|
|
73655
73707
|
}
|
|
73656
|
-
if (isNodeOfType(callee, "MemberExpression")) {
|
|
73657
|
-
const receiver = stripParenExpression(callee.object);
|
|
73658
|
-
if (isNodeOfType(receiver, "Identifier") && receiver.name === "console" && context.scopes.isGlobalReference(receiver) && NON_REJECTING_CONSOLE_METHOD_NAMES.has(getStaticPropertyName(callee) ?? "")) {
|
|
73659
|
-
didFindKnownNonRejectingCall = true;
|
|
73660
|
-
return;
|
|
73661
|
-
}
|
|
73662
|
-
}
|
|
73663
73708
|
canReject = true;
|
|
73664
73709
|
return false;
|
|
73665
73710
|
});
|
|
@@ -73668,11 +73713,10 @@ const isKnownNonRejectingHandler = (argument, context) => {
|
|
|
73668
73713
|
const isTrustedNonThrowingMethodCallee = (member, context) => {
|
|
73669
73714
|
const parent = member.parent;
|
|
73670
73715
|
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== member) return false;
|
|
73716
|
+
if (isProvenNonThrowingBuiltInCall(parent, context.scopes)) return true;
|
|
73671
73717
|
const receiver = stripParenExpression(member.object);
|
|
73672
73718
|
if (!isNodeOfType(receiver, "Identifier") || !context.scopes.isGlobalReference(receiver)) return false;
|
|
73673
|
-
|
|
73674
|
-
if (receiver.name === "console") return NON_REJECTING_CONSOLE_METHOD_NAMES.has(methodName ?? "");
|
|
73675
|
-
return receiver.name === "Promise" && methodName === "resolve";
|
|
73719
|
+
return receiver.name === "Promise" && getStaticPropertyName(member) === "resolve";
|
|
73676
73720
|
};
|
|
73677
73721
|
const handlerHasPotentiallyThrowingMemberRead = (argument, context) => {
|
|
73678
73722
|
if (!argument) return false;
|
|
@@ -91330,6 +91374,35 @@ const getModuleNamespaceSource = (expression, scopes, visitedSymbolIds = /* @__P
|
|
|
91330
91374
|
return getModuleNamespaceSource(symbol.initializer, scopes, visitedSymbolIds);
|
|
91331
91375
|
};
|
|
91332
91376
|
//#endregion
|
|
91377
|
+
//#region src/plugin/rules/r3f/utils/r3f-public-modules.ts
|
|
91378
|
+
const R3F_PUBLIC_MODULES = new Set([
|
|
91379
|
+
"@react-three/fiber",
|
|
91380
|
+
"@react-three/fiber/legacy",
|
|
91381
|
+
"@react-three/fiber/native",
|
|
91382
|
+
"@react-three/fiber/webgpu",
|
|
91383
|
+
"react-three-fiber"
|
|
91384
|
+
]);
|
|
91385
|
+
//#endregion
|
|
91386
|
+
//#region src/plugin/rules/r3f/utils/has-r3f-runtime-import.ts
|
|
91387
|
+
const isR3fRuntimeModule = (moduleSource) => R3F_PUBLIC_MODULES.has(moduleSource) || moduleSource.startsWith("@react-three/");
|
|
91388
|
+
const hasR3fRuntimeImport = (program, scopes) => program.body.some((statement) => {
|
|
91389
|
+
if (isNodeOfType(statement, "ImportDeclaration") && !isTypeOnlyImport(statement) && typeof statement.source.value === "string") return isR3fRuntimeModule(statement.source.value);
|
|
91390
|
+
if (isNodeOfType(statement, "TSImportEqualsDeclaration")) {
|
|
91391
|
+
const moduleSource = getModuleNamespaceSource(statement.id, scopes);
|
|
91392
|
+
return moduleSource !== null && isR3fRuntimeModule(moduleSource);
|
|
91393
|
+
}
|
|
91394
|
+
if (isNodeOfType(statement, "ExpressionStatement")) {
|
|
91395
|
+
const moduleSource = getGlobalRequireModuleSource(statement.expression, scopes);
|
|
91396
|
+
return moduleSource !== null && isR3fRuntimeModule(moduleSource);
|
|
91397
|
+
}
|
|
91398
|
+
if (!isNodeOfType(statement, "VariableDeclaration")) return false;
|
|
91399
|
+
return statement.declarations.some((declaration) => {
|
|
91400
|
+
if (!declaration.init) return false;
|
|
91401
|
+
const moduleSource = getGlobalRequireModuleSource(declaration.init, scopes);
|
|
91402
|
+
return moduleSource !== null && isR3fRuntimeModule(moduleSource);
|
|
91403
|
+
});
|
|
91404
|
+
});
|
|
91405
|
+
//#endregion
|
|
91333
91406
|
//#region src/plugin/rules/r3f/utils/get-api-reference-provenance.ts
|
|
91334
91407
|
const getApiReferenceProvenance = (reference, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
91335
91408
|
const candidate = stripParenExpression(reference);
|
|
@@ -91385,35 +91458,6 @@ const getApiReferenceProvenance = (reference, scopes, visitedSymbolIds = /* @__P
|
|
|
91385
91458
|
return getApiReferenceProvenance(symbol.initializer, scopes, visitedSymbolIds);
|
|
91386
91459
|
};
|
|
91387
91460
|
//#endregion
|
|
91388
|
-
//#region src/plugin/rules/r3f/utils/r3f-public-modules.ts
|
|
91389
|
-
const R3F_PUBLIC_MODULES = new Set([
|
|
91390
|
-
"@react-three/fiber",
|
|
91391
|
-
"@react-three/fiber/legacy",
|
|
91392
|
-
"@react-three/fiber/native",
|
|
91393
|
-
"@react-three/fiber/webgpu",
|
|
91394
|
-
"react-three-fiber"
|
|
91395
|
-
]);
|
|
91396
|
-
//#endregion
|
|
91397
|
-
//#region src/plugin/rules/r3f/utils/has-r3f-runtime-import.ts
|
|
91398
|
-
const isR3fRuntimeModule = (moduleSource) => R3F_PUBLIC_MODULES.has(moduleSource) || moduleSource.startsWith("@react-three/");
|
|
91399
|
-
const hasR3fRuntimeImport = (program, scopes) => program.body.some((statement) => {
|
|
91400
|
-
if (isNodeOfType(statement, "ImportDeclaration") && !isTypeOnlyImport(statement) && typeof statement.source.value === "string") return isR3fRuntimeModule(statement.source.value);
|
|
91401
|
-
if (isNodeOfType(statement, "TSImportEqualsDeclaration")) {
|
|
91402
|
-
const moduleSource = getModuleNamespaceSource(statement.id, scopes);
|
|
91403
|
-
return moduleSource !== null && isR3fRuntimeModule(moduleSource);
|
|
91404
|
-
}
|
|
91405
|
-
if (isNodeOfType(statement, "ExpressionStatement")) {
|
|
91406
|
-
const moduleSource = getGlobalRequireModuleSource(statement.expression, scopes);
|
|
91407
|
-
return moduleSource !== null && isR3fRuntimeModule(moduleSource);
|
|
91408
|
-
}
|
|
91409
|
-
if (!isNodeOfType(statement, "VariableDeclaration")) return false;
|
|
91410
|
-
return statement.declarations.some((declaration) => {
|
|
91411
|
-
if (!declaration.init) return false;
|
|
91412
|
-
const moduleSource = getGlobalRequireModuleSource(declaration.init, scopes);
|
|
91413
|
-
return moduleSource !== null && isR3fRuntimeModule(moduleSource);
|
|
91414
|
-
});
|
|
91415
|
-
});
|
|
91416
|
-
//#endregion
|
|
91417
91461
|
//#region src/plugin/rules/r3f/utils/is-r3f-canvas.ts
|
|
91418
91462
|
const isR3fCanvas = (node, context) => {
|
|
91419
91463
|
const provenance = getApiReferenceProvenance(node.name, context.scopes);
|
|
@@ -91501,6 +91545,43 @@ const resolveLocalReactCallback = (expression, scopes) => {
|
|
|
91501
91545
|
return resolveExactLocalFunction(wrappedCallback, scopes);
|
|
91502
91546
|
};
|
|
91503
91547
|
//#endregion
|
|
91548
|
+
//#region src/plugin/rules/r3f/utils/resolve-raw-device-pixel-ratio.ts
|
|
91549
|
+
const resolveRawDevicePixelRatio = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
91550
|
+
const candidate = stripParenExpression(expression);
|
|
91551
|
+
if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "+") return resolveRawDevicePixelRatio(candidate.argument, scopes, visitedSymbolIds);
|
|
91552
|
+
if (isNodeOfType(candidate, "BinaryExpression")) {
|
|
91553
|
+
const rawLeft = resolveRawDevicePixelRatio(candidate.left, scopes, new Set(visitedSymbolIds));
|
|
91554
|
+
const rawRight = resolveRawDevicePixelRatio(candidate.right, scopes, new Set(visitedSymbolIds));
|
|
91555
|
+
if (rawLeft && !rawRight) {
|
|
91556
|
+
const rightOperand = stripParenExpression(candidate.right);
|
|
91557
|
+
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;
|
|
91558
|
+
}
|
|
91559
|
+
if (rawRight && !rawLeft) {
|
|
91560
|
+
const leftOperand = stripParenExpression(candidate.left);
|
|
91561
|
+
if (isNodeOfType(leftOperand, "Literal") && typeof leftOperand.value === "number" && Number.isFinite(leftOperand.value) && (candidate.operator === "+" || candidate.operator === "*" && leftOperand.value > 0)) return rawRight;
|
|
91562
|
+
}
|
|
91563
|
+
return null;
|
|
91564
|
+
}
|
|
91565
|
+
if (isNodeOfType(candidate, "ArrayExpression") && candidate.elements.length === 2) {
|
|
91566
|
+
const upperBound = candidate.elements[1];
|
|
91567
|
+
return upperBound && !isNodeOfType(upperBound, "SpreadElement") ? resolveRawDevicePixelRatio(upperBound, scopes, new Set(visitedSymbolIds)) : null;
|
|
91568
|
+
}
|
|
91569
|
+
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
91570
|
+
const receiver = stripParenExpression(candidate.object);
|
|
91571
|
+
return getStaticPropertyName(candidate) === "devicePixelRatio" && isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && scopes.isGlobalReference(receiver) ? candidate : null;
|
|
91572
|
+
}
|
|
91573
|
+
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
91574
|
+
const symbol = scopes.symbolFor(candidate);
|
|
91575
|
+
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
|
|
91576
|
+
visitedSymbolIds.add(symbol.id);
|
|
91577
|
+
if (getDestructuredBindingPropertyName(symbol.bindingIdentifier) === "devicePixelRatio") {
|
|
91578
|
+
const initializer = stripParenExpression(symbol.initializer);
|
|
91579
|
+
if (isNodeOfType(initializer, "Identifier") && (initializer.name === "window" || initializer.name === "globalThis") && scopes.isGlobalReference(initializer)) return candidate;
|
|
91580
|
+
}
|
|
91581
|
+
if (symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
91582
|
+
return resolveRawDevicePixelRatio(symbol.initializer, scopes, visitedSymbolIds);
|
|
91583
|
+
};
|
|
91584
|
+
//#endregion
|
|
91504
91585
|
//#region src/plugin/rules/r3f/utils/walk-function-execution.ts
|
|
91505
91586
|
const isUseTransitionCall = (expression, scopes) => {
|
|
91506
91587
|
const candidate = stripParenExpression(expression);
|
|
@@ -91575,43 +91656,6 @@ const walkFunctionExecution = (functionNode, scopes, visitor) => {
|
|
|
91575
91656
|
};
|
|
91576
91657
|
//#endregion
|
|
91577
91658
|
//#region src/plugin/rules/r3f/r3f-cap-device-pixel-ratio.ts
|
|
91578
|
-
const THREE_RENDERER_CONSTRUCTOR_NAMES = new Set(["WebGLRenderer", "WebGPURenderer"]);
|
|
91579
|
-
const isThreeModuleSource$4 = (moduleSource) => moduleSource === "three" || moduleSource.startsWith("three/") || moduleSource === "three-stdlib";
|
|
91580
|
-
const resolveRawDevicePixelRatio = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
91581
|
-
const candidate = stripParenExpression(expression);
|
|
91582
|
-
if (isNodeOfType(candidate, "UnaryExpression") && candidate.operator === "+") return resolveRawDevicePixelRatio(candidate.argument, context, visitedSymbolIds);
|
|
91583
|
-
if (isNodeOfType(candidate, "BinaryExpression")) {
|
|
91584
|
-
const rawLeft = resolveRawDevicePixelRatio(candidate.left, context, new Set(visitedSymbolIds));
|
|
91585
|
-
const rawRight = resolveRawDevicePixelRatio(candidate.right, context, new Set(visitedSymbolIds));
|
|
91586
|
-
if (rawLeft && !rawRight) {
|
|
91587
|
-
const rightOperand = stripParenExpression(candidate.right);
|
|
91588
|
-
if (isNodeOfType(rightOperand, "Literal") && typeof rightOperand.value === "number" && Number.isFinite(rightOperand.value) && (candidate.operator === "+" || candidate.operator === "-" || (candidate.operator === "*" || candidate.operator === "/" || candidate.operator === "**") && rightOperand.value > 0)) return rawLeft;
|
|
91589
|
-
}
|
|
91590
|
-
if (rawRight && !rawLeft) {
|
|
91591
|
-
const leftOperand = stripParenExpression(candidate.left);
|
|
91592
|
-
if (isNodeOfType(leftOperand, "Literal") && typeof leftOperand.value === "number" && Number.isFinite(leftOperand.value) && (candidate.operator === "+" || candidate.operator === "*" && leftOperand.value > 0)) return rawRight;
|
|
91593
|
-
}
|
|
91594
|
-
return null;
|
|
91595
|
-
}
|
|
91596
|
-
if (isNodeOfType(candidate, "ArrayExpression") && candidate.elements.length === 2) {
|
|
91597
|
-
const upperBound = candidate.elements[1];
|
|
91598
|
-
return upperBound && !isNodeOfType(upperBound, "SpreadElement") ? resolveRawDevicePixelRatio(upperBound, context, new Set(visitedSymbolIds)) : null;
|
|
91599
|
-
}
|
|
91600
|
-
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
91601
|
-
const receiver = stripParenExpression(candidate.object);
|
|
91602
|
-
return getStaticPropertyName(candidate) === "devicePixelRatio" && isNodeOfType(receiver, "Identifier") && (receiver.name === "window" || receiver.name === "globalThis") && context.scopes.isGlobalReference(receiver) ? candidate : null;
|
|
91603
|
-
}
|
|
91604
|
-
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
91605
|
-
const symbol = context.scopes.symbolFor(candidate);
|
|
91606
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return null;
|
|
91607
|
-
visitedSymbolIds.add(symbol.id);
|
|
91608
|
-
if (getDestructuredBindingPropertyName(symbol.bindingIdentifier) === "devicePixelRatio") {
|
|
91609
|
-
const initializer = stripParenExpression(symbol.initializer);
|
|
91610
|
-
if (isNodeOfType(initializer, "Identifier") && (initializer.name === "window" || initializer.name === "globalThis") && context.scopes.isGlobalReference(initializer)) return candidate;
|
|
91611
|
-
}
|
|
91612
|
-
if (symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
91613
|
-
return resolveRawDevicePixelRatio(symbol.initializer, context, visitedSymbolIds);
|
|
91614
|
-
};
|
|
91615
91659
|
const getExplicitObjectPropertyValue = (expression, propertyName) => {
|
|
91616
91660
|
const candidate = stripParenExpression(expression);
|
|
91617
91661
|
if (!isNodeOfType(candidate, "ObjectExpression")) return null;
|
|
@@ -91644,18 +91688,6 @@ const isR3fRootReceiver = (expression, context, visitedSymbolIds = /* @__PURE__
|
|
|
91644
91688
|
if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
91645
91689
|
return isR3fRootReceiver(symbol.initializer, context, visitedSymbolIds);
|
|
91646
91690
|
};
|
|
91647
|
-
const isThreeRendererReceiver = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
91648
|
-
const candidate = stripParenExpression(expression);
|
|
91649
|
-
if (isNodeOfType(candidate, "NewExpression")) {
|
|
91650
|
-
const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
|
|
91651
|
-
return Boolean(provenance && isThreeModuleSource$4(provenance.moduleSource) && THREE_RENDERER_CONSTRUCTOR_NAMES.has(provenance.apiName));
|
|
91652
|
-
}
|
|
91653
|
-
if (!isNodeOfType(candidate, "Identifier")) return false;
|
|
91654
|
-
const symbol = context.scopes.symbolFor(candidate);
|
|
91655
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
91656
|
-
visitedSymbolIds.add(symbol.id);
|
|
91657
|
-
return isThreeRendererReceiver(symbol.initializer, context, visitedSymbolIds);
|
|
91658
|
-
};
|
|
91659
91691
|
const useThreeSelectsSetDpr = (call, context) => {
|
|
91660
91692
|
if (!isR3fApiCall(call, "useThree", context.scopes)) return false;
|
|
91661
91693
|
const selectorExpression = call.arguments[0];
|
|
@@ -91687,7 +91719,7 @@ const r3fCapDevicePixelRatio = defineRule({
|
|
|
91687
91719
|
create: (context) => {
|
|
91688
91720
|
let importsReactThreeFiber = false;
|
|
91689
91721
|
const reportRawDpr = (expression) => {
|
|
91690
|
-
const rawDpr = resolveRawDevicePixelRatio(expression, context);
|
|
91722
|
+
const rawDpr = resolveRawDevicePixelRatio(expression, context.scopes);
|
|
91691
91723
|
if (!rawDpr) return;
|
|
91692
91724
|
context.report({
|
|
91693
91725
|
node: rawDpr,
|
|
@@ -91716,10 +91748,6 @@ const r3fCapDevicePixelRatio = defineRule({
|
|
|
91716
91748
|
if (dprValue) reportRawDpr(dprValue);
|
|
91717
91749
|
return;
|
|
91718
91750
|
}
|
|
91719
|
-
if (methodName === "setPixelRatio" && isThreeRendererReceiver(node.callee.object, context)) {
|
|
91720
|
-
reportRawDpr(firstArgument);
|
|
91721
|
-
return;
|
|
91722
|
-
}
|
|
91723
91751
|
},
|
|
91724
91752
|
Identifier(node) {
|
|
91725
91753
|
const parent = node.parent;
|
|
@@ -91733,6 +91761,14 @@ const r3fCapDevicePixelRatio = defineRule({
|
|
|
91733
91761
|
});
|
|
91734
91762
|
//#endregion
|
|
91735
91763
|
//#region src/plugin/rules/r3f/constants.ts
|
|
91764
|
+
const THREE_INTERPOLATION_FACTOR_ARGUMENT_BY_METHOD = new Map([
|
|
91765
|
+
["lerp", 1],
|
|
91766
|
+
["lerpColors", 2],
|
|
91767
|
+
["lerpHSL", 1],
|
|
91768
|
+
["lerpVectors", 2],
|
|
91769
|
+
["slerp", 1],
|
|
91770
|
+
["slerpQuaternions", 2]
|
|
91771
|
+
]);
|
|
91736
91772
|
const THREE_POSTPROCESSING_PASS_DISPOSAL_RELEASES = new Map([
|
|
91737
91773
|
["RenderPixelatedPass", 147],
|
|
91738
91774
|
["OutputPass", 153],
|
|
@@ -92011,7 +92047,7 @@ const THREE_OBJECT_MEMBER_PROPERTIES = new Set([
|
|
|
92011
92047
|
"rotation",
|
|
92012
92048
|
"scale"
|
|
92013
92049
|
]);
|
|
92014
|
-
const hasThreeObjectProvenance = (expression, callback, managedRefSymbolIds, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
92050
|
+
const hasThreeObjectProvenance$1 = (expression, callback, managedRefSymbolIds, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
92015
92051
|
const firstCallbackParameter = isFunctionLike$1(callback) ? callback.params[0] : null;
|
|
92016
92052
|
const callbackParameter = isNodeOfType(firstCallbackParameter, "AssignmentPattern") ? firstCallbackParameter.left : firstCallbackParameter;
|
|
92017
92053
|
let current = stripParenExpression(expression);
|
|
@@ -92032,7 +92068,7 @@ const hasThreeObjectProvenance = (expression, callback, managedRefSymbolIds, con
|
|
|
92032
92068
|
const symbol = context.scopes.symbolFor(current);
|
|
92033
92069
|
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
92034
92070
|
visitedSymbolIds.add(symbol.id);
|
|
92035
|
-
return hasThreeObjectProvenance(symbol.initializer, callback, managedRefSymbolIds, context, visitedSymbolIds);
|
|
92071
|
+
return hasThreeObjectProvenance$1(symbol.initializer, callback, managedRefSymbolIds, context, visitedSymbolIds);
|
|
92036
92072
|
};
|
|
92037
92073
|
const r3fNoCloneInUseFrame = defineRule({
|
|
92038
92074
|
id: "r3f-no-clone-in-use-frame",
|
|
@@ -92049,7 +92085,7 @@ const r3fNoCloneInUseFrame = defineRule({
|
|
|
92049
92085
|
const callback = resolveR3fCallback(node, "useFrame", context.scopes);
|
|
92050
92086
|
if (!callback) return;
|
|
92051
92087
|
walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
|
|
92052
|
-
if (isConditionallyExecuted || !isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance(candidate.callee.object, callback, managedRefSymbolIds, context)) return;
|
|
92088
|
+
if (isConditionallyExecuted || !isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance$1(candidate.callee.object, callback, managedRefSymbolIds, context)) return;
|
|
92053
92089
|
context.report({
|
|
92054
92090
|
node: candidate,
|
|
92055
92091
|
message: "This clone allocates a new Three.js object every executed frame. Reuse a scratch object or clone once outside useFrame"
|
|
@@ -92720,6 +92756,9 @@ const r3fNoFreshUseThreeSelector = defineRule({
|
|
|
92720
92756
|
} })
|
|
92721
92757
|
});
|
|
92722
92758
|
//#endregion
|
|
92759
|
+
//#region src/plugin/rules/r3f/utils/is-three-module-source.ts
|
|
92760
|
+
const isThreeModuleSource$1 = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
|
|
92761
|
+
//#endregion
|
|
92723
92762
|
//#region src/plugin/rules/r3f/r3f-no-imperative-attach-of-managed-ref.ts
|
|
92724
92763
|
const IMPERATIVE_ATTACH_METHOD_NAMES = new Set(["add", "attach"]);
|
|
92725
92764
|
const THREE_OBJECT3D_CONSTRUCTOR_NAMES = new Set([
|
|
@@ -92748,12 +92787,11 @@ const THREE_OBJECT3D_CONSTRUCTOR_NAMES = new Set([
|
|
|
92748
92787
|
"SpotLight",
|
|
92749
92788
|
"Sprite"
|
|
92750
92789
|
]);
|
|
92751
|
-
const isThreeModuleSource$3 = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
|
|
92752
92790
|
const hasThreeObject3DProvenance = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
92753
92791
|
const candidate = stripParenExpression(expression);
|
|
92754
92792
|
if (isNodeOfType(candidate, "NewExpression")) {
|
|
92755
92793
|
const provenance = getApiReferenceProvenance(candidate.callee, scopes);
|
|
92756
|
-
return Boolean(provenance && isThreeModuleSource$
|
|
92794
|
+
return Boolean(provenance && isThreeModuleSource$1(provenance.moduleSource) && THREE_OBJECT3D_CONSTRUCTOR_NAMES.has(provenance.apiName));
|
|
92757
92795
|
}
|
|
92758
92796
|
if (!isNodeOfType(candidate, "Identifier")) return false;
|
|
92759
92797
|
const symbol = scopes.symbolFor(candidate);
|
|
@@ -92918,15 +92956,15 @@ const GEOMETRY_OWNER_CONSTRUCTORS = new Set([
|
|
|
92918
92956
|
"SkinnedMesh"
|
|
92919
92957
|
]);
|
|
92920
92958
|
const MATERIAL_OWNER_CONSTRUCTORS = new Set([...GEOMETRY_OWNER_CONSTRUCTORS, "Sprite"]);
|
|
92921
|
-
const isThreeModuleSource
|
|
92922
|
-
const getThreeConstructorName = (constructorExpression, scopes) => {
|
|
92959
|
+
const isThreeModuleSource = (source) => typeof source === "string" && (source === "three" || source.startsWith("three/") || source === "three-stdlib");
|
|
92960
|
+
const getThreeConstructorName$1 = (constructorExpression, scopes) => {
|
|
92923
92961
|
const provenance = getApiReferenceProvenance(stripParenExpression(constructorExpression), scopes);
|
|
92924
|
-
return provenance && isThreeModuleSource
|
|
92962
|
+
return provenance && isThreeModuleSource(provenance.moduleSource) ? provenance.apiName : null;
|
|
92925
92963
|
};
|
|
92926
92964
|
const hasThreeResourceOwnerProvenance = (expression, ownerConstructors, scopes, visitedSymbolIds) => {
|
|
92927
92965
|
const candidate = stripParenExpression(expression);
|
|
92928
92966
|
if (isNodeOfType(candidate, "NewExpression")) {
|
|
92929
|
-
const constructorName = getThreeConstructorName(candidate.callee, scopes);
|
|
92967
|
+
const constructorName = getThreeConstructorName$1(candidate.callee, scopes);
|
|
92930
92968
|
return Boolean(constructorName && ownerConstructors.has(constructorName));
|
|
92931
92969
|
}
|
|
92932
92970
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
@@ -92940,7 +92978,7 @@ const hasThreeResourceOwnerProvenance = (expression, ownerConstructors, scopes,
|
|
|
92940
92978
|
const getResourceMethods = (constructorSuffix) => constructorSuffix === "Geometry" ? GEOMETRY_RESOURCE_METHODS : MATERIAL_RESOURCE_METHODS;
|
|
92941
92979
|
const hasThreeResourceProvenance = (expression, constructorSuffix, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
92942
92980
|
const candidate = stripParenExpression(expression);
|
|
92943
|
-
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;
|
|
92944
92982
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
92945
92983
|
const symbol = scopes.symbolFor(candidate);
|
|
92946
92984
|
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
@@ -92980,7 +93018,7 @@ const hasProvenIndexedThreeGeometry = (expression, scopes, visitedSymbolIds = /*
|
|
|
92980
93018
|
};
|
|
92981
93019
|
const hasFreshThreeResource = (expression, constructorSuffix, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
92982
93020
|
const candidate = stripParenExpression(expression);
|
|
92983
|
-
if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
|
|
93021
|
+
if (isNodeOfType(candidate, "NewExpression")) return getThreeConstructorName$1(candidate.callee, scopes)?.endsWith(constructorSuffix) ?? false;
|
|
92984
93022
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
92985
93023
|
const symbol = scopes.symbolFor(candidate);
|
|
92986
93024
|
if (symbol?.kind !== "const" || symbol.scope.kind === "module" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
@@ -93599,32 +93637,39 @@ const isGlobalBrowserFunctionCall = (call, functionName, scopes) => {
|
|
|
93599
93637
|
return isNodeOfType(receiver, "Identifier") && (receiver.name === "globalThis" || receiver.name === "window") && scopes.isGlobalReference(receiver);
|
|
93600
93638
|
};
|
|
93601
93639
|
//#endregion
|
|
93602
|
-
//#region src/plugin/
|
|
93603
|
-
const EFFECT_HOOK_NAMES$3 = new Set([
|
|
93604
|
-
"useEffect",
|
|
93605
|
-
"useInsertionEffect",
|
|
93606
|
-
"useLayoutEffect"
|
|
93607
|
-
]);
|
|
93640
|
+
//#region src/plugin/utils/resolve-recursive-animation-frame-callback.ts
|
|
93608
93641
|
const getAnimationFrameCallback = (call, scopes) => {
|
|
93609
93642
|
const callbackArgument = call.arguments[0];
|
|
93610
|
-
|
|
93611
|
-
return resolveExactLocalFunction(callbackArgument, scopes);
|
|
93643
|
+
return callbackArgument && !isNodeOfType(callbackArgument, "SpreadElement") ? resolveExactLocalFunction(callbackArgument, scopes) : null;
|
|
93612
93644
|
};
|
|
93613
|
-
const
|
|
93645
|
+
const callbackSchedulesItself = (callback, scopes) => {
|
|
93614
93646
|
let doesScheduleItself = false;
|
|
93615
93647
|
walkAst(callback, (candidate) => {
|
|
93616
93648
|
if (doesScheduleItself || candidate !== callback && isFunctionLike$1(candidate)) return false;
|
|
93617
|
-
if (
|
|
93618
|
-
|
|
93649
|
+
if (isNodeOfType(candidate, "CallExpression") && isGlobalBrowserFunctionCall(candidate, "requestAnimationFrame", scopes) && getAnimationFrameCallback(candidate, scopes) === callback) {
|
|
93650
|
+
doesScheduleItself = true;
|
|
93651
|
+
return false;
|
|
93652
|
+
}
|
|
93619
93653
|
});
|
|
93620
93654
|
return doesScheduleItself;
|
|
93621
93655
|
};
|
|
93656
|
+
const resolveRecursiveAnimationFrameCallback = (call, scopes) => {
|
|
93657
|
+
if (!isGlobalBrowserFunctionCall(call, "requestAnimationFrame", scopes)) return null;
|
|
93658
|
+
const callback = getAnimationFrameCallback(call, scopes);
|
|
93659
|
+
return callback && callbackSchedulesItself(callback, scopes) ? callback : null;
|
|
93660
|
+
};
|
|
93661
|
+
//#endregion
|
|
93662
|
+
//#region src/plugin/rules/r3f/r3f-no-recursive-raf-with-use-frame.ts
|
|
93663
|
+
const EFFECT_HOOK_NAMES$3 = new Set([
|
|
93664
|
+
"useEffect",
|
|
93665
|
+
"useInsertionEffect",
|
|
93666
|
+
"useLayoutEffect"
|
|
93667
|
+
]);
|
|
93622
93668
|
const collectRecursiveAnimationFrameStarts = (executedFunction, scopes) => {
|
|
93623
93669
|
const starts = /* @__PURE__ */ new Set();
|
|
93624
93670
|
walkFunctionExecution(executedFunction, scopes, (candidate) => {
|
|
93625
93671
|
if (!isNodeOfType(candidate, "CallExpression") || !isGlobalBrowserFunctionCall(candidate, "requestAnimationFrame", scopes)) return;
|
|
93626
|
-
|
|
93627
|
-
if (callback && callbackDirectlySchedulesItself(callback, scopes)) starts.add(candidate);
|
|
93672
|
+
if (resolveRecursiveAnimationFrameCallback(candidate, scopes)) starts.add(candidate);
|
|
93628
93673
|
});
|
|
93629
93674
|
return starts;
|
|
93630
93675
|
};
|
|
@@ -94128,7 +94173,7 @@ const r3fNoStateInPointerMove = defineRule({
|
|
|
94128
94173
|
}
|
|
94129
94174
|
});
|
|
94130
94175
|
//#endregion
|
|
94131
|
-
//#region src/plugin/
|
|
94176
|
+
//#region src/plugin/utils/is-cpu-typed-array.ts
|
|
94132
94177
|
const CPU_TYPED_ARRAY_CONSTRUCTORS = new Set([
|
|
94133
94178
|
"BigInt64Array",
|
|
94134
94179
|
"BigUint64Array",
|
|
@@ -94142,44 +94187,61 @@ const CPU_TYPED_ARRAY_CONSTRUCTORS = new Set([
|
|
|
94142
94187
|
"Uint16Array",
|
|
94143
94188
|
"Uint32Array"
|
|
94144
94189
|
]);
|
|
94145
|
-
const
|
|
94190
|
+
const isCpuTypedArray = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
94191
|
+
const candidate = stripParenExpression(expression);
|
|
94192
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
94193
|
+
const symbol = scopes.symbolFor(candidate);
|
|
94194
|
+
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
94195
|
+
visitedSymbolIds.add(symbol.id);
|
|
94196
|
+
return isCpuTypedArray(symbol.initializer, scopes, visitedSymbolIds);
|
|
94197
|
+
}
|
|
94198
|
+
if (!isNodeOfType(candidate, "NewExpression")) return false;
|
|
94199
|
+
const callee = stripParenExpression(candidate.callee);
|
|
94200
|
+
return isNodeOfType(callee, "Identifier") && CPU_TYPED_ARRAY_CONSTRUCTORS.has(callee.name) && scopes.isGlobalReference(callee);
|
|
94201
|
+
};
|
|
94202
|
+
//#endregion
|
|
94203
|
+
//#region src/plugin/utils/is-webgl-context-reference.ts
|
|
94146
94204
|
const WEBGL_CONTEXT_NAMES = new Set([
|
|
94147
94205
|
"experimental-webgl",
|
|
94148
94206
|
"webgl",
|
|
94149
94207
|
"webgl2"
|
|
94150
94208
|
]);
|
|
94151
|
-
const
|
|
94209
|
+
const isWebglContextReference = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
94152
94210
|
const candidate = stripParenExpression(expression);
|
|
94153
94211
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
94154
94212
|
const symbol = scopes.symbolFor(candidate);
|
|
94155
94213
|
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
94156
94214
|
visitedSymbolIds.add(symbol.id);
|
|
94157
|
-
return
|
|
94215
|
+
return isWebglContextReference(symbol.initializer, scopes, visitedSymbolIds);
|
|
94158
94216
|
}
|
|
94159
94217
|
if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "getContext") return false;
|
|
94160
94218
|
const contextName = candidate.arguments[0];
|
|
94161
|
-
|
|
94162
|
-
|
|
94163
|
-
return Boolean(isNodeOfType(staticContextName, "Literal") && typeof staticContextName.value === "string" && contextNames.has(staticContextName.value));
|
|
94219
|
+
const staticContextName = contextName && !isNodeOfType(contextName, "SpreadElement") ? stripParenExpression(contextName) : null;
|
|
94220
|
+
return Boolean(staticContextName && isNodeOfType(staticContextName, "Literal") && typeof staticContextName.value === "string" && WEBGL_CONTEXT_NAMES.has(staticContextName.value));
|
|
94164
94221
|
};
|
|
94165
|
-
|
|
94222
|
+
//#endregion
|
|
94223
|
+
//#region src/plugin/rules/r3f/r3f-no-sync-readback-in-use-frame.ts
|
|
94224
|
+
const CANVAS_2D_CONTEXT_NAMES = new Set(["2d"]);
|
|
94225
|
+
const isContextFromGetContext = (expression, contextNames, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
94166
94226
|
const candidate = stripParenExpression(expression);
|
|
94167
94227
|
if (isNodeOfType(candidate, "Identifier")) {
|
|
94168
94228
|
const symbol = scopes.symbolFor(candidate);
|
|
94169
94229
|
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return false;
|
|
94170
94230
|
visitedSymbolIds.add(symbol.id);
|
|
94171
|
-
return
|
|
94231
|
+
return isContextFromGetContext(symbol.initializer, contextNames, scopes, visitedSymbolIds);
|
|
94172
94232
|
}
|
|
94173
|
-
if (!isNodeOfType(candidate, "
|
|
94174
|
-
const
|
|
94175
|
-
|
|
94233
|
+
if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "getContext") return false;
|
|
94234
|
+
const contextName = candidate.arguments[0];
|
|
94235
|
+
if (!contextName || isNodeOfType(contextName, "SpreadElement")) return false;
|
|
94236
|
+
const staticContextName = stripParenExpression(contextName);
|
|
94237
|
+
return Boolean(isNodeOfType(staticContextName, "Literal") && typeof staticContextName.value === "string" && contextNames.has(staticContextName.value));
|
|
94176
94238
|
};
|
|
94177
94239
|
const getReadbackKind = (node, callback, context) => {
|
|
94178
94240
|
if (!isNodeOfType(node.callee, "MemberExpression")) return null;
|
|
94179
94241
|
const methodName = getStaticPropertyName(node.callee);
|
|
94180
94242
|
if (methodName === "readRenderTargetPixels" && (isR3fCallbackStateProperty(node.callee.object, callback, "gl", context.scopes) || isR3fCallbackStateProperty(node.callee.object, callback, "renderer", context.scopes))) return "three";
|
|
94181
94243
|
if (methodName === "getImageData" && isContextFromGetContext(node.callee.object, CANVAS_2D_CONTEXT_NAMES, context.scopes)) return "canvas";
|
|
94182
|
-
if (methodName === "readPixels" &&
|
|
94244
|
+
if (methodName === "readPixels" && isWebglContextReference(node.callee.object, context.scopes)) {
|
|
94183
94245
|
const destination = node.arguments[6];
|
|
94184
94246
|
return destination && !isNodeOfType(destination, "SpreadElement") && isCpuTypedArray(destination, context.scopes) ? "webgl" : null;
|
|
94185
94247
|
}
|
|
@@ -94374,6 +94436,36 @@ const r3fPreferUseLoader = defineRule({
|
|
|
94374
94436
|
}
|
|
94375
94437
|
});
|
|
94376
94438
|
//#endregion
|
|
94439
|
+
//#region src/plugin/rules/r3f/utils/resolve-static-number.ts
|
|
94440
|
+
const resolveStaticNumber = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
94441
|
+
const candidate = stripParenExpression(expression);
|
|
94442
|
+
if (isNodeOfType(candidate, "Literal") && typeof candidate.value === "number") return Number.isFinite(candidate.value) ? candidate.value : null;
|
|
94443
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
94444
|
+
const symbol = scopes.symbolFor(candidate);
|
|
94445
|
+
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
94446
|
+
visitedSymbolIds.add(symbol.id);
|
|
94447
|
+
return resolveStaticNumber(symbol.initializer, scopes, visitedSymbolIds);
|
|
94448
|
+
}
|
|
94449
|
+
if (isNodeOfType(candidate, "UnaryExpression")) {
|
|
94450
|
+
const argument = resolveStaticNumber(candidate.argument, scopes, visitedSymbolIds);
|
|
94451
|
+
if (argument === null) return null;
|
|
94452
|
+
if (candidate.operator === "+") return argument;
|
|
94453
|
+
if (candidate.operator === "-") return -argument;
|
|
94454
|
+
return null;
|
|
94455
|
+
}
|
|
94456
|
+
if (!isNodeOfType(candidate, "BinaryExpression")) return null;
|
|
94457
|
+
const left = resolveStaticNumber(candidate.left, scopes, new Set(visitedSymbolIds));
|
|
94458
|
+
const right = resolveStaticNumber(candidate.right, scopes, new Set(visitedSymbolIds));
|
|
94459
|
+
if (left === null || right === null) return null;
|
|
94460
|
+
let result = null;
|
|
94461
|
+
if (candidate.operator === "+") result = left + right;
|
|
94462
|
+
if (candidate.operator === "-") result = left - right;
|
|
94463
|
+
if (candidate.operator === "*") result = left * right;
|
|
94464
|
+
if (candidate.operator === "/") result = left / right;
|
|
94465
|
+
if (candidate.operator === "**") result = left ** right;
|
|
94466
|
+
return result !== null && Number.isFinite(result) ? result : null;
|
|
94467
|
+
};
|
|
94468
|
+
//#endregion
|
|
94377
94469
|
//#region src/plugin/rules/r3f/r3f-require-frame-delta.ts
|
|
94378
94470
|
const TRANSFORM_PROPERTIES = new Set([
|
|
94379
94471
|
"position",
|
|
@@ -94436,34 +94528,6 @@ const expressionReferencesDelta = (expression, callback, context, visitedSymbolI
|
|
|
94436
94528
|
});
|
|
94437
94529
|
return referencesDelta;
|
|
94438
94530
|
};
|
|
94439
|
-
const resolveStaticNumber = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
94440
|
-
const candidate = stripParenExpression(expression);
|
|
94441
|
-
if (isNodeOfType(candidate, "Literal") && typeof candidate.value === "number") return Number.isFinite(candidate.value) ? candidate.value : null;
|
|
94442
|
-
if (isNodeOfType(candidate, "Identifier")) {
|
|
94443
|
-
const symbol = context.scopes.symbolFor(candidate);
|
|
94444
|
-
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
94445
|
-
visitedSymbolIds.add(symbol.id);
|
|
94446
|
-
return resolveStaticNumber(symbol.initializer, context, visitedSymbolIds);
|
|
94447
|
-
}
|
|
94448
|
-
if (isNodeOfType(candidate, "UnaryExpression")) {
|
|
94449
|
-
const argument = resolveStaticNumber(candidate.argument, context, visitedSymbolIds);
|
|
94450
|
-
if (argument === null) return null;
|
|
94451
|
-
if (candidate.operator === "+") return argument;
|
|
94452
|
-
if (candidate.operator === "-") return -argument;
|
|
94453
|
-
return null;
|
|
94454
|
-
}
|
|
94455
|
-
if (!isNodeOfType(candidate, "BinaryExpression")) return null;
|
|
94456
|
-
const left = resolveStaticNumber(candidate.left, context, new Set(visitedSymbolIds));
|
|
94457
|
-
const right = resolveStaticNumber(candidate.right, context, new Set(visitedSymbolIds));
|
|
94458
|
-
if (left === null || right === null) return null;
|
|
94459
|
-
let result = null;
|
|
94460
|
-
if (candidate.operator === "+") result = left + right;
|
|
94461
|
-
if (candidate.operator === "-") result = left - right;
|
|
94462
|
-
if (candidate.operator === "*") result = left * right;
|
|
94463
|
-
if (candidate.operator === "/") result = left / right;
|
|
94464
|
-
if (candidate.operator === "**") result = left ** right;
|
|
94465
|
-
return result !== null && Number.isFinite(result) ? result : null;
|
|
94466
|
-
};
|
|
94467
94531
|
const isThreeMathUtils = (expression, context) => {
|
|
94468
94532
|
return getApiReferenceModuleSource(expression, "MathUtils", context.scopes) === "three";
|
|
94469
94533
|
};
|
|
@@ -94523,7 +94587,7 @@ const isConditionallyExecutedOnlyByReactRefAvailability = (node, callback, conte
|
|
|
94523
94587
|
}
|
|
94524
94588
|
return didFindRefAvailabilityCondition;
|
|
94525
94589
|
};
|
|
94526
|
-
const getFixedInterpolationFactor = (node, callback, managedRefSymbolIds, context) => {
|
|
94590
|
+
const getFixedInterpolationFactor$1 = (node, callback, managedRefSymbolIds, context) => {
|
|
94527
94591
|
if (!isNodeOfType(node.callee, "MemberExpression")) return null;
|
|
94528
94592
|
const methodName = getStaticPropertyName(node.callee);
|
|
94529
94593
|
let factorArgumentIndex;
|
|
@@ -94532,7 +94596,7 @@ const getFixedInterpolationFactor = (node, callback, managedRefSymbolIds, contex
|
|
|
94532
94596
|
if (factorArgumentIndex === void 0) return null;
|
|
94533
94597
|
const factor = node.arguments[factorArgumentIndex];
|
|
94534
94598
|
if (!factor || isNodeOfType(factor, "SpreadElement")) return null;
|
|
94535
|
-
const staticFactor = resolveStaticNumber(factor, context);
|
|
94599
|
+
const staticFactor = resolveStaticNumber(factor, context.scopes);
|
|
94536
94600
|
return staticFactor !== null && staticFactor > 0 && staticFactor < 1 ? factor : null;
|
|
94537
94601
|
};
|
|
94538
94602
|
const getRotationOwner = (expression) => {
|
|
@@ -94606,7 +94670,7 @@ const r3fRequireFrameDelta = defineRule({
|
|
|
94606
94670
|
return;
|
|
94607
94671
|
}
|
|
94608
94672
|
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
94609
|
-
const factor = getFixedInterpolationFactor(candidate, callback, managedRefSymbolIds, context);
|
|
94673
|
+
const factor = getFixedInterpolationFactor$1(candidate, callback, managedRefSymbolIds, context);
|
|
94610
94674
|
if (!factor || expressionReferencesDelta(factor, callback, context) || isConditionallyExecuted && !isBehindReactRefAvailabilityGuard) return;
|
|
94611
94675
|
context.report({
|
|
94612
94676
|
node: factor,
|
|
@@ -95646,20 +95710,30 @@ const ownedResourceHasMethodCall = (analysis, methodName, scopes, matchesCall =
|
|
|
95646
95710
|
return false;
|
|
95647
95711
|
};
|
|
95648
95712
|
//#endregion
|
|
95713
|
+
//#region src/plugin/rules/r3f/utils/create-owned-three-resource-cleanup-visitors.ts
|
|
95714
|
+
const createOwnedThreeResourceCleanupVisitors = ({ analysisOptions, constructorNameSuffix, context, message }) => ({ NewExpression(node) {
|
|
95715
|
+
const provenance = getApiReferenceProvenance(node.callee, context.scopes);
|
|
95716
|
+
if (!provenance || !isThreeModuleSource$1(provenance.moduleSource) || !provenance.apiName.endsWith(constructorNameSuffix)) return;
|
|
95717
|
+
const ownership = analyzeOwnedLifecycleResource(node, context, analysisOptions);
|
|
95718
|
+
if (!ownership || ownership.hasUnknownOwnershipTransfer) return;
|
|
95719
|
+
const allocationFunction = findEnclosingFunction$1(node);
|
|
95720
|
+
if (allocationFunction && ownedResourceHasMethodCall(ownership, "dispose", context.scopes, (call) => call.range[0] > node.range[1] && findEnclosingFunction$1(call) === allocationFunction && !isNodeConditionallyExecuted(call, allocationFunction))) return;
|
|
95721
|
+
const cleanup = analyzeOwnedLifecycleCleanup(ownership, context, (cleanupFunction) => functionInvokesOwnedResourceMethod(cleanupFunction, ownership, "dispose", context.scopes));
|
|
95722
|
+
if (cleanup.isProven || cleanup.isUnknown) return;
|
|
95723
|
+
context.report({
|
|
95724
|
+
node,
|
|
95725
|
+
message
|
|
95726
|
+
});
|
|
95727
|
+
} });
|
|
95728
|
+
//#endregion
|
|
95649
95729
|
//#region src/plugin/rules/r3f/r3f-require-owned-texture-cleanup.ts
|
|
95650
|
-
const OWNED_TEXTURE_CONSTRUCTOR_NAMES = new Set([
|
|
95651
|
-
"CanvasTexture",
|
|
95652
|
-
"DataTexture",
|
|
95653
|
-
"Texture",
|
|
95654
|
-
"VideoTexture"
|
|
95655
|
-
]);
|
|
95656
95730
|
const TEXTURE_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
|
|
95657
95731
|
const isMaterialTexturePropertyName = (propertyName) => propertyName === "map" || propertyName === "matcap" || Boolean(propertyName?.endsWith("Map"));
|
|
95658
95732
|
const isThreeMaterialAllocation = (expression, context) => {
|
|
95659
95733
|
const candidate = stripParenExpression(expression);
|
|
95660
95734
|
if (!isNodeOfType(candidate, "NewExpression")) return false;
|
|
95661
95735
|
const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
|
|
95662
|
-
return Boolean(provenance
|
|
95736
|
+
return Boolean(provenance && isThreeModuleSource$1(provenance.moduleSource) && provenance.apiName.endsWith("Material"));
|
|
95663
95737
|
};
|
|
95664
95738
|
const expressionResolvesToThreeMaterial = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
95665
95739
|
const candidate = stripParenExpression(expression);
|
|
@@ -95722,27 +95796,21 @@ const r3fRequireOwnedTextureCleanup = defineRule({
|
|
|
95722
95796
|
category: "Performance",
|
|
95723
95797
|
severity: "warn",
|
|
95724
95798
|
recommendation: "Dispose locally constructed textures in a React effect cleanup",
|
|
95725
|
-
create: (context) => ({
|
|
95726
|
-
|
|
95727
|
-
if (provenance?.moduleSource !== "three" || !OWNED_TEXTURE_CONSTRUCTOR_NAMES.has(provenance.apiName)) return;
|
|
95728
|
-
const ownership = analyzeOwnedLifecycleResource(node, context, {
|
|
95799
|
+
create: (context) => createOwnedThreeResourceCleanupVisitors({
|
|
95800
|
+
analysisOptions: {
|
|
95729
95801
|
borrowedArgumentMethodNames: TEXTURE_BORROWING_METHOD_NAMES,
|
|
95730
95802
|
isBorrowedReference: (reference) => isBorrowedByThreeMaterial(reference, context),
|
|
95731
95803
|
retainsOwnershipInJsx: true
|
|
95732
|
-
}
|
|
95733
|
-
|
|
95734
|
-
|
|
95735
|
-
|
|
95736
|
-
|
|
95737
|
-
node,
|
|
95738
|
-
message: "This locally constructed Three.js texture owns GPU resources but has no provable React cleanup. Dispose it when the owning component or hook releases it"
|
|
95739
|
-
});
|
|
95740
|
-
} })
|
|
95804
|
+
},
|
|
95805
|
+
constructorNameSuffix: "Texture",
|
|
95806
|
+
context,
|
|
95807
|
+
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"
|
|
95808
|
+
})
|
|
95741
95809
|
});
|
|
95742
95810
|
//#endregion
|
|
95743
95811
|
//#region src/plugin/rules/r3f/r3f-require-projection-matrix-update.ts
|
|
95744
95812
|
const CAMERA_HOST_NAMES = new Set(["orthographicCamera", "perspectiveCamera"]);
|
|
95745
|
-
const PROJECTION_PROPERTY_NAMES = new Set([
|
|
95813
|
+
const PROJECTION_PROPERTY_NAMES$1 = new Set([
|
|
95746
95814
|
"aspect",
|
|
95747
95815
|
"bottom",
|
|
95748
95816
|
"far",
|
|
@@ -95805,7 +95873,7 @@ const hasManagedCameraRefProvenance = (expression, managedCameraRefSymbolIds, co
|
|
|
95805
95873
|
const hasR3fCameraProvenance = (expression, frameCallbacks, managedCameraRefSymbolIds, context) => hasStableRootBinding(expression, context.scopes) && (hasUseThreeCameraProvenance(expression, context) || hasFrameCameraProvenance(expression, frameCallbacks, context) || hasManagedCameraRefProvenance(expression, managedCameraRefSymbolIds, context));
|
|
95806
95874
|
const getProjectionMutationReceiver = (node) => {
|
|
95807
95875
|
const mutationTarget = isNodeOfType(node, "AssignmentExpression") ? stripParenExpression(node.left) : isNodeOfType(node, "UpdateExpression") ? stripParenExpression(node.argument) : null;
|
|
95808
|
-
return mutationTarget && isNodeOfType(mutationTarget, "MemberExpression") && PROJECTION_PROPERTY_NAMES.has(getStaticPropertyName(mutationTarget) ?? "") ? mutationTarget.object : null;
|
|
95876
|
+
return mutationTarget && isNodeOfType(mutationTarget, "MemberExpression") && PROJECTION_PROPERTY_NAMES$1.has(getStaticPropertyName(mutationTarget) ?? "") ? mutationTarget.object : null;
|
|
95809
95877
|
};
|
|
95810
95878
|
const getOnlyCallExpression = (statement) => {
|
|
95811
95879
|
if (isNodeOfType(statement, "BlockStatement")) return statement.body.length === 1 ? getOnlyCallExpression(statement.body[0]) : null;
|
|
@@ -96229,14 +96297,16 @@ const r3fWebgpuNoGlState = defineRule({
|
|
|
96229
96297
|
})
|
|
96230
96298
|
});
|
|
96231
96299
|
//#endregion
|
|
96232
|
-
//#region src/plugin/rules/r3f/
|
|
96233
|
-
const
|
|
96234
|
-
"
|
|
96235
|
-
"
|
|
96236
|
-
"
|
|
96237
|
-
"
|
|
96238
|
-
|
|
96239
|
-
|
|
96300
|
+
//#region src/plugin/rules/r3f/utils/get-control-flow-test.ts
|
|
96301
|
+
const getControlFlowTest = (node) => {
|
|
96302
|
+
if (isNodeOfType(node, "IfStatement") || isNodeOfType(node, "WhileStatement") || isNodeOfType(node, "DoWhileStatement") || isNodeOfType(node, "ConditionalExpression")) return node.test;
|
|
96303
|
+
if (isNodeOfType(node, "SwitchStatement")) return node.discriminant;
|
|
96304
|
+
if (isNodeOfType(node, "ForStatement")) return node.test;
|
|
96305
|
+
if (isNodeOfType(node, "LogicalExpression")) return node.left;
|
|
96306
|
+
return null;
|
|
96307
|
+
};
|
|
96308
|
+
//#endregion
|
|
96309
|
+
//#region src/plugin/rules/r3f/utils/resolves-to-tsl-uniform.ts
|
|
96240
96310
|
const TSL_UNIFORM_MODULES = new Set(["three/tsl", "three/webgpu"]);
|
|
96241
96311
|
const resolvesToTslUniform = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
96242
96312
|
const candidate = stripParenExpression(expression);
|
|
@@ -96247,6 +96317,15 @@ const resolvesToTslUniform = (expression, scopes, visitedSymbolIds = /* @__PURE_
|
|
|
96247
96317
|
visitedSymbolIds.add(symbol.id);
|
|
96248
96318
|
return resolvesToTslUniform(symbol.initializer, scopes, visitedSymbolIds);
|
|
96249
96319
|
};
|
|
96320
|
+
//#endregion
|
|
96321
|
+
//#region src/plugin/rules/r3f/r3f-webgpu-no-js-uniform-branch.ts
|
|
96322
|
+
const WEBGPU_GRAPH_HOOKS = new Set([
|
|
96323
|
+
"useLocalNodes",
|
|
96324
|
+
"useNodes",
|
|
96325
|
+
"usePostProcessing",
|
|
96326
|
+
"useRenderPipeline"
|
|
96327
|
+
]);
|
|
96328
|
+
const WEBGPU_TWO_CALLBACK_HOOKS = new Set(["usePostProcessing", "useRenderPipeline"]);
|
|
96250
96329
|
const isUniformValueMember = (expression, callback, scopes) => {
|
|
96251
96330
|
const candidate = stripParenExpression(expression);
|
|
96252
96331
|
if (!isNodeOfType(candidate, "MemberExpression") || getStaticPropertyName(candidate) !== "value") return false;
|
|
@@ -96277,13 +96356,6 @@ const expressionReferencesUniformValue = (expression, callback, scopes, visitedS
|
|
|
96277
96356
|
});
|
|
96278
96357
|
return didFindUniformValue;
|
|
96279
96358
|
};
|
|
96280
|
-
const getControlFlowTest = (node) => {
|
|
96281
|
-
if (isNodeOfType(node, "IfStatement") || isNodeOfType(node, "WhileStatement") || isNodeOfType(node, "DoWhileStatement") || isNodeOfType(node, "ConditionalExpression")) return node.test;
|
|
96282
|
-
if (isNodeOfType(node, "SwitchStatement")) return node.discriminant;
|
|
96283
|
-
if (isNodeOfType(node, "ForStatement")) return node.test;
|
|
96284
|
-
if (isNodeOfType(node, "LogicalExpression")) return node.left;
|
|
96285
|
-
return null;
|
|
96286
|
-
};
|
|
96287
96359
|
const getWebgpuGraphHookName = (node, context) => {
|
|
96288
96360
|
for (const hookName of WEBGPU_GRAPH_HOOKS) if (isApiCallFromModules(node, hookName, R3F_WEBGPU_MODULES, context.scopes)) return hookName;
|
|
96289
96361
|
return null;
|
|
@@ -113807,6 +113879,363 @@ const tenantStaticProxyRisk = defineRule({
|
|
|
113807
113879
|
})
|
|
113808
113880
|
});
|
|
113809
113881
|
//#endregion
|
|
113882
|
+
//#region src/plugin/rules/r3f/utils/get-three-constructor-name.ts
|
|
113883
|
+
const getThreeConstructorName = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
113884
|
+
const candidate = stripParenExpression(expression);
|
|
113885
|
+
if (isNodeOfType(candidate, "NewExpression")) {
|
|
113886
|
+
const provenance = getApiReferenceProvenance(candidate.callee, scopes);
|
|
113887
|
+
return provenance && isThreeModuleSource$1(provenance.moduleSource) ? provenance.apiName : null;
|
|
113888
|
+
}
|
|
113889
|
+
if (!isNodeOfType(candidate, "Identifier")) return null;
|
|
113890
|
+
const symbol = scopes.symbolFor(candidate);
|
|
113891
|
+
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
|
|
113892
|
+
visitedSymbolIds.add(symbol.id);
|
|
113893
|
+
return getThreeConstructorName(symbol.initializer, scopes, visitedSymbolIds);
|
|
113894
|
+
};
|
|
113895
|
+
//#endregion
|
|
113896
|
+
//#region src/plugin/rules/r3f/utils/is-three-renderer-reference.ts
|
|
113897
|
+
const THREE_RENDERER_CONSTRUCTOR_NAMES = new Set(["WebGLRenderer", "WebGPURenderer"]);
|
|
113898
|
+
const isThreeRendererReference = (expression, scopes) => THREE_RENDERER_CONSTRUCTOR_NAMES.has(getThreeConstructorName(expression, scopes) ?? "");
|
|
113899
|
+
//#endregion
|
|
113900
|
+
//#region src/plugin/rules/r3f/three-cap-device-pixel-ratio.ts
|
|
113901
|
+
const threeCapDevicePixelRatio = defineRule({
|
|
113902
|
+
id: "three-cap-device-pixel-ratio",
|
|
113903
|
+
title: "Unbounded Three.js device pixel ratio",
|
|
113904
|
+
category: "Performance",
|
|
113905
|
+
severity: "warn",
|
|
113906
|
+
recommendation: "Cap renderer pixel ratio, commonly at 2, so high-density displays do not multiply GPU work without a bound",
|
|
113907
|
+
create: (context) => ({ CallExpression(node) {
|
|
113908
|
+
if (!isNodeOfType(node.callee, "MemberExpression") || getStaticPropertyName(node.callee) !== "setPixelRatio" || !isThreeRendererReference(node.callee.object, context.scopes)) return;
|
|
113909
|
+
const pixelRatio = node.arguments[0];
|
|
113910
|
+
if (!pixelRatio || isNodeOfType(pixelRatio, "SpreadElement")) return;
|
|
113911
|
+
const rawPixelRatio = resolveRawDevicePixelRatio(pixelRatio, context.scopes);
|
|
113912
|
+
if (!rawPixelRatio) return;
|
|
113913
|
+
context.report({
|
|
113914
|
+
node: rawPixelRatio,
|
|
113915
|
+
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"
|
|
113916
|
+
});
|
|
113917
|
+
} })
|
|
113918
|
+
});
|
|
113919
|
+
//#endregion
|
|
113920
|
+
//#region src/plugin/rules/r3f/three-limit-shadowed-point-lights.ts
|
|
113921
|
+
const getExecutionOwner = (node, program) => findEnclosingFunction$1(node) ?? program;
|
|
113922
|
+
const threeLimitShadowedPointLights = defineRule({
|
|
113923
|
+
id: "three-limit-shadowed-point-lights",
|
|
113924
|
+
title: "Too many shadow-casting Three.js point lights",
|
|
113925
|
+
category: "Performance",
|
|
113926
|
+
severity: "warn",
|
|
113927
|
+
recommendation: "Keep at most two shadow-casting point lights in one scene, or use cheaper directional, spot, baked, or fake shadows",
|
|
113928
|
+
create: (context) => {
|
|
113929
|
+
const sceneLightFacts = [];
|
|
113930
|
+
const shadowedPointLights = [];
|
|
113931
|
+
let program = null;
|
|
113932
|
+
return {
|
|
113933
|
+
Program(node) {
|
|
113934
|
+
program = node;
|
|
113935
|
+
},
|
|
113936
|
+
CallExpression(node) {
|
|
113937
|
+
if (!program || !isNodeOfType(node.callee, "MemberExpression") || getStaticPropertyName(node.callee) !== "add" || getThreeConstructorName(node.callee.object, context.scopes) !== "Scene") return;
|
|
113938
|
+
const owner = getExecutionOwner(node, program);
|
|
113939
|
+
if (isNodeConditionallyExecuted(node, owner)) return;
|
|
113940
|
+
const sceneKey = resolveExpressionKey$1(node.callee.object, context);
|
|
113941
|
+
if (!sceneKey) return;
|
|
113942
|
+
for (const light of node.arguments) {
|
|
113943
|
+
if (isNodeOfType(light, "SpreadElement") || getThreeConstructorName(light, context.scopes) !== "PointLight") continue;
|
|
113944
|
+
const lightKey = resolveExpressionKey$1(light, context);
|
|
113945
|
+
if (lightKey) sceneLightFacts.push({
|
|
113946
|
+
lightKey,
|
|
113947
|
+
owner,
|
|
113948
|
+
sceneKey
|
|
113949
|
+
});
|
|
113950
|
+
}
|
|
113951
|
+
},
|
|
113952
|
+
AssignmentExpression(node) {
|
|
113953
|
+
const assignedValue = stripParenExpression(node.right);
|
|
113954
|
+
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;
|
|
113955
|
+
const owner = getExecutionOwner(node, program);
|
|
113956
|
+
if (isNodeConditionallyExecuted(node, owner)) return;
|
|
113957
|
+
const lightKey = resolveExpressionKey$1(node.left.object, context);
|
|
113958
|
+
if (lightKey) shadowedPointLights.push({
|
|
113959
|
+
lightKey,
|
|
113960
|
+
node,
|
|
113961
|
+
owner
|
|
113962
|
+
});
|
|
113963
|
+
},
|
|
113964
|
+
"Program:exit"() {
|
|
113965
|
+
const countByOwnerAndScene = /* @__PURE__ */ new Map();
|
|
113966
|
+
const countedLights = /* @__PURE__ */ new Map();
|
|
113967
|
+
for (const shadowedLight of shadowedPointLights) {
|
|
113968
|
+
const matchingScene = sceneLightFacts.find((sceneLight) => sceneLight.owner === shadowedLight.owner && sceneLight.lightKey === shadowedLight.lightKey);
|
|
113969
|
+
if (!matchingScene) continue;
|
|
113970
|
+
const ownerCounts = countByOwnerAndScene.get(shadowedLight.owner) ?? /* @__PURE__ */ new Map();
|
|
113971
|
+
const ownerLights = countedLights.get(shadowedLight.owner) ?? /* @__PURE__ */ new Map();
|
|
113972
|
+
countByOwnerAndScene.set(shadowedLight.owner, ownerCounts);
|
|
113973
|
+
countedLights.set(shadowedLight.owner, ownerLights);
|
|
113974
|
+
const sceneLights = ownerLights.get(matchingScene.sceneKey) ?? /* @__PURE__ */ new Set();
|
|
113975
|
+
ownerLights.set(matchingScene.sceneKey, sceneLights);
|
|
113976
|
+
if (sceneLights.has(shadowedLight.lightKey)) continue;
|
|
113977
|
+
sceneLights.add(shadowedLight.lightKey);
|
|
113978
|
+
const nextCount = (ownerCounts.get(matchingScene.sceneKey) ?? 0) + 1;
|
|
113979
|
+
ownerCounts.set(matchingScene.sceneKey, nextCount);
|
|
113980
|
+
if (nextCount <= 2) continue;
|
|
113981
|
+
context.report({
|
|
113982
|
+
node: shadowedLight.node,
|
|
113983
|
+
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"
|
|
113984
|
+
});
|
|
113985
|
+
}
|
|
113986
|
+
}
|
|
113987
|
+
};
|
|
113988
|
+
}
|
|
113989
|
+
});
|
|
113990
|
+
//#endregion
|
|
113991
|
+
//#region src/plugin/rules/r3f/utils/has-three-object-provenance.ts
|
|
113992
|
+
const hasThreeObjectProvenance = (expression, scopes) => {
|
|
113993
|
+
let candidate = stripParenExpression(expression);
|
|
113994
|
+
while (isNodeOfType(candidate, "MemberExpression")) candidate = stripParenExpression(candidate.object);
|
|
113995
|
+
return getThreeConstructorName(candidate, scopes) !== null;
|
|
113996
|
+
};
|
|
113997
|
+
//#endregion
|
|
113998
|
+
//#region src/plugin/rules/r3f/utils/resolve-three-pointer-move-callback.ts
|
|
113999
|
+
const callbackExecutesThreeWork = (callback, context) => {
|
|
114000
|
+
let executesThreeWork = false;
|
|
114001
|
+
walkFunctionExecution(callback, context.scopes, (candidate) => {
|
|
114002
|
+
if (executesThreeWork) return;
|
|
114003
|
+
if (isNodeOfType(candidate, "NewExpression")) {
|
|
114004
|
+
const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
|
|
114005
|
+
executesThreeWork = Boolean(provenance && isThreeModuleSource$1(provenance.moduleSource));
|
|
114006
|
+
return;
|
|
114007
|
+
}
|
|
114008
|
+
if (isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression")) {
|
|
114009
|
+
executesThreeWork = hasThreeObjectProvenance(candidate.callee.object, context.scopes);
|
|
114010
|
+
return;
|
|
114011
|
+
}
|
|
114012
|
+
const target = isNodeOfType(candidate, "AssignmentExpression") ? stripParenExpression(candidate.left) : isNodeOfType(candidate, "UpdateExpression") ? stripParenExpression(candidate.argument) : null;
|
|
114013
|
+
executesThreeWork = Boolean(target && isNodeOfType(target, "MemberExpression") && hasThreeObjectProvenance(target.object, context.scopes));
|
|
114014
|
+
});
|
|
114015
|
+
return executesThreeWork;
|
|
114016
|
+
};
|
|
114017
|
+
const getPointerMoveListenerCallback = (node, context) => {
|
|
114018
|
+
if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "MemberExpression") || getStaticPropertyName(node.callee) !== "addEventListener") return null;
|
|
114019
|
+
const eventName = node.arguments[0];
|
|
114020
|
+
const callback = node.arguments[1];
|
|
114021
|
+
const listenerTarget = stripParenExpression(node.callee.object);
|
|
114022
|
+
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;
|
|
114023
|
+
return resolveLocalReactCallback(callback, context.scopes);
|
|
114024
|
+
};
|
|
114025
|
+
const resolveThreePointerMoveCallback = (node, context) => {
|
|
114026
|
+
if (isNodeOfType(node, "CallExpression")) return getPointerMoveListenerCallback(node, context);
|
|
114027
|
+
if (!isNodeOfType(node, "JSXOpeningElement") || resolveJsxElementType(node) !== "canvas") return null;
|
|
114028
|
+
const attribute = getAuthoritativeJsxAttribute(node.attributes, "onPointerMove");
|
|
114029
|
+
if (!attribute?.value || !isNodeOfType(attribute.value, "JSXExpressionContainer") || isNodeOfType(attribute.value.expression, "JSXEmptyExpression")) return null;
|
|
114030
|
+
const callback = resolveLocalReactCallback(attribute.value.expression, context.scopes);
|
|
114031
|
+
return callback && callbackExecutesThreeWork(callback, context) ? callback : null;
|
|
114032
|
+
};
|
|
114033
|
+
//#endregion
|
|
114034
|
+
//#region src/plugin/rules/r3f/three-no-allocation-in-pointer-move.ts
|
|
114035
|
+
const threeNoAllocationInPointerMove = defineRule({
|
|
114036
|
+
id: "three-no-allocation-in-pointer-move",
|
|
114037
|
+
title: "Three.js allocation inside pointer-move handler",
|
|
114038
|
+
severity: "warn",
|
|
114039
|
+
recommendation: "Reuse vectors, raycasters, and other Three.js objects while handling continuous pointer movement",
|
|
114040
|
+
create: (context) => {
|
|
114041
|
+
const analyzedCallbacks = /* @__PURE__ */ new Set();
|
|
114042
|
+
const analyzeCallback = (node) => {
|
|
114043
|
+
const callback = resolveThreePointerMoveCallback(node, context);
|
|
114044
|
+
if (!callback || analyzedCallbacks.has(callback)) return;
|
|
114045
|
+
analyzedCallbacks.add(callback);
|
|
114046
|
+
walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
|
|
114047
|
+
if (isConditionallyExecuted) return;
|
|
114048
|
+
if (isNodeOfType(candidate, "NewExpression")) {
|
|
114049
|
+
const provenance = getApiReferenceProvenance(candidate.callee, context.scopes);
|
|
114050
|
+
if (!provenance || !isThreeModuleSource$1(provenance.moduleSource)) return;
|
|
114051
|
+
context.report({
|
|
114052
|
+
node: candidate,
|
|
114053
|
+
message: "This Three.js constructor allocates on every pointer movement. Reuse an object created outside the handler"
|
|
114054
|
+
});
|
|
114055
|
+
return;
|
|
114056
|
+
}
|
|
114057
|
+
if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance(candidate.callee.object, context.scopes)) return;
|
|
114058
|
+
context.report({
|
|
114059
|
+
node: candidate,
|
|
114060
|
+
message: "This clone allocates a Three.js object on every pointer movement. Copy into a reusable object instead"
|
|
114061
|
+
});
|
|
114062
|
+
});
|
|
114063
|
+
};
|
|
114064
|
+
return {
|
|
114065
|
+
CallExpression(node) {
|
|
114066
|
+
analyzeCallback(node);
|
|
114067
|
+
},
|
|
114068
|
+
JSXOpeningElement(node) {
|
|
114069
|
+
analyzeCallback(node);
|
|
114070
|
+
}
|
|
114071
|
+
};
|
|
114072
|
+
}
|
|
114073
|
+
});
|
|
114074
|
+
//#endregion
|
|
114075
|
+
//#region src/plugin/rules/r3f/utils/resolve-three-animation-loop-callback.ts
|
|
114076
|
+
const callbackRendersWithThree = (callback, scopes) => {
|
|
114077
|
+
let doesRenderWithThree = false;
|
|
114078
|
+
walkFunctionExecution(callback, scopes, (candidate) => {
|
|
114079
|
+
if (!doesRenderWithThree && isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression") && THREE_RENDER_METHOD_NAMES.has(getStaticPropertyName(candidate.callee) ?? "") && isThreeRendererReference(candidate.callee.object, scopes)) doesRenderWithThree = true;
|
|
114080
|
+
});
|
|
114081
|
+
return doesRenderWithThree;
|
|
114082
|
+
};
|
|
114083
|
+
const resolveThreeAnimationLoopCallback = (call, scopes) => {
|
|
114084
|
+
if (isNodeOfType(call.callee, "MemberExpression") && getStaticPropertyName(call.callee) === "setAnimationLoop" && isThreeRendererReference(call.callee.object, scopes)) {
|
|
114085
|
+
const callbackArgument = call.arguments[0];
|
|
114086
|
+
return callbackArgument && !isNodeOfType(callbackArgument, "SpreadElement") ? resolveLocalReactCallback(callbackArgument, scopes) : null;
|
|
114087
|
+
}
|
|
114088
|
+
const callback = resolveRecursiveAnimationFrameCallback(call, scopes);
|
|
114089
|
+
return callback && callbackRendersWithThree(callback, scopes) ? callback : null;
|
|
114090
|
+
};
|
|
114091
|
+
//#endregion
|
|
114092
|
+
//#region src/plugin/rules/r3f/three-no-async-animation-loop.ts
|
|
114093
|
+
const threeNoAsyncAnimationLoop = defineRule({
|
|
114094
|
+
id: "three-no-async-animation-loop",
|
|
114095
|
+
title: "Async Three.js animation callback",
|
|
114096
|
+
category: "Correctness",
|
|
114097
|
+
severity: "warn",
|
|
114098
|
+
recommendation: "Keep animation callbacks synchronous; start asynchronous work outside the loop and consume completed state during frames",
|
|
114099
|
+
create: (context) => {
|
|
114100
|
+
const reportedCallbacks = /* @__PURE__ */ new Set();
|
|
114101
|
+
return { CallExpression(node) {
|
|
114102
|
+
const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
|
|
114103
|
+
if (!isFunctionLike$1(callback) || !callback.async || reportedCallbacks.has(callback)) return;
|
|
114104
|
+
reportedCallbacks.add(callback);
|
|
114105
|
+
context.report({
|
|
114106
|
+
node: callback,
|
|
114107
|
+
message: "The animation scheduler ignores this Promise, so rejected work can become unhandled and awaited work can overlap across frames. Keep the callback synchronous"
|
|
114108
|
+
});
|
|
114109
|
+
} };
|
|
114110
|
+
}
|
|
114111
|
+
});
|
|
114112
|
+
//#endregion
|
|
114113
|
+
//#region src/plugin/rules/r3f/three-no-clone-in-animation-loop.ts
|
|
114114
|
+
const threeNoCloneInAnimationLoop = defineRule({
|
|
114115
|
+
id: "three-no-clone-in-animation-loop",
|
|
114116
|
+
title: "Three.js clone inside animation loop",
|
|
114117
|
+
severity: "warn",
|
|
114118
|
+
recommendation: "Clone once before the animation loop or copy values into a reusable scratch object during frames",
|
|
114119
|
+
create: (context) => {
|
|
114120
|
+
const analyzedCallbacks = /* @__PURE__ */ new Set();
|
|
114121
|
+
return { CallExpression(node) {
|
|
114122
|
+
const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
|
|
114123
|
+
if (!callback || analyzedCallbacks.has(callback)) return;
|
|
114124
|
+
analyzedCallbacks.add(callback);
|
|
114125
|
+
walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
|
|
114126
|
+
if (isConditionallyExecuted || !isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || getStaticPropertyName(candidate.callee) !== "clone" || !hasThreeObjectProvenance(candidate.callee.object, context.scopes)) return;
|
|
114127
|
+
context.report({
|
|
114128
|
+
node: candidate,
|
|
114129
|
+
message: "This clone allocates a new Three.js object every executed frame. Copy into a reusable object or clone once outside the animation loop"
|
|
114130
|
+
});
|
|
114131
|
+
});
|
|
114132
|
+
} };
|
|
114133
|
+
}
|
|
114134
|
+
});
|
|
114135
|
+
//#endregion
|
|
114136
|
+
//#region src/plugin/rules/r3f/three-no-new-in-animation-loop.ts
|
|
114137
|
+
const threeNoNewInAnimationLoop = defineRule({
|
|
114138
|
+
id: "three-no-new-in-animation-loop",
|
|
114139
|
+
title: "Allocation inside Three.js animation loop",
|
|
114140
|
+
severity: "warn",
|
|
114141
|
+
recommendation: "Allocate reusable objects before the animation loop and mutate them in place during each frame",
|
|
114142
|
+
create: (context) => {
|
|
114143
|
+
const analyzedCallbacks = /* @__PURE__ */ new Set();
|
|
114144
|
+
return { CallExpression(node) {
|
|
114145
|
+
const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
|
|
114146
|
+
if (!callback || analyzedCallbacks.has(callback)) return;
|
|
114147
|
+
analyzedCallbacks.add(callback);
|
|
114148
|
+
walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
|
|
114149
|
+
if (candidate.type !== "NewExpression" || isConditionallyExecuted) return;
|
|
114150
|
+
context.report({
|
|
114151
|
+
node: candidate,
|
|
114152
|
+
message: "This constructor allocates a new object every executed frame. Reuse an object allocated outside the Three.js animation loop"
|
|
114153
|
+
});
|
|
114154
|
+
});
|
|
114155
|
+
} };
|
|
114156
|
+
}
|
|
114157
|
+
});
|
|
114158
|
+
//#endregion
|
|
114159
|
+
//#region src/plugin/utils/function-is-referenced-as-jsx-element.ts
|
|
114160
|
+
const functionIsReferencedAsJsxElement = (functionNode, scopes) => getFunctionBindingSymbols(functionNode, scopes).some((symbol) => symbol.references.some((reference) => {
|
|
114161
|
+
const referenceNode = reference.identifier;
|
|
114162
|
+
const parentNode = referenceNode.parent;
|
|
114163
|
+
return Boolean(isNodeOfType(referenceNode, "JSXIdentifier") && parentNode && isNodeOfType(parentNode, "JSXOpeningElement") && parentNode.name === referenceNode);
|
|
114164
|
+
}));
|
|
114165
|
+
//#endregion
|
|
114166
|
+
//#region src/plugin/rules/r3f/three-no-object-construction-in-render.ts
|
|
114167
|
+
const threeNoObjectConstructionInRender = defineRule({
|
|
114168
|
+
id: "three-no-object-construction-in-render",
|
|
114169
|
+
title: "Three.js object constructed during React render",
|
|
114170
|
+
severity: "warn",
|
|
114171
|
+
recommendation: "Construct mutable Three.js objects in a stable initializer, effect, event, or module scope instead of recreating them during React render",
|
|
114172
|
+
create: (context) => ({ NewExpression(node) {
|
|
114173
|
+
const provenance = getApiReferenceProvenance(node.callee, context.scopes);
|
|
114174
|
+
const renderOwner = findRenderPhaseComponentOrHook(node, context.scopes);
|
|
114175
|
+
if (!provenance || !isThreeModuleSource$1(provenance.moduleSource) || !renderOwner || isInsideStableReactInitializer(node, context.scopes)) return;
|
|
114176
|
+
const renderOwnerName = componentOrHookDisplayNameForFunction(renderOwner);
|
|
114177
|
+
if (!renderOwnerName || !isReactHookName(renderOwnerName) && !functionHasReactComponentEvidence(renderOwner, context.scopes, context.cfg) && !functionIsReferencedAsJsxElement(renderOwner, context.scopes)) return;
|
|
114178
|
+
context.report({
|
|
114179
|
+
node,
|
|
114180
|
+
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`
|
|
114181
|
+
});
|
|
114182
|
+
} })
|
|
114183
|
+
});
|
|
114184
|
+
//#endregion
|
|
114185
|
+
//#region src/plugin/rules/r3f/three-no-state-in-animation-loop.ts
|
|
114186
|
+
const threeNoStateInAnimationLoop = defineRule({
|
|
114187
|
+
id: "three-no-state-in-animation-loop",
|
|
114188
|
+
title: "React state update inside Three.js animation loop",
|
|
114189
|
+
severity: "warn",
|
|
114190
|
+
recommendation: "Mutate Three.js objects or refs during frames; reserve React state for guarded, infrequent transitions",
|
|
114191
|
+
create: (context) => {
|
|
114192
|
+
const analyzedCallbacks = /* @__PURE__ */ new Set();
|
|
114193
|
+
return { CallExpression(node) {
|
|
114194
|
+
const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
|
|
114195
|
+
if (!callback || analyzedCallbacks.has(callback)) return;
|
|
114196
|
+
analyzedCallbacks.add(callback);
|
|
114197
|
+
walkFunctionExecution(callback, context.scopes, (candidate) => {
|
|
114198
|
+
if (!isNodeOfType(candidate, "CallExpression") || !resolveStateSetterBinding(candidate.callee, context.scopes) || isGuardedStateTransition(candidate, callback, context.scopes)) return;
|
|
114199
|
+
context.report({
|
|
114200
|
+
node: candidate,
|
|
114201
|
+
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"
|
|
114202
|
+
});
|
|
114203
|
+
});
|
|
114204
|
+
} };
|
|
114205
|
+
}
|
|
114206
|
+
});
|
|
114207
|
+
//#endregion
|
|
114208
|
+
//#region src/plugin/rules/r3f/three-no-state-in-pointer-move.ts
|
|
114209
|
+
const threeNoStateInPointerMove = defineRule({
|
|
114210
|
+
id: "three-no-state-in-pointer-move",
|
|
114211
|
+
title: "React state update inside Three.js pointer-move handler",
|
|
114212
|
+
severity: "warn",
|
|
114213
|
+
recommendation: "Keep continuous pointer previews in Three.js objects or refs and publish React state when the interaction commits",
|
|
114214
|
+
create: (context) => {
|
|
114215
|
+
const analyzedCallbacks = /* @__PURE__ */ new Set();
|
|
114216
|
+
const analyzeCallback = (node) => {
|
|
114217
|
+
const callback = resolveThreePointerMoveCallback(node, context);
|
|
114218
|
+
if (!callback || analyzedCallbacks.has(callback)) return;
|
|
114219
|
+
analyzedCallbacks.add(callback);
|
|
114220
|
+
walkFunctionExecution(callback, context.scopes, (candidate) => {
|
|
114221
|
+
if (!isNodeOfType(candidate, "CallExpression") || !resolveStateSetterBinding(candidate.callee, context.scopes) || isGuardedStateTransition(candidate, callback, context.scopes)) return;
|
|
114222
|
+
context.report({
|
|
114223
|
+
node: candidate,
|
|
114224
|
+
message: "This React state update can render on every pointer movement. Mutate a Three.js object or ref and publish state on pointer-up"
|
|
114225
|
+
});
|
|
114226
|
+
});
|
|
114227
|
+
};
|
|
114228
|
+
return {
|
|
114229
|
+
CallExpression(node) {
|
|
114230
|
+
analyzeCallback(node);
|
|
114231
|
+
},
|
|
114232
|
+
JSXOpeningElement(node) {
|
|
114233
|
+
analyzeCallback(node);
|
|
114234
|
+
}
|
|
114235
|
+
};
|
|
114236
|
+
}
|
|
114237
|
+
});
|
|
114238
|
+
//#endregion
|
|
113810
114239
|
//#region src/plugin/rules/r3f/three-require-animation-mixer-cleanup.ts
|
|
113811
114240
|
const MIXER_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
|
|
113812
114241
|
const FINE_GRAINED_UNCACHE_METHOD_NAMES = ["uncacheAction", "uncacheClip"];
|
|
@@ -113914,6 +114343,247 @@ const threeRequireControlsCleanup = defineRule({
|
|
|
113914
114343
|
} })
|
|
113915
114344
|
});
|
|
113916
114345
|
//#endregion
|
|
114346
|
+
//#region src/plugin/rules/r3f/three-require-frame-delta.ts
|
|
114347
|
+
const TRANSFORM_PROPERTY_NAMES = new Set([
|
|
114348
|
+
"position",
|
|
114349
|
+
"quaternion",
|
|
114350
|
+
"rotation",
|
|
114351
|
+
"scale"
|
|
114352
|
+
]);
|
|
114353
|
+
const isThreeTransformMember = (expression, context) => {
|
|
114354
|
+
let candidate = stripParenExpression(expression);
|
|
114355
|
+
let hasTransformProperty = false;
|
|
114356
|
+
while (isNodeOfType(candidate, "MemberExpression")) {
|
|
114357
|
+
if (TRANSFORM_PROPERTY_NAMES.has(getStaticPropertyName(candidate) ?? "")) hasTransformProperty = true;
|
|
114358
|
+
candidate = stripParenExpression(candidate.object);
|
|
114359
|
+
}
|
|
114360
|
+
return hasTransformProperty && hasThreeObjectProvenance(expression, context.scopes);
|
|
114361
|
+
};
|
|
114362
|
+
const expressionUsesThreeClockDelta = (expression, context, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
114363
|
+
let usesDelta = false;
|
|
114364
|
+
walkAst(expression, (candidate) => {
|
|
114365
|
+
if (isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression") && getStaticPropertyName(candidate.callee) === "getDelta" && getThreeConstructorName(candidate.callee.object, context.scopes) === "Clock") {
|
|
114366
|
+
usesDelta = true;
|
|
114367
|
+
return false;
|
|
114368
|
+
}
|
|
114369
|
+
if (!isNodeOfType(candidate, "Identifier")) return;
|
|
114370
|
+
const symbol = context.scopes.symbolFor(candidate);
|
|
114371
|
+
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return;
|
|
114372
|
+
visitedSymbolIds.add(symbol.id);
|
|
114373
|
+
if (expressionUsesThreeClockDelta(symbol.initializer, context, visitedSymbolIds)) {
|
|
114374
|
+
usesDelta = true;
|
|
114375
|
+
return false;
|
|
114376
|
+
}
|
|
114377
|
+
});
|
|
114378
|
+
return usesDelta;
|
|
114379
|
+
};
|
|
114380
|
+
const getFixedInterpolationFactor = (node, context) => {
|
|
114381
|
+
if (!isNodeOfType(node.callee, "MemberExpression")) return null;
|
|
114382
|
+
const methodName = getStaticPropertyName(node.callee);
|
|
114383
|
+
let factorArgumentIndex;
|
|
114384
|
+
if (methodName === "lerp" && getApiReferenceModuleSource(node.callee.object, "MathUtils", context.scopes) === "three") factorArgumentIndex = 2;
|
|
114385
|
+
else if (methodName && hasThreeObjectProvenance(node.callee.object, context.scopes)) factorArgumentIndex = THREE_INTERPOLATION_FACTOR_ARGUMENT_BY_METHOD.get(methodName);
|
|
114386
|
+
if (factorArgumentIndex === void 0) return null;
|
|
114387
|
+
const factor = node.arguments[factorArgumentIndex];
|
|
114388
|
+
if (!factor || isNodeOfType(factor, "SpreadElement")) return null;
|
|
114389
|
+
const staticFactor = resolveStaticNumber(factor, context.scopes);
|
|
114390
|
+
return staticFactor !== null && staticFactor > 0 && staticFactor < 1 ? factor : null;
|
|
114391
|
+
};
|
|
114392
|
+
const threeRequireFrameDelta = defineRule({
|
|
114393
|
+
id: "three-require-frame-delta",
|
|
114394
|
+
title: "Frame-rate-dependent Three.js animation",
|
|
114395
|
+
category: "Correctness",
|
|
114396
|
+
severity: "warn",
|
|
114397
|
+
recommendation: "Scale incremental transforms and interpolation by Clock.getDelta(), use delta-aware damping, or assign from absolute animation time",
|
|
114398
|
+
create: (context) => {
|
|
114399
|
+
const analyzedCallbacks = /* @__PURE__ */ new Set();
|
|
114400
|
+
return { CallExpression(node) {
|
|
114401
|
+
const callback = resolveThreeAnimationLoopCallback(node, context.scopes);
|
|
114402
|
+
if (!callback || analyzedCallbacks.has(callback)) return;
|
|
114403
|
+
analyzedCallbacks.add(callback);
|
|
114404
|
+
walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
|
|
114405
|
+
if (isConditionallyExecuted) return;
|
|
114406
|
+
if (isNodeOfType(candidate, "UpdateExpression") && isThreeTransformMember(candidate.argument, context)) {
|
|
114407
|
+
context.report({
|
|
114408
|
+
node: candidate,
|
|
114409
|
+
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"
|
|
114410
|
+
});
|
|
114411
|
+
return;
|
|
114412
|
+
}
|
|
114413
|
+
if (isNodeOfType(candidate, "AssignmentExpression") && (candidate.operator === "+=" || candidate.operator === "-=") && isThreeTransformMember(candidate.left, context) && !expressionUsesThreeClockDelta(candidate.right, context)) {
|
|
114414
|
+
context.report({
|
|
114415
|
+
node: candidate,
|
|
114416
|
+
message: "This transform changes by a fixed amount per frame, so animation speed depends on refresh rate. Multiply the increment by Clock.getDelta()"
|
|
114417
|
+
});
|
|
114418
|
+
return;
|
|
114419
|
+
}
|
|
114420
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
114421
|
+
const factor = getFixedInterpolationFactor(candidate, context);
|
|
114422
|
+
if (!factor || expressionUsesThreeClockDelta(factor, context)) return;
|
|
114423
|
+
context.report({
|
|
114424
|
+
node: factor,
|
|
114425
|
+
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"
|
|
114426
|
+
});
|
|
114427
|
+
});
|
|
114428
|
+
} };
|
|
114429
|
+
}
|
|
114430
|
+
});
|
|
114431
|
+
//#endregion
|
|
114432
|
+
//#region src/plugin/rules/r3f/three-require-instanced-buffer-update.ts
|
|
114433
|
+
const getInstancedBufferMutation = (node, context) => {
|
|
114434
|
+
const callee = stripParenExpression(node.callee);
|
|
114435
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
114436
|
+
const methodName = getStaticPropertyName(callee);
|
|
114437
|
+
if (methodName !== "setMatrixAt" && methodName !== "setColorAt") return null;
|
|
114438
|
+
if (getThreeConstructorName(callee.object, context.scopes) !== "InstancedMesh") return null;
|
|
114439
|
+
const receiverKey = resolveExpressionKey$1(callee.object, context);
|
|
114440
|
+
if (!receiverKey) return null;
|
|
114441
|
+
return {
|
|
114442
|
+
bufferPropertyName: methodName === "setMatrixAt" ? "instanceMatrix" : "instanceColor",
|
|
114443
|
+
methodName,
|
|
114444
|
+
node,
|
|
114445
|
+
receiverKey
|
|
114446
|
+
};
|
|
114447
|
+
};
|
|
114448
|
+
const getInstancedBufferCompletion = (node, context) => {
|
|
114449
|
+
const assignedValue = stripParenExpression(node.right);
|
|
114450
|
+
const needsUpdateMember = stripParenExpression(node.left);
|
|
114451
|
+
if (node.operator !== "=" || !isNodeOfType(assignedValue, "Literal") || assignedValue.value !== true || !isNodeOfType(needsUpdateMember, "MemberExpression") || getStaticPropertyName(needsUpdateMember) !== "needsUpdate") return null;
|
|
114452
|
+
const bufferMember = stripParenExpression(needsUpdateMember.object);
|
|
114453
|
+
if (!isNodeOfType(bufferMember, "MemberExpression")) return null;
|
|
114454
|
+
const bufferPropertyName = getStaticPropertyName(bufferMember);
|
|
114455
|
+
if (bufferPropertyName !== "instanceMatrix" && bufferPropertyName !== "instanceColor") return null;
|
|
114456
|
+
if (getThreeConstructorName(bufferMember.object, context.scopes) !== "InstancedMesh") return null;
|
|
114457
|
+
const receiverKey = resolveExpressionKey$1(bufferMember.object, context);
|
|
114458
|
+
return receiverKey ? {
|
|
114459
|
+
bufferPropertyName,
|
|
114460
|
+
node,
|
|
114461
|
+
receiverKey
|
|
114462
|
+
} : null;
|
|
114463
|
+
};
|
|
114464
|
+
const getOpaqueInstancedBufferCompletions = (node, context) => {
|
|
114465
|
+
if (!isImportedOrStableParameterCall(node, context.scopes)) return [];
|
|
114466
|
+
const completions = [];
|
|
114467
|
+
for (const argument of node.arguments) {
|
|
114468
|
+
if (isNodeOfType(argument, "SpreadElement")) continue;
|
|
114469
|
+
const candidate = stripParenExpression(argument);
|
|
114470
|
+
if (isNodeOfType(candidate, "MemberExpression")) {
|
|
114471
|
+
const bufferPropertyName = getStaticPropertyName(candidate);
|
|
114472
|
+
if ((bufferPropertyName === "instanceMatrix" || bufferPropertyName === "instanceColor") && getThreeConstructorName(candidate.object, context.scopes) === "InstancedMesh") {
|
|
114473
|
+
const receiverKey = resolveExpressionKey$1(candidate.object, context);
|
|
114474
|
+
if (receiverKey) completions.push({
|
|
114475
|
+
bufferPropertyName,
|
|
114476
|
+
node,
|
|
114477
|
+
receiverKey
|
|
114478
|
+
});
|
|
114479
|
+
continue;
|
|
114480
|
+
}
|
|
114481
|
+
}
|
|
114482
|
+
if (getThreeConstructorName(candidate, context.scopes) !== "InstancedMesh") continue;
|
|
114483
|
+
const receiverKey = resolveExpressionKey$1(candidate, context);
|
|
114484
|
+
if (!receiverKey) continue;
|
|
114485
|
+
completions.push({
|
|
114486
|
+
bufferPropertyName: "instanceMatrix",
|
|
114487
|
+
node,
|
|
114488
|
+
receiverKey
|
|
114489
|
+
});
|
|
114490
|
+
completions.push({
|
|
114491
|
+
bufferPropertyName: "instanceColor",
|
|
114492
|
+
node,
|
|
114493
|
+
receiverKey
|
|
114494
|
+
});
|
|
114495
|
+
}
|
|
114496
|
+
return completions;
|
|
114497
|
+
};
|
|
114498
|
+
const completionCoversMutation = (mutation, completions, program, context) => {
|
|
114499
|
+
const owner = context.cfg.enclosingFunction(mutation.node);
|
|
114500
|
+
const matchingCompletions = completions.filter((completion) => completion.receiverKey === mutation.receiverKey && completion.bufferPropertyName === mutation.bufferPropertyName && context.cfg.enclosingFunction(completion.node) === owner);
|
|
114501
|
+
if (owner) return doNodesCoverEveryPathAfterNode(mutation.node, matchingCompletions.map((completion) => completion.node), context);
|
|
114502
|
+
const mutationStart = getRangeStart(mutation.node);
|
|
114503
|
+
return matchingCompletions.some((completion) => {
|
|
114504
|
+
const completionStart = getRangeStart(completion.node);
|
|
114505
|
+
return mutationStart !== null && completionStart !== null && completionStart > mutationStart && !isNodeConditionallyExecuted(completion.node, program);
|
|
114506
|
+
});
|
|
114507
|
+
};
|
|
114508
|
+
const threeRequireInstancedBufferUpdate = defineRule({
|
|
114509
|
+
id: "three-require-instanced-buffer-update",
|
|
114510
|
+
title: "Three.js instanced mesh buffer is not marked for upload",
|
|
114511
|
+
category: "Correctness",
|
|
114512
|
+
severity: "error",
|
|
114513
|
+
recommendation: "After setMatrixAt or setColorAt, set the matching instance buffer's needsUpdate flag to true",
|
|
114514
|
+
create: (context) => {
|
|
114515
|
+
const mutations = [];
|
|
114516
|
+
const completions = [];
|
|
114517
|
+
let program = null;
|
|
114518
|
+
return {
|
|
114519
|
+
Program(node) {
|
|
114520
|
+
program = node;
|
|
114521
|
+
},
|
|
114522
|
+
AssignmentExpression(node) {
|
|
114523
|
+
const completion = getInstancedBufferCompletion(node, context);
|
|
114524
|
+
if (completion) completions.push(completion);
|
|
114525
|
+
},
|
|
114526
|
+
CallExpression(node) {
|
|
114527
|
+
const mutation = getInstancedBufferMutation(node, context);
|
|
114528
|
+
if (mutation) {
|
|
114529
|
+
mutations.push(mutation);
|
|
114530
|
+
return;
|
|
114531
|
+
}
|
|
114532
|
+
completions.push(...getOpaqueInstancedBufferCompletions(node, context));
|
|
114533
|
+
},
|
|
114534
|
+
"Program:exit"() {
|
|
114535
|
+
if (!program) return;
|
|
114536
|
+
for (const mutation of mutations) {
|
|
114537
|
+
if (completionCoversMutation(mutation, completions, program, context)) continue;
|
|
114538
|
+
context.report({
|
|
114539
|
+
node: mutation.node,
|
|
114540
|
+
message: `After ${mutation.methodName}, set ${mutation.bufferPropertyName}.needsUpdate to true so Three.js uploads the changed instance data`
|
|
114541
|
+
});
|
|
114542
|
+
}
|
|
114543
|
+
}
|
|
114544
|
+
};
|
|
114545
|
+
}
|
|
114546
|
+
});
|
|
114547
|
+
//#endregion
|
|
114548
|
+
//#region src/plugin/rules/r3f/three-require-owned-geometry-cleanup.ts
|
|
114549
|
+
const threeRequireOwnedGeometryCleanup = defineRule({
|
|
114550
|
+
id: "three-require-owned-geometry-cleanup",
|
|
114551
|
+
title: "Locally owned Three.js geometry is not disposed",
|
|
114552
|
+
category: "Performance",
|
|
114553
|
+
severity: "warn",
|
|
114554
|
+
recommendation: "Dispose locally constructed geometries when their React owner releases them",
|
|
114555
|
+
create: (context) => createOwnedThreeResourceCleanupVisitors({
|
|
114556
|
+
constructorNameSuffix: "Geometry",
|
|
114557
|
+
context,
|
|
114558
|
+
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"
|
|
114559
|
+
})
|
|
114560
|
+
});
|
|
114561
|
+
//#endregion
|
|
114562
|
+
//#region src/plugin/rules/r3f/three-require-owned-material-cleanup.ts
|
|
114563
|
+
const threeRequireOwnedMaterialCleanup = defineRule({
|
|
114564
|
+
id: "three-require-owned-material-cleanup",
|
|
114565
|
+
title: "Locally owned Three.js material is not disposed",
|
|
114566
|
+
category: "Performance",
|
|
114567
|
+
severity: "warn",
|
|
114568
|
+
recommendation: "Dispose locally constructed materials when their React owner releases them",
|
|
114569
|
+
create: (context) => createOwnedThreeResourceCleanupVisitors({
|
|
114570
|
+
constructorNameSuffix: "Material",
|
|
114571
|
+
context,
|
|
114572
|
+
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"
|
|
114573
|
+
})
|
|
114574
|
+
});
|
|
114575
|
+
//#endregion
|
|
114576
|
+
//#region src/plugin/rules/r3f/three-require-owned-texture-cleanup.ts
|
|
114577
|
+
const threeRequireOwnedTextureCleanup = defineRule({
|
|
114578
|
+
id: "three-require-owned-texture-cleanup",
|
|
114579
|
+
title: "Locally owned Three.js texture is not disposed",
|
|
114580
|
+
category: "Performance",
|
|
114581
|
+
severity: "warn",
|
|
114582
|
+
disabledWhen: ["r3f"],
|
|
114583
|
+
recommendation: "Dispose locally constructed textures when their React owner releases them",
|
|
114584
|
+
create: r3fRequireOwnedTextureCleanup.create
|
|
114585
|
+
});
|
|
114586
|
+
//#endregion
|
|
113917
114587
|
//#region src/plugin/rules/r3f/three-require-postprocessing-cleanup.ts
|
|
113918
114588
|
const POSTPROCESSING_BORROWING_METHOD_NAMES = /* @__PURE__ */ new Set();
|
|
113919
114589
|
const THREE_COMPOSER_BORROWING_METHOD_NAMES = new Set(["addPass", "insertPass"]);
|
|
@@ -114033,6 +114703,110 @@ const threeRequirePostprocessingCleanup = defineRule({
|
|
|
114033
114703
|
} })
|
|
114034
114704
|
});
|
|
114035
114705
|
//#endregion
|
|
114706
|
+
//#region src/plugin/rules/r3f/three-require-projection-matrix-update.ts
|
|
114707
|
+
const CAMERA_CONSTRUCTOR_NAMES = new Set(["OrthographicCamera", "PerspectiveCamera"]);
|
|
114708
|
+
const PROJECTION_PROPERTY_NAMES = new Set([
|
|
114709
|
+
"aspect",
|
|
114710
|
+
"bottom",
|
|
114711
|
+
"far",
|
|
114712
|
+
"filmGauge",
|
|
114713
|
+
"filmOffset",
|
|
114714
|
+
"fov",
|
|
114715
|
+
"left",
|
|
114716
|
+
"near",
|
|
114717
|
+
"right",
|
|
114718
|
+
"top",
|
|
114719
|
+
"zoom"
|
|
114720
|
+
]);
|
|
114721
|
+
const getProjectionMutation = (node, context) => {
|
|
114722
|
+
const target = isNodeOfType(node, "AssignmentExpression") ? stripParenExpression(node.left) : isNodeOfType(node, "UpdateExpression") ? stripParenExpression(node.argument) : null;
|
|
114723
|
+
if (!target || !isNodeOfType(target, "MemberExpression") || !PROJECTION_PROPERTY_NAMES.has(getStaticPropertyName(target) ?? "") || !CAMERA_CONSTRUCTOR_NAMES.has(getThreeConstructorName(target.object, context.scopes) ?? "")) return null;
|
|
114724
|
+
const receiverKey = resolveExpressionKey$1(target.object, context);
|
|
114725
|
+
return receiverKey ? {
|
|
114726
|
+
node,
|
|
114727
|
+
receiverKey
|
|
114728
|
+
} : null;
|
|
114729
|
+
};
|
|
114730
|
+
const getProjectionUpdate = (node, context) => {
|
|
114731
|
+
const callee = stripParenExpression(node.callee);
|
|
114732
|
+
if (!isNodeOfType(callee, "MemberExpression") || getStaticPropertyName(callee) !== "updateProjectionMatrix" || !CAMERA_CONSTRUCTOR_NAMES.has(getThreeConstructorName(callee.object, context.scopes) ?? "")) return null;
|
|
114733
|
+
const receiverKey = resolveExpressionKey$1(callee.object, context);
|
|
114734
|
+
return receiverKey ? {
|
|
114735
|
+
node,
|
|
114736
|
+
receiverKey
|
|
114737
|
+
} : null;
|
|
114738
|
+
};
|
|
114739
|
+
const getOpaqueProjectionUpdates = (node, context) => {
|
|
114740
|
+
if (!isImportedOrStableParameterCall(node, context.scopes)) return [];
|
|
114741
|
+
const updates = [];
|
|
114742
|
+
for (const argument of node.arguments) {
|
|
114743
|
+
if (isNodeOfType(argument, "SpreadElement") || !CAMERA_CONSTRUCTOR_NAMES.has(getThreeConstructorName(argument, context.scopes) ?? "")) continue;
|
|
114744
|
+
const receiverKey = resolveExpressionKey$1(argument, context);
|
|
114745
|
+
if (receiverKey) updates.push({
|
|
114746
|
+
node,
|
|
114747
|
+
receiverKey
|
|
114748
|
+
});
|
|
114749
|
+
}
|
|
114750
|
+
return updates;
|
|
114751
|
+
};
|
|
114752
|
+
const moduleUpdateCoversMutation = (mutation, update, program) => {
|
|
114753
|
+
const mutationStart = getRangeStart(mutation.node);
|
|
114754
|
+
const updateStart = getRangeStart(update.node);
|
|
114755
|
+
if (mutationStart === null || updateStart === null || updateStart <= mutationStart) return false;
|
|
114756
|
+
if (!isNodeConditionallyExecuted(update.node, program)) return true;
|
|
114757
|
+
const mutationRegions = getConditionalExecutionRegions(mutation.node, program);
|
|
114758
|
+
return [...getConditionalExecutionRegions(update.node, program)].every((region) => mutationRegions.has(region));
|
|
114759
|
+
};
|
|
114760
|
+
const updateCoversMutation = (mutation, updates, program, context) => {
|
|
114761
|
+
const owner = context.cfg.enclosingFunction(mutation.node);
|
|
114762
|
+
const matchingUpdates = updates.filter((update) => update.receiverKey === mutation.receiverKey && context.cfg.enclosingFunction(update.node) === owner);
|
|
114763
|
+
if (owner) return doNodesCoverEveryPathAfterNode(mutation.node, matchingUpdates.map((update) => update.node), context);
|
|
114764
|
+
return matchingUpdates.some((update) => moduleUpdateCoversMutation(mutation, update, program));
|
|
114765
|
+
};
|
|
114766
|
+
const threeRequireProjectionMatrixUpdate = defineRule({
|
|
114767
|
+
id: "three-require-projection-matrix-update",
|
|
114768
|
+
title: "Missing Three.js camera projection-matrix update",
|
|
114769
|
+
category: "Correctness",
|
|
114770
|
+
severity: "error",
|
|
114771
|
+
recommendation: "Call camera.updateProjectionMatrix() after changing projection properties so Three.js renders the new frustum",
|
|
114772
|
+
create: (context) => {
|
|
114773
|
+
const mutations = [];
|
|
114774
|
+
const updates = [];
|
|
114775
|
+
let program = null;
|
|
114776
|
+
return {
|
|
114777
|
+
Program(node) {
|
|
114778
|
+
program = node;
|
|
114779
|
+
},
|
|
114780
|
+
AssignmentExpression(node) {
|
|
114781
|
+
const mutation = getProjectionMutation(node, context);
|
|
114782
|
+
if (mutation) mutations.push(mutation);
|
|
114783
|
+
},
|
|
114784
|
+
UpdateExpression(node) {
|
|
114785
|
+
const mutation = getProjectionMutation(node, context);
|
|
114786
|
+
if (mutation) mutations.push(mutation);
|
|
114787
|
+
},
|
|
114788
|
+
CallExpression(node) {
|
|
114789
|
+
const update = getProjectionUpdate(node, context);
|
|
114790
|
+
if (update) {
|
|
114791
|
+
updates.push(update);
|
|
114792
|
+
return;
|
|
114793
|
+
}
|
|
114794
|
+
updates.push(...getOpaqueProjectionUpdates(node, context));
|
|
114795
|
+
},
|
|
114796
|
+
"Program:exit"() {
|
|
114797
|
+
if (!program) return;
|
|
114798
|
+
for (const mutation of mutations) {
|
|
114799
|
+
if (updateCoversMutation(mutation, updates, program, context)) continue;
|
|
114800
|
+
context.report({
|
|
114801
|
+
node: mutation.node,
|
|
114802
|
+
message: "This camera projection property changes without a later updateProjectionMatrix() call on every path, so Three.js can keep rendering a stale projection matrix"
|
|
114803
|
+
});
|
|
114804
|
+
}
|
|
114805
|
+
}
|
|
114806
|
+
};
|
|
114807
|
+
}
|
|
114808
|
+
});
|
|
114809
|
+
//#endregion
|
|
114036
114810
|
//#region src/plugin/rules/r3f/three-require-render-target-cleanup.ts
|
|
114037
114811
|
const RENDER_TARGET_CONSTRUCTORS = new Set([
|
|
114038
114812
|
"RenderTarget",
|
|
@@ -114045,7 +114819,6 @@ const RENDER_TARGET_BORROWING_METHODS = new Set([
|
|
|
114045
114819
|
"setRenderTarget",
|
|
114046
114820
|
"setRenderTargetTextures"
|
|
114047
114821
|
]);
|
|
114048
|
-
const isThreeModuleSource$1 = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
|
|
114049
114822
|
const threeRequireRenderTargetCleanup = defineRule({
|
|
114050
114823
|
id: "three-require-render-target-cleanup",
|
|
114051
114824
|
title: "Undisposed Three.js render target",
|
|
@@ -114070,7 +114843,6 @@ const threeRequireRenderTargetCleanup = defineRule({
|
|
|
114070
114843
|
//#endregion
|
|
114071
114844
|
//#region src/plugin/rules/r3f/three-require-renderer-cleanup.ts
|
|
114072
114845
|
const RENDERER_CONSTRUCTORS = new Set(["WebGLRenderer", "WebGPURenderer"]);
|
|
114073
|
-
const isThreeModuleSource = (moduleSource) => moduleSource === "three" || moduleSource === "three-stdlib" || moduleSource.startsWith("three/");
|
|
114074
114846
|
const isNullArgument = (call) => {
|
|
114075
114847
|
const argument = call.arguments[0];
|
|
114076
114848
|
return Boolean(isNodeOfType(argument, "Literal") && argument.value === null);
|
|
@@ -114199,7 +114971,7 @@ const threeRequireRendererCleanup = defineRule({
|
|
|
114199
114971
|
recommendation: "Dispose component-owned renderers and stop their animation loop or animation frame in matching React cleanup",
|
|
114200
114972
|
create: (context) => ({ NewExpression(node) {
|
|
114201
114973
|
const provenance = getApiReferenceProvenance(node.callee, context.scopes);
|
|
114202
|
-
if (!provenance || !RENDERER_CONSTRUCTORS.has(provenance.apiName) || !isThreeModuleSource(provenance.moduleSource)) return;
|
|
114974
|
+
if (!provenance || !RENDERER_CONSTRUCTORS.has(provenance.apiName) || !isThreeModuleSource$1(provenance.moduleSource)) return;
|
|
114203
114975
|
const analysis = analyzeOwnedLifecycleResource(node, context, { isBorrowedReference: (reference) => isRetainedByUnusedLocalReactRef(reference, context.scopes) });
|
|
114204
114976
|
if (!analysis || analysis.hasUnknownOwnershipTransfer || isRendererSuppliedToR3fCanvas(analysis, context)) return;
|
|
114205
114977
|
const disposeCleanup = analyzeOwnedLifecycleCleanup(analysis, context, (cleanupFunction) => functionInvokesOwnedResourceMethod(cleanupFunction, analysis, "dispose", context.scopes));
|
|
@@ -114221,6 +114993,124 @@ const threeRequireRendererCleanup = defineRule({
|
|
|
114221
114993
|
} })
|
|
114222
114994
|
});
|
|
114223
114995
|
//#endregion
|
|
114996
|
+
//#region src/plugin/rules/r3f/three-tsl-no-js-uniform-branch.ts
|
|
114997
|
+
const TSL_MODULES = new Set(["three/tsl", "three/webgpu"]);
|
|
114998
|
+
const expressionReferencesTslUniformValue = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
114999
|
+
let referencesUniformValue = false;
|
|
115000
|
+
walkAst(expression, (candidate) => {
|
|
115001
|
+
if (referencesUniformValue) return false;
|
|
115002
|
+
if (isNodeOfType(candidate, "MemberExpression") && getStaticPropertyName(candidate) === "value" && resolvesToTslUniform(candidate.object, scopes)) {
|
|
115003
|
+
referencesUniformValue = true;
|
|
115004
|
+
return false;
|
|
115005
|
+
}
|
|
115006
|
+
if (!isNodeOfType(candidate, "Identifier")) return;
|
|
115007
|
+
const symbol = scopes.symbolFor(candidate);
|
|
115008
|
+
if (symbol?.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read")) return;
|
|
115009
|
+
visitedSymbolIds.add(symbol.id);
|
|
115010
|
+
if (expressionReferencesTslUniformValue(symbol.initializer, scopes, visitedSymbolIds)) {
|
|
115011
|
+
referencesUniformValue = true;
|
|
115012
|
+
return false;
|
|
115013
|
+
}
|
|
115014
|
+
});
|
|
115015
|
+
return referencesUniformValue;
|
|
115016
|
+
};
|
|
115017
|
+
const threeTslNoJsUniformBranch = defineRule({
|
|
115018
|
+
id: "three-tsl-no-js-uniform-branch",
|
|
115019
|
+
title: "JavaScript branch reads a TSL uniform value",
|
|
115020
|
+
category: "Correctness",
|
|
115021
|
+
severity: "warn",
|
|
115022
|
+
recommendation: "Express uniform-dependent shader control flow with TSL If, select, or Loop nodes so it runs on the GPU",
|
|
115023
|
+
create: (context) => ({ CallExpression(node) {
|
|
115024
|
+
if (!isApiCallFromModules(node, "Fn", TSL_MODULES, context.scopes)) return;
|
|
115025
|
+
const callbackArgument = node.arguments[0];
|
|
115026
|
+
if (!callbackArgument || isNodeOfType(callbackArgument, "SpreadElement")) return;
|
|
115027
|
+
const callback = resolveExactLocalFunction(callbackArgument, context.scopes);
|
|
115028
|
+
if (!isFunctionLike$1(callback)) return;
|
|
115029
|
+
const reportedTests = /* @__PURE__ */ new Set();
|
|
115030
|
+
walkFunctionExecution(callback, context.scopes, (candidate) => {
|
|
115031
|
+
const controlFlowTest = getControlFlowTest(candidate);
|
|
115032
|
+
if (!controlFlowTest || !expressionReferencesTslUniformValue(controlFlowTest, context.scopes)) return;
|
|
115033
|
+
for (const reportedTest of reportedTests) if (isAstDescendant(controlFlowTest, reportedTest)) return;
|
|
115034
|
+
reportedTests.add(controlFlowTest);
|
|
115035
|
+
context.report({
|
|
115036
|
+
node: controlFlowTest,
|
|
115037
|
+
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"
|
|
115038
|
+
});
|
|
115039
|
+
});
|
|
115040
|
+
} })
|
|
115041
|
+
});
|
|
115042
|
+
//#endregion
|
|
115043
|
+
//#region src/plugin/rules/r3f/utils/program-constructs-three-webgpu-renderer.ts
|
|
115044
|
+
const programConstructsThreeWebgpuRenderer = (program, scopes) => {
|
|
115045
|
+
let doesConstructRenderer = false;
|
|
115046
|
+
walkAst(program, (candidate) => {
|
|
115047
|
+
if (!doesConstructRenderer && isNodeOfType(candidate, "NewExpression") && getThreeConstructorName(candidate, scopes) === "WebGPURenderer") {
|
|
115048
|
+
doesConstructRenderer = true;
|
|
115049
|
+
return false;
|
|
115050
|
+
}
|
|
115051
|
+
});
|
|
115052
|
+
return doesConstructRenderer;
|
|
115053
|
+
};
|
|
115054
|
+
//#endregion
|
|
115055
|
+
//#region src/plugin/rules/r3f/three-webgpu-no-legacy-effect-composer.ts
|
|
115056
|
+
const threeWebgpuNoLegacyEffectComposer = defineRule({
|
|
115057
|
+
id: "three-webgpu-no-legacy-effect-composer",
|
|
115058
|
+
title: "Legacy EffectComposer used with WebGPURenderer",
|
|
115059
|
+
category: "Correctness",
|
|
115060
|
+
severity: "error",
|
|
115061
|
+
recommendation: "Use WebGPURenderer's node-based post-processing pipeline instead of the legacy WebGL EffectComposer",
|
|
115062
|
+
create: (context) => {
|
|
115063
|
+
let constructsWebgpuRenderer = false;
|
|
115064
|
+
return {
|
|
115065
|
+
Program(node) {
|
|
115066
|
+
constructsWebgpuRenderer = programConstructsThreeWebgpuRenderer(node, context.scopes);
|
|
115067
|
+
},
|
|
115068
|
+
NewExpression(node) {
|
|
115069
|
+
if (!constructsWebgpuRenderer || getThreeConstructorName(node, context.scopes) !== "EffectComposer") return;
|
|
115070
|
+
context.report({
|
|
115071
|
+
node,
|
|
115072
|
+
message: "Legacy EffectComposer does not support Three.js WebGPURenderer. Build post-processing with the renderer's node-based pipeline"
|
|
115073
|
+
});
|
|
115074
|
+
}
|
|
115075
|
+
};
|
|
115076
|
+
}
|
|
115077
|
+
});
|
|
115078
|
+
//#endregion
|
|
115079
|
+
//#region src/plugin/rules/r3f/three-webgpu-no-legacy-material-api.ts
|
|
115080
|
+
const LEGACY_SHADER_MATERIAL_NAMES = new Set(["RawShaderMaterial", "ShaderMaterial"]);
|
|
115081
|
+
const threeWebgpuNoLegacyMaterialApi = defineRule({
|
|
115082
|
+
id: "three-webgpu-no-legacy-material-api",
|
|
115083
|
+
title: "Legacy material API used with WebGPURenderer",
|
|
115084
|
+
category: "Correctness",
|
|
115085
|
+
severity: "error",
|
|
115086
|
+
recommendation: "Use Three.js node materials and TSL for custom shaders rendered by WebGPURenderer",
|
|
115087
|
+
create: (context) => {
|
|
115088
|
+
let constructsWebgpuRenderer = false;
|
|
115089
|
+
return {
|
|
115090
|
+
Program(node) {
|
|
115091
|
+
constructsWebgpuRenderer = programConstructsThreeWebgpuRenderer(node, context.scopes);
|
|
115092
|
+
},
|
|
115093
|
+
NewExpression(node) {
|
|
115094
|
+
const constructorName = getThreeConstructorName(node, context.scopes);
|
|
115095
|
+
if (!constructsWebgpuRenderer || !constructorName) return;
|
|
115096
|
+
if (!LEGACY_SHADER_MATERIAL_NAMES.has(constructorName)) return;
|
|
115097
|
+
context.report({
|
|
115098
|
+
node,
|
|
115099
|
+
message: "ShaderMaterial and RawShaderMaterial are not supported by Three.js WebGPURenderer. Build this shader with a node material and TSL"
|
|
115100
|
+
});
|
|
115101
|
+
},
|
|
115102
|
+
AssignmentExpression(node) {
|
|
115103
|
+
if (!constructsWebgpuRenderer || !isNodeOfType(node.left, "MemberExpression") || getStaticPropertyName(node.left) !== "onBeforeCompile") return;
|
|
115104
|
+
if (!getThreeConstructorName(node.left.object, context.scopes)?.endsWith("Material")) return;
|
|
115105
|
+
context.report({
|
|
115106
|
+
node,
|
|
115107
|
+
message: "onBeforeCompile patches WebGL shader source and is not supported by Three.js WebGPURenderer. Use a node material and TSL"
|
|
115108
|
+
});
|
|
115109
|
+
}
|
|
115110
|
+
};
|
|
115111
|
+
}
|
|
115112
|
+
});
|
|
115113
|
+
//#endregion
|
|
114224
115114
|
//#region src/plugin/rules/security-scan/unsafe-json-in-html.ts
|
|
114225
115115
|
const SINK_JSON_STRINGIFY_PATTERNS = [/dangerouslySetInnerHTML\s*=\s*\{\{\s*__html\s*:[\s\S]{0,300}?\bJSON\.stringify\s*\(/gi, /<script\b[^>]*>(?:(?!<\/script>)[\s\S]){0,300}?\bJSON\.stringify\s*\(/gi];
|
|
114226
115116
|
const INLINE_SCRIPT_PATTERN_INDEX = 1;
|
|
@@ -114978,6 +115868,44 @@ const webAnimationOffsetsValid = defineRule({
|
|
|
114978
115868
|
} })
|
|
114979
115869
|
});
|
|
114980
115870
|
//#endregion
|
|
115871
|
+
//#region src/plugin/rules/webgl/webgl-no-sync-readback-in-animation-loop.ts
|
|
115872
|
+
const getBlockingReadbackKind = (call, context) => {
|
|
115873
|
+
if (!isNodeOfType(call.callee, "MemberExpression")) return null;
|
|
115874
|
+
const methodName = getStaticPropertyName(call.callee);
|
|
115875
|
+
if (methodName === "readRenderTargetPixels" && isThreeRendererReference(call.callee.object, context.scopes)) return "three";
|
|
115876
|
+
if (!isWebglContextReference(call.callee.object, context.scopes)) return null;
|
|
115877
|
+
if (methodName === "finish") return "finish";
|
|
115878
|
+
let destinationArgumentIndex = null;
|
|
115879
|
+
if (methodName === "readPixels") destinationArgumentIndex = 6;
|
|
115880
|
+
else if (methodName === "getBufferSubData") destinationArgumentIndex = 2;
|
|
115881
|
+
if (destinationArgumentIndex === null) return null;
|
|
115882
|
+
const destination = call.arguments[destinationArgumentIndex];
|
|
115883
|
+
return destination && !isNodeOfType(destination, "SpreadElement") && isCpuTypedArray(destination, context.scopes) ? "raw" : null;
|
|
115884
|
+
};
|
|
115885
|
+
const webglNoSyncReadbackInAnimationLoop = defineRule({
|
|
115886
|
+
id: "webgl-no-sync-readback-in-animation-loop",
|
|
115887
|
+
title: "Synchronous GPU readback inside animation loop",
|
|
115888
|
+
severity: "warn",
|
|
115889
|
+
recommendation: "Move GPU readback to a discrete or asynchronous path and reuse the latest completed result during frames",
|
|
115890
|
+
create: (context) => {
|
|
115891
|
+
const analyzedCallbacks = /* @__PURE__ */ new Set();
|
|
115892
|
+
return { CallExpression(node) {
|
|
115893
|
+
const callback = resolveThreeAnimationLoopCallback(node, context.scopes) ?? resolveRecursiveAnimationFrameCallback(node, context.scopes);
|
|
115894
|
+
if (!callback || analyzedCallbacks.has(callback)) return;
|
|
115895
|
+
analyzedCallbacks.add(callback);
|
|
115896
|
+
walkFunctionExecution(callback, context.scopes, (candidate, isConditionallyExecuted) => {
|
|
115897
|
+
if (!isNodeOfType(candidate, "CallExpression") || isConditionallyExecuted) return;
|
|
115898
|
+
const readbackKind = getBlockingReadbackKind(candidate, context);
|
|
115899
|
+
if (!readbackKind) return;
|
|
115900
|
+
context.report({
|
|
115901
|
+
node: candidate,
|
|
115902
|
+
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"
|
|
115903
|
+
});
|
|
115904
|
+
});
|
|
115905
|
+
} };
|
|
115906
|
+
}
|
|
115907
|
+
});
|
|
115908
|
+
//#endregion
|
|
114981
115909
|
//#region src/plugin/rules/security-scan/webhook-signature-risk.ts
|
|
114982
115910
|
const WEBHOOK_HANDLER_PATTERN = /(?:^|\/)[^/]*webhook[^/]*\/|(?:^|\/)[^/]*webhook[^/]*\.[cm]?[jt]s$|\bwebhook\b/i;
|
|
114983
115911
|
const WEBHOOK_ENTRYPOINT_PATTERN = /\b(?:export\s+(?:async\s+)?function\s+POST|export\s+const\s+(?:POST|handler|webhook)|webhookHandler|webhookRoute)\b/i;
|
|
@@ -126797,6 +127725,171 @@ const reactDoctorRules = [
|
|
|
126797
127725
|
tags: [...new Set(["security-scan", ...tenantStaticProxyRisk.tags ?? []])]
|
|
126798
127726
|
}
|
|
126799
127727
|
},
|
|
127728
|
+
{
|
|
127729
|
+
key: "react-doctor/three-cap-device-pixel-ratio",
|
|
127730
|
+
id: "three-cap-device-pixel-ratio",
|
|
127731
|
+
source: "react-doctor",
|
|
127732
|
+
originallyExternal: false,
|
|
127733
|
+
rule: {
|
|
127734
|
+
...threeCapDevicePixelRatio,
|
|
127735
|
+
framework: "global",
|
|
127736
|
+
category: "Performance",
|
|
127737
|
+
tags: [...new Set([
|
|
127738
|
+
"three",
|
|
127739
|
+
"webgl",
|
|
127740
|
+
...threeCapDevicePixelRatio.tags ?? []
|
|
127741
|
+
])],
|
|
127742
|
+
requires: [...new Set(["three", ...threeCapDevicePixelRatio.requires ?? []])]
|
|
127743
|
+
}
|
|
127744
|
+
},
|
|
127745
|
+
{
|
|
127746
|
+
key: "react-doctor/three-limit-shadowed-point-lights",
|
|
127747
|
+
id: "three-limit-shadowed-point-lights",
|
|
127748
|
+
source: "react-doctor",
|
|
127749
|
+
originallyExternal: false,
|
|
127750
|
+
rule: {
|
|
127751
|
+
...threeLimitShadowedPointLights,
|
|
127752
|
+
framework: "global",
|
|
127753
|
+
category: "Performance",
|
|
127754
|
+
tags: [...new Set([
|
|
127755
|
+
"three",
|
|
127756
|
+
"webgl",
|
|
127757
|
+
...threeLimitShadowedPointLights.tags ?? []
|
|
127758
|
+
])],
|
|
127759
|
+
requires: [...new Set(["three", ...threeLimitShadowedPointLights.requires ?? []])]
|
|
127760
|
+
}
|
|
127761
|
+
},
|
|
127762
|
+
{
|
|
127763
|
+
key: "react-doctor/three-no-allocation-in-pointer-move",
|
|
127764
|
+
id: "three-no-allocation-in-pointer-move",
|
|
127765
|
+
source: "react-doctor",
|
|
127766
|
+
originallyExternal: false,
|
|
127767
|
+
rule: {
|
|
127768
|
+
...threeNoAllocationInPointerMove,
|
|
127769
|
+
framework: "global",
|
|
127770
|
+
category: "Performance",
|
|
127771
|
+
tags: [...new Set([
|
|
127772
|
+
"three",
|
|
127773
|
+
"webgl",
|
|
127774
|
+
...threeNoAllocationInPointerMove.tags ?? []
|
|
127775
|
+
])],
|
|
127776
|
+
requires: [...new Set(["three", ...threeNoAllocationInPointerMove.requires ?? []])]
|
|
127777
|
+
}
|
|
127778
|
+
},
|
|
127779
|
+
{
|
|
127780
|
+
key: "react-doctor/three-no-async-animation-loop",
|
|
127781
|
+
id: "three-no-async-animation-loop",
|
|
127782
|
+
source: "react-doctor",
|
|
127783
|
+
originallyExternal: false,
|
|
127784
|
+
rule: {
|
|
127785
|
+
...threeNoAsyncAnimationLoop,
|
|
127786
|
+
framework: "global",
|
|
127787
|
+
category: "Bugs",
|
|
127788
|
+
tags: [...new Set([
|
|
127789
|
+
"three",
|
|
127790
|
+
"webgl",
|
|
127791
|
+
...threeNoAsyncAnimationLoop.tags ?? []
|
|
127792
|
+
])],
|
|
127793
|
+
requires: [...new Set(["three", ...threeNoAsyncAnimationLoop.requires ?? []])]
|
|
127794
|
+
}
|
|
127795
|
+
},
|
|
127796
|
+
{
|
|
127797
|
+
key: "react-doctor/three-no-clone-in-animation-loop",
|
|
127798
|
+
id: "three-no-clone-in-animation-loop",
|
|
127799
|
+
source: "react-doctor",
|
|
127800
|
+
originallyExternal: false,
|
|
127801
|
+
rule: {
|
|
127802
|
+
...threeNoCloneInAnimationLoop,
|
|
127803
|
+
framework: "global",
|
|
127804
|
+
category: "Performance",
|
|
127805
|
+
tags: [...new Set([
|
|
127806
|
+
"three",
|
|
127807
|
+
"webgl",
|
|
127808
|
+
...threeNoCloneInAnimationLoop.tags ?? []
|
|
127809
|
+
])],
|
|
127810
|
+
requires: [...new Set(["three", ...threeNoCloneInAnimationLoop.requires ?? []])]
|
|
127811
|
+
}
|
|
127812
|
+
},
|
|
127813
|
+
{
|
|
127814
|
+
key: "react-doctor/three-no-new-in-animation-loop",
|
|
127815
|
+
id: "three-no-new-in-animation-loop",
|
|
127816
|
+
source: "react-doctor",
|
|
127817
|
+
originallyExternal: false,
|
|
127818
|
+
rule: {
|
|
127819
|
+
...threeNoNewInAnimationLoop,
|
|
127820
|
+
framework: "global",
|
|
127821
|
+
category: "Performance",
|
|
127822
|
+
tags: [...new Set([
|
|
127823
|
+
"three",
|
|
127824
|
+
"webgl",
|
|
127825
|
+
...threeNoNewInAnimationLoop.tags ?? []
|
|
127826
|
+
])],
|
|
127827
|
+
requires: [...new Set(["three", ...threeNoNewInAnimationLoop.requires ?? []])]
|
|
127828
|
+
}
|
|
127829
|
+
},
|
|
127830
|
+
{
|
|
127831
|
+
key: "react-doctor/three-no-object-construction-in-render",
|
|
127832
|
+
id: "three-no-object-construction-in-render",
|
|
127833
|
+
source: "react-doctor",
|
|
127834
|
+
originallyExternal: false,
|
|
127835
|
+
rule: {
|
|
127836
|
+
...threeNoObjectConstructionInRender,
|
|
127837
|
+
framework: "global",
|
|
127838
|
+
category: "Performance",
|
|
127839
|
+
tags: [...new Set([
|
|
127840
|
+
"three",
|
|
127841
|
+
"webgl",
|
|
127842
|
+
...threeNoObjectConstructionInRender.tags ?? []
|
|
127843
|
+
])],
|
|
127844
|
+
requires: [...new Set([
|
|
127845
|
+
"react",
|
|
127846
|
+
"three",
|
|
127847
|
+
...threeNoObjectConstructionInRender.requires ?? []
|
|
127848
|
+
])]
|
|
127849
|
+
}
|
|
127850
|
+
},
|
|
127851
|
+
{
|
|
127852
|
+
key: "react-doctor/three-no-state-in-animation-loop",
|
|
127853
|
+
id: "three-no-state-in-animation-loop",
|
|
127854
|
+
source: "react-doctor",
|
|
127855
|
+
originallyExternal: false,
|
|
127856
|
+
rule: {
|
|
127857
|
+
...threeNoStateInAnimationLoop,
|
|
127858
|
+
framework: "global",
|
|
127859
|
+
category: "Performance",
|
|
127860
|
+
tags: [...new Set([
|
|
127861
|
+
"three",
|
|
127862
|
+
"webgl",
|
|
127863
|
+
...threeNoStateInAnimationLoop.tags ?? []
|
|
127864
|
+
])],
|
|
127865
|
+
requires: [...new Set([
|
|
127866
|
+
"react",
|
|
127867
|
+
"three",
|
|
127868
|
+
...threeNoStateInAnimationLoop.requires ?? []
|
|
127869
|
+
])]
|
|
127870
|
+
}
|
|
127871
|
+
},
|
|
127872
|
+
{
|
|
127873
|
+
key: "react-doctor/three-no-state-in-pointer-move",
|
|
127874
|
+
id: "three-no-state-in-pointer-move",
|
|
127875
|
+
source: "react-doctor",
|
|
127876
|
+
originallyExternal: false,
|
|
127877
|
+
rule: {
|
|
127878
|
+
...threeNoStateInPointerMove,
|
|
127879
|
+
framework: "global",
|
|
127880
|
+
category: "Performance",
|
|
127881
|
+
tags: [...new Set([
|
|
127882
|
+
"three",
|
|
127883
|
+
"webgl",
|
|
127884
|
+
...threeNoStateInPointerMove.tags ?? []
|
|
127885
|
+
])],
|
|
127886
|
+
requires: [...new Set([
|
|
127887
|
+
"react",
|
|
127888
|
+
"three",
|
|
127889
|
+
...threeNoStateInPointerMove.requires ?? []
|
|
127890
|
+
])]
|
|
127891
|
+
}
|
|
127892
|
+
},
|
|
126800
127893
|
{
|
|
126801
127894
|
key: "react-doctor/three-require-animation-mixer-cleanup",
|
|
126802
127895
|
id: "three-require-animation-mixer-cleanup",
|
|
@@ -126807,7 +127900,7 @@ const reactDoctorRules = [
|
|
|
126807
127900
|
framework: "global",
|
|
126808
127901
|
category: "Bugs",
|
|
126809
127902
|
tags: [...new Set([
|
|
126810
|
-
"
|
|
127903
|
+
"three",
|
|
126811
127904
|
"webgl",
|
|
126812
127905
|
...threeRequireAnimationMixerCleanup.tags ?? []
|
|
126813
127906
|
])],
|
|
@@ -126828,7 +127921,7 @@ const reactDoctorRules = [
|
|
|
126828
127921
|
framework: "global",
|
|
126829
127922
|
category: "Bugs",
|
|
126830
127923
|
tags: [...new Set([
|
|
126831
|
-
"
|
|
127924
|
+
"three",
|
|
126832
127925
|
"webgl",
|
|
126833
127926
|
...threeRequireControlsCleanup.tags ?? []
|
|
126834
127927
|
])],
|
|
@@ -126839,6 +127932,103 @@ const reactDoctorRules = [
|
|
|
126839
127932
|
])]
|
|
126840
127933
|
}
|
|
126841
127934
|
},
|
|
127935
|
+
{
|
|
127936
|
+
key: "react-doctor/three-require-frame-delta",
|
|
127937
|
+
id: "three-require-frame-delta",
|
|
127938
|
+
source: "react-doctor",
|
|
127939
|
+
originallyExternal: false,
|
|
127940
|
+
rule: {
|
|
127941
|
+
...threeRequireFrameDelta,
|
|
127942
|
+
framework: "global",
|
|
127943
|
+
category: "Bugs",
|
|
127944
|
+
tags: [...new Set([
|
|
127945
|
+
"three",
|
|
127946
|
+
"webgl",
|
|
127947
|
+
...threeRequireFrameDelta.tags ?? []
|
|
127948
|
+
])],
|
|
127949
|
+
requires: [...new Set(["three", ...threeRequireFrameDelta.requires ?? []])]
|
|
127950
|
+
}
|
|
127951
|
+
},
|
|
127952
|
+
{
|
|
127953
|
+
key: "react-doctor/three-require-instanced-buffer-update",
|
|
127954
|
+
id: "three-require-instanced-buffer-update",
|
|
127955
|
+
source: "react-doctor",
|
|
127956
|
+
originallyExternal: false,
|
|
127957
|
+
rule: {
|
|
127958
|
+
...threeRequireInstancedBufferUpdate,
|
|
127959
|
+
framework: "global",
|
|
127960
|
+
category: "Bugs",
|
|
127961
|
+
tags: [...new Set([
|
|
127962
|
+
"three",
|
|
127963
|
+
"webgl",
|
|
127964
|
+
...threeRequireInstancedBufferUpdate.tags ?? []
|
|
127965
|
+
])],
|
|
127966
|
+
requires: [...new Set(["three", ...threeRequireInstancedBufferUpdate.requires ?? []])]
|
|
127967
|
+
}
|
|
127968
|
+
},
|
|
127969
|
+
{
|
|
127970
|
+
key: "react-doctor/three-require-owned-geometry-cleanup",
|
|
127971
|
+
id: "three-require-owned-geometry-cleanup",
|
|
127972
|
+
source: "react-doctor",
|
|
127973
|
+
originallyExternal: false,
|
|
127974
|
+
rule: {
|
|
127975
|
+
...threeRequireOwnedGeometryCleanup,
|
|
127976
|
+
framework: "global",
|
|
127977
|
+
category: "Performance",
|
|
127978
|
+
tags: [...new Set([
|
|
127979
|
+
"three",
|
|
127980
|
+
"webgl",
|
|
127981
|
+
...threeRequireOwnedGeometryCleanup.tags ?? []
|
|
127982
|
+
])],
|
|
127983
|
+
requires: [...new Set([
|
|
127984
|
+
"react",
|
|
127985
|
+
"three",
|
|
127986
|
+
...threeRequireOwnedGeometryCleanup.requires ?? []
|
|
127987
|
+
])]
|
|
127988
|
+
}
|
|
127989
|
+
},
|
|
127990
|
+
{
|
|
127991
|
+
key: "react-doctor/three-require-owned-material-cleanup",
|
|
127992
|
+
id: "three-require-owned-material-cleanup",
|
|
127993
|
+
source: "react-doctor",
|
|
127994
|
+
originallyExternal: false,
|
|
127995
|
+
rule: {
|
|
127996
|
+
...threeRequireOwnedMaterialCleanup,
|
|
127997
|
+
framework: "global",
|
|
127998
|
+
category: "Performance",
|
|
127999
|
+
tags: [...new Set([
|
|
128000
|
+
"three",
|
|
128001
|
+
"webgl",
|
|
128002
|
+
...threeRequireOwnedMaterialCleanup.tags ?? []
|
|
128003
|
+
])],
|
|
128004
|
+
requires: [...new Set([
|
|
128005
|
+
"react",
|
|
128006
|
+
"three",
|
|
128007
|
+
...threeRequireOwnedMaterialCleanup.requires ?? []
|
|
128008
|
+
])]
|
|
128009
|
+
}
|
|
128010
|
+
},
|
|
128011
|
+
{
|
|
128012
|
+
key: "react-doctor/three-require-owned-texture-cleanup",
|
|
128013
|
+
id: "three-require-owned-texture-cleanup",
|
|
128014
|
+
source: "react-doctor",
|
|
128015
|
+
originallyExternal: false,
|
|
128016
|
+
rule: {
|
|
128017
|
+
...threeRequireOwnedTextureCleanup,
|
|
128018
|
+
framework: "global",
|
|
128019
|
+
category: "Performance",
|
|
128020
|
+
tags: [...new Set([
|
|
128021
|
+
"three",
|
|
128022
|
+
"webgl",
|
|
128023
|
+
...threeRequireOwnedTextureCleanup.tags ?? []
|
|
128024
|
+
])],
|
|
128025
|
+
requires: [...new Set([
|
|
128026
|
+
"react",
|
|
128027
|
+
"three",
|
|
128028
|
+
...threeRequireOwnedTextureCleanup.requires ?? []
|
|
128029
|
+
])]
|
|
128030
|
+
}
|
|
128031
|
+
},
|
|
126842
128032
|
{
|
|
126843
128033
|
key: "react-doctor/three-require-postprocessing-cleanup",
|
|
126844
128034
|
id: "three-require-postprocessing-cleanup",
|
|
@@ -126849,7 +128039,7 @@ const reactDoctorRules = [
|
|
|
126849
128039
|
framework: "global",
|
|
126850
128040
|
category: "Bugs",
|
|
126851
128041
|
tags: [...new Set([
|
|
126852
|
-
"
|
|
128042
|
+
"three",
|
|
126853
128043
|
"webgl",
|
|
126854
128044
|
...threeRequirePostprocessingCleanup.tags ?? []
|
|
126855
128045
|
])],
|
|
@@ -126860,6 +128050,23 @@ const reactDoctorRules = [
|
|
|
126860
128050
|
])]
|
|
126861
128051
|
}
|
|
126862
128052
|
},
|
|
128053
|
+
{
|
|
128054
|
+
key: "react-doctor/three-require-projection-matrix-update",
|
|
128055
|
+
id: "three-require-projection-matrix-update",
|
|
128056
|
+
source: "react-doctor",
|
|
128057
|
+
originallyExternal: false,
|
|
128058
|
+
rule: {
|
|
128059
|
+
...threeRequireProjectionMatrixUpdate,
|
|
128060
|
+
framework: "global",
|
|
128061
|
+
category: "Bugs",
|
|
128062
|
+
tags: [...new Set([
|
|
128063
|
+
"three",
|
|
128064
|
+
"webgl",
|
|
128065
|
+
...threeRequireProjectionMatrixUpdate.tags ?? []
|
|
128066
|
+
])],
|
|
128067
|
+
requires: [...new Set(["three", ...threeRequireProjectionMatrixUpdate.requires ?? []])]
|
|
128068
|
+
}
|
|
128069
|
+
},
|
|
126863
128070
|
{
|
|
126864
128071
|
key: "react-doctor/three-require-render-target-cleanup",
|
|
126865
128072
|
id: "three-require-render-target-cleanup",
|
|
@@ -126870,7 +128077,7 @@ const reactDoctorRules = [
|
|
|
126870
128077
|
framework: "global",
|
|
126871
128078
|
category: "Bugs",
|
|
126872
128079
|
tags: [...new Set([
|
|
126873
|
-
"
|
|
128080
|
+
"three",
|
|
126874
128081
|
"webgl",
|
|
126875
128082
|
...threeRequireRenderTargetCleanup.tags ?? []
|
|
126876
128083
|
])],
|
|
@@ -126891,7 +128098,7 @@ const reactDoctorRules = [
|
|
|
126891
128098
|
framework: "global",
|
|
126892
128099
|
category: "Bugs",
|
|
126893
128100
|
tags: [...new Set([
|
|
126894
|
-
"
|
|
128101
|
+
"three",
|
|
126895
128102
|
"webgl",
|
|
126896
128103
|
...threeRequireRendererCleanup.tags ?? []
|
|
126897
128104
|
])],
|
|
@@ -126902,6 +128109,57 @@ const reactDoctorRules = [
|
|
|
126902
128109
|
])]
|
|
126903
128110
|
}
|
|
126904
128111
|
},
|
|
128112
|
+
{
|
|
128113
|
+
key: "react-doctor/three-tsl-no-js-uniform-branch",
|
|
128114
|
+
id: "three-tsl-no-js-uniform-branch",
|
|
128115
|
+
source: "react-doctor",
|
|
128116
|
+
originallyExternal: false,
|
|
128117
|
+
rule: {
|
|
128118
|
+
...threeTslNoJsUniformBranch,
|
|
128119
|
+
framework: "global",
|
|
128120
|
+
category: "Bugs",
|
|
128121
|
+
tags: [...new Set([
|
|
128122
|
+
"three",
|
|
128123
|
+
"webgl",
|
|
128124
|
+
...threeTslNoJsUniformBranch.tags ?? []
|
|
128125
|
+
])],
|
|
128126
|
+
requires: [...new Set(["three", ...threeTslNoJsUniformBranch.requires ?? []])]
|
|
128127
|
+
}
|
|
128128
|
+
},
|
|
128129
|
+
{
|
|
128130
|
+
key: "react-doctor/three-webgpu-no-legacy-effect-composer",
|
|
128131
|
+
id: "three-webgpu-no-legacy-effect-composer",
|
|
128132
|
+
source: "react-doctor",
|
|
128133
|
+
originallyExternal: false,
|
|
128134
|
+
rule: {
|
|
128135
|
+
...threeWebgpuNoLegacyEffectComposer,
|
|
128136
|
+
framework: "global",
|
|
128137
|
+
category: "Bugs",
|
|
128138
|
+
tags: [...new Set([
|
|
128139
|
+
"three",
|
|
128140
|
+
"webgl",
|
|
128141
|
+
...threeWebgpuNoLegacyEffectComposer.tags ?? []
|
|
128142
|
+
])],
|
|
128143
|
+
requires: [...new Set(["three", ...threeWebgpuNoLegacyEffectComposer.requires ?? []])]
|
|
128144
|
+
}
|
|
128145
|
+
},
|
|
128146
|
+
{
|
|
128147
|
+
key: "react-doctor/three-webgpu-no-legacy-material-api",
|
|
128148
|
+
id: "three-webgpu-no-legacy-material-api",
|
|
128149
|
+
source: "react-doctor",
|
|
128150
|
+
originallyExternal: false,
|
|
128151
|
+
rule: {
|
|
128152
|
+
...threeWebgpuNoLegacyMaterialApi,
|
|
128153
|
+
framework: "global",
|
|
128154
|
+
category: "Bugs",
|
|
128155
|
+
tags: [...new Set([
|
|
128156
|
+
"three",
|
|
128157
|
+
"webgl",
|
|
128158
|
+
...threeWebgpuNoLegacyMaterialApi.tags ?? []
|
|
128159
|
+
])],
|
|
128160
|
+
requires: [...new Set(["three", ...threeWebgpuNoLegacyMaterialApi.requires ?? []])]
|
|
128161
|
+
}
|
|
128162
|
+
},
|
|
126905
128163
|
{
|
|
126906
128164
|
key: "react-doctor/unsafe-json-in-html",
|
|
126907
128165
|
id: "unsafe-json-in-html",
|
|
@@ -127007,6 +128265,18 @@ const reactDoctorRules = [
|
|
|
127007
128265
|
category: "Bugs"
|
|
127008
128266
|
}
|
|
127009
128267
|
},
|
|
128268
|
+
{
|
|
128269
|
+
key: "react-doctor/webgl-no-sync-readback-in-animation-loop",
|
|
128270
|
+
id: "webgl-no-sync-readback-in-animation-loop",
|
|
128271
|
+
source: "react-doctor",
|
|
128272
|
+
originallyExternal: false,
|
|
128273
|
+
rule: {
|
|
128274
|
+
...webglNoSyncReadbackInAnimationLoop,
|
|
128275
|
+
framework: "global",
|
|
128276
|
+
category: "Performance",
|
|
128277
|
+
tags: [...new Set(["webgl", ...webglNoSyncReadbackInAnimationLoop.tags ?? []])]
|
|
128278
|
+
}
|
|
128279
|
+
},
|
|
127010
128280
|
{
|
|
127011
128281
|
key: "react-doctor/webhook-signature-risk",
|
|
127012
128282
|
id: "webhook-signature-risk",
|