oxlint-plugin-react-doctor 0.9.2-dev.16972ae → 0.9.2-dev.1f6e181
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 +138 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -82275,6 +82275,45 @@ const noRefCallbackCleanupBeforeReact19 = defineRule({
|
|
|
82275
82275
|
} })
|
|
82276
82276
|
});
|
|
82277
82277
|
//#endregion
|
|
82278
|
+
//#region src/plugin/utils/contains-non-deterministic-source.ts
|
|
82279
|
+
const NON_DETERMINISTIC_MEMBER_CALLS = new Set([
|
|
82280
|
+
"Math.random",
|
|
82281
|
+
"Date.now",
|
|
82282
|
+
"performance.now",
|
|
82283
|
+
"crypto.randomUUID",
|
|
82284
|
+
"crypto.getRandomValues"
|
|
82285
|
+
]);
|
|
82286
|
+
const NON_DETERMINISTIC_ID_GENERATOR_NAMES = new Set([
|
|
82287
|
+
"nanoid",
|
|
82288
|
+
"uuid",
|
|
82289
|
+
"cuid",
|
|
82290
|
+
"ulid",
|
|
82291
|
+
"createId"
|
|
82292
|
+
]);
|
|
82293
|
+
const isZeroArgDateConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Date" && (node.arguments?.length ?? 0) === 0;
|
|
82294
|
+
const containsNonDeterministicSource = (root) => {
|
|
82295
|
+
let found = false;
|
|
82296
|
+
walkAst(root, (child) => {
|
|
82297
|
+
if (found) return false;
|
|
82298
|
+
if (isFunctionLike$1(child)) return false;
|
|
82299
|
+
if (isZeroArgDateConstruction(child)) {
|
|
82300
|
+
found = true;
|
|
82301
|
+
return false;
|
|
82302
|
+
}
|
|
82303
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
82304
|
+
const callee = child.callee;
|
|
82305
|
+
if (isNodeOfType(callee, "Identifier") && NON_DETERMINISTIC_ID_GENERATOR_NAMES.has(callee.name)) {
|
|
82306
|
+
found = true;
|
|
82307
|
+
return false;
|
|
82308
|
+
}
|
|
82309
|
+
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && isNodeOfType(callee.property, "Identifier") && NON_DETERMINISTIC_MEMBER_CALLS.has(`${callee.object.name}.${callee.property.name}`)) {
|
|
82310
|
+
found = true;
|
|
82311
|
+
return false;
|
|
82312
|
+
}
|
|
82313
|
+
});
|
|
82314
|
+
return found;
|
|
82315
|
+
};
|
|
82316
|
+
//#endregion
|
|
82278
82317
|
//#region src/plugin/rules/state-and-effects/no-ref-current-in-render.ts
|
|
82279
82318
|
const REPEATED_ANCESTOR_TYPES = new Set([
|
|
82280
82319
|
"DoWhileStatement",
|
|
@@ -82305,45 +82344,75 @@ const resolveImmutableInitializationValue = (node, scopes, visitedSymbolIds = /*
|
|
|
82305
82344
|
};
|
|
82306
82345
|
const isProvablyTruthyInitializationValue = (node, scopes) => {
|
|
82307
82346
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
82308
|
-
|
|
82347
|
+
if (!expression) return false;
|
|
82348
|
+
if (isNodeOfType(expression, "CallExpression")) {
|
|
82349
|
+
const callee = stripParenExpression(expression.callee);
|
|
82350
|
+
return isNodeOfType(callee, "Identifier") && callee.name.startsWith("create");
|
|
82351
|
+
}
|
|
82352
|
+
return isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "ObjectExpression") || isNodeOfType(expression, "ArrayExpression") || isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "ClassExpression");
|
|
82309
82353
|
};
|
|
82310
|
-
const
|
|
82354
|
+
const getInitializationValueName = (node, scopes) => {
|
|
82311
82355
|
const expression = resolveImmutableInitializationValue(node, scopes);
|
|
82312
82356
|
if (!expression) return null;
|
|
82313
|
-
if (isNodeOfType(expression, "NewExpression")) {
|
|
82357
|
+
if (isNodeOfType(expression, "NewExpression") || isNodeOfType(expression, "CallExpression")) {
|
|
82314
82358
|
const callee = stripParenExpression(expression.callee);
|
|
82315
|
-
|
|
82359
|
+
if (!isNodeOfType(callee, "Identifier")) return null;
|
|
82360
|
+
return callee.name.startsWith("create") && callee.name.length > 6 ? callee.name.slice(6) : callee.name;
|
|
82316
82361
|
}
|
|
82317
82362
|
return null;
|
|
82318
82363
|
};
|
|
82364
|
+
const isMatchingReturnType = (typeNode, initializationValue, scopes) => {
|
|
82365
|
+
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82366
|
+
const typeName = typeNode.typeName;
|
|
82367
|
+
if (!isNodeOfType(typeName, "Identifier") || typeName.name !== "ReturnType") return false;
|
|
82368
|
+
const [returnTypeArgument] = typeNode.typeArguments?.params ?? [];
|
|
82369
|
+
if (!returnTypeArgument || !isNodeOfType(returnTypeArgument, "TSTypeQuery")) return false;
|
|
82370
|
+
const queriedName = returnTypeArgument.exprName;
|
|
82371
|
+
const expression = stripParenExpression(initializationValue);
|
|
82372
|
+
if (!isNodeOfType(queriedName, "Identifier") || !isNodeOfType(expression, "CallExpression")) return false;
|
|
82373
|
+
const callee = stripParenExpression(expression.callee);
|
|
82374
|
+
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
82375
|
+
const queriedSymbol = scopes.symbolFor(queriedName);
|
|
82376
|
+
const calleeSymbol = scopes.symbolFor(callee);
|
|
82377
|
+
return queriedSymbol && calleeSymbol ? queriedSymbol.id === calleeSymbol.id : queriedName.name === callee.name;
|
|
82378
|
+
};
|
|
82319
82379
|
const isClosedTruthyTypeDomain = (typeNode, initializationValue, scopes) => {
|
|
82320
82380
|
const initializationExpression = stripParenExpression(initializationValue);
|
|
82321
82381
|
if (isNodeOfType(typeNode, "TSTypeLiteral")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
82322
82382
|
if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return isNodeOfType(initializationExpression, "ArrayExpression");
|
|
82323
82383
|
if (isNodeOfType(typeNode, "TSFunctionType") || isNodeOfType(typeNode, "TSConstructorType")) return isNodeOfType(initializationExpression, "ArrowFunctionExpression") || isNodeOfType(initializationExpression, "FunctionExpression") || isNodeOfType(initializationExpression, "ClassExpression");
|
|
82324
82384
|
if (isNodeOfType(typeNode, "TSObjectKeyword")) return true;
|
|
82385
|
+
if (isNodeOfType(typeNode, "TSIndexedAccessType")) return isNodeOfType(initializationExpression, "ObjectExpression");
|
|
82325
82386
|
if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
|
|
82326
82387
|
const typeName = typeNode.typeName;
|
|
82327
|
-
|
|
82388
|
+
if (isNodeOfType(initializationExpression, "ObjectExpression") || isMatchingReturnType(typeNode, initializationExpression, scopes)) return true;
|
|
82389
|
+
return isNodeOfType(typeName, "Identifier") && typeName.name === getInitializationValueName(initializationExpression, scopes);
|
|
82328
82390
|
};
|
|
82329
82391
|
const refHasClosedFalsySentinelDomain = (refSymbol, initializationValue, scopes) => {
|
|
82330
82392
|
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82331
82393
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82332
82394
|
const [initialValue] = initializer.arguments ?? [];
|
|
82333
|
-
if (
|
|
82395
|
+
if (initialValue && isNodeOfType(initialValue, "SpreadElement") || initialValue && !isEmptySentinel(initialValue, scopes)) return false;
|
|
82334
82396
|
const [declaredType] = initializer.typeArguments?.params ?? [];
|
|
82335
|
-
if (!declaredType
|
|
82336
|
-
|
|
82397
|
+
if (!declaredType) return false;
|
|
82398
|
+
const domainTypes = isNodeOfType(declaredType, "TSUnionType") ? declaredType.types : [declaredType];
|
|
82337
82399
|
let hasTruthyDomain = false;
|
|
82338
|
-
for (const memberType of
|
|
82339
|
-
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword"))
|
|
82340
|
-
hasEmptySentinel = true;
|
|
82341
|
-
continue;
|
|
82342
|
-
}
|
|
82400
|
+
for (const memberType of domainTypes) {
|
|
82401
|
+
if (isNodeOfType(memberType, "TSNullKeyword") || isNodeOfType(memberType, "TSUndefinedKeyword")) continue;
|
|
82343
82402
|
if (!isClosedTruthyTypeDomain(memberType, initializationValue, scopes)) return false;
|
|
82344
82403
|
hasTruthyDomain = true;
|
|
82345
82404
|
}
|
|
82346
|
-
return
|
|
82405
|
+
return hasTruthyDomain;
|
|
82406
|
+
};
|
|
82407
|
+
const refHasEmptySentinelInitializer = (refSymbol, scopes) => {
|
|
82408
|
+
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82409
|
+
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
82410
|
+
const [initialValue] = initializer.arguments ?? [];
|
|
82411
|
+
return Boolean(!initialValue || !isNodeOfType(initialValue, "SpreadElement") && isEmptySentinel(initialValue, scopes));
|
|
82412
|
+
};
|
|
82413
|
+
const refHasDeclaredType = (refSymbol) => {
|
|
82414
|
+
const initializer = refSymbol.initializer ? stripParenExpression(refSymbol.initializer) : null;
|
|
82415
|
+
return Boolean(initializer && isNodeOfType(initializer, "CallExpression") && (initializer.typeArguments?.params.length ?? 0) > 0);
|
|
82347
82416
|
};
|
|
82348
82417
|
const isSafeRefIdentifierUse = (identifier) => {
|
|
82349
82418
|
const expressionRoot = findTransparentExpressionRoot(identifier);
|
|
@@ -82375,25 +82444,40 @@ const expressionContainsRefCurrent = (expression, refSymbol, scopes) => {
|
|
|
82375
82444
|
});
|
|
82376
82445
|
return didFindRefCurrent;
|
|
82377
82446
|
};
|
|
82378
|
-
const
|
|
82379
|
-
|
|
82380
|
-
|
|
82381
|
-
|
|
82382
|
-
|
|
82383
|
-
|
|
82384
|
-
|
|
82447
|
+
const isEmptySentinel = (node, scopes) => {
|
|
82448
|
+
const expression = stripParenExpression(node);
|
|
82449
|
+
return isNodeOfType(expression, "Literal") && expression.value === null || isNodeOfType(expression, "Identifier") && expression.name === "undefined" && scopes.isGlobalReference(expression);
|
|
82450
|
+
};
|
|
82451
|
+
const isInitializationInputIndependent = (node, renderOwner, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
82452
|
+
let isInputIndependent = true;
|
|
82453
|
+
walkAst(node, (child) => {
|
|
82454
|
+
if (!isInputIndependent) return false;
|
|
82455
|
+
if (resolveReactRefSymbol(child, scopes)) return false;
|
|
82456
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
82457
|
+
const symbol = scopes.symbolFor(child);
|
|
82458
|
+
if (!symbol) return;
|
|
82459
|
+
if (symbol.kind === "import") return false;
|
|
82460
|
+
if (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "using") {
|
|
82461
|
+
isInputIndependent = false;
|
|
82462
|
+
return false;
|
|
82385
82463
|
}
|
|
82386
|
-
if (
|
|
82387
|
-
|
|
82388
|
-
|
|
82464
|
+
if (isOutsideAllFunctions(symbol)) return false;
|
|
82465
|
+
if (symbol.kind === "parameter") {
|
|
82466
|
+
if (symbol.scope.node === renderOwner) isInputIndependent = false;
|
|
82467
|
+
return false;
|
|
82389
82468
|
}
|
|
82390
|
-
if (
|
|
82391
|
-
|
|
82469
|
+
if (!symbol.initializer || symbol.references.some((reference) => reference.flag !== "read")) {
|
|
82470
|
+
isInputIndependent = false;
|
|
82471
|
+
return false;
|
|
82392
82472
|
}
|
|
82473
|
+
if (visitedSymbolIds.has(symbol.id)) return false;
|
|
82474
|
+
visitedSymbolIds.add(symbol.id);
|
|
82475
|
+
if (!isInitializationInputIndependent(symbol.initializer, renderOwner, scopes, visitedSymbolIds)) isInputIndependent = false;
|
|
82476
|
+
return false;
|
|
82393
82477
|
});
|
|
82394
|
-
return
|
|
82478
|
+
return isInputIndependent;
|
|
82395
82479
|
};
|
|
82396
|
-
const
|
|
82480
|
+
const isPredictableInitializationValue = (node, refSymbol, renderOwner, scopes, requiresClosedTruthyDomain) => isInitializationInputIndependent(node, renderOwner, scopes) && !containsNonDeterministicSource(node) && (isProvablyTruthyInitializationValue(node, scopes) && (!requiresClosedTruthyDomain || !refHasDeclaredType(refSymbol)) || refHasClosedFalsySentinelDomain(refSymbol, node, scopes));
|
|
82397
82481
|
const hasRepeatedExecutionAncestor = (node, stop) => {
|
|
82398
82482
|
let ancestor = node.parent;
|
|
82399
82483
|
while (ancestor && ancestor !== stop) {
|
|
@@ -82423,6 +82507,27 @@ const canExecuteTogether = (firstConstraints, secondConstraints) => {
|
|
|
82423
82507
|
}
|
|
82424
82508
|
return true;
|
|
82425
82509
|
};
|
|
82510
|
+
const hasNoCoExecutableCompetingWrite = (assignmentExpression, renderOwner, refSymbol, scopes) => {
|
|
82511
|
+
const assignmentConstraints = getBranchConstraints(assignmentExpression, renderOwner);
|
|
82512
|
+
const synchronouslyInvokedFunctions = collectSynchronouslyEffectInvokedFunctions(renderOwner, scopes);
|
|
82513
|
+
let hasCompetingWrite = false;
|
|
82514
|
+
walkAst(renderOwner, (child) => {
|
|
82515
|
+
if (hasCompetingWrite) return false;
|
|
82516
|
+
let writtenExpression = null;
|
|
82517
|
+
if (isNodeOfType(child, "AssignmentExpression")) {
|
|
82518
|
+
if (child === assignmentExpression) return;
|
|
82519
|
+
writtenExpression = child.left;
|
|
82520
|
+
} else if (isNodeOfType(child, "UpdateExpression") || isNodeOfType(child, "UnaryExpression") && child.operator === "delete") writtenExpression = child.argument;
|
|
82521
|
+
else if (isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement")) writtenExpression = child.left;
|
|
82522
|
+
const deferredExecutionBoundary = findDeferredExecutionBoundary(child);
|
|
82523
|
+
const deferredWriteValue = isNodeOfType(child, "AssignmentExpression") && child.operator === "=" ? resolveImmutableInitializationValue(child.right, scopes) : null;
|
|
82524
|
+
const isDeferredTruthyWrite = deferredExecutionBoundary !== null && deferredExecutionBoundary !== renderOwner && !synchronouslyInvokedFunctions.has(deferredExecutionBoundary) && !executesDuringRender(deferredExecutionBoundary, scopes) && deferredWriteValue !== null && !isNodeOfType(deferredWriteValue, "CallExpression") && isProvablyTruthyInitializationValue(deferredWriteValue, scopes);
|
|
82525
|
+
if (!writtenExpression || isDeferredTruthyWrite || !expressionContainsRefCurrent(writtenExpression, refSymbol, scopes) || !canExecuteTogether(assignmentConstraints, getBranchConstraints(child, renderOwner))) return;
|
|
82526
|
+
hasCompetingWrite = true;
|
|
82527
|
+
return false;
|
|
82528
|
+
});
|
|
82529
|
+
return !hasCompetingWrite;
|
|
82530
|
+
};
|
|
82426
82531
|
const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol, scopes) => {
|
|
82427
82532
|
const assignmentConstraints = getBranchConstraints(assignmentExpression, branchRoot);
|
|
82428
82533
|
const assignmentStart = getRangeStart(assignmentExpression);
|
|
@@ -82438,16 +82543,17 @@ const hasNoPriorCoExecutableWrite = (assignmentExpression, branchRoot, refSymbol
|
|
|
82438
82543
|
});
|
|
82439
82544
|
return !hasCoExecutableWrite;
|
|
82440
82545
|
};
|
|
82546
|
+
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
82547
|
const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes) => {
|
|
82442
|
-
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return true;
|
|
82443
|
-
if (assignmentExpression.operator !== "=") return false;
|
|
82444
82548
|
const renderOwner = findRenderPhaseComponentOrHook(assignmentExpression, scopes);
|
|
82445
82549
|
if (!renderOwner) return false;
|
|
82550
|
+
if (assignmentExpression.operator === "??=" || assignmentExpression.operator === "||=") return isPredictableGuardedInitialization(assignmentExpression, renderOwner, renderOwner, refSymbol, scopes, assignmentExpression.operator === "||=");
|
|
82551
|
+
if (assignmentExpression.operator !== "=") return false;
|
|
82446
82552
|
let descendant = assignmentExpression;
|
|
82447
82553
|
let ancestor = descendant.parent;
|
|
82448
82554
|
while (ancestor) {
|
|
82449
82555
|
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 &&
|
|
82556
|
+
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
82557
|
if (isNodeOfType(ancestor, "IfStatement") && isNodeOfType(test, "BinaryExpression") && [
|
|
82452
82558
|
"===",
|
|
82453
82559
|
"==",
|
|
@@ -82457,7 +82563,7 @@ const isDocumentedLazyInitialization = (assignmentExpression, refSymbol, scopes)
|
|
|
82457
82563
|
const { left, right } = test;
|
|
82458
82564
|
const comparesEmptySentinel = isSameRefCurrentAlias(left, refSymbol, scopes) && isEmptySentinel(right, scopes) || isSameRefCurrentAlias(right, refSymbol, scopes) && isEmptySentinel(left, scopes);
|
|
82459
82565
|
const guardedBranch = test.operator === "===" || test.operator === "==" ? ancestor.consequent : ancestor.alternate;
|
|
82460
|
-
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch &&
|
|
82566
|
+
if (comparesEmptySentinel && guardedBranch === descendant && guardedBranch && isPredictableGuardedInitialization(assignmentExpression, guardedBranch, renderOwner, refSymbol, scopes, false)) return true;
|
|
82461
82567
|
}
|
|
82462
82568
|
descendant = ancestor;
|
|
82463
82569
|
ancestor = descendant.parent;
|