oxlint-plugin-react-doctor 0.9.2-dev.16972ae → 0.9.2-dev.1d01dd3
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.js +328 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -29461,6 +29461,11 @@ const jsCacheStorage = defineRule({
|
|
|
29461
29461
|
});
|
|
29462
29462
|
//#endregion
|
|
29463
29463
|
//#region src/plugin/rules/js-performance/js-combine-iterations.ts
|
|
29464
|
+
const SMALL_ARRAY_NON_MUTATING_METHODS = new Set([
|
|
29465
|
+
...CHAINABLE_ITERATION_METHODS,
|
|
29466
|
+
"find",
|
|
29467
|
+
"some"
|
|
29468
|
+
]);
|
|
29464
29469
|
const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
|
|
29465
29470
|
const callee = callExpression.callee;
|
|
29466
29471
|
if (isNodeOfType(callee, "MemberExpression")) {
|
|
@@ -29572,21 +29577,34 @@ const isStringSplitRootedChain = (receiverNode) => {
|
|
|
29572
29577
|
return false;
|
|
29573
29578
|
};
|
|
29574
29579
|
const isSmallLiteralArray = (node) => {
|
|
29575
|
-
|
|
29576
|
-
|
|
29577
|
-
|
|
29580
|
+
const arrayNode = stripParenExpression(node);
|
|
29581
|
+
if (!isNodeOfType(arrayNode, "ArrayExpression")) return false;
|
|
29582
|
+
const elements = arrayNode.elements ?? [];
|
|
29583
|
+
if (elements.length === 0 || elements.length > 9) return false;
|
|
29578
29584
|
for (const element of elements) {
|
|
29579
29585
|
if (!element) continue;
|
|
29580
29586
|
if (isNodeOfType(element, "SpreadElement")) return false;
|
|
29581
29587
|
}
|
|
29582
29588
|
return true;
|
|
29583
29589
|
};
|
|
29584
|
-
const
|
|
29590
|
+
const isNonMutatingSmallArrayMethodReference = (identifier) => {
|
|
29591
|
+
const identifierRoot = findTransparentExpressionRoot(identifier);
|
|
29592
|
+
const memberExpression = identifierRoot.parent;
|
|
29593
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || memberExpression.object !== identifierRoot || !isNodeOfType(memberExpression.property, "Identifier") || !SMALL_ARRAY_NON_MUTATING_METHODS.has(memberExpression.property.name)) return false;
|
|
29594
|
+
const callExpression = memberExpression.parent;
|
|
29595
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === memberExpression;
|
|
29596
|
+
};
|
|
29597
|
+
const isSmallLiteralArrayRootedChain = (receiverNode, scopes) => {
|
|
29585
29598
|
let cursor = receiverNode;
|
|
29586
29599
|
while (cursor) {
|
|
29587
29600
|
cursor = stripParenExpression(cursor);
|
|
29588
29601
|
if (isNodeOfType(cursor, "ArrayExpression")) return isSmallLiteralArray(cursor);
|
|
29589
|
-
if (isNodeOfType(cursor, "Identifier"))
|
|
29602
|
+
if (isNodeOfType(cursor, "Identifier")) {
|
|
29603
|
+
const symbol = scopes.symbolFor(cursor);
|
|
29604
|
+
if (!symbol?.initializer || !isSmallLiteralArray(symbol.initializer)) return false;
|
|
29605
|
+
if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || !isNodeOfType(symbol.declarationNode.id, "Identifier")) return false;
|
|
29606
|
+
return (symbol.kind === "const" || symbol.kind === "let" || symbol.kind === "var") && symbol.references.every((reference) => reference.flag === "read" && isNonMutatingSmallArrayMethodReference(reference.identifier));
|
|
29607
|
+
}
|
|
29590
29608
|
if (!isNodeOfType(cursor, "CallExpression")) return false;
|
|
29591
29609
|
if (!isChainPassThroughCall(cursor)) return false;
|
|
29592
29610
|
const nextCallee = cursor.callee;
|
|
@@ -29595,22 +29613,6 @@ const isSmallLiteralArrayRootedChain = (receiverNode, smallConstArrayNames) => {
|
|
|
29595
29613
|
}
|
|
29596
29614
|
return false;
|
|
29597
29615
|
};
|
|
29598
|
-
const collectSmallConstArrayNames = (programNode) => {
|
|
29599
|
-
const names = /* @__PURE__ */ new Set();
|
|
29600
|
-
const statements = programNode.body ?? [];
|
|
29601
|
-
for (const statement of statements) {
|
|
29602
|
-
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
29603
|
-
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) continue;
|
|
29604
|
-
if (declaration.kind !== "const") continue;
|
|
29605
|
-
for (const declarator of declaration.declarations ?? []) {
|
|
29606
|
-
if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
|
|
29607
|
-
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
29608
|
-
if (!declarator.init || !isSmallLiteralArray(declarator.init)) continue;
|
|
29609
|
-
names.add(declarator.id.name);
|
|
29610
|
-
}
|
|
29611
|
-
}
|
|
29612
|
-
return names;
|
|
29613
|
-
};
|
|
29614
29616
|
const collectGeneratorNames = (programNode) => {
|
|
29615
29617
|
const generatorNames = /* @__PURE__ */ new Set();
|
|
29616
29618
|
walkAst(programNode, (child) => {
|
|
@@ -29631,16 +29633,11 @@ const jsCombineIterations = defineRule({
|
|
|
29631
29633
|
create: (context) => {
|
|
29632
29634
|
let programNode = null;
|
|
29633
29635
|
let generatorNamesInFile = null;
|
|
29634
|
-
let smallConstArrayNames = null;
|
|
29635
29636
|
const coveredChainCalls = /* @__PURE__ */ new WeakSet();
|
|
29636
29637
|
const getGeneratorNamesInFile = () => {
|
|
29637
29638
|
generatorNamesInFile ??= programNode ? collectGeneratorNames(programNode) : /* @__PURE__ */ new Set();
|
|
29638
29639
|
return generatorNamesInFile;
|
|
29639
29640
|
};
|
|
29640
|
-
const getSmallConstArrayNames = () => {
|
|
29641
|
-
smallConstArrayNames ??= programNode ? collectSmallConstArrayNames(programNode) : /* @__PURE__ */ new Set();
|
|
29642
|
-
return smallConstArrayNames;
|
|
29643
|
-
};
|
|
29644
29641
|
return {
|
|
29645
29642
|
Program(node) {
|
|
29646
29643
|
programNode = node;
|
|
@@ -29670,7 +29667,7 @@ const jsCombineIterations = defineRule({
|
|
|
29670
29667
|
if (isTypePredicateArrow(filterArgument)) return;
|
|
29671
29668
|
}
|
|
29672
29669
|
if (isReceiverChainIteratorRooted(innerCall.callee.object, getGeneratorNamesInFile())) return;
|
|
29673
|
-
if (isSmallLiteralArrayRootedChain(innerCall.callee.object,
|
|
29670
|
+
if (isSmallLiteralArrayRootedChain(innerCall.callee.object, context.scopes)) return;
|
|
29674
29671
|
if (isStringSplitRootedChain(innerCall.callee.object)) return;
|
|
29675
29672
|
coveredChainCalls.add(innerCall);
|
|
29676
29673
|
context.report({
|
|
@@ -56090,9 +56087,13 @@ const isInitialOnlyPropName = (propName) => {
|
|
|
56090
56087
|
return /^initial[A-Z]/.test(propName) || /^default[A-Z]/.test(propName) || /^seed[A-Z]/.test(propName) || /^starting[A-Z]/.test(propName) || /^baseline[A-Z]/.test(propName) || /^preset[A-Z]/.test(propName);
|
|
56091
56088
|
};
|
|
56092
56089
|
//#endregion
|
|
56090
|
+
//#region src/plugin/utils/nextjs-page-data-export-names.ts
|
|
56091
|
+
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
56092
|
+
//#endregion
|
|
56093
56093
|
//#region src/plugin/rules/state-and-effects/no-derived-use-state.ts
|
|
56094
56094
|
const isInitialOnlySeedName = (propName) => isInitialOnlyPropName(propName) || propName === "initial" || propName === "autoFocus" || propName === "autoPlay" || propName === "startOpen" || /^initially[A-Z]/.test(propName) || /Initial([A-Z]|$)/.test(propName);
|
|
56095
56095
|
const SNAPSHOT_STATE_NAME_PATTERN = /^(initial|previous|prev|preserved|saved|original|cached|snapshot|prior|debounced|deferred)([A-Z_]|$)/;
|
|
56096
|
+
const INTERNAL_STATE_NAME_PATTERN = /^(internal|uncontrolled)([A-Z_]|$)/;
|
|
56096
56097
|
const getStateSetterName = (useStateCall) => {
|
|
56097
56098
|
const declarator = useStateCall.parent;
|
|
56098
56099
|
if (!isNodeOfType(declarator, "VariableDeclarator")) return null;
|
|
@@ -56246,7 +56247,6 @@ const isDraftCommittedToParent = (componentFunction, stateValueName, isPropName)
|
|
|
56246
56247
|
});
|
|
56247
56248
|
return isCommitted;
|
|
56248
56249
|
};
|
|
56249
|
-
const NEXTJS_PAGE_DATA_EXPORT_NAMES = new Set(["getServerSideProps", "getStaticProps"]);
|
|
56250
56250
|
const isNextjsDataFetchingPage = (node) => {
|
|
56251
56251
|
const program = findProgramRoot(node);
|
|
56252
56252
|
if (!program) return false;
|
|
@@ -56290,6 +56290,48 @@ const isInRenderScope = (node, componentFunction) => {
|
|
|
56290
56290
|
}
|
|
56291
56291
|
return true;
|
|
56292
56292
|
};
|
|
56293
|
+
const isReferenceToBinding = (reference, bindingIdentifier, context) => {
|
|
56294
|
+
if (!isNodeOfType(reference, "Identifier")) return false;
|
|
56295
|
+
const bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
|
|
56296
|
+
if (!bindingSymbol) return false;
|
|
56297
|
+
return (context.scopes.referenceFor(reference)?.resolvedSymbol)?.id === bindingSymbol.id;
|
|
56298
|
+
};
|
|
56299
|
+
const isControlledPropFallbackExpression = (expression, stateBinding, isPropName, context) => {
|
|
56300
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
56301
|
+
const consequent = unwrapInitializerSeed(expression.consequent);
|
|
56302
|
+
const alternate = unwrapInitializerSeed(expression.alternate);
|
|
56303
|
+
return isReferenceToBinding(consequent, stateBinding, context) && isPropDerivedArgument(alternate, isPropName) || isPropDerivedArgument(consequent, isPropName) && isReferenceToBinding(alternate, stateBinding, context);
|
|
56304
|
+
}
|
|
56305
|
+
return isNodeOfType(expression, "LogicalExpression") && expression.operator === "??" && isPropDerivedArgument(unwrapInitializerSeed(expression.left), isPropName) && isReferenceToBinding(unwrapInitializerSeed(expression.right), stateBinding, context);
|
|
56306
|
+
};
|
|
56307
|
+
const isUserEditableControlledFallback = (useStateCall, isPropName, context) => {
|
|
56308
|
+
const declarator = useStateCall.parent;
|
|
56309
|
+
if (!isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
56310
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) return false;
|
|
56311
|
+
const stateBinding = declarator.id.elements?.[0];
|
|
56312
|
+
const setterBinding = declarator.id.elements?.[1];
|
|
56313
|
+
if (!isNodeOfType(stateBinding, "Identifier") || !isNodeOfType(setterBinding, "Identifier")) return false;
|
|
56314
|
+
if (!INTERNAL_STATE_NAME_PATTERN.test(stateBinding.name)) return false;
|
|
56315
|
+
const componentFunction = findEnclosingFunction$1(useStateCall);
|
|
56316
|
+
if (!componentFunction) return false;
|
|
56317
|
+
let hasControlledFallback = false;
|
|
56318
|
+
let hasUserEdit = false;
|
|
56319
|
+
walkAst(componentFunction, (child) => {
|
|
56320
|
+
if (child !== componentFunction && isFunctionLike$1(child)) return false;
|
|
56321
|
+
if (!hasControlledFallback && (isNodeOfType(child, "ConditionalExpression") || isNodeOfType(child, "LogicalExpression")) && isControlledPropFallbackExpression(child, stateBinding, isPropName, context)) hasControlledFallback = true;
|
|
56322
|
+
});
|
|
56323
|
+
walkAst(componentFunction, (child) => {
|
|
56324
|
+
if (hasUserEdit) return false;
|
|
56325
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
56326
|
+
if (!isReferenceToBinding(child.callee, setterBinding, context)) return;
|
|
56327
|
+
if (!isHandlerShapedReseed(child, componentFunction)) return;
|
|
56328
|
+
const setterArgument = child.arguments?.[0];
|
|
56329
|
+
if (!setterArgument || isPropDerivedArgument(unwrapInitializerSeed(setterArgument), isPropName)) return;
|
|
56330
|
+
hasUserEdit = true;
|
|
56331
|
+
return false;
|
|
56332
|
+
});
|
|
56333
|
+
return hasControlledFallback && hasUserEdit;
|
|
56334
|
+
};
|
|
56293
56335
|
const getStateValueName = (useStateCall) => {
|
|
56294
56336
|
const declarator = useStateCall.parent;
|
|
56295
56337
|
if (!isNodeOfType(declarator, "VariableDeclarator")) return null;
|
|
@@ -56354,6 +56396,7 @@ const noDerivedUseState = defineRule({
|
|
|
56354
56396
|
const reportStalePropCopy = (propName) => {
|
|
56355
56397
|
if (isIntentionalSnapshotState(node)) return;
|
|
56356
56398
|
if (hasSessionDismissProp(propStackTracker.getCurrentPropNames())) return;
|
|
56399
|
+
if (isUserEditableControlledFallback(node, propStackTracker.isPropName, context)) return;
|
|
56357
56400
|
if (isDraftReseedOrRenderAdjusted(node, propStackTracker.isPropName)) return;
|
|
56358
56401
|
if (isEffectDrivenResync(node)) return;
|
|
56359
56402
|
if (isNextjsDataFetchingPage(node)) return;
|
|
@@ -67191,6 +67234,124 @@ const isInsideSnapshotHelper = (node) => {
|
|
|
67191
67234
|
}
|
|
67192
67235
|
return false;
|
|
67193
67236
|
};
|
|
67237
|
+
const findEnclosingNextjsPageDataFunction = (node) => {
|
|
67238
|
+
let outermostFunction = null;
|
|
67239
|
+
let cursor = node.parent;
|
|
67240
|
+
while (cursor) {
|
|
67241
|
+
if (isFunctionLike$1(cursor)) outermostFunction = cursor;
|
|
67242
|
+
if (isNodeOfType(cursor, "Program")) {
|
|
67243
|
+
if (!outermostFunction) return null;
|
|
67244
|
+
for (const exportName of NEXTJS_PAGE_DATA_EXPORT_NAMES) {
|
|
67245
|
+
const exportedValue = findExportedValue(cursor, exportName);
|
|
67246
|
+
if (exportedValue && isAstDescendant(outermostFunction, exportedValue)) return outermostFunction;
|
|
67247
|
+
}
|
|
67248
|
+
return null;
|
|
67249
|
+
}
|
|
67250
|
+
cursor = cursor.parent ?? null;
|
|
67251
|
+
}
|
|
67252
|
+
return null;
|
|
67253
|
+
};
|
|
67254
|
+
const findConditionalReturnExpressionRoot = (node) => {
|
|
67255
|
+
let expressionRoot = findTransparentExpressionRoot(node);
|
|
67256
|
+
while (expressionRoot.parent && isNodeOfType(expressionRoot.parent, "ConditionalExpression") && (expressionRoot.parent.consequent === expressionRoot || expressionRoot.parent.alternate === expressionRoot)) expressionRoot = findTransparentExpressionRoot(expressionRoot.parent);
|
|
67257
|
+
return expressionRoot;
|
|
67258
|
+
};
|
|
67259
|
+
const isReturnedPageDataResultBinding = (returnExpression, pageDataFunction, context) => {
|
|
67260
|
+
const declarator = returnExpression.parent;
|
|
67261
|
+
if (!isNodeOfType(declarator, "VariableDeclarator") || declarator.init !== returnExpression || !isNodeOfType(declarator.id, "Identifier") || findEnclosingFunction$1(declarator) !== pageDataFunction) return false;
|
|
67262
|
+
const bindingSymbol = context.scopes.symbolFor(declarator.id);
|
|
67263
|
+
if (!bindingSymbol || bindingSymbol.references.length !== 1) return false;
|
|
67264
|
+
const referenceRoot = findTransparentExpressionRoot(bindingSymbol.references[0].identifier);
|
|
67265
|
+
const returnStatement = referenceRoot.parent;
|
|
67266
|
+
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === referenceRoot && findEnclosingFunction$1(returnStatement) === pageDataFunction;
|
|
67267
|
+
};
|
|
67268
|
+
const isSameShorthandPropertyValue = (node, property) => property.shorthand && (node === property.key || node === property.value);
|
|
67269
|
+
const isValueForwardedThroughLiteralStructure = (node, structure) => {
|
|
67270
|
+
const strippedNode = stripParenExpression(node);
|
|
67271
|
+
const strippedStructure = stripParenExpression(structure);
|
|
67272
|
+
if (strippedNode === strippedStructure) return true;
|
|
67273
|
+
if (isNodeOfType(strippedStructure, "ConditionalExpression")) return isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.consequent) || isValueForwardedThroughLiteralStructure(strippedNode, strippedStructure.alternate);
|
|
67274
|
+
if (isNodeOfType(strippedStructure, "ArrayExpression")) return strippedStructure.elements.some((element) => element && !isNodeOfType(element, "SpreadElement") && isValueForwardedThroughLiteralStructure(strippedNode, element));
|
|
67275
|
+
if (!isNodeOfType(strippedStructure, "ObjectExpression")) return false;
|
|
67276
|
+
return strippedStructure.properties.some((property) => {
|
|
67277
|
+
if (isNodeOfType(property, "SpreadElement")) return isValueForwardedThroughLiteralStructure(strippedNode, property.argument);
|
|
67278
|
+
if (!isNodeOfType(property, "Property")) return false;
|
|
67279
|
+
if (isValueForwardedThroughLiteralStructure(strippedNode, property.value)) return true;
|
|
67280
|
+
return isSameShorthandPropertyValue(strippedNode, property);
|
|
67281
|
+
});
|
|
67282
|
+
};
|
|
67283
|
+
const isValueForwardedToPropertyValue = (node, property) => {
|
|
67284
|
+
const directValue = findConditionalReturnExpressionRoot(node);
|
|
67285
|
+
if (isValueForwardedThroughLiteralStructure(directValue, property.value)) return true;
|
|
67286
|
+
return isSameShorthandPropertyValue(directValue, property);
|
|
67287
|
+
};
|
|
67288
|
+
const isInsideReturnedNextjsProps = (node, pageDataFunction, context) => {
|
|
67289
|
+
let cursor = node.parent;
|
|
67290
|
+
while (cursor && cursor !== pageDataFunction) {
|
|
67291
|
+
if (isNodeOfType(cursor, "Property") && getStaticPropertyKeyName(cursor, { allowComputedString: true }) === "props" && isValueForwardedToPropertyValue(node, cursor)) {
|
|
67292
|
+
const propertyContainer = cursor.parent;
|
|
67293
|
+
if (!propertyContainer) return false;
|
|
67294
|
+
const returnExpression = findConditionalReturnExpressionRoot(propertyContainer);
|
|
67295
|
+
const returnStatement = returnExpression.parent;
|
|
67296
|
+
if (isNodeOfType(returnStatement, "ReturnStatement") && findEnclosingFunction$1(returnStatement) === pageDataFunction) return true;
|
|
67297
|
+
if (isNodeOfType(pageDataFunction, "ArrowFunctionExpression") && !isNodeOfType(pageDataFunction.body, "BlockStatement") && stripParenExpression(pageDataFunction.body) === stripParenExpression(returnExpression)) return true;
|
|
67298
|
+
if (isReturnedPageDataResultBinding(returnExpression, pageDataFunction, context)) return true;
|
|
67299
|
+
}
|
|
67300
|
+
cursor = cursor.parent ?? null;
|
|
67301
|
+
}
|
|
67302
|
+
return false;
|
|
67303
|
+
};
|
|
67304
|
+
const isExpressionReturnedByFunction = (node, functionNode) => {
|
|
67305
|
+
const returnExpression = findConditionalReturnExpressionRoot(node);
|
|
67306
|
+
if (isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode.body, "BlockStatement")) return stripParenExpression(functionNode.body) === stripParenExpression(returnExpression);
|
|
67307
|
+
const returnStatement = returnExpression.parent;
|
|
67308
|
+
return isNodeOfType(returnStatement, "ReturnStatement") && returnStatement.argument === returnExpression && findEnclosingFunction$1(returnStatement) === functionNode;
|
|
67309
|
+
};
|
|
67310
|
+
const isValueForwardedToBindingInitializer = (node, bindingInitializer) => {
|
|
67311
|
+
if (isValueForwardedThroughLiteralStructure(findConditionalReturnExpressionRoot(node), bindingInitializer)) return true;
|
|
67312
|
+
const initializer = stripParenExpression(bindingInitializer);
|
|
67313
|
+
if (!isNodeOfType(initializer, "CallExpression")) return false;
|
|
67314
|
+
const callee = stripParenExpression(initializer.callee);
|
|
67315
|
+
return isFunctionLike$1(callee) && isExpressionReturnedByFunction(node, callee);
|
|
67316
|
+
};
|
|
67317
|
+
const findPageDataResultBinding = (node) => {
|
|
67318
|
+
let cursor = node.parent;
|
|
67319
|
+
while (cursor) {
|
|
67320
|
+
if (isNodeOfType(cursor, "VariableDeclarator")) {
|
|
67321
|
+
if (cursor.init && isNodeOfType(cursor.id, "Identifier") && isValueForwardedToBindingInitializer(node, cursor.init)) return cursor.id;
|
|
67322
|
+
return null;
|
|
67323
|
+
}
|
|
67324
|
+
cursor = cursor.parent ?? null;
|
|
67325
|
+
}
|
|
67326
|
+
return null;
|
|
67327
|
+
};
|
|
67328
|
+
const isUsedToSerializeNextjsPageProps = (node, context) => {
|
|
67329
|
+
if (!isInProjectDirectory(context, "pages") || isInProjectDirectory(context, "pages/api")) return false;
|
|
67330
|
+
const pageDataFunction = findEnclosingNextjsPageDataFunction(node);
|
|
67331
|
+
if (!pageDataFunction) return false;
|
|
67332
|
+
if (isInsideReturnedNextjsProps(node, pageDataFunction, context)) return true;
|
|
67333
|
+
const bindingIdentifier = findPageDataResultBinding(node);
|
|
67334
|
+
const bindingSymbol = bindingIdentifier ? context.scopes.symbolFor(bindingIdentifier) : null;
|
|
67335
|
+
if (!bindingSymbol) return false;
|
|
67336
|
+
const aliasSymbols = collectConstAliasSymbols(bindingSymbol, context.scopes);
|
|
67337
|
+
const aliasSymbolIds = new Set(aliasSymbols.map((aliasSymbol) => aliasSymbol.id));
|
|
67338
|
+
let hasPagePropsReference = false;
|
|
67339
|
+
for (const aliasSymbol of aliasSymbols) for (const reference of aliasSymbol.references) {
|
|
67340
|
+
if (findEnclosingFunction$1(reference.identifier) !== pageDataFunction) return false;
|
|
67341
|
+
if (isInsideReturnedNextjsProps(reference.identifier, pageDataFunction, context)) {
|
|
67342
|
+
hasPagePropsReference = true;
|
|
67343
|
+
continue;
|
|
67344
|
+
}
|
|
67345
|
+
const referenceRoot = findTransparentExpressionRoot(reference.identifier);
|
|
67346
|
+
const declarator = referenceRoot.parent;
|
|
67347
|
+
if (isNodeOfType(declarator, "VariableDeclarator") && declarator.init === referenceRoot && isNodeOfType(declarator.id, "Identifier")) {
|
|
67348
|
+
const aliasSymbolForReference = context.scopes.symbolFor(declarator.id);
|
|
67349
|
+
if (aliasSymbolForReference && aliasSymbolIds.has(aliasSymbolForReference.id)) continue;
|
|
67350
|
+
}
|
|
67351
|
+
return false;
|
|
67352
|
+
}
|
|
67353
|
+
return hasPagePropsReference;
|
|
67354
|
+
};
|
|
67194
67355
|
const noJsonParseStringifyClone = defineRule({
|
|
67195
67356
|
id: "no-json-parse-stringify-clone",
|
|
67196
67357
|
title: "JSON parse/stringify deep clone",
|
|
@@ -67208,6 +67369,7 @@ const noJsonParseStringifyClone = defineRule({
|
|
|
67208
67369
|
if (isInsideSnapshotHelper(node)) return;
|
|
67209
67370
|
if (isAssignedToNormalizationBinding(node)) return;
|
|
67210
67371
|
if (isCatchParameterRoundTrip(firstArgument)) return;
|
|
67372
|
+
if (isUsedToSerializeNextjsPageProps(node, context)) return;
|
|
67211
67373
|
context.report({
|
|
67212
67374
|
node,
|
|
67213
67375
|
message: MESSAGE$35
|
|
@@ -82275,6 +82437,45 @@ const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
|
82275
82437
|
} })
|
|
82276
82438
|
});
|
|
82277
82439
|
//#endregion
|
|
82440
|
+
//#region src/plugin/utils/contains-non-deterministic-source.ts
|
|
82441
|
+
const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
|
|
82442
|
+
"Math.random",
|
|
82443
|
+
"Date.now",
|
|
82444
|
+
"performance.now",
|
|
82445
|
+
"crypto.randomUUID",
|
|
82446
|
+
"crypto.getRandomValues"
|
|
82447
|
+
]);
|
|
82448
|
+
const NON_DETERMINISTIC_ID_GENERATOR_NAMES = new Set([
|
|
82449
|
+
"nanoid",
|
|
82450
|
+
"uuid",
|
|
82451
|
+
"cuid",
|
|
82452
|
+
"ulid",
|
|
82453
|
+
"createId"
|
|
82454
|
+
]);
|
|
82455
|
+
const isZeroArgDateConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Date" && (node.arguments?.length ?? 0) === 0;
|
|
82456
|
+
const containsNonDeterministicSource = (root) => {
|
|
82457
|
+
let found = false;
|
|
82458
|
+
walkAst(root, (child) => {
|
|
82459
|
+
if (found) return false;
|
|
82460
|
+
if (isFunctionLike$1(child)) return false;
|
|
82461
|
+
if (isZeroArgDateConstruction(child)) {
|
|
82462
|
+
found = true;
|
|
82463
|
+
return false;
|
|
82464
|
+
}
|
|
82465
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
82466
|
+
const callee = child.callee;
|
|
82467
|
+
if (isNodeOfType(callee, "Identifier") && NON_DETERMINISTIC_ID_GENERATOR_NAMES.has(callee.name)) {
|
|
82468
|
+
found = true;
|
|
82469
|
+
return false;
|
|
82470
|
+
}
|
|
82471
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && NON_DETERMINISTIC_MEMBER_CALLS.has(`${callee.object.name}.${callee.property.name}`)) {
|
|
82472
|
+
found = true;
|
|
82473
|
+
return false;
|
|
82474
|
+
}
|
|
82475
|
+
});
|
|
82476
|
+
return found;
|
|
82477
|
+
};
|
|
82478
|
+
//#endregion
|
|
82278
82479
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
82279
82480
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
82280
82481
|
"DoWhileStatement",
|
|
@@ -82305,45 +82506,75 @@ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /*
|
|
|
82305
82506
|
};
|
|
82306
82507
|
const isProvablyTruthyInitializationValue = (node, scopes) => {
|
|
82307
82508
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
82308
|
-
|
|
82509
|
+
if (!expression) return false;
|
|
82510
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
82511
|
+
const callee = stripParenExpression(expression.callee);
|
|
82512
|
+
return isNodeOfType(callee, "Identifier") && callee.name.startsWith("create");
|
|
82513
|
+
}
|
|
82514
|
+
return isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression");
|
|
82309
82515
|
};
|
|
82310
|
-
const
|
|
82516
|
+
const getInitializationValueName = (node, scopes) => {
|
|
82311
82517
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
82312
82518
|
if (!expression) return null;
|
|
82313
|
-
if (isNodeOfType(expression, "NewExpression")) {
|
|
82519
|
+
if (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "CallExpression")) {
|
|
82314
82520
|
const callee = stripParenExpression(expression.callee);
|
|
82315
|
-
|
|
82521
|
+
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
82522
|
+
return callee.name.startsWith("create") && callee.name.length > 6 ? callee.name.slice(6) : callee.name;
|
|
82316
82523
|
}
|
|
82317
82524
|
return null;
|
|
82318
82525
|
};
|
|
82526
|
+
const isMatchingReturnType = (typeNode, initializationValue, scopes) => {
|
|
82527
|
+
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82528
|
+
const typeName = typeNode.typeName;
|
|
82529
|
+
if (!isNodeOfType(typeName, "Identifier") || typeName.name !== "ReturnType") return false;
|
|
82530
|
+
const [returnTypeArgument] = typeNode.typeArguments?.params ?? [];
|
|
82531
|
+
if (!returnTypeArgument || !isNodeOfType(returnTypeArgument, "TSTypeQuery")) return false;
|
|
82532
|
+
const queriedName = returnTypeArgument.exprName;
|
|
82533
|
+
const expression = stripParenExpression(initializationValue);
|
|
82534
|
+
if (!isNodeOfType(queriedName, "Identifier") || !isNodeOfType(expression, "CallExpression")) return false;
|
|
82535
|
+
const callee = stripParenExpression(expression.callee);
|
|
82536
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
82537
|
+
const queriedSymbol = scopes.symbolFor(queriedName);
|
|
82538
|
+
const calleeSymbol = scopes.symbolFor(callee);
|
|
82539
|
+
return queriedSymbol && calleeSymbol ? queriedSymbol.id === calleeSymbol.id : queriedName.name === callee.name;
|
|
82540
|
+
};
|
|
82319
82541
|
const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
|
|
82320
82542
|
const initializationExpression = stripParenExpression(initializationValue);
|
|
82321
82543
|
if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
82322
82544
|
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
|
|
82323
82545
|
if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
|
|
82324
82546
|
if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
|
|
82547
|
+
if (isNodeOfType(typeNode, "TSIndexedAccessType")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
82325
82548
|
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82326
82549
|
const typeName = typeNode.typeName;
|
|
82327
|
-
|
|
82550
|
+
if (isNodeOfType(initializationExpression, "ObjectExpression") || isMatchingReturnType(typeNode, initializationExpression, scopes)) return true;
|
|
82551
|
+
return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationValueName(initializationExpression, scopes);
|
|
82328
82552
|
};
|
|
82329
82553
|
const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
|
|
82330
82554
|
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82331
82555
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82332
82556
|
const [initialValue] = initializer.arguments ?? [];
|
|
82333
|
-
if (
|
|
82557
|
+
if (initialValue && isNodeOfType(initialValue, "SpreadElement") || initialValue && !isEmptySentinel(initialValue, scopes)) return false;
|
|
82334
82558
|
const [declaredType] = initializer.typeArguments?.params ?? [];
|
|
82335
|
-
if (!declaredType
|
|
82336
|
-
|
|
82559
|
+
if (!declaredType) return false;
|
|
82560
|
+
const domainTypes = isNodeOfType(declaredType, "TSUnionType") ? declaredType.types : [declaredType];
|
|
82337
82561
|
let hasTruthyDomain = false;
|
|
82338
|
-
for (const memberType of
|
|
82339
|
-
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword"))
|
|
82340
|
-
hasEmptySentinel = true;
|
|
82341
|
-
continue;
|
|
82342
|
-
}
|
|
82562
|
+
for (const memberType of domainTypes) {
|
|
82563
|
+
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) continue;
|
|
82343
82564
|
if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
|
|
82344
82565
|
hasTruthyDomain = true;
|
|
82345
82566
|
}
|
|
82346
|
-
return
|
|
82567
|
+
return hasTruthyDomain;
|
|
82568
|
+
};
|
|
82569
|
+
const refHasEmptySentinelInitializer = (refSymbol, scopes) => {
|
|
82570
|
+
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82571
|
+
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82572
|
+
const [initialValue] = initializer.arguments ?? [];
|
|
82573
|
+
return Boolean(!initialValue || !isNodeOfType(initialValue, "SpreadElement") && isEmptySentinel(initialValue, scopes));
|
|
82574
|
+
};
|
|
82575
|
+
const refHasDeclaredType = (refSymbol) => {
|
|
82576
|
+
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82577
|
+
return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && (initializer.typeArguments?.params.length ?? 0) > 0);
|
|
82347
82578
|
};
|
|
82348
82579
|
const isSafeRefIdentifierUse = (identifier) => {
|
|
82349
82580
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
@@ -82375,25 +82606,40 @@ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
|
|
|
82375
82606
|
});
|
|
82376
82607
|
return didFindRefCurrent;
|
|
82377
82608
|
};
|
|
82378
|
-
const
|
|
82379
|
-
|
|
82380
|
-
|
|
82381
|
-
|
|
82382
|
-
|
|
82383
|
-
|
|
82384
|
-
|
|
82609
|
+
const isEmptySentinel = (node, scopes) => {
|
|
82610
|
+
const expression = stripParenExpression(node);
|
|
82611
|
+
return isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression);
|
|
82612
|
+
};
|
|
82613
|
+
const isInitializationInputIndependent = (node, renderOwner, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
82614
|
+
let isInputIndependent = true;
|
|
82615
|
+
walkAst(node, (child) => {
|
|
82616
|
+
if (!isInputIndependent) return false;
|
|
82617
|
+
if (resolveReactRefSymbol(child, scopes)) return false;
|
|
82618
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
82619
|
+
const symbol = scopes.symbolFor(child);
|
|
82620
|
+
if (!symbol) return;
|
|
82621
|
+
if (symbol.kind === "import") return false;
|
|
82622
|
+
if (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "using") {
|
|
82623
|
+
isInputIndependent = false;
|
|
82624
|
+
return false;
|
|
82385
82625
|
}
|
|
82386
|
-
if (
|
|
82387
|
-
|
|
82388
|
-
|
|
82626
|
+
if (isOutsideAllFunctions(symbol)) return false;
|
|
82627
|
+
if (symbol.kind === "parameter") {
|
|
82628
|
+
if (symbol.scope.node === renderOwner) isInputIndependent = false;
|
|
82629
|
+
return false;
|
|
82389
82630
|
}
|
|
82390
|
-
if (
|
|
82391
|
-
|
|
82631
|
+
if (!symbol.initializer || symbol.references.some((reference) => reference.flag !== "read")) {
|
|
82632
|
+
isInputIndependent = false;
|
|
82633
|
+
return false;
|
|
82392
82634
|
}
|
|
82635
|
+
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
82636
|
+
visitedSymbolIds.add(symbol.id);
|
|
82637
|
+
if (!isInitializationInputIndependent(symbol.initializer, renderOwner, scopes, visitedSymbolIds)) isInputIndependent = false;
|
|
82638
|
+
return false;
|
|
82393
82639
|
});
|
|
82394
|
-
return
|
|
82640
|
+
return isInputIndependent;
|
|
82395
82641
|
};
|
|
82396
|
-
const
|
|
82642
|
+
const isPredictableInitializationValue = (node, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) => isInitializationInputIndependent(node, renderOwner, scopes) && !containsNonDeterministicSource(node) && (isProvablyTruthyInitializationValue(node, scopes) && (!requiresClosedTruthyDomain || !refHasDeclaredType(refSymbol)) || refHasClosedFalsySentinelDomain(refSymbol, node, scopes));
|
|
82397
82643
|
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
82398
82644
|
let ancestor = node.parent;
|
|
82399
82645
|
while (ancestor && ancestor !== stop) {
|
|
@@ -82423,6 +82669,27 @@ const canExecuteTogether = (firstConstraints, secondConstraints) => {
|
|
|
82423
82669
|
}
|
|
82424
82670
|
return true;
|
|
82425
82671
|
};
|
|
82672
|
+
const hasNoCoExecutableCompetingWrite = (assignmentExpression, renderOwner, refSymbol, scopes) => {
|
|
82673
|
+
const assignmentConstraints = getBranchConstraints(assignmentExpression, renderOwner);
|
|
82674
|
+
const synchronouslyInvokedFunctions = collectSynchronouslyEffectInvokedFunctions(renderOwner, scopes);
|
|
82675
|
+
let hasCompetingWrite = false;
|
|
82676
|
+
walkAst(renderOwner, (child) => {
|
|
82677
|
+
if (hasCompetingWrite) return false;
|
|
82678
|
+
let writtenExpression = null;
|
|
82679
|
+
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
82680
|
+
if (child === assignmentExpression) return;
|
|
82681
|
+
writtenExpression = child.left;
|
|
82682
|
+
} else if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") writtenExpression = child.argument;
|
|
82683
|
+
else if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) writtenExpression = child.left;
|
|
82684
|
+
const deferredExecutionBoundary = findDeferredExecutionBoundary(child);
|
|
82685
|
+
const deferredWriteValue = isNodeOfType(child, "AssignmentExpression") && child.operator === "=" ? resolveImmutableInitializationValue(child.right, scopes) : null;
|
|
82686
|
+
const isDeferredTruthyWrite = deferredExecutionBoundary !== null && deferredExecutionBoundary !== renderOwner && !synchronouslyInvokedFunctions.has(deferredExecutionBoundary) && !executesDuringRender(deferredExecutionBoundary, scopes) && deferredWriteValue !== null && !isNodeOfType(deferredWriteValue, "CallExpression") && isProvablyTruthyInitializationValue(deferredWriteValue, scopes);
|
|
82687
|
+
if (!writtenExpression || isDeferredTruthyWrite || !expressionContainsRefCurrent(writtenExpression, refSymbol, scopes) || !canExecuteTogether(assignmentConstraints, getBranchConstraints(child, renderOwner))) return;
|
|
82688
|
+
hasCompetingWrite = true;
|
|
82689
|
+
return false;
|
|
82690
|
+
});
|
|
82691
|
+
return !hasCompetingWrite;
|
|
82692
|
+
};
|
|
82426
82693
|
const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
|
|
82427
82694
|
const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
|
|
82428
82695
|
const assignmentStart = getRangeStart(assignmentExpression);
|
|
@@ -82438,16 +82705,17 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
|
|
|
82438
82705
|
});
|
|
82439
82706
|
return !hasCoExecutableWrite;
|
|
82440
82707
|
};
|
|
82708
|
+
const isPredictableGuardedInitialization = (assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, requiresClosedTruthyDomain) => refHasEmptySentinelInitializer(refSymbol, scopes) && isPredictableInitializationValue(assignmentExpression.right, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) && !hasRepeatedExecutionAncestor(assignmentExpression, guardedBranch) && (guardedBranch === renderOwner || !hasRepeatedExecutionAncestor(guardedBranch, renderOwner)) && hasNoPriorCoExecutableWrite(assignmentExpression, renderOwner, refSymbol, scopes) && hasNoCoExecutableCompetingWrite(assignmentExpression, renderOwner, refSymbol, scopes) && refDoesNotEscape(renderOwner, refSymbol, scopes);
|
|
82441
82709
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
82442
|
-
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
|
|
82443
|
-
if (assignmentExpression.operator !== "=") return false;
|
|
82444
82710
|
const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
|
|
82445
82711
|
if (!renderOwner) return false;
|
|
82712
|
+
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return isPredictableGuardedInitialization(assignmentExpression, renderOwner, renderOwner, refSymbol, scopes, assignmentExpression.operator === "||=");
|
|
82713
|
+
if (assignmentExpression.operator !== "=") return false;
|
|
82446
82714
|
let descendant = assignmentExpression;
|
|
82447
82715
|
let ancestor = descendant.parent;
|
|
82448
82716
|
while (ancestor) {
|
|
82449
82717
|
const test = isNodeOfType(ancestor, "IfStatement") ? stripParenExpression(ancestor.test) : null;
|
|
82450
|
-
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant &&
|
|
82718
|
+
if (isNodeOfType(ancestor, "IfStatement") && test && isNodeOfType(test, "UnaryExpression") && test.operator === "!" && isSameRefCurrentAlias(test.argument, refSymbol, scopes) && ancestor.consequent === descendant && isPredictableGuardedInitialization(assignmentExpression, ancestor.consequent, renderOwner, refSymbol, scopes, true)) return true;
|
|
82451
82719
|
if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
|
|
82452
82720
|
"===",
|
|
82453
82721
|
"==",
|
|
@@ -82457,7 +82725,7 @@ const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes)
|
|
|
82457
82725
|
const { left, right } = test;
|
|
82458
82726
|
const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
|
|
82459
82727
|
const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
82460
|
-
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch &&
|
|
82728
|
+
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && isPredictableGuardedInitialization(assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, false)) return true;
|
|
82461
82729
|
}
|
|
82462
82730
|
descendant = ancestor;
|
|
82463
82731
|
ancestor = descendant.parent;
|