oxlint-plugin-react-doctor 0.7.4-dev.63fc41f → 0.7.4-dev.6821fe0
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 +159 -19
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -553,7 +553,7 @@ const EXTERNAL_SYNC_OBSERVER_CONSTRUCTORS = new Set([
|
|
|
553
553
|
"ResizeObserver",
|
|
554
554
|
"PerformanceObserver"
|
|
555
555
|
]);
|
|
556
|
-
const STORAGE_OBJECTS
|
|
556
|
+
const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
|
|
557
557
|
//#endregion
|
|
558
558
|
//#region src/plugin/constants/react.ts
|
|
559
559
|
const INDEX_PARAMETER_NAMES = new Set([
|
|
@@ -5139,8 +5139,10 @@ const STORAGE_GLOBALS = new Set([
|
|
|
5139
5139
|
const SENSITIVE_KEY_PATTERN = /token|jwt|secret|password|passwd|credential|api[-_]?key|bearer|private[-_]?key/i;
|
|
5140
5140
|
const NON_AUTH_TOKEN_PATTERN = /csrf|xsrf|device|fcm|apns|push|design|tokeniz|syntax|css|theme|color/i;
|
|
5141
5141
|
const STRONG_AUTH_KEY_PATTERN = /jwt|secret|password|passwd|credential|private[-_]?key|api[-_]?key|bearer|access[-_]?token|refresh[-_]?token|auth[-_]?token|id[-_]?token|session/i;
|
|
5142
|
+
const PRODUCT_API_KEY_COLLECTION_PATTERN = /[._:-](?:created|generated|saved)[-_]?api[-_]?keys$/i;
|
|
5142
5143
|
const isAuthCredentialKey = (key) => {
|
|
5143
5144
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
5145
|
+
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
5144
5146
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
5145
5147
|
return true;
|
|
5146
5148
|
};
|
|
@@ -6135,7 +6137,6 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
|
|
|
6135
6137
|
//#region src/plugin/rules/client/client-localstorage-no-version.ts
|
|
6136
6138
|
const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
|
|
6137
6139
|
const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
|
|
6138
|
-
const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
|
|
6139
6140
|
const isVersionedKey = (key) => VERSIONED_KEY_PATTERN.test(key) || CAMEL_CASE_VERSIONED_KEY_PATTERN.test(key);
|
|
6140
6141
|
const isJsonStringifyCall = (node) => isGlobalMethodCall(node, "JSON", "stringify");
|
|
6141
6142
|
const resolveStringKey = (keyArg, context) => {
|
|
@@ -6158,7 +6159,7 @@ const clientLocalstorageNoVersion = defineRule({
|
|
|
6158
6159
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
6159
6160
|
const receiver = stripParenExpression(node.callee.object);
|
|
6160
6161
|
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
6161
|
-
if (
|
|
6162
|
+
if (receiver.name !== "localStorage") return;
|
|
6162
6163
|
if (!isNodeOfType(node.callee.property, "Identifier")) return;
|
|
6163
6164
|
if (node.callee.property.name !== "setItem") return;
|
|
6164
6165
|
const keyArg = node.arguments?.[0];
|
|
@@ -8938,7 +8939,7 @@ const getListenerAbortControllerKey = (usage, context) => {
|
|
|
8938
8939
|
}
|
|
8939
8940
|
return null;
|
|
8940
8941
|
};
|
|
8941
|
-
const SYNCHRONOUS_ITERATOR_METHOD_NAMES$
|
|
8942
|
+
const SYNCHRONOUS_ITERATOR_METHOD_NAMES$2 = new Set([
|
|
8942
8943
|
"every",
|
|
8943
8944
|
"filter",
|
|
8944
8945
|
"flatMap",
|
|
@@ -8954,7 +8955,7 @@ const isSynchronousIteratorCallback = (functionNode) => {
|
|
|
8954
8955
|
const callee = stripParenExpression(callNode.callee);
|
|
8955
8956
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
8956
8957
|
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
|
|
8957
|
-
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$
|
|
8958
|
+
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$2.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
|
|
8958
8959
|
};
|
|
8959
8960
|
const findDirectCallForReference = (identifier) => {
|
|
8960
8961
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
@@ -10589,6 +10590,19 @@ const isUnstableInitializer = (node) => {
|
|
|
10589
10590
|
if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
|
|
10590
10591
|
return isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "ArrayExpression") || isNodeOfType(stripped, "ClassExpression") || isNodeOfType(stripped, "ClassDeclaration") || isNodeOfType(stripped, "JSXElement") || isNodeOfType(stripped, "JSXFragment") || isNodeOfType(stripped, "AssignmentExpression") || isNodeOfType(stripped, "NewExpression");
|
|
10591
10592
|
};
|
|
10593
|
+
const isPotentiallyFreshComparedValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
10594
|
+
const candidate = unwrapExpression$3(node);
|
|
10595
|
+
if (isUnstableInitializer(candidate)) return true;
|
|
10596
|
+
if (isNodeOfType(candidate, "ConditionalExpression")) return isPotentiallyFreshComparedValue(candidate.consequent, scopes, visitedSymbolIds) || isPotentiallyFreshComparedValue(candidate.alternate, scopes, visitedSymbolIds);
|
|
10597
|
+
if (isNodeOfType(candidate, "LogicalExpression")) return isPotentiallyFreshComparedValue(candidate.left, scopes, visitedSymbolIds) || isPotentiallyFreshComparedValue(candidate.right, scopes, visitedSymbolIds);
|
|
10598
|
+
if (!isNodeOfType(candidate, "Identifier")) return false;
|
|
10599
|
+
const symbol = scopes.symbolFor(candidate);
|
|
10600
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
10601
|
+
if (symbol.kind === "let" || symbol.kind === "var") return true;
|
|
10602
|
+
if (symbol.kind !== "const" || !symbol.initializer) return false;
|
|
10603
|
+
visitedSymbolIds.add(symbol.id);
|
|
10604
|
+
return isPotentiallyFreshComparedValue(symbol.initializer, scopes, visitedSymbolIds);
|
|
10605
|
+
};
|
|
10592
10606
|
const isExtraDepAllowedForHook = (hookName, node, scopes) => {
|
|
10593
10607
|
if (!isExtraReactiveDepAllowed(node, scopes)) return false;
|
|
10594
10608
|
if (EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName)) return true;
|
|
@@ -10602,6 +10616,52 @@ const isStableSetterLikeSymbol = (symbol, scopes) => {
|
|
|
10602
10616
|
if (!symbolHasStableHookOrigin(symbol, scopes)) return false;
|
|
10603
10617
|
return symbol.name.startsWith("set") || symbol.name.startsWith("dispatch") || symbol.name.startsWith("startTransition");
|
|
10604
10618
|
};
|
|
10619
|
+
const isConvergingFunctionalUpdater = (node, scopes) => {
|
|
10620
|
+
const updater = unwrapExpression$3(node);
|
|
10621
|
+
if (!isNodeOfType(updater, "ArrowFunctionExpression") && !isNodeOfType(updater, "FunctionExpression")) return false;
|
|
10622
|
+
const previousValueParameter = updater.params?.[0];
|
|
10623
|
+
if (!previousValueParameter || !isNodeOfType(previousValueParameter, "Identifier")) return false;
|
|
10624
|
+
let returnedExpression = updater.body;
|
|
10625
|
+
if (isNodeOfType(updater.body, "BlockStatement")) {
|
|
10626
|
+
returnedExpression = null;
|
|
10627
|
+
for (const statement of updater.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument) {
|
|
10628
|
+
returnedExpression = statement.argument;
|
|
10629
|
+
break;
|
|
10630
|
+
}
|
|
10631
|
+
}
|
|
10632
|
+
const conditional = returnedExpression ? unwrapExpression$3(returnedExpression) : null;
|
|
10633
|
+
if (!conditional || !isNodeOfType(conditional, "ConditionalExpression")) return false;
|
|
10634
|
+
const test = unwrapExpression$3(conditional.test);
|
|
10635
|
+
if (!isNodeOfType(test, "BinaryExpression") || ![
|
|
10636
|
+
"===",
|
|
10637
|
+
"!==",
|
|
10638
|
+
"==",
|
|
10639
|
+
"!="
|
|
10640
|
+
].includes(test.operator)) return false;
|
|
10641
|
+
const isPreviousValue = (expression) => {
|
|
10642
|
+
const candidate = unwrapExpression$3(expression);
|
|
10643
|
+
return isNodeOfType(candidate, "Identifier") && candidate.name === previousValueParameter.name;
|
|
10644
|
+
};
|
|
10645
|
+
let comparedValue = null;
|
|
10646
|
+
if (isPreviousValue(test.left)) comparedValue = test.right;
|
|
10647
|
+
else if (isPreviousValue(test.right)) comparedValue = test.left;
|
|
10648
|
+
if (!comparedValue) return false;
|
|
10649
|
+
if (isPotentiallyFreshComparedValue(comparedValue, scopes)) return false;
|
|
10650
|
+
const isSameComparedValue = (expression) => {
|
|
10651
|
+
const candidate = unwrapExpression$3(expression);
|
|
10652
|
+
const compared = unwrapExpression$3(comparedValue);
|
|
10653
|
+
if (isNodeOfType(candidate, "Identifier") && isNodeOfType(compared, "Identifier")) return candidate.name === compared.name;
|
|
10654
|
+
if (isNodeOfType(candidate, "Literal") && isNodeOfType(compared, "Literal")) return candidate.value === compared.value;
|
|
10655
|
+
return false;
|
|
10656
|
+
};
|
|
10657
|
+
return test.operator === "===" || test.operator === "==" ? isPreviousValue(conditional.consequent) && isSameComparedValue(conditional.alternate) : isSameComparedValue(conditional.consequent) && isPreviousValue(conditional.alternate);
|
|
10658
|
+
};
|
|
10659
|
+
const isGuardedStableSetterCall = (identifier, symbol, scopes) => {
|
|
10660
|
+
const parent = identifier.parent;
|
|
10661
|
+
if (!parent || !isNodeOfType(parent, "CallExpression") || parent.callee !== identifier || !isStableSetterLikeSymbol(symbol, scopes)) return false;
|
|
10662
|
+
const writtenValue = parent.arguments?.[0];
|
|
10663
|
+
return Boolean(writtenValue && isConvergingFunctionalUpdater(writtenValue, scopes));
|
|
10664
|
+
};
|
|
10605
10665
|
const findStableSetterReference = (node, scopes) => {
|
|
10606
10666
|
let setterName = null;
|
|
10607
10667
|
const visit = (current) => {
|
|
@@ -10609,7 +10669,7 @@ const findStableSetterReference = (node, scopes) => {
|
|
|
10609
10669
|
if (current !== node && (isNodeOfType(current, "FunctionDeclaration") || isNodeOfType(current, "FunctionExpression") || isNodeOfType(current, "ArrowFunctionExpression"))) return;
|
|
10610
10670
|
if (isNodeOfType(current, "Identifier")) {
|
|
10611
10671
|
const symbol = scopes.referenceFor(current)?.resolvedSymbol;
|
|
10612
|
-
if (symbol && isStableSetterLikeSymbol(symbol, scopes)) {
|
|
10672
|
+
if (symbol && isStableSetterLikeSymbol(symbol, scopes) && !isGuardedStableSetterCall(current, symbol, scopes)) {
|
|
10613
10673
|
setterName = symbol.name;
|
|
10614
10674
|
return;
|
|
10615
10675
|
}
|
|
@@ -13502,7 +13562,7 @@ const jsCacheStorage = defineRule({
|
|
|
13502
13562
|
CallExpression(node) {
|
|
13503
13563
|
if (!isMemberProperty(node.callee, "getItem")) return;
|
|
13504
13564
|
const receiver = stripParenExpression(node.callee.object);
|
|
13505
|
-
if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS
|
|
13565
|
+
if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS.has(receiver.name)) return;
|
|
13506
13566
|
if (!isNodeOfType(node.arguments?.[0], "Literal")) return;
|
|
13507
13567
|
const storageReadCounts = storageReadCountStack[storageReadCountStack.length - 1];
|
|
13508
13568
|
const storageKey = String(node.arguments[0].value);
|
|
@@ -13769,6 +13829,11 @@ const jsEarlyExit = defineRule({
|
|
|
13769
13829
|
});
|
|
13770
13830
|
//#endregion
|
|
13771
13831
|
//#region src/plugin/rules/js-performance/js-flatmap-filter.ts
|
|
13832
|
+
const BOUNDED_PIPELINE_SOURCE_METHOD_NAMES = new Set(["slice", "split"]);
|
|
13833
|
+
const isBoundedPipelineSource = (node) => {
|
|
13834
|
+
const receiver = stripParenExpression(node);
|
|
13835
|
+
return isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && BOUNDED_PIPELINE_SOURCE_METHOD_NAMES.has(receiver.callee.property.name);
|
|
13836
|
+
};
|
|
13772
13837
|
const jsFlatmapFilter = defineRule({
|
|
13773
13838
|
id: "js-flatmap-filter",
|
|
13774
13839
|
title: ".map().filter(Boolean) loops twice",
|
|
@@ -13786,6 +13851,7 @@ const jsFlatmapFilter = defineRule({
|
|
|
13786
13851
|
if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
|
|
13787
13852
|
if (innerCall.callee.property.name !== "map") return;
|
|
13788
13853
|
const receiver = stripParenExpression(innerCall.callee.object);
|
|
13854
|
+
if (receiver && isBoundedPipelineSource(receiver)) return;
|
|
13789
13855
|
if (receiver && isNodeOfType(receiver, "ArrayExpression")) {
|
|
13790
13856
|
const elements = receiver.elements ?? [];
|
|
13791
13857
|
if (elements.length > 0 && elements.length <= 8 && elements.every((element) => element == null || !isNodeOfType(element, "SpreadElement"))) return;
|
|
@@ -22752,7 +22818,7 @@ const readsPostMountValueThroughLocals = (root, effectFn, options = {}, visitedL
|
|
|
22752
22818
|
};
|
|
22753
22819
|
//#endregion
|
|
22754
22820
|
//#region src/plugin/rules/state-and-effects/utils/collect-effect-state-write-facts.ts
|
|
22755
|
-
const SYNCHRONOUS_ITERATOR_METHOD_NAMES = new Set([
|
|
22821
|
+
const SYNCHRONOUS_ITERATOR_METHOD_NAMES$1 = new Set([
|
|
22756
22822
|
"every",
|
|
22757
22823
|
"filter",
|
|
22758
22824
|
"find",
|
|
@@ -23060,7 +23126,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
|
|
|
23060
23126
|
const calleeName = getCallCalleeName$1(child);
|
|
23061
23127
|
const memberName = getStaticMemberName(callee);
|
|
23062
23128
|
const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
|
|
23063
|
-
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23129
|
+
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23064
23130
|
const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
|
|
23065
23131
|
const callable = resolveWrappedCallable(analysis, callableNode);
|
|
23066
23132
|
if (!callable || callable.async === true || callable === effectFunction) return;
|
|
@@ -23297,10 +23363,15 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
|
|
|
23297
23363
|
const definitionNode = definition.node;
|
|
23298
23364
|
return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
|
|
23299
23365
|
}) === true;
|
|
23366
|
+
const getUseRefDeclarator = (analysis, memberExpression) => {
|
|
23367
|
+
if (!isNodeOfType(memberExpression, "MemberExpression") || getStaticMemberName(memberExpression) !== "current" || !isNodeOfType(memberExpression.object, "Identifier")) return null;
|
|
23368
|
+
return getRef(analysis, memberExpression.object)?.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator") && isNodeOfType(definitionNode.init, "CallExpression") && getCallCalleeName$1(definitionNode.init) === "useRef") ?? null;
|
|
23369
|
+
};
|
|
23300
23370
|
const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
|
|
23301
23371
|
const node = stripParenExpression(expression);
|
|
23302
23372
|
const evidence = emptyEvidence();
|
|
23303
|
-
|
|
23373
|
+
const useRefDeclarator = getUseRefDeclarator(analysis, node);
|
|
23374
|
+
if (!useRefDeclarator && (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode))) {
|
|
23304
23375
|
evidence.readsExternalValue = true;
|
|
23305
23376
|
return evidence;
|
|
23306
23377
|
}
|
|
@@ -23342,7 +23413,11 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23342
23413
|
evidence.hasUnknownSource = true;
|
|
23343
23414
|
return evidence;
|
|
23344
23415
|
}
|
|
23345
|
-
|
|
23416
|
+
const nonInitializerWrites = reference.resolved.references.filter((candidateReference) => candidateReference.isWrite() && !candidateReference.init);
|
|
23417
|
+
if (nonInitializerWrites.length > 0) {
|
|
23418
|
+
const writtenIdentifier = nonInitializerWrites[0]?.identifier;
|
|
23419
|
+
const assignment = writtenIdentifier.parent;
|
|
23420
|
+
if (nonInitializerWrites.length === 1 && assignment && isNodeOfType(assignment, "AssignmentExpression") && assignment.operator === "=" && assignment.left === writtenIdentifier) return collectValueEvidence(analysis, assignment.right, frame, remainingCallFrames, visitedBindings);
|
|
23346
23421
|
evidence.hasUnknownSource = true;
|
|
23347
23422
|
return evidence;
|
|
23348
23423
|
}
|
|
@@ -23358,6 +23433,40 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23358
23433
|
return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
|
|
23359
23434
|
}
|
|
23360
23435
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
23436
|
+
if (getStaticMemberName(node) === "current" && isNodeOfType(node.object, "Identifier")) {
|
|
23437
|
+
const refBinding = getRef(analysis, node.object)?.resolved;
|
|
23438
|
+
const refDeclarator = useRefDeclarator;
|
|
23439
|
+
if (refBinding && refDeclarator && isNodeOfType(refDeclarator, "VariableDeclarator") && isNodeOfType(refDeclarator.init, "CallExpression")) {
|
|
23440
|
+
if (visitedBindings.has(refBinding)) {
|
|
23441
|
+
evidence.hasUnknownSource = true;
|
|
23442
|
+
return evidence;
|
|
23443
|
+
}
|
|
23444
|
+
const refVisitedBindings = new Set(visitedBindings);
|
|
23445
|
+
refVisitedBindings.add(refBinding);
|
|
23446
|
+
const initialValue = refDeclarator.init.arguments?.[0];
|
|
23447
|
+
if (initialValue) mergeEvidence(evidence, collectValueEvidence(analysis, initialValue, frame, remainingCallFrames, new Set(refVisitedBindings)));
|
|
23448
|
+
for (const candidateReference of refBinding.references) {
|
|
23449
|
+
if (candidateReference.init) continue;
|
|
23450
|
+
const identifier = candidateReference.identifier;
|
|
23451
|
+
const member = identifier.parent;
|
|
23452
|
+
if (!member || !isNodeOfType(member, "MemberExpression") || member.object !== identifier || getStaticMemberName(member) !== "current") {
|
|
23453
|
+
evidence.hasUnknownSource = true;
|
|
23454
|
+
continue;
|
|
23455
|
+
}
|
|
23456
|
+
const memberParent = member.parent;
|
|
23457
|
+
if (memberParent && isNodeOfType(memberParent, "AssignmentExpression") && memberParent.left === member) {
|
|
23458
|
+
if (memberParent.operator !== "=") {
|
|
23459
|
+
evidence.hasUnknownSource = true;
|
|
23460
|
+
continue;
|
|
23461
|
+
}
|
|
23462
|
+
mergeEvidence(evidence, collectValueEvidence(analysis, memberParent.right, frame, remainingCallFrames, new Set(refVisitedBindings)));
|
|
23463
|
+
continue;
|
|
23464
|
+
}
|
|
23465
|
+
if (memberParent && isNodeOfType(memberParent, "UpdateExpression")) evidence.hasUnknownSource = true;
|
|
23466
|
+
}
|
|
23467
|
+
return evidence;
|
|
23468
|
+
}
|
|
23469
|
+
}
|
|
23361
23470
|
if (isNodeOfType(node.object, "Identifier")) {
|
|
23362
23471
|
const objectReference = getRef(analysis, node.object);
|
|
23363
23472
|
if (objectReference && isLocallyConstructedObjectMember(objectReference, node)) {
|
|
@@ -29136,6 +29245,18 @@ const MUTATING_COLLECTION_METHOD_NAMES = new Set([
|
|
|
29136
29245
|
"set",
|
|
29137
29246
|
"add"
|
|
29138
29247
|
]);
|
|
29248
|
+
const SYNCHRONOUS_ITERATOR_METHOD_NAMES = new Set([
|
|
29249
|
+
"every",
|
|
29250
|
+
"filter",
|
|
29251
|
+
"find",
|
|
29252
|
+
"findIndex",
|
|
29253
|
+
"flatMap",
|
|
29254
|
+
"forEach",
|
|
29255
|
+
"map",
|
|
29256
|
+
"reduce",
|
|
29257
|
+
"reduceRight",
|
|
29258
|
+
"some"
|
|
29259
|
+
]);
|
|
29139
29260
|
const getMemberRootBindingName = (node, scope) => {
|
|
29140
29261
|
let currentNode = node;
|
|
29141
29262
|
while (isNodeOfType(currentNode, "MemberExpression")) currentNode = currentNode.object;
|
|
@@ -29189,6 +29310,26 @@ const addMutatingCollectionCallDependencies = (graph, expression, scope, eventHa
|
|
|
29189
29310
|
addDependencyNames(dependencyNames, controlDependencyNames);
|
|
29190
29311
|
addDependencies(graph, receiverRootName, dependencyNames);
|
|
29191
29312
|
};
|
|
29313
|
+
const addIteratorMutationDependencies = (graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames) => {
|
|
29314
|
+
if (!isNodeOfType(expression, "CallExpression")) return;
|
|
29315
|
+
if (!isNodeOfType(expression.callee, "MemberExpression")) return;
|
|
29316
|
+
const methodName = getStaticMemberPropertyName(expression.callee);
|
|
29317
|
+
if (!methodName || !SYNCHRONOUS_ITERATOR_METHOD_NAMES.has(methodName)) return;
|
|
29318
|
+
const iteratorDependencyNames = collectScopedReferenceNames(expression.callee.object, scope, eventHandlerReferenceNames);
|
|
29319
|
+
addDependencyNames(iteratorDependencyNames, controlDependencyNames);
|
|
29320
|
+
for (const argument of expression.arguments ?? []) {
|
|
29321
|
+
if (!isNodeOfType(argument, "ArrowFunctionExpression") && !isNodeOfType(argument, "FunctionExpression")) continue;
|
|
29322
|
+
walkAst(argument.body, (node) => {
|
|
29323
|
+
if (node !== argument.body && isFunctionLike$1(node)) return false;
|
|
29324
|
+
if (!isNodeOfType(node, "CallExpression")) return;
|
|
29325
|
+
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
29326
|
+
const nestedMethodName = getStaticMemberPropertyName(node.callee);
|
|
29327
|
+
if (!nestedMethodName || !MUTATING_COLLECTION_METHOD_NAMES.has(nestedMethodName)) return;
|
|
29328
|
+
const receiverRootName = getMemberRootBindingName(node.callee.object, scope);
|
|
29329
|
+
if (receiverRootName) addDependencies(graph, receiverRootName, iteratorDependencyNames);
|
|
29330
|
+
});
|
|
29331
|
+
}
|
|
29332
|
+
};
|
|
29192
29333
|
const collectExpressionDependencies = (graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames) => {
|
|
29193
29334
|
if (isNodeOfType(expression, "AssignmentExpression")) {
|
|
29194
29335
|
addAssignmentExpressionDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
|
|
@@ -29196,6 +29337,7 @@ const collectExpressionDependencies = (graph, expression, scope, eventHandlerRef
|
|
|
29196
29337
|
}
|
|
29197
29338
|
if (isNodeOfType(expression, "CallExpression")) {
|
|
29198
29339
|
addMutatingCollectionCallDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
|
|
29340
|
+
addIteratorMutationDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
|
|
29199
29341
|
return;
|
|
29200
29342
|
}
|
|
29201
29343
|
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
@@ -30236,28 +30378,26 @@ const noGiantComponent = defineRule({
|
|
|
30236
30378
|
const lineCount = bodyNode.loc.end.line - bodyNode.loc.start.line + 1;
|
|
30237
30379
|
return lineCount > 300 ? lineCount : null;
|
|
30238
30380
|
};
|
|
30239
|
-
const reportOversizedComponent = (nameNode, componentName
|
|
30381
|
+
const reportOversizedComponent = (nameNode, componentName) => {
|
|
30240
30382
|
context.report({
|
|
30241
30383
|
node: nameNode,
|
|
30242
|
-
message: `Component "${componentName}" is
|
|
30384
|
+
message: `Component "${componentName}" is over 300 lines long, which is hard to read & change. Split it into a few smaller components.`
|
|
30243
30385
|
});
|
|
30244
30386
|
};
|
|
30245
30387
|
return {
|
|
30246
30388
|
FunctionDeclaration(node) {
|
|
30247
30389
|
if (!node.id?.name || !isUppercaseName(node.id.name)) return;
|
|
30248
|
-
|
|
30249
|
-
if (lineCount === null) return;
|
|
30390
|
+
if (getOversizedComponentLineCount(node) === null) return;
|
|
30250
30391
|
if (!functionContainsReactRenderOutput(node, context.scopes)) return;
|
|
30251
|
-
reportOversizedComponent(node.id, node.id.name
|
|
30392
|
+
reportOversizedComponent(node.id, node.id.name);
|
|
30252
30393
|
},
|
|
30253
30394
|
VariableDeclarator(node) {
|
|
30254
30395
|
if (!isNodeOfType(node.id, "Identifier") || !isUppercaseName(node.id.name)) return;
|
|
30255
30396
|
const functionNode = unwrapReactHocFunction(node.init);
|
|
30256
30397
|
if (!functionNode) return;
|
|
30257
|
-
|
|
30258
|
-
if (lineCount === null) return;
|
|
30398
|
+
if (getOversizedComponentLineCount(functionNode) === null) return;
|
|
30259
30399
|
if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
|
|
30260
|
-
reportOversizedComponent(node.id, node.id.name
|
|
30400
|
+
reportOversizedComponent(node.id, node.id.name);
|
|
30261
30401
|
}
|
|
30262
30402
|
};
|
|
30263
30403
|
}
|