oxlint-plugin-react-doctor 0.7.7-dev.dc5c828 → 0.7.7-dev.e632f8a
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 +429 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4573,6 +4573,23 @@ const hasPossibleStaticPropertyWrite = (identifier, propertyName, scopes) => {
|
|
|
4573
4573
|
return writtenPropertyName === null || writtenPropertyName === propertyName;
|
|
4574
4574
|
}));
|
|
4575
4575
|
};
|
|
4576
|
+
const canExecuteBefore = (candidateNode, referenceNode, scopes) => {
|
|
4577
|
+
const candidateBoundary = findExecutionBoundary(candidateNode);
|
|
4578
|
+
const referenceBoundary = findExecutionBoundary(referenceNode);
|
|
4579
|
+
if (!candidateBoundary || !referenceBoundary) return true;
|
|
4580
|
+
if (candidateBoundary === referenceBoundary) return candidateNode.range[0] < referenceNode.range[0];
|
|
4581
|
+
if (!isFunctionLike$1(candidateBoundary)) return true;
|
|
4582
|
+
return isFunctionSynchronouslyInvokedBefore(candidateBoundary, referenceNode, scopes);
|
|
4583
|
+
};
|
|
4584
|
+
const hasPossibleStaticPropertyWriteBefore = (identifier, propertyName, referenceNode, scopes) => {
|
|
4585
|
+
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4586
|
+
return getPotentiallyAliasedSymbols(identifier, scopes).some((symbol) => symbol.references.some((reference) => {
|
|
4587
|
+
const writeTarget = getMemberWriteTarget(reference.identifier);
|
|
4588
|
+
if (!writeTarget || !canExecuteBefore(writeTarget, referenceNode, scopes)) return false;
|
|
4589
|
+
const writtenPropertyName = getResolvedStaticPropertyName(writeTarget, scopes);
|
|
4590
|
+
return writtenPropertyName === null || writtenPropertyName === propertyName;
|
|
4591
|
+
}));
|
|
4592
|
+
};
|
|
4576
4593
|
const hasPossibleStaticPropertyMutationOrEscape = (identifier, propertyName, scopes) => {
|
|
4577
4594
|
if (!isNodeOfType(identifier, "Identifier")) return false;
|
|
4578
4595
|
if (hasPossibleStaticPropertyWrite(identifier, propertyName, scopes)) return true;
|
|
@@ -20147,14 +20164,14 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
|
|
|
20147
20164
|
if (isNegativeOneLiteral(otherOperand)) return true;
|
|
20148
20165
|
return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
|
|
20149
20166
|
};
|
|
20150
|
-
const getTypeAnnotation = (node) => {
|
|
20167
|
+
const getTypeAnnotation$1 = (node) => {
|
|
20151
20168
|
if (!node || !("typeAnnotation" in node)) return null;
|
|
20152
20169
|
const annotation = node.typeAnnotation;
|
|
20153
20170
|
if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
|
|
20154
20171
|
return annotation.typeAnnotation;
|
|
20155
20172
|
};
|
|
20156
20173
|
const getDeclaredPropertyType = (members, propertyName) => {
|
|
20157
|
-
for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
|
|
20174
|
+
for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation$1(member);
|
|
20158
20175
|
return null;
|
|
20159
20176
|
};
|
|
20160
20177
|
const getArrayElementType = (typeNode) => {
|
|
@@ -20173,7 +20190,7 @@ const getDestructuredDeclaredType = (identifier) => {
|
|
|
20173
20190
|
const property = binding.bindingIdentifier.parent;
|
|
20174
20191
|
const objectPattern = property?.parent;
|
|
20175
20192
|
if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
|
|
20176
|
-
const propsType = getTypeAnnotation(objectPattern);
|
|
20193
|
+
const propsType = getTypeAnnotation$1(objectPattern);
|
|
20177
20194
|
if (!propsType) return null;
|
|
20178
20195
|
if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
|
|
20179
20196
|
if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
|
|
@@ -20190,7 +20207,7 @@ const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @_
|
|
|
20190
20207
|
const binding = findVariableInitializer(identifier, identifier.name);
|
|
20191
20208
|
if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
|
|
20192
20209
|
visitedBindingIdentifiers.add(binding.bindingIdentifier);
|
|
20193
|
-
const directType = getTypeAnnotation(binding.bindingIdentifier);
|
|
20210
|
+
const directType = getTypeAnnotation$1(binding.bindingIdentifier);
|
|
20194
20211
|
if (directType) return directType;
|
|
20195
20212
|
const initializer = binding.initializer;
|
|
20196
20213
|
if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
|
|
@@ -27355,6 +27372,211 @@ const computeExternallyDriven = (analysis, declarator) => {
|
|
|
27355
27372
|
return hasDeferredCallSite;
|
|
27356
27373
|
};
|
|
27357
27374
|
//#endregion
|
|
27375
|
+
//#region src/plugin/rules/state-and-effects/utils/effect/is-proven-native-read-method.ts
|
|
27376
|
+
const ARRAY_READ_METHOD_NAMES$1 = new Set([
|
|
27377
|
+
"at",
|
|
27378
|
+
"concat",
|
|
27379
|
+
"entries",
|
|
27380
|
+
"every",
|
|
27381
|
+
"filter",
|
|
27382
|
+
"find",
|
|
27383
|
+
"findIndex",
|
|
27384
|
+
"findLast",
|
|
27385
|
+
"findLastIndex",
|
|
27386
|
+
"flat",
|
|
27387
|
+
"flatMap",
|
|
27388
|
+
"forEach",
|
|
27389
|
+
"includes",
|
|
27390
|
+
"indexOf",
|
|
27391
|
+
"join",
|
|
27392
|
+
"keys",
|
|
27393
|
+
"lastIndexOf",
|
|
27394
|
+
"map",
|
|
27395
|
+
"reduce",
|
|
27396
|
+
"reduceRight",
|
|
27397
|
+
"slice",
|
|
27398
|
+
"some",
|
|
27399
|
+
"toLocaleString",
|
|
27400
|
+
"toReversed",
|
|
27401
|
+
"toSorted",
|
|
27402
|
+
"toSpliced",
|
|
27403
|
+
"toString",
|
|
27404
|
+
"values",
|
|
27405
|
+
"with"
|
|
27406
|
+
]);
|
|
27407
|
+
const MAP_READ_METHOD_NAMES = new Set([
|
|
27408
|
+
"entries",
|
|
27409
|
+
"forEach",
|
|
27410
|
+
"get",
|
|
27411
|
+
"has",
|
|
27412
|
+
"keys",
|
|
27413
|
+
"values"
|
|
27414
|
+
]);
|
|
27415
|
+
const SET_READ_METHOD_NAMES = new Set([
|
|
27416
|
+
"difference",
|
|
27417
|
+
"entries",
|
|
27418
|
+
"forEach",
|
|
27419
|
+
"has",
|
|
27420
|
+
"intersection",
|
|
27421
|
+
"isDisjointFrom",
|
|
27422
|
+
"isSubsetOf",
|
|
27423
|
+
"isSupersetOf",
|
|
27424
|
+
"keys",
|
|
27425
|
+
"symmetricDifference",
|
|
27426
|
+
"union",
|
|
27427
|
+
"values"
|
|
27428
|
+
]);
|
|
27429
|
+
const FUNCTION_READ_METHOD_NAMES = new Set(["bind"]);
|
|
27430
|
+
const PROMISE_READ_METHOD_NAMES = new Set([
|
|
27431
|
+
"catch",
|
|
27432
|
+
"finally",
|
|
27433
|
+
"then"
|
|
27434
|
+
]);
|
|
27435
|
+
const STRING_READ_METHOD_NAMES$1 = new Set([
|
|
27436
|
+
"at",
|
|
27437
|
+
"charAt",
|
|
27438
|
+
"charCodeAt",
|
|
27439
|
+
"codePointAt",
|
|
27440
|
+
"concat",
|
|
27441
|
+
"endsWith",
|
|
27442
|
+
"includes",
|
|
27443
|
+
"indexOf",
|
|
27444
|
+
"isWellFormed",
|
|
27445
|
+
"lastIndexOf",
|
|
27446
|
+
"localeCompare",
|
|
27447
|
+
"match",
|
|
27448
|
+
"matchAll",
|
|
27449
|
+
"normalize",
|
|
27450
|
+
"padEnd",
|
|
27451
|
+
"padStart",
|
|
27452
|
+
"repeat",
|
|
27453
|
+
"replace",
|
|
27454
|
+
"replaceAll",
|
|
27455
|
+
"search",
|
|
27456
|
+
"slice",
|
|
27457
|
+
"split",
|
|
27458
|
+
"startsWith",
|
|
27459
|
+
"substring",
|
|
27460
|
+
"toLocaleLowerCase",
|
|
27461
|
+
"toLocaleUpperCase",
|
|
27462
|
+
"toLowerCase",
|
|
27463
|
+
"toString",
|
|
27464
|
+
"toUpperCase",
|
|
27465
|
+
"toWellFormed",
|
|
27466
|
+
"trim",
|
|
27467
|
+
"trimEnd",
|
|
27468
|
+
"trimStart",
|
|
27469
|
+
"valueOf"
|
|
27470
|
+
]);
|
|
27471
|
+
const NATIVE_TYPE_NAMES = new Set([
|
|
27472
|
+
"Array",
|
|
27473
|
+
"Map",
|
|
27474
|
+
"Promise",
|
|
27475
|
+
"PromiseLike",
|
|
27476
|
+
"ReadonlyArray",
|
|
27477
|
+
"ReadonlyMap",
|
|
27478
|
+
"ReadonlySet",
|
|
27479
|
+
"Set"
|
|
27480
|
+
]);
|
|
27481
|
+
const nativeTypeDeclarationsByProgram = /* @__PURE__ */ new WeakMap();
|
|
27482
|
+
const shadowedNativeTypeNamesByBinding = /* @__PURE__ */ new WeakMap();
|
|
27483
|
+
const isWithinNode = (node, ancestor) => {
|
|
27484
|
+
let current = node;
|
|
27485
|
+
while (current) {
|
|
27486
|
+
if (current === ancestor) return true;
|
|
27487
|
+
current = current.parent;
|
|
27488
|
+
}
|
|
27489
|
+
return false;
|
|
27490
|
+
};
|
|
27491
|
+
const findTypeDeclarationScope = (declaration) => {
|
|
27492
|
+
let current = declaration.parent;
|
|
27493
|
+
while (current) {
|
|
27494
|
+
if (isNodeOfType(current, "Program") || isNodeOfType(current, "BlockStatement") || isNodeOfType(current, "TSModuleBlock") || isNodeOfType(current, "StaticBlock")) return current;
|
|
27495
|
+
current = current.parent;
|
|
27496
|
+
}
|
|
27497
|
+
return null;
|
|
27498
|
+
};
|
|
27499
|
+
const getDeclaredTypeName = (declaration) => {
|
|
27500
|
+
if (!isNodeOfType(declaration, "ClassDeclaration") && !isNodeOfType(declaration, "TSEnumDeclaration") && !isNodeOfType(declaration, "TSImportEqualsDeclaration") && !isNodeOfType(declaration, "TSInterfaceDeclaration") && !isNodeOfType(declaration, "TSModuleDeclaration") && !isNodeOfType(declaration, "TSTypeAliasDeclaration")) return null;
|
|
27501
|
+
return declaration.id && isNodeOfType(declaration.id, "Identifier") ? declaration.id.name : null;
|
|
27502
|
+
};
|
|
27503
|
+
const getNativeTypeDeclarations = (program) => {
|
|
27504
|
+
const cachedDeclarations = nativeTypeDeclarationsByProgram.get(program);
|
|
27505
|
+
if (cachedDeclarations) return cachedDeclarations;
|
|
27506
|
+
const declarations = [];
|
|
27507
|
+
walkAst(program, (candidate) => {
|
|
27508
|
+
if (isNodeOfType(candidate, "ImportDeclaration")) {
|
|
27509
|
+
for (const specifier of candidate.specifiers) if (NATIVE_TYPE_NAMES.has(specifier.local.name)) declarations.push({
|
|
27510
|
+
name: specifier.local.name,
|
|
27511
|
+
scope: program
|
|
27512
|
+
});
|
|
27513
|
+
return;
|
|
27514
|
+
}
|
|
27515
|
+
const declaredTypeName = getDeclaredTypeName(candidate);
|
|
27516
|
+
if (!declaredTypeName || !NATIVE_TYPE_NAMES.has(declaredTypeName)) return;
|
|
27517
|
+
const declarationScope = findTypeDeclarationScope(candidate);
|
|
27518
|
+
if (declarationScope) declarations.push({
|
|
27519
|
+
name: declaredTypeName,
|
|
27520
|
+
scope: declarationScope
|
|
27521
|
+
});
|
|
27522
|
+
});
|
|
27523
|
+
nativeTypeDeclarationsByProgram.set(program, declarations);
|
|
27524
|
+
return declarations;
|
|
27525
|
+
};
|
|
27526
|
+
const getShadowedNativeTypeNames = (binding) => {
|
|
27527
|
+
const cachedNames = shadowedNativeTypeNamesByBinding.get(binding);
|
|
27528
|
+
if (cachedNames) return cachedNames;
|
|
27529
|
+
const shadowedNames = /* @__PURE__ */ new Set();
|
|
27530
|
+
for (const typeName of NATIVE_TYPE_NAMES) if (hasEnclosingTypeParameterNamed(binding, typeName)) shadowedNames.add(typeName);
|
|
27531
|
+
const program = findProgramRoot(binding);
|
|
27532
|
+
if (program) {
|
|
27533
|
+
for (const declaration of getNativeTypeDeclarations(program)) if (isWithinNode(binding, declaration.scope)) shadowedNames.add(declaration.name);
|
|
27534
|
+
}
|
|
27535
|
+
shadowedNativeTypeNamesByBinding.set(binding, shadowedNames);
|
|
27536
|
+
return shadowedNames;
|
|
27537
|
+
};
|
|
27538
|
+
const getParameterBinding = (definitionName) => {
|
|
27539
|
+
if (isNodeOfType(definitionName, "Identifier")) return definitionName;
|
|
27540
|
+
if (!isNodeOfType(definitionName, "AssignmentPattern")) return null;
|
|
27541
|
+
return isNodeOfType(definitionName.left, "Identifier") ? definitionName.left : null;
|
|
27542
|
+
};
|
|
27543
|
+
const getTypeAnnotation = (binding) => {
|
|
27544
|
+
if (!isNodeOfType(binding, "Identifier")) return null;
|
|
27545
|
+
const annotation = binding.typeAnnotation;
|
|
27546
|
+
return annotation && isNodeOfType(annotation, "TSTypeAnnotation") ? annotation.typeAnnotation : null;
|
|
27547
|
+
};
|
|
27548
|
+
const isNullishType = (typeNode) => isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword");
|
|
27549
|
+
const isNativeReadMethod = (typeNode, methodName, shadowedTypeNames) => {
|
|
27550
|
+
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return ARRAY_READ_METHOD_NAMES$1.has(methodName);
|
|
27551
|
+
if (isNodeOfType(typeNode, "TSStringKeyword")) return STRING_READ_METHOD_NAMES$1.has(methodName);
|
|
27552
|
+
if (isNodeOfType(typeNode, "TSFunctionType")) return FUNCTION_READ_METHOD_NAMES.has(methodName);
|
|
27553
|
+
if (isNodeOfType(typeNode, "TSLiteralType") && isNodeOfType(typeNode.literal, "Literal") && typeof typeNode.literal.value === "string") return STRING_READ_METHOD_NAMES$1.has(methodName);
|
|
27554
|
+
if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "readonly" && typeNode.typeAnnotation) return isNativeReadMethod(typeNode.typeAnnotation, methodName, shadowedTypeNames);
|
|
27555
|
+
if (isNodeOfType(typeNode, "TSUnionType")) {
|
|
27556
|
+
let hasNonNullishMember = false;
|
|
27557
|
+
for (const memberType of typeNode.types) {
|
|
27558
|
+
if (isNullishType(memberType)) continue;
|
|
27559
|
+
hasNonNullishMember = true;
|
|
27560
|
+
if (!isNativeReadMethod(memberType, methodName, shadowedTypeNames)) return false;
|
|
27561
|
+
}
|
|
27562
|
+
return hasNonNullishMember;
|
|
27563
|
+
}
|
|
27564
|
+
if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
|
|
27565
|
+
if (shadowedTypeNames.has(typeNode.typeName.name)) return false;
|
|
27566
|
+
if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return ARRAY_READ_METHOD_NAMES$1.has(methodName);
|
|
27567
|
+
if (typeNode.typeName.name === "Map" || typeNode.typeName.name === "ReadonlyMap") return MAP_READ_METHOD_NAMES.has(methodName);
|
|
27568
|
+
if (typeNode.typeName.name === "Set" || typeNode.typeName.name === "ReadonlySet") return SET_READ_METHOD_NAMES.has(methodName);
|
|
27569
|
+
if (typeNode.typeName.name === "Promise" || typeNode.typeName.name === "PromiseLike") return PROMISE_READ_METHOD_NAMES.has(methodName);
|
|
27570
|
+
return false;
|
|
27571
|
+
};
|
|
27572
|
+
const isProvenNativeReadMethod = (ref, methodName) => Boolean(ref.resolved?.defs.some((definition) => {
|
|
27573
|
+
if (definition.type !== "Parameter") return false;
|
|
27574
|
+
const parameterBinding = getParameterBinding(definition.name);
|
|
27575
|
+
if (!parameterBinding) return false;
|
|
27576
|
+
const typeAnnotation = getTypeAnnotation(parameterBinding);
|
|
27577
|
+
return Boolean(typeAnnotation && isNativeReadMethod(typeAnnotation, methodName, getShadowedNativeTypeNames(parameterBinding)));
|
|
27578
|
+
}));
|
|
27579
|
+
//#endregion
|
|
27358
27580
|
//#region src/plugin/rules/state-and-effects/utils/effect/react.ts
|
|
27359
27581
|
const KNOWN_PURE_HOC_NAMES = new Set(["memo", "forwardRef"]);
|
|
27360
27582
|
const startsWithUppercase = (name) => Boolean(name && name.length > 0 && name[0] >= "A" && name[0] <= "Z");
|
|
@@ -27581,7 +27803,132 @@ const isRefCurrent = (ref) => {
|
|
|
27581
27803
|
const isStateSetterCall = (analysis, ref) => isEventualCallTo(analysis, ref, (innerRef) => isStateSetter(analysis, innerRef));
|
|
27582
27804
|
const isSyncStateSetterCall = (analysis, ref, effectFn) => isStateSetterCall(analysis, ref) && isSynchronous(ref.identifier, effectFn) && !resolvesToAsyncFunction(ref);
|
|
27583
27805
|
const HANDLER_NAMED_METHOD_PATTERN = /^(on|handle)[A-Z]/;
|
|
27584
|
-
const
|
|
27806
|
+
const SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD = new Map([
|
|
27807
|
+
["every", 0],
|
|
27808
|
+
["filter", 0],
|
|
27809
|
+
["find", 0],
|
|
27810
|
+
["findIndex", 0],
|
|
27811
|
+
["findLast", 0],
|
|
27812
|
+
["findLastIndex", 0],
|
|
27813
|
+
["flatMap", 0],
|
|
27814
|
+
["forEach", 0],
|
|
27815
|
+
["map", 0],
|
|
27816
|
+
["reduce", 0],
|
|
27817
|
+
["reduceRight", 0],
|
|
27818
|
+
["replace", 1],
|
|
27819
|
+
["replaceAll", 1],
|
|
27820
|
+
["some", 0]
|
|
27821
|
+
]);
|
|
27822
|
+
const addSimpleBindingNames = (pattern, names) => {
|
|
27823
|
+
if (isNodeOfType(pattern, "Identifier")) {
|
|
27824
|
+
if (names.has(pattern.name)) return false;
|
|
27825
|
+
names.add(pattern.name);
|
|
27826
|
+
return true;
|
|
27827
|
+
}
|
|
27828
|
+
if (isNodeOfType(pattern, "AssignmentPattern")) return addSimpleBindingNames(pattern.left, names);
|
|
27829
|
+
if (isNodeOfType(pattern, "RestElement")) return addSimpleBindingNames(pattern.argument, names);
|
|
27830
|
+
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
27831
|
+
let didAddName = false;
|
|
27832
|
+
for (const property of pattern.properties) didAddName = addSimpleBindingNames(isNodeOfType(property, "Property") ? property.value : property, names) || didAddName;
|
|
27833
|
+
return didAddName;
|
|
27834
|
+
}
|
|
27835
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
27836
|
+
let didAddName = false;
|
|
27837
|
+
for (const element of pattern.elements) if (element) didAddName = addSimpleBindingNames(element, names) || didAddName;
|
|
27838
|
+
return didAddName;
|
|
27839
|
+
}
|
|
27840
|
+
return false;
|
|
27841
|
+
};
|
|
27842
|
+
const getSimpleParameterNames = (callback) => {
|
|
27843
|
+
if (!isFunctionLike$1(callback)) return /* @__PURE__ */ new Set();
|
|
27844
|
+
const names = /* @__PURE__ */ new Set();
|
|
27845
|
+
for (const parameter of callback.params ?? []) addSimpleBindingNames(parameter, names);
|
|
27846
|
+
return names;
|
|
27847
|
+
};
|
|
27848
|
+
const inlineCallbackInvokesParameter = (callback) => {
|
|
27849
|
+
const parameterNames = getSimpleParameterNames(callback);
|
|
27850
|
+
if (parameterNames.size === 0 || !isFunctionLike$1(callback)) return false;
|
|
27851
|
+
let didAddAlias = true;
|
|
27852
|
+
while (didAddAlias) {
|
|
27853
|
+
didAddAlias = false;
|
|
27854
|
+
walkAst(callback.body, (candidate) => {
|
|
27855
|
+
if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
|
|
27856
|
+
if (isNodeOfType(candidate, "AssignmentExpression") && candidate.operator === "=" && isNodeOfType(candidate.left, "Identifier")) {
|
|
27857
|
+
const assignedValueRoot = getRootIdentifier$1(candidate.right);
|
|
27858
|
+
if (isNodeOfType(assignedValueRoot, "Identifier") && parameterNames.has(assignedValueRoot.name) && !parameterNames.has(candidate.left.name)) {
|
|
27859
|
+
parameterNames.add(candidate.left.name);
|
|
27860
|
+
didAddAlias = true;
|
|
27861
|
+
}
|
|
27862
|
+
return;
|
|
27863
|
+
}
|
|
27864
|
+
if (!isNodeOfType(candidate, "VariableDeclarator") || !candidate.init) return;
|
|
27865
|
+
const initializerRoot = getRootIdentifier$1(candidate.init);
|
|
27866
|
+
if (!isNodeOfType(initializerRoot, "Identifier") || !parameterNames.has(initializerRoot.name)) return;
|
|
27867
|
+
if (addSimpleBindingNames(candidate.id, parameterNames)) didAddAlias = true;
|
|
27868
|
+
});
|
|
27869
|
+
}
|
|
27870
|
+
let didInvokeParameter = false;
|
|
27871
|
+
walkAst(callback.body, (candidate) => {
|
|
27872
|
+
if (didInvokeParameter) return false;
|
|
27873
|
+
if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
|
|
27874
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
27875
|
+
const callee = stripParenExpression(candidate.callee);
|
|
27876
|
+
if (isNodeOfType(callee, "Identifier") && parameterNames.has(callee.name)) {
|
|
27877
|
+
didInvokeParameter = true;
|
|
27878
|
+
return false;
|
|
27879
|
+
}
|
|
27880
|
+
if (isNodeOfType(callee, "MemberExpression")) {
|
|
27881
|
+
const receiverRoot = getRootIdentifier$1(callee.object);
|
|
27882
|
+
if (receiverRoot && parameterNames.has(receiverRoot.name)) {
|
|
27883
|
+
didInvokeParameter = true;
|
|
27884
|
+
return false;
|
|
27885
|
+
}
|
|
27886
|
+
}
|
|
27887
|
+
});
|
|
27888
|
+
return didInvokeParameter;
|
|
27889
|
+
};
|
|
27890
|
+
const callbackInvokesPropCallback = (analysis, callback) => {
|
|
27891
|
+
if (!isFunctionLike$1(callback)) return false;
|
|
27892
|
+
let didInvokePropCallback = false;
|
|
27893
|
+
walkAst(callback.body, (candidate) => {
|
|
27894
|
+
if (didInvokePropCallback) return false;
|
|
27895
|
+
if (candidate !== callback.body && isFunctionLike$1(candidate)) return false;
|
|
27896
|
+
if (!isNodeOfType(candidate, "CallExpression")) return;
|
|
27897
|
+
if (getDownstreamRefs(analysis, stripParenExpression(candidate.callee)).some((reference) => isPropCallbackInvocationRef(analysis, reference))) {
|
|
27898
|
+
didInvokePropCallback = true;
|
|
27899
|
+
return false;
|
|
27900
|
+
}
|
|
27901
|
+
});
|
|
27902
|
+
return didInvokePropCallback;
|
|
27903
|
+
};
|
|
27904
|
+
const isNativeMethodSuppressionSafe = (analysis, callExpression, methodName, scopes) => {
|
|
27905
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
27906
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
27907
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
27908
|
+
const receiver = stripParenExpression(callee.object);
|
|
27909
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
27910
|
+
const receiverSymbol = scopes.symbolFor(receiver);
|
|
27911
|
+
if (!receiverSymbol || hasSymbolWriteBefore(receiverSymbol, callExpression, scopes) || hasPossibleStaticPropertyWriteBefore(receiver, methodName, callExpression, scopes)) return false;
|
|
27912
|
+
let callResult = callExpression;
|
|
27913
|
+
let callResultConsumer = callResult.parent;
|
|
27914
|
+
while (callResultConsumer && (TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callResultConsumer.type) && "expression" in callResultConsumer && callResultConsumer.expression === callResult || isNodeOfType(callResultConsumer, "MemberExpression") && callResultConsumer.object === callResult)) {
|
|
27915
|
+
callResult = callResultConsumer;
|
|
27916
|
+
callResultConsumer = callResult.parent;
|
|
27917
|
+
}
|
|
27918
|
+
if (isNodeOfType(callResultConsumer, "CallExpression") && callResultConsumer.callee === callResult) return false;
|
|
27919
|
+
const callbackArgumentIndex = SYNCHRONOUS_CALLBACK_ARGUMENT_INDEX_BY_METHOD.get(methodName);
|
|
27920
|
+
if (callbackArgumentIndex === void 0) return true;
|
|
27921
|
+
const callbackArgument = callExpression.arguments?.[callbackArgumentIndex];
|
|
27922
|
+
if (!callbackArgument) return true;
|
|
27923
|
+
const callbackValue = stripParenExpression(callbackArgument);
|
|
27924
|
+
if (isFunctionLike$1(callbackValue)) return !inlineCallbackInvokesParameter(callbackValue) && !callbackInvokesPropCallback(analysis, callbackValue);
|
|
27925
|
+
if (!isNodeOfType(callbackValue, "Identifier")) return false;
|
|
27926
|
+
const callbackRef = getRef(analysis, callbackValue);
|
|
27927
|
+
if (!callbackRef || isPropAlias(analysis, callbackRef)) return false;
|
|
27928
|
+
const callbackFunction = resolveToFunction(callbackRef);
|
|
27929
|
+
return Boolean(callbackFunction && isFunctionLike$1(callbackFunction) && !inlineCallbackInvokesParameter(callbackFunction) && !callbackInvokesPropCallback(analysis, callbackFunction));
|
|
27930
|
+
};
|
|
27931
|
+
const isPropCallbackInvocationRef = (analysis, ref, options = {}) => {
|
|
27585
27932
|
if (!isPropAlias(analysis, ref)) return false;
|
|
27586
27933
|
let effectiveNode = ref.identifier;
|
|
27587
27934
|
let parent = effectiveNode.parent;
|
|
@@ -27594,7 +27941,9 @@ const isPropCallbackInvocationRef = (analysis, ref) => {
|
|
|
27594
27941
|
if (isNodeOfType(parent, "MemberExpression") && parent.object === effectiveNode) {
|
|
27595
27942
|
const memberParent = parent.parent;
|
|
27596
27943
|
if (isNodeOfType(memberParent, "CallExpression") && memberParent.callee === parent) {
|
|
27597
|
-
|
|
27944
|
+
const propertyName = getStaticPropertyName(parent);
|
|
27945
|
+
if (propertyName && HANDLER_NAMED_METHOD_PATTERN.test(propertyName)) return true;
|
|
27946
|
+
if (options.nativeMethodScopes && isCustomHookParameter(ref) && propertyName && isProvenNativeReadMethod(ref, propertyName) && isNativeMethodSuppressionSafe(analysis, memberParent, propertyName, options.nativeMethodScopes)) return false;
|
|
27598
27947
|
return isWholePropsObjectReference(analysis, ref);
|
|
27599
27948
|
}
|
|
27600
27949
|
}
|
|
@@ -41065,7 +41414,7 @@ const noPropCallbackInRender = defineRule({
|
|
|
41065
41414
|
if (!analysis) return;
|
|
41066
41415
|
const callee = stripParenExpression(node.callee);
|
|
41067
41416
|
if (isFunctionLike$1(callee)) return;
|
|
41068
|
-
if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference))) return;
|
|
41417
|
+
if (!getDownstreamRefs(analysis, callee).some((reference) => isPropCallbackInvocationRef(analysis, reference, { nativeMethodScopes: context.scopes }))) return;
|
|
41069
41418
|
context.report({
|
|
41070
41419
|
node,
|
|
41071
41420
|
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."
|
|
@@ -59029,6 +59378,78 @@ const inferDestructureSourceKey = (bindingIdentifier) => {
|
|
|
59029
59378
|
return null;
|
|
59030
59379
|
};
|
|
59031
59380
|
const isReactUseHook = (hookName) => hookName === "use";
|
|
59381
|
+
const isReactHookCapabilityValue = (expression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
59382
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59383
|
+
if (isNodeOfType(unwrappedExpression, "Identifier")) {
|
|
59384
|
+
const symbol = scopes.symbolFor(unwrappedExpression);
|
|
59385
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
59386
|
+
visitedSymbolIds.add(symbol.id);
|
|
59387
|
+
if (symbol.kind === "import") return isReactImport(symbol) && isReactHookName(getImportedName(symbol.declarationNode) ?? "");
|
|
59388
|
+
if (symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
|
|
59389
|
+
const destructuredPropertyName = getDestructuredBindingPropertyName(symbol.bindingIdentifier);
|
|
59390
|
+
if (destructuredPropertyName) {
|
|
59391
|
+
const namespaceExpression = stripParenExpression(symbol.initializer);
|
|
59392
|
+
return isReactHookName(destructuredPropertyName) && isNodeOfType(namespaceExpression, "Identifier") && isReactNamespaceImport(namespaceExpression, scopes) && !hasPossibleStaticPropertyMutationOrEscape(namespaceExpression, destructuredPropertyName, scopes);
|
|
59393
|
+
}
|
|
59394
|
+
if (symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
|
|
59395
|
+
return isReactHookCapabilityValue(symbol.initializer, scopes, visitedSymbolIds);
|
|
59396
|
+
}
|
|
59397
|
+
if (!isNodeOfType(unwrappedExpression, "MemberExpression")) return false;
|
|
59398
|
+
const propertyName = getStaticPropertyName(unwrappedExpression);
|
|
59399
|
+
if (!propertyName || !isReactHookName(propertyName)) return false;
|
|
59400
|
+
const namespaceExpression = stripParenExpression(unwrappedExpression.object);
|
|
59401
|
+
return isNodeOfType(namespaceExpression, "Identifier") && isReactNamespaceImport(namespaceExpression, scopes) && !hasPossibleStaticPropertyMutationOrEscape(namespaceExpression, propertyName, scopes);
|
|
59402
|
+
};
|
|
59403
|
+
const isReactHookCapabilityComparisonOperand = (expression, scopes) => {
|
|
59404
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59405
|
+
if (isReactHookCapabilityValue(unwrappedExpression, scopes)) return true;
|
|
59406
|
+
return isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "typeof" && isReactHookCapabilityValue(unwrappedExpression.argument, scopes);
|
|
59407
|
+
};
|
|
59408
|
+
const isStaticCapabilityComparisonValue = (expression, scopes) => {
|
|
59409
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59410
|
+
if (isNodeOfType(unwrappedExpression, "Literal")) return true;
|
|
59411
|
+
return isNodeOfType(unwrappedExpression, "Identifier") && unwrappedExpression.name === "undefined" && scopes.isGlobalReference(unwrappedExpression);
|
|
59412
|
+
};
|
|
59413
|
+
const CAPABILITY_COMPARISON_OPERATORS = new Set([
|
|
59414
|
+
"==",
|
|
59415
|
+
"!=",
|
|
59416
|
+
"===",
|
|
59417
|
+
"!=="
|
|
59418
|
+
]);
|
|
59419
|
+
const isInvariantReactHookCapabilityCondition = (expression, scopes) => {
|
|
59420
|
+
const unwrappedExpression = stripParenExpression(expression);
|
|
59421
|
+
if (isReactHookCapabilityValue(unwrappedExpression, scopes)) return true;
|
|
59422
|
+
if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return isInvariantReactHookCapabilityCondition(unwrappedExpression.argument, scopes);
|
|
59423
|
+
if (isNodeOfType(unwrappedExpression, "LogicalExpression")) return isInvariantReactHookCapabilityCondition(unwrappedExpression.left, scopes) && isInvariantReactHookCapabilityCondition(unwrappedExpression.right, scopes);
|
|
59424
|
+
if (!isNodeOfType(unwrappedExpression, "BinaryExpression") || !CAPABILITY_COMPARISON_OPERATORS.has(unwrappedExpression.operator)) return false;
|
|
59425
|
+
return isReactHookCapabilityComparisonOperand(unwrappedExpression.left, scopes) && isStaticCapabilityComparisonValue(unwrappedExpression.right, scopes) || isReactHookCapabilityComparisonOperand(unwrappedExpression.right, scopes) && isStaticCapabilityComparisonValue(unwrappedExpression.left, scopes);
|
|
59426
|
+
};
|
|
59427
|
+
const statementContainsOwnAbruptCompletion = (statement) => {
|
|
59428
|
+
let doesContainAbruptCompletion = false;
|
|
59429
|
+
walkAst(statement, (child) => {
|
|
59430
|
+
if (child !== statement && isFunctionLike$1(child)) return false;
|
|
59431
|
+
if (isNodeOfType(child, "ReturnStatement") || isNodeOfType(child, "ThrowStatement")) {
|
|
59432
|
+
doesContainAbruptCompletion = true;
|
|
59433
|
+
return false;
|
|
59434
|
+
}
|
|
59435
|
+
});
|
|
59436
|
+
return doesContainAbruptCompletion;
|
|
59437
|
+
};
|
|
59438
|
+
const isInvariantReactHookCapabilityExit = (statement, scopes) => isNodeOfType(statement, "IfStatement") && statement.alternate === null && statementAlwaysExits(statement.consequent) && isInvariantReactHookCapabilityCondition(statement.test, scopes);
|
|
59439
|
+
const isAfterOnlyInvariantReactHookCapabilityExits = (node, enclosingFunction, scopes) => {
|
|
59440
|
+
if (!isFunctionLike$1(enclosingFunction) || !isNodeOfType(enclosingFunction.body, "BlockStatement")) return false;
|
|
59441
|
+
let containingStatement = node;
|
|
59442
|
+
while (containingStatement.parent && containingStatement.parent !== enclosingFunction.body) {
|
|
59443
|
+
if (isFunctionLike$1(containingStatement.parent)) return false;
|
|
59444
|
+
if (isNodeOfType(containingStatement.parent, "IfStatement") || isNodeOfType(containingStatement.parent, "SwitchStatement") || isNodeOfType(containingStatement.parent, "SwitchCase") || isNodeOfType(containingStatement.parent, "ConditionalExpression") || isNodeOfType(containingStatement.parent, "LogicalExpression")) return false;
|
|
59445
|
+
containingStatement = containingStatement.parent;
|
|
59446
|
+
}
|
|
59447
|
+
if (containingStatement.parent !== enclosingFunction.body) return false;
|
|
59448
|
+
const statementIndex = enclosingFunction.body.body.findIndex((statement) => statement === containingStatement);
|
|
59449
|
+
if (statementIndex <= 0) return false;
|
|
59450
|
+
const bypassingStatements = enclosingFunction.body.body.slice(0, statementIndex).filter((statement) => statementContainsOwnAbruptCompletion(statement));
|
|
59451
|
+
return bypassingStatements.length > 0 && bypassingStatements.every((statement) => isInvariantReactHookCapabilityExit(statement, scopes));
|
|
59452
|
+
};
|
|
59032
59453
|
const getCallExpressionCalleeName = (callExpression) => {
|
|
59033
59454
|
const callee = callExpression.callee;
|
|
59034
59455
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
@@ -59324,7 +59745,7 @@ const rulesOfHooks = defineRule({
|
|
|
59324
59745
|
});
|
|
59325
59746
|
return;
|
|
59326
59747
|
}
|
|
59327
|
-
if (!context.cfg.isUnconditionalFromEntry(node)) context.report({
|
|
59748
|
+
if (!context.cfg.isUnconditionalFromEntry(node) && !isAfterOnlyInvariantReactHookCapabilityExits(node, enclosing.node, context.scopes)) context.report({
|
|
59328
59749
|
node: node.callee,
|
|
59329
59750
|
message: buildConditionalMessage(hookName)
|
|
59330
59751
|
});
|