oxlint-plugin-react-doctor 0.7.4-dev.6821fe0 → 0.7.4-dev.7bbb792
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 +0 -46
- package/dist/index.js +19 -231
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5296,29 +5296,6 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
5296
5296
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
5297
5297
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5298
5298
|
};
|
|
5299
|
-
}, {
|
|
5300
|
-
readonly key: "react-doctor/no-prop-callback-in-render";
|
|
5301
|
-
readonly id: "no-prop-callback-in-render";
|
|
5302
|
-
readonly source: "react-doctor";
|
|
5303
|
-
readonly originallyExternal: false;
|
|
5304
|
-
readonly rule: {
|
|
5305
|
-
readonly framework: "global";
|
|
5306
|
-
readonly category: "Bugs";
|
|
5307
|
-
readonly requires: readonly Capability[];
|
|
5308
|
-
readonly id: string;
|
|
5309
|
-
readonly title?: string;
|
|
5310
|
-
readonly severity: RuleSeverity;
|
|
5311
|
-
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
5312
|
-
readonly tags?: ReadonlyArray<string>;
|
|
5313
|
-
readonly matchByOccurrence?: boolean;
|
|
5314
|
-
readonly defaultEnabled?: boolean;
|
|
5315
|
-
readonly lifecycle?: "retired";
|
|
5316
|
-
readonly scan?: FileScan;
|
|
5317
|
-
readonly committedFilesOnly?: boolean;
|
|
5318
|
-
readonly recommendation?: string;
|
|
5319
|
-
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
5320
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5321
|
-
};
|
|
5322
5299
|
}, {
|
|
5323
5300
|
readonly key: "react-doctor/no-prop-types";
|
|
5324
5301
|
readonly id: "no-prop-types";
|
|
@@ -14562,29 +14539,6 @@ declare const RULES: readonly [{
|
|
|
14562
14539
|
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
14563
14540
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
14564
14541
|
};
|
|
14565
|
-
}, {
|
|
14566
|
-
readonly key: "react-doctor/no-prop-callback-in-render";
|
|
14567
|
-
readonly id: "no-prop-callback-in-render";
|
|
14568
|
-
readonly source: "react-doctor";
|
|
14569
|
-
readonly originallyExternal: false;
|
|
14570
|
-
readonly rule: {
|
|
14571
|
-
readonly framework: "global";
|
|
14572
|
-
readonly category: "Bugs";
|
|
14573
|
-
readonly requires: readonly Capability[];
|
|
14574
|
-
readonly id: string;
|
|
14575
|
-
readonly title?: string;
|
|
14576
|
-
readonly severity: RuleSeverity;
|
|
14577
|
-
readonly disabledWhen?: ReadonlyArray<Capability>;
|
|
14578
|
-
readonly tags?: ReadonlyArray<string>;
|
|
14579
|
-
readonly matchByOccurrence?: boolean;
|
|
14580
|
-
readonly defaultEnabled?: boolean;
|
|
14581
|
-
readonly lifecycle?: "retired";
|
|
14582
|
-
readonly scan?: FileScan;
|
|
14583
|
-
readonly committedFilesOnly?: boolean;
|
|
14584
|
-
readonly recommendation?: string;
|
|
14585
|
-
readonly recommendationFor?: (hasCapability: CapabilityQuery) => string | undefined;
|
|
14586
|
-
readonly create: (context: RuleContext) => RuleVisitors;
|
|
14587
|
-
};
|
|
14588
14542
|
}, {
|
|
14589
14543
|
readonly key: "react-doctor/no-prop-types";
|
|
14590
14544
|
readonly id: "no-prop-types";
|
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 = new Set(["localStorage", "sessionStorage"]);
|
|
556
|
+
const STORAGE_OBJECTS$1 = new Set(["localStorage", "sessionStorage"]);
|
|
557
557
|
//#endregion
|
|
558
558
|
//#region src/plugin/constants/react.ts
|
|
559
559
|
const INDEX_PARAMETER_NAMES = new Set([
|
|
@@ -5139,10 +5139,8 @@ 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;
|
|
5143
5142
|
const isAuthCredentialKey = (key) => {
|
|
5144
5143
|
if (!SENSITIVE_KEY_PATTERN.test(key)) return false;
|
|
5145
|
-
if (PRODUCT_API_KEY_COLLECTION_PATTERN.test(key)) return false;
|
|
5146
5144
|
if (NON_AUTH_TOKEN_PATTERN.test(key) && !STRONG_AUTH_KEY_PATTERN.test(key)) return false;
|
|
5147
5145
|
return true;
|
|
5148
5146
|
};
|
|
@@ -6137,6 +6135,7 @@ const isGlobalMethodCall = (node, objectName, methodName) => {
|
|
|
6137
6135
|
//#region src/plugin/rules/client/client-localstorage-no-version.ts
|
|
6138
6136
|
const VERSIONED_KEY_PATTERN = /(?:[._:-]v\d+|@\d+|\bv\d+\b)/i;
|
|
6139
6137
|
const CAMEL_CASE_VERSIONED_KEY_PATTERN = /[a-z]V\d+/;
|
|
6138
|
+
const STORAGE_OBJECTS = new Set(["localStorage", "sessionStorage"]);
|
|
6140
6139
|
const isVersionedKey = (key) => VERSIONED_KEY_PATTERN.test(key) || CAMEL_CASE_VERSIONED_KEY_PATTERN.test(key);
|
|
6141
6140
|
const isJsonStringifyCall = (node) => isGlobalMethodCall(node, "JSON", "stringify");
|
|
6142
6141
|
const resolveStringKey = (keyArg, context) => {
|
|
@@ -6159,7 +6158,7 @@ const clientLocalstorageNoVersion = defineRule({
|
|
|
6159
6158
|
if (!isNodeOfType(node.callee, "MemberExpression")) return;
|
|
6160
6159
|
const receiver = stripParenExpression(node.callee.object);
|
|
6161
6160
|
if (!isNodeOfType(receiver, "Identifier")) return;
|
|
6162
|
-
if (receiver.name
|
|
6161
|
+
if (!STORAGE_OBJECTS.has(receiver.name)) return;
|
|
6163
6162
|
if (!isNodeOfType(node.callee.property, "Identifier")) return;
|
|
6164
6163
|
if (node.callee.property.name !== "setItem") return;
|
|
6165
6164
|
const keyArg = node.arguments?.[0];
|
|
@@ -8939,7 +8938,7 @@ const getListenerAbortControllerKey = (usage, context) => {
|
|
|
8939
8938
|
}
|
|
8940
8939
|
return null;
|
|
8941
8940
|
};
|
|
8942
|
-
const SYNCHRONOUS_ITERATOR_METHOD_NAMES$
|
|
8941
|
+
const SYNCHRONOUS_ITERATOR_METHOD_NAMES$1 = new Set([
|
|
8943
8942
|
"every",
|
|
8944
8943
|
"filter",
|
|
8945
8944
|
"flatMap",
|
|
@@ -8955,7 +8954,7 @@ const isSynchronousIteratorCallback = (functionNode) => {
|
|
|
8955
8954
|
const callee = stripParenExpression(callNode.callee);
|
|
8956
8955
|
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier")) return false;
|
|
8957
8956
|
if (isNodeOfType(callee.object, "Identifier") && callee.object.name === "Array" && callee.property.name === "from") return callNode.arguments?.[1] === functionNode;
|
|
8958
|
-
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$
|
|
8957
|
+
return SYNCHRONOUS_ITERATOR_METHOD_NAMES$1.has(callee.property.name) && callNode.arguments?.[0] === functionNode;
|
|
8959
8958
|
};
|
|
8960
8959
|
const findDirectCallForReference = (identifier) => {
|
|
8961
8960
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
@@ -10590,19 +10589,6 @@ const isUnstableInitializer = (node) => {
|
|
|
10590
10589
|
if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
|
|
10591
10590
|
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");
|
|
10592
10591
|
};
|
|
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
|
-
};
|
|
10606
10592
|
const isExtraDepAllowedForHook = (hookName, node, scopes) => {
|
|
10607
10593
|
if (!isExtraReactiveDepAllowed(node, scopes)) return false;
|
|
10608
10594
|
if (EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName)) return true;
|
|
@@ -10616,52 +10602,6 @@ const isStableSetterLikeSymbol = (symbol, scopes) => {
|
|
|
10616
10602
|
if (!symbolHasStableHookOrigin(symbol, scopes)) return false;
|
|
10617
10603
|
return symbol.name.startsWith("set") || symbol.name.startsWith("dispatch") || symbol.name.startsWith("startTransition");
|
|
10618
10604
|
};
|
|
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
|
-
};
|
|
10665
10605
|
const findStableSetterReference = (node, scopes) => {
|
|
10666
10606
|
let setterName = null;
|
|
10667
10607
|
const visit = (current) => {
|
|
@@ -10669,7 +10609,7 @@ const findStableSetterReference = (node, scopes) => {
|
|
|
10669
10609
|
if (current !== node && (isNodeOfType(current, "FunctionDeclaration") || isNodeOfType(current, "FunctionExpression") || isNodeOfType(current, "ArrowFunctionExpression"))) return;
|
|
10670
10610
|
if (isNodeOfType(current, "Identifier")) {
|
|
10671
10611
|
const symbol = scopes.referenceFor(current)?.resolvedSymbol;
|
|
10672
|
-
if (symbol && isStableSetterLikeSymbol(symbol, scopes)
|
|
10612
|
+
if (symbol && isStableSetterLikeSymbol(symbol, scopes)) {
|
|
10673
10613
|
setterName = symbol.name;
|
|
10674
10614
|
return;
|
|
10675
10615
|
}
|
|
@@ -13562,7 +13502,7 @@ const jsCacheStorage = defineRule({
|
|
|
13562
13502
|
CallExpression(node) {
|
|
13563
13503
|
if (!isMemberProperty(node.callee, "getItem")) return;
|
|
13564
13504
|
const receiver = stripParenExpression(node.callee.object);
|
|
13565
|
-
if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS.has(receiver.name)) return;
|
|
13505
|
+
if (!isNodeOfType(receiver, "Identifier") || !STORAGE_OBJECTS$1.has(receiver.name)) return;
|
|
13566
13506
|
if (!isNodeOfType(node.arguments?.[0], "Literal")) return;
|
|
13567
13507
|
const storageReadCounts = storageReadCountStack[storageReadCountStack.length - 1];
|
|
13568
13508
|
const storageKey = String(node.arguments[0].value);
|
|
@@ -13829,11 +13769,6 @@ const jsEarlyExit = defineRule({
|
|
|
13829
13769
|
});
|
|
13830
13770
|
//#endregion
|
|
13831
13771
|
//#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
|
-
};
|
|
13837
13772
|
const jsFlatmapFilter = defineRule({
|
|
13838
13773
|
id: "js-flatmap-filter",
|
|
13839
13774
|
title: ".map().filter(Boolean) loops twice",
|
|
@@ -13851,7 +13786,6 @@ const jsFlatmapFilter = defineRule({
|
|
|
13851
13786
|
if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
|
|
13852
13787
|
if (innerCall.callee.property.name !== "map") return;
|
|
13853
13788
|
const receiver = stripParenExpression(innerCall.callee.object);
|
|
13854
|
-
if (receiver && isBoundedPipelineSource(receiver)) return;
|
|
13855
13789
|
if (receiver && isNodeOfType(receiver, "ArrayExpression")) {
|
|
13856
13790
|
const elements = receiver.elements ?? [];
|
|
13857
13791
|
if (elements.length > 0 && elements.length <= 8 && elements.every((element) => element == null || !isNodeOfType(element, "SpreadElement"))) return;
|
|
@@ -22818,7 +22752,7 @@ const readsPostMountValueThroughLocals = (root, effectFn, options = {}, visitedL
|
|
|
22818
22752
|
};
|
|
22819
22753
|
//#endregion
|
|
22820
22754
|
//#region src/plugin/rules/state-and-effects/utils/collect-effect-state-write-facts.ts
|
|
22821
|
-
const SYNCHRONOUS_ITERATOR_METHOD_NAMES
|
|
22755
|
+
const SYNCHRONOUS_ITERATOR_METHOD_NAMES = new Set([
|
|
22822
22756
|
"every",
|
|
22823
22757
|
"filter",
|
|
22824
22758
|
"find",
|
|
@@ -23126,7 +23060,7 @@ const collectBoundedEffectExecutionFrames = (analysis, effectNode, currentFilena
|
|
|
23126
23060
|
const calleeName = getCallCalleeName$1(child);
|
|
23127
23061
|
const memberName = getStaticMemberName(callee);
|
|
23128
23062
|
const isDeferringCall = calleeName !== null && DEFERRING_CALLEE_NAMES.has(calleeName) || memberName !== null && DEFERRING_MEMBER_NAMES.has(memberName);
|
|
23129
|
-
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES
|
|
23063
|
+
const isIteratorCall = memberName !== null && SYNCHRONOUS_ITERATOR_METHOD_NAMES.has(memberName) && isNodeOfType(callee, "MemberExpression");
|
|
23130
23064
|
const enqueueFrame = (callableNode, argumentsForCallable, isDeferred, introducedBindings, allowWithoutInvocationEvidence = false) => {
|
|
23131
23065
|
const callable = resolveWrappedCallable(analysis, callableNode);
|
|
23132
23066
|
if (!callable || callable.async === true || callable === effectFunction) return;
|
|
@@ -23363,15 +23297,10 @@ const isLocallyConstructedObjectMember = (reference, memberExpression) => isNode
|
|
|
23363
23297
|
const definitionNode = definition.node;
|
|
23364
23298
|
return isNodeOfType(definitionNode, "VariableDeclarator") && Boolean(definitionNode.init) && (isNodeOfType(definitionNode.init, "ObjectExpression") || isNodeOfType(definitionNode.init, "ArrayExpression"));
|
|
23365
23299
|
}) === 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
|
-
};
|
|
23370
23300
|
const collectValueEvidence = (analysis, expression, frame, remainingCallFrames, visitedBindings = /* @__PURE__ */ new Set()) => {
|
|
23371
23301
|
const node = stripParenExpression(expression);
|
|
23372
23302
|
const evidence = emptyEvidence();
|
|
23373
|
-
|
|
23374
|
-
if (!useRefDeclarator && (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode))) {
|
|
23303
|
+
if (readsPostMountValue(node) || readsPostMountValueThroughLocals(node, frame.functionNode)) {
|
|
23375
23304
|
evidence.readsExternalValue = true;
|
|
23376
23305
|
return evidence;
|
|
23377
23306
|
}
|
|
@@ -23413,11 +23342,7 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23413
23342
|
evidence.hasUnknownSource = true;
|
|
23414
23343
|
return evidence;
|
|
23415
23344
|
}
|
|
23416
|
-
|
|
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);
|
|
23345
|
+
if (reference.resolved.references.some((candidateReference) => candidateReference.isWrite() && !candidateReference.init)) {
|
|
23421
23346
|
evidence.hasUnknownSource = true;
|
|
23422
23347
|
return evidence;
|
|
23423
23348
|
}
|
|
@@ -23433,40 +23358,6 @@ const collectValueEvidence = (analysis, expression, frame, remainingCallFrames,
|
|
|
23433
23358
|
return collectValueEvidence(analysis, initializer.init, frame, remainingCallFrames, visitedBindings);
|
|
23434
23359
|
}
|
|
23435
23360
|
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
|
-
}
|
|
23470
23361
|
if (isNodeOfType(node.object, "Identifier")) {
|
|
23471
23362
|
const objectReference = getRef(analysis, node.object);
|
|
23472
23363
|
if (objectReference && isLocallyConstructedObjectMember(objectReference, node)) {
|
|
@@ -29245,18 +29136,6 @@ const MUTATING_COLLECTION_METHOD_NAMES = new Set([
|
|
|
29245
29136
|
"set",
|
|
29246
29137
|
"add"
|
|
29247
29138
|
]);
|
|
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
|
-
]);
|
|
29260
29139
|
const getMemberRootBindingName = (node, scope) => {
|
|
29261
29140
|
let currentNode = node;
|
|
29262
29141
|
while (isNodeOfType(currentNode, "MemberExpression")) currentNode = currentNode.object;
|
|
@@ -29310,26 +29189,6 @@ const addMutatingCollectionCallDependencies = (graph, expression, scope, eventHa
|
|
|
29310
29189
|
addDependencyNames(dependencyNames, controlDependencyNames);
|
|
29311
29190
|
addDependencies(graph, receiverRootName, dependencyNames);
|
|
29312
29191
|
};
|
|
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
|
-
};
|
|
29333
29192
|
const collectExpressionDependencies = (graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames) => {
|
|
29334
29193
|
if (isNodeOfType(expression, "AssignmentExpression")) {
|
|
29335
29194
|
addAssignmentExpressionDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
|
|
@@ -29337,7 +29196,6 @@ const collectExpressionDependencies = (graph, expression, scope, eventHandlerRef
|
|
|
29337
29196
|
}
|
|
29338
29197
|
if (isNodeOfType(expression, "CallExpression")) {
|
|
29339
29198
|
addMutatingCollectionCallDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
|
|
29340
|
-
addIteratorMutationDependencies(graph, expression, scope, eventHandlerReferenceNames, controlDependencyNames);
|
|
29341
29199
|
return;
|
|
29342
29200
|
}
|
|
29343
29201
|
if (isNodeOfType(expression, "SequenceExpression")) {
|
|
@@ -30378,26 +30236,28 @@ const noGiantComponent = defineRule({
|
|
|
30378
30236
|
const lineCount = bodyNode.loc.end.line - bodyNode.loc.start.line + 1;
|
|
30379
30237
|
return lineCount > 300 ? lineCount : null;
|
|
30380
30238
|
};
|
|
30381
|
-
const reportOversizedComponent = (nameNode, componentName) => {
|
|
30239
|
+
const reportOversizedComponent = (nameNode, componentName, lineCount) => {
|
|
30382
30240
|
context.report({
|
|
30383
30241
|
node: nameNode,
|
|
30384
|
-
message: `Component "${componentName}" is
|
|
30242
|
+
message: `Component "${componentName}" is ${lineCount} lines long, which is hard to read & change. Split it into a few smaller components.`
|
|
30385
30243
|
});
|
|
30386
30244
|
};
|
|
30387
30245
|
return {
|
|
30388
30246
|
FunctionDeclaration(node) {
|
|
30389
30247
|
if (!node.id?.name || !isUppercaseName(node.id.name)) return;
|
|
30390
|
-
|
|
30248
|
+
const lineCount = getOversizedComponentLineCount(node);
|
|
30249
|
+
if (lineCount === null) return;
|
|
30391
30250
|
if (!functionContainsReactRenderOutput(node, context.scopes)) return;
|
|
30392
|
-
reportOversizedComponent(node.id, node.id.name);
|
|
30251
|
+
reportOversizedComponent(node.id, node.id.name, lineCount);
|
|
30393
30252
|
},
|
|
30394
30253
|
VariableDeclarator(node) {
|
|
30395
30254
|
if (!isNodeOfType(node.id, "Identifier") || !isUppercaseName(node.id.name)) return;
|
|
30396
30255
|
const functionNode = unwrapReactHocFunction(node.init);
|
|
30397
30256
|
if (!functionNode) return;
|
|
30398
|
-
|
|
30257
|
+
const lineCount = getOversizedComponentLineCount(functionNode);
|
|
30258
|
+
if (lineCount === null) return;
|
|
30399
30259
|
if (!functionContainsReactRenderOutput(functionNode, context.scopes)) return;
|
|
30400
|
-
reportOversizedComponent(node.id, node.id.name);
|
|
30260
|
+
reportOversizedComponent(node.id, node.id.name, lineCount);
|
|
30401
30261
|
}
|
|
30402
30262
|
};
|
|
30403
30263
|
}
|
|
@@ -35168,66 +35028,6 @@ const noPropCallbackInEffect = defineRule({
|
|
|
35168
35028
|
}
|
|
35169
35029
|
});
|
|
35170
35030
|
//#endregion
|
|
35171
|
-
//#region src/plugin/rules/state-and-effects/no-prop-callback-in-render.ts
|
|
35172
|
-
const isPreservedThroughConciseArrow = (callExpression, scopes) => {
|
|
35173
|
-
let node = callExpression;
|
|
35174
|
-
let parent = node.parent;
|
|
35175
|
-
while (parent) {
|
|
35176
|
-
if (isNodeOfType(parent, "ChainExpression")) {
|
|
35177
|
-
node = parent;
|
|
35178
|
-
parent = node.parent;
|
|
35179
|
-
continue;
|
|
35180
|
-
}
|
|
35181
|
-
if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
|
|
35182
|
-
node = parent;
|
|
35183
|
-
parent = node.parent;
|
|
35184
|
-
continue;
|
|
35185
|
-
}
|
|
35186
|
-
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
|
|
35187
|
-
node = parent;
|
|
35188
|
-
parent = node.parent;
|
|
35189
|
-
continue;
|
|
35190
|
-
}
|
|
35191
|
-
if (isNodeOfType(parent, "SequenceExpression")) {
|
|
35192
|
-
const expressions = parent.expressions ?? [];
|
|
35193
|
-
if (expressions[expressions.length - 1] !== node) return false;
|
|
35194
|
-
node = parent;
|
|
35195
|
-
parent = node.parent;
|
|
35196
|
-
continue;
|
|
35197
|
-
}
|
|
35198
|
-
if (!isNodeOfType(parent, "ArrowFunctionExpression") || parent.body !== node) return !isResultDiscardedCall(node);
|
|
35199
|
-
const invocation = parent.parent;
|
|
35200
|
-
if (!isNodeOfType(invocation, "CallExpression") || !executesDuringRender(parent, scopes)) return true;
|
|
35201
|
-
if (invocation.arguments?.[0] === parent || invocation.arguments?.[1] === parent) {
|
|
35202
|
-
const callee = stripParenExpression(invocation.callee);
|
|
35203
|
-
return !(isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "forEach" && invocation.arguments[0] === parent);
|
|
35204
|
-
}
|
|
35205
|
-
node = invocation;
|
|
35206
|
-
parent = node.parent;
|
|
35207
|
-
}
|
|
35208
|
-
return false;
|
|
35209
|
-
};
|
|
35210
|
-
const noPropCallbackInRender = defineRule({
|
|
35211
|
-
id: "no-prop-callback-in-render",
|
|
35212
|
-
title: "Prop callback invoked during render",
|
|
35213
|
-
severity: "error",
|
|
35214
|
-
recommendation: "Invoke the callback from the event or asynchronous operation that produced the value, or from an effect when synchronizing with an external system. Render must stay pure because React can replay or discard it.",
|
|
35215
|
-
create: (context) => ({ CallExpression(node) {
|
|
35216
|
-
if (!isResultDiscardedCall(node)) return;
|
|
35217
|
-
if (isPreservedThroughConciseArrow(node, context.scopes)) return;
|
|
35218
|
-
if (!findRenderPhaseComponentOrHook(node, context.scopes)) return;
|
|
35219
|
-
const analysis = getProgramAnalysis(node);
|
|
35220
|
-
if (!analysis) return;
|
|
35221
|
-
const callee = stripParenExpression(node.callee);
|
|
35222
|
-
if (isFunctionLike$1(callee)) return;
|
|
35223
|
-
if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference))) return;
|
|
35224
|
-
context.report({
|
|
35225
|
-
node,
|
|
35226
|
-
message: "This prop callback runs during render. React can replay or discard render work, so the callback can fire more than once or for UI that never commits."
|
|
35227
|
-
});
|
|
35228
|
-
} })
|
|
35229
|
-
});
|
|
35230
|
-
//#endregion
|
|
35231
35031
|
//#region src/plugin/rules/architecture/no-prop-types.ts
|
|
35232
35032
|
const PROP_TYPES_PROPERTY = "propTypes";
|
|
35233
35033
|
const isPropTypesKey = (key, computed) => {
|
|
@@ -57369,18 +57169,6 @@ const reactDoctorRules = [
|
|
|
57369
57169
|
requires: [...new Set(["react", ...noPropCallbackInEffect.requires ?? []])]
|
|
57370
57170
|
}
|
|
57371
57171
|
},
|
|
57372
|
-
{
|
|
57373
|
-
key: "react-doctor/no-prop-callback-in-render",
|
|
57374
|
-
id: "no-prop-callback-in-render",
|
|
57375
|
-
source: "react-doctor",
|
|
57376
|
-
originallyExternal: false,
|
|
57377
|
-
rule: {
|
|
57378
|
-
...noPropCallbackInRender,
|
|
57379
|
-
framework: "global",
|
|
57380
|
-
category: "Bugs",
|
|
57381
|
-
requires: [...new Set(["react", ...noPropCallbackInRender.requires ?? []])]
|
|
57382
|
-
}
|
|
57383
|
-
},
|
|
57384
57172
|
{
|
|
57385
57173
|
key: "react-doctor/no-prop-types",
|
|
57386
57174
|
id: "no-prop-types",
|