oxlint-plugin-react-doctor 0.2.18-dev.3cc9971 → 0.2.18-dev.5d7b36b
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 +659 -2
- package/dist/index.js +230 -88
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2574,7 +2574,7 @@ const INTENTIONAL_SEQUENCING_CALLEE_NAMES = new Set([
|
|
|
2574
2574
|
* (`FUNCTION_LIKE_TYPES.has(node.type)`) and as a type-guard. The
|
|
2575
2575
|
* type-guard form covers both shapes without callers paying a cast.
|
|
2576
2576
|
*/
|
|
2577
|
-
const isFunctionLike$
|
|
2577
|
+
const isFunctionLike$3 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")));
|
|
2578
2578
|
//#endregion
|
|
2579
2579
|
//#region src/plugin/utils/is-inline-function-expression.ts
|
|
2580
2580
|
/**
|
|
@@ -2597,7 +2597,7 @@ const findFirstAwaitOutsideNestedFunctions = (block) => {
|
|
|
2597
2597
|
let firstAwait = null;
|
|
2598
2598
|
walkAst(block, (child) => {
|
|
2599
2599
|
if (firstAwait) return false;
|
|
2600
|
-
if (child !== block && isFunctionLike$
|
|
2600
|
+
if (child !== block && isFunctionLike$3(child)) return false;
|
|
2601
2601
|
if (isNodeOfType(child, "AwaitExpression")) firstAwait = child;
|
|
2602
2602
|
});
|
|
2603
2603
|
return firstAwait;
|
|
@@ -3054,13 +3054,13 @@ const asyncDeferAwait = defineRule({
|
|
|
3054
3054
|
const inspectAllStatementBlocks = (functionBody) => {
|
|
3055
3055
|
if (!functionBody) return;
|
|
3056
3056
|
walkAst(functionBody, (descendant) => {
|
|
3057
|
-
if (isFunctionLike$
|
|
3057
|
+
if (isFunctionLike$3(descendant)) return false;
|
|
3058
3058
|
if (isNodeOfType(descendant, "BlockStatement")) inspectStatements(descendant.body ?? []);
|
|
3059
3059
|
else if (isNodeOfType(descendant, "SwitchCase")) inspectStatements(descendant.consequent ?? []);
|
|
3060
3060
|
});
|
|
3061
3061
|
};
|
|
3062
3062
|
const enterFunction = (node) => {
|
|
3063
|
-
if (!isFunctionLike$
|
|
3063
|
+
if (!isFunctionLike$3(node)) return;
|
|
3064
3064
|
if (!node.async) return;
|
|
3065
3065
|
if (!isNodeOfType(node.body, "BlockStatement")) return;
|
|
3066
3066
|
inspectAllStatementBlocks(node.body);
|
|
@@ -4509,7 +4509,7 @@ const displayName = defineRule({
|
|
|
4509
4509
|
//#region src/plugin/utils/walk-inside-statement-blocks.ts
|
|
4510
4510
|
const walkInsideStatementBlocks = (node, visitor) => {
|
|
4511
4511
|
if (!node || typeof node !== "object") return;
|
|
4512
|
-
if (isFunctionLike$
|
|
4512
|
+
if (isFunctionLike$3(node)) return;
|
|
4513
4513
|
visitor(node);
|
|
4514
4514
|
const nodeRecord = node;
|
|
4515
4515
|
for (const key of Object.keys(nodeRecord)) {
|
|
@@ -4597,7 +4597,7 @@ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubs
|
|
|
4597
4597
|
let didFindRelease = false;
|
|
4598
4598
|
walkAst(node, (child) => {
|
|
4599
4599
|
if (didFindRelease) return false;
|
|
4600
|
-
if (child !== node && isFunctionLike$
|
|
4600
|
+
if (child !== node && isFunctionLike$3(child) && !isIteratorCallbackArgument(child)) return false;
|
|
4601
4601
|
if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
|
|
4602
4602
|
didFindRelease = true;
|
|
4603
4603
|
return false;
|
|
@@ -4606,14 +4606,20 @@ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubs
|
|
|
4606
4606
|
return didFindRelease;
|
|
4607
4607
|
};
|
|
4608
4608
|
const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
|
|
4609
|
-
if (!isFunctionLike$
|
|
4609
|
+
if (!isFunctionLike$3(node)) return false;
|
|
4610
4610
|
return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
|
|
4611
4611
|
};
|
|
4612
|
-
const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
|
|
4612
|
+
const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames, options = {}) => {
|
|
4613
4613
|
if (!returnedValue) return false;
|
|
4614
4614
|
const unwrappedValue = unwrapChainExpression$2(returnedValue);
|
|
4615
|
-
if (isNodeOfType(unwrappedValue, "
|
|
4615
|
+
if (isNodeOfType(unwrappedValue, "Literal") && unwrappedValue.value === null) return false;
|
|
4616
|
+
if (isNodeOfType(unwrappedValue, "Identifier")) {
|
|
4617
|
+
if (unwrappedValue.name === "undefined") return false;
|
|
4618
|
+
if (knownCleanupFunctionNames.has(unwrappedValue.name)) return true;
|
|
4619
|
+
return options.allowOpaqueReturn === true && !knownBoundSubscriptionNames.has(unwrappedValue.name);
|
|
4620
|
+
}
|
|
4616
4621
|
if (isCleanupReturningSubscribeLikeCallExpression(unwrappedValue)) return true;
|
|
4622
|
+
if (options.allowOpaqueReturn === true && !isSubscribeLikeCallExpression(unwrappedValue)) return true;
|
|
4617
4623
|
if (isCleanupFunctionLike(unwrappedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames)) return true;
|
|
4618
4624
|
return false;
|
|
4619
4625
|
};
|
|
@@ -4634,12 +4640,14 @@ const findSubscribeLikeUsages = (callback) => {
|
|
|
4634
4640
|
if (isNodeOfType(child.callee, "Identifier") && TIMER_CALLEE_NAMES_REQUIRING_CLEANUP.has(child.callee.name)) {
|
|
4635
4641
|
usages.push({
|
|
4636
4642
|
kind: "timer",
|
|
4643
|
+
node: child,
|
|
4637
4644
|
resourceName: child.callee.name
|
|
4638
4645
|
});
|
|
4639
4646
|
return;
|
|
4640
4647
|
}
|
|
4641
4648
|
if (isNodeOfType(child.callee, "MemberExpression") && isNodeOfType(child.callee.property, "Identifier") && SUBSCRIPTION_METHOD_NAMES.has(child.callee.property.name)) usages.push({
|
|
4642
4649
|
kind: "subscribe",
|
|
4650
|
+
node: child,
|
|
4643
4651
|
resourceName: child.callee.property.name
|
|
4644
4652
|
});
|
|
4645
4653
|
});
|
|
@@ -4686,7 +4694,20 @@ const collectCleanupBindings = (effectCallback) => {
|
|
|
4686
4694
|
});
|
|
4687
4695
|
return bindings;
|
|
4688
4696
|
};
|
|
4689
|
-
const
|
|
4697
|
+
const getRangeStart = (node) => {
|
|
4698
|
+
const rangeStart = node.range?.[0];
|
|
4699
|
+
return typeof rangeStart === "number" ? rangeStart : null;
|
|
4700
|
+
};
|
|
4701
|
+
const cleanupReturnRunsAfterUsage = (returnStatement, usages) => {
|
|
4702
|
+
if (returnStatement.argument && isCleanupReturningSubscribeLikeCallExpression(returnStatement.argument)) return true;
|
|
4703
|
+
const returnStart = getRangeStart(returnStatement);
|
|
4704
|
+
if (returnStart === null) return true;
|
|
4705
|
+
return usages.some((usage) => {
|
|
4706
|
+
const usageStart = getRangeStart(usage.node);
|
|
4707
|
+
return usageStart === null || usageStart < returnStart;
|
|
4708
|
+
});
|
|
4709
|
+
};
|
|
4710
|
+
const effectHasCleanupReturn = (callback, usages) => {
|
|
4690
4711
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
|
|
4691
4712
|
if (!isNodeOfType(callback.body, "BlockStatement")) return isCleanupReturningSubscribeLikeCallExpression(callback.body);
|
|
4692
4713
|
const cleanupBindings = collectCleanupBindings(callback);
|
|
@@ -4694,7 +4715,8 @@ const effectHasCleanupRelease = (callback) => {
|
|
|
4694
4715
|
walkInsideStatementBlocks(callback.body, (child) => {
|
|
4695
4716
|
if (didFindCleanupReturn) return;
|
|
4696
4717
|
if (!isNodeOfType(child, "ReturnStatement")) return;
|
|
4697
|
-
if (
|
|
4718
|
+
if (!cleanupReturnRunsAfterUsage(child, usages)) return;
|
|
4719
|
+
if (isCleanupReturn(child.argument, cleanupBindings.cleanupFunctionNames, cleanupBindings.subscriptionNames, { allowOpaqueReturn: true })) didFindCleanupReturn = true;
|
|
4698
4720
|
});
|
|
4699
4721
|
return didFindCleanupReturn;
|
|
4700
4722
|
};
|
|
@@ -4710,7 +4732,7 @@ const effectNeedsCleanup = defineRule({
|
|
|
4710
4732
|
if (!callback) return;
|
|
4711
4733
|
const usages = findSubscribeLikeUsages(callback);
|
|
4712
4734
|
if (usages.length === 0) return;
|
|
4713
|
-
if (
|
|
4735
|
+
if (effectHasCleanupReturn(callback, usages)) return;
|
|
4714
4736
|
const firstUsage = usages[0];
|
|
4715
4737
|
const verb = firstUsage.kind === "timer" ? "schedules" : "subscribes via";
|
|
4716
4738
|
context.report({
|
|
@@ -4848,7 +4870,7 @@ const recordReference = (state, identifier, flag) => {
|
|
|
4848
4870
|
};
|
|
4849
4871
|
const isFunctionBodyBlock = (block) => {
|
|
4850
4872
|
if (!block.parent) return false;
|
|
4851
|
-
return isFunctionLike$
|
|
4873
|
+
return isFunctionLike$3(block.parent);
|
|
4852
4874
|
};
|
|
4853
4875
|
const isCatchClauseBlock = (block) => block.parent !== null && block.parent !== void 0 && block.parent.type === "CatchClause";
|
|
4854
4876
|
const handleVariableDeclaration = (declaration, state) => {
|
|
@@ -5003,7 +5025,7 @@ const walkParameterReferences = (pattern, state) => {
|
|
|
5003
5025
|
if (isNodeOfType(pattern, "RestElement")) walkParameterReferences(pattern.argument, state);
|
|
5004
5026
|
};
|
|
5005
5027
|
const walk = (node, state) => {
|
|
5006
|
-
if (isFunctionLike$
|
|
5028
|
+
if (isFunctionLike$3(node)) {
|
|
5007
5029
|
if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
|
|
5008
5030
|
setNodeScope(node, state);
|
|
5009
5031
|
const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
|
|
@@ -5249,7 +5271,7 @@ const closureCaptures = (functionNode, scopes) => {
|
|
|
5249
5271
|
const out = [];
|
|
5250
5272
|
const seen = /* @__PURE__ */ new Set();
|
|
5251
5273
|
const visit = (node) => {
|
|
5252
|
-
if (node !== functionNode && isFunctionLike$
|
|
5274
|
+
if (node !== functionNode && isFunctionLike$3(node)) {
|
|
5253
5275
|
const innerCaptures = closureCaptures(node, scopes);
|
|
5254
5276
|
for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
|
|
5255
5277
|
if (!seen.has(reference.id)) {
|
|
@@ -7369,7 +7391,7 @@ const isAtomFromJotai = (callExpression) => {
|
|
|
7369
7391
|
if (!isImportedFromModule(callExpression, localName, "jotai")) return false;
|
|
7370
7392
|
return getImportedNameFromModule(callExpression, localName, "jotai") === "atom";
|
|
7371
7393
|
};
|
|
7372
|
-
const isFunctionLike$
|
|
7394
|
+
const isFunctionLike$2 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
|
|
7373
7395
|
const getFirstParameterName = (fn) => {
|
|
7374
7396
|
const parameters = fn.params ?? [];
|
|
7375
7397
|
if (parameters.length !== 1) return null;
|
|
@@ -7488,7 +7510,7 @@ const jotaiDerivedAtomReturnsFreshObject = defineRule({
|
|
|
7488
7510
|
const args = node.arguments ?? [];
|
|
7489
7511
|
if (args.length === 0) return;
|
|
7490
7512
|
const reader = args[0];
|
|
7491
|
-
if (!isFunctionLike$
|
|
7513
|
+
if (!isFunctionLike$2(reader)) return;
|
|
7492
7514
|
const getParameterName = getFirstParameterName(reader);
|
|
7493
7515
|
if (!getParameterName) return;
|
|
7494
7516
|
const freshReturn = getFreshReturnForFunction(reader);
|
|
@@ -7744,7 +7766,7 @@ const isInsideLoopContext = (node) => {
|
|
|
7744
7766
|
let current = node.parent;
|
|
7745
7767
|
while (current) {
|
|
7746
7768
|
if (isNodeOfType(current, "ForStatement") || isNodeOfType(current, "ForInStatement") || isNodeOfType(current, "ForOfStatement") || isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return true;
|
|
7747
|
-
if (isFunctionLike$
|
|
7769
|
+
if (isFunctionLike$3(current)) {
|
|
7748
7770
|
if (isIteratorCallback(current)) return true;
|
|
7749
7771
|
return false;
|
|
7750
7772
|
}
|
|
@@ -8105,7 +8127,7 @@ const jsHoistIntl = defineRule({
|
|
|
8105
8127
|
let cursor = node.parent ?? null;
|
|
8106
8128
|
let inFunctionBody = false;
|
|
8107
8129
|
while (cursor) {
|
|
8108
|
-
if (isFunctionLike$
|
|
8130
|
+
if (isFunctionLike$3(cursor)) {
|
|
8109
8131
|
inFunctionBody = true;
|
|
8110
8132
|
const fnParent = cursor.parent;
|
|
8111
8133
|
if (fnParent && isNodeOfType(fnParent, "CallExpression") && fnParent.arguments?.[0] === cursor) {
|
|
@@ -9703,6 +9725,7 @@ const jsxNoConstructedContextValues = defineRule({
|
|
|
9703
9725
|
title: "Unstable context provider value",
|
|
9704
9726
|
tags: ["react-jsx-only"],
|
|
9705
9727
|
severity: "warn",
|
|
9728
|
+
disabledBy: ["react-compiler"],
|
|
9706
9729
|
recommendation: "Wrap the context value in `useMemo`, or move it outside the component.",
|
|
9707
9730
|
category: "Performance",
|
|
9708
9731
|
create: (context) => {
|
|
@@ -13059,7 +13082,7 @@ const collectChainedGetHandlerBodies = (initNode) => {
|
|
|
13059
13082
|
};
|
|
13060
13083
|
const resolveBodiesFromExpression = (expression, resolveBinding, remainingDepth) => {
|
|
13061
13084
|
if (remainingDepth <= 0) return [];
|
|
13062
|
-
if (isFunctionLike$
|
|
13085
|
+
if (isFunctionLike$3(expression)) return expression.body ? [expression.body] : [];
|
|
13063
13086
|
if (isNodeOfType(expression, "CallExpression")) {
|
|
13064
13087
|
for (const callArgument of expression.arguments ?? []) {
|
|
13065
13088
|
if (isNodeOfType(callArgument, "ArrowFunctionExpression") || isNodeOfType(callArgument, "FunctionExpression")) {
|
|
@@ -13529,7 +13552,7 @@ const getEffectFn = (analysis, node) => {
|
|
|
13529
13552
|
if (isNodeOfType(fn, "ArrowFunctionExpression") || isNodeOfType(fn, "FunctionExpression")) return fn;
|
|
13530
13553
|
if (isNodeOfType(fn, "Identifier")) {
|
|
13531
13554
|
const definitionNode = getRef(analysis, fn)?.resolved?.defs[0]?.node;
|
|
13532
|
-
if (definitionNode && isFunctionLike$
|
|
13555
|
+
if (definitionNode && isFunctionLike$3(definitionNode)) return definitionNode;
|
|
13533
13556
|
if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
13534
13557
|
const initializer = definitionNode.init;
|
|
13535
13558
|
if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) return initializer;
|
|
@@ -13622,14 +13645,14 @@ const getUseStateDecl = (analysis, ref) => {
|
|
|
13622
13645
|
return node ?? null;
|
|
13623
13646
|
};
|
|
13624
13647
|
const isCleanupReturnArgument = (analysis, node) => {
|
|
13625
|
-
if (isFunctionLike$
|
|
13648
|
+
if (isFunctionLike$3(node)) return true;
|
|
13626
13649
|
if (isNodeOfType(node, "MemberExpression")) return true;
|
|
13627
13650
|
if (isNodeOfType(node, "Identifier")) {
|
|
13628
13651
|
const definitionNode = getRef(analysis, node)?.resolved?.defs[0]?.node;
|
|
13629
|
-
if (definitionNode && isFunctionLike$
|
|
13652
|
+
if (definitionNode && isFunctionLike$3(definitionNode)) return true;
|
|
13630
13653
|
if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
13631
13654
|
const initializer = definitionNode.init;
|
|
13632
|
-
return isFunctionLike$
|
|
13655
|
+
return isFunctionLike$3(initializer);
|
|
13633
13656
|
}
|
|
13634
13657
|
}
|
|
13635
13658
|
if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
|
|
@@ -13639,7 +13662,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
|
|
|
13639
13662
|
if (visited.has(node)) return false;
|
|
13640
13663
|
visited.add(node);
|
|
13641
13664
|
if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
|
|
13642
|
-
if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$
|
|
13665
|
+
if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$3(node)) return false;
|
|
13643
13666
|
const record = node;
|
|
13644
13667
|
for (const [key, value] of Object.entries(record)) {
|
|
13645
13668
|
if (key === "parent") continue;
|
|
@@ -13651,7 +13674,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
|
|
|
13651
13674
|
};
|
|
13652
13675
|
const hasCleanup = (analysis, node) => {
|
|
13653
13676
|
const fn = getEffectFn(analysis, node);
|
|
13654
|
-
if (!isFunctionLike$
|
|
13677
|
+
if (!isFunctionLike$3(fn)) return false;
|
|
13655
13678
|
if (!isNodeOfType(fn.body, "BlockStatement")) return false;
|
|
13656
13679
|
return hasCleanupReturn(analysis, fn.body);
|
|
13657
13680
|
};
|
|
@@ -14001,7 +14024,7 @@ const isInsideStaticPlaceholderMap = (node) => {
|
|
|
14001
14024
|
let current = node;
|
|
14002
14025
|
while (current.parent) {
|
|
14003
14026
|
const parent = current.parent;
|
|
14004
|
-
if (isFunctionLike$
|
|
14027
|
+
if (isFunctionLike$3(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
|
|
14005
14028
|
const callee = parent.callee;
|
|
14006
14029
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach")) return isStaticPlaceholderReceiver(callee.object);
|
|
14007
14030
|
if (isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current) return isArrayFromLengthObjectCall(parent);
|
|
@@ -14020,7 +14043,7 @@ const findIteratorItemName$1 = (node) => {
|
|
|
14020
14043
|
let current = node;
|
|
14021
14044
|
while (current.parent) {
|
|
14022
14045
|
const parent = current.parent;
|
|
14023
|
-
if (isFunctionLike$
|
|
14046
|
+
if (isFunctionLike$3(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
|
|
14024
14047
|
const callee = parent.callee;
|
|
14025
14048
|
const isIteratorMethodCall = isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach");
|
|
14026
14049
|
const isArrayFromCallback = isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current;
|
|
@@ -14965,7 +14988,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
|
|
|
14965
14988
|
const nearestEnclosingFunction = (node) => {
|
|
14966
14989
|
let cursor = node.parent;
|
|
14967
14990
|
while (cursor) {
|
|
14968
|
-
if (isFunctionLike$
|
|
14991
|
+
if (isFunctionLike$3(cursor)) return cursor;
|
|
14969
14992
|
cursor = cursor.parent ?? null;
|
|
14970
14993
|
}
|
|
14971
14994
|
return null;
|
|
@@ -15648,7 +15671,7 @@ const extractDestructuredPropNames = (params) => {
|
|
|
15648
15671
|
};
|
|
15649
15672
|
const getInlineFunctionNode = (node) => {
|
|
15650
15673
|
if (!node) return null;
|
|
15651
|
-
if (isFunctionLike$
|
|
15674
|
+
if (isFunctionLike$3(node)) return node;
|
|
15652
15675
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
15653
15676
|
for (const argument of node.arguments ?? []) {
|
|
15654
15677
|
const inlineFunctionNode = getInlineFunctionNode(argument);
|
|
@@ -15659,7 +15682,7 @@ const getInlineFunctionNode = (node) => {
|
|
|
15659
15682
|
const getNearestComponentFunction = (node) => {
|
|
15660
15683
|
let cursor = node.parent ?? null;
|
|
15661
15684
|
while (cursor) {
|
|
15662
|
-
if (isFunctionLike$
|
|
15685
|
+
if (isFunctionLike$3(cursor)) return cursor;
|
|
15663
15686
|
cursor = cursor.parent ?? null;
|
|
15664
15687
|
}
|
|
15665
15688
|
return null;
|
|
@@ -15989,7 +16012,7 @@ const collectFunctionLocalBindings = (functionNode) => {
|
|
|
15989
16012
|
const walkComponentRespectingShadows = (node, shadowedStateNames, visit) => {
|
|
15990
16013
|
if (!node || typeof node !== "object") return;
|
|
15991
16014
|
let nextShadowedStateNames = shadowedStateNames;
|
|
15992
|
-
if (isFunctionLike$
|
|
16015
|
+
if (isFunctionLike$3(node)) {
|
|
15993
16016
|
const localBindings = collectFunctionLocalBindings(node);
|
|
15994
16017
|
if (localBindings.size > 0) {
|
|
15995
16018
|
const merged = new Set(shadowedStateNames);
|
|
@@ -18246,7 +18269,7 @@ const isInsideClassBody = (node) => {
|
|
|
18246
18269
|
let current = node.parent;
|
|
18247
18270
|
while (current) {
|
|
18248
18271
|
if (isNodeOfType(current, "ClassBody")) return true;
|
|
18249
|
-
if (isFunctionLike$
|
|
18272
|
+
if (isFunctionLike$3(current)) return false;
|
|
18250
18273
|
current = current.parent;
|
|
18251
18274
|
}
|
|
18252
18275
|
return false;
|
|
@@ -18950,7 +18973,7 @@ const isLodashMutatorCall = (callExpression) => {
|
|
|
18950
18973
|
};
|
|
18951
18974
|
//#endregion
|
|
18952
18975
|
//#region src/plugin/utils/find-exported-function-body.ts
|
|
18953
|
-
const isFunctionLike = (node) => {
|
|
18976
|
+
const isFunctionLike$1 = (node) => {
|
|
18954
18977
|
if (!node) return false;
|
|
18955
18978
|
return isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
|
|
18956
18979
|
};
|
|
@@ -18965,7 +18988,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
|
|
|
18965
18988
|
if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
|
|
18966
18989
|
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
18967
18990
|
const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
|
|
18968
|
-
if (initializer && isFunctionLike(initializer)) localBindings.set(declarator.id.name, initializer);
|
|
18991
|
+
if (initializer && isFunctionLike$1(initializer)) localBindings.set(declarator.id.name, initializer);
|
|
18969
18992
|
}
|
|
18970
18993
|
};
|
|
18971
18994
|
for (const statement of programRoot.body ?? []) {
|
|
@@ -19009,7 +19032,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
|
|
|
19009
19032
|
defaultExport = declaration;
|
|
19010
19033
|
continue;
|
|
19011
19034
|
}
|
|
19012
|
-
if (isFunctionLike(declaration)) {
|
|
19035
|
+
if (isFunctionLike$1(declaration)) {
|
|
19013
19036
|
defaultExport = declaration;
|
|
19014
19037
|
continue;
|
|
19015
19038
|
}
|
|
@@ -19170,7 +19193,7 @@ const resolveCrossFileFunctionExport = (fromFilename, source, exportedName) => {
|
|
|
19170
19193
|
const resolveReducerFunction = (node, currentFilename) => {
|
|
19171
19194
|
if (!node) return null;
|
|
19172
19195
|
const unwrappedNode = stripParenExpression(node);
|
|
19173
|
-
if (isFunctionLike$
|
|
19196
|
+
if (isFunctionLike$3(unwrappedNode)) return {
|
|
19174
19197
|
functionNode: unwrappedNode,
|
|
19175
19198
|
crossFileSourceDisplay: null
|
|
19176
19199
|
};
|
|
@@ -19178,7 +19201,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
19178
19201
|
const initializer = findVariableInitializer(unwrappedNode, unwrappedNode.name)?.initializer;
|
|
19179
19202
|
if (!initializer) return null;
|
|
19180
19203
|
const unwrappedInitializer = stripParenExpression(initializer);
|
|
19181
|
-
if (isFunctionLike$
|
|
19204
|
+
if (isFunctionLike$3(unwrappedInitializer)) return {
|
|
19182
19205
|
functionNode: unwrappedInitializer,
|
|
19183
19206
|
crossFileSourceDisplay: null
|
|
19184
19207
|
};
|
|
@@ -19292,11 +19315,11 @@ const canExpressionReturnOriginalReducerStateReference = (node, state) => {
|
|
|
19292
19315
|
return false;
|
|
19293
19316
|
};
|
|
19294
19317
|
const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
|
|
19295
|
-
if (isFunctionLike$
|
|
19318
|
+
if (isFunctionLike$3(node)) return [];
|
|
19296
19319
|
const mutations = [];
|
|
19297
19320
|
walkAst(node, (child) => {
|
|
19298
19321
|
const unwrappedChild = stripParenExpression(child);
|
|
19299
|
-
if (child !== node && isFunctionLike$
|
|
19322
|
+
if (child !== node && isFunctionLike$3(unwrappedChild)) return false;
|
|
19300
19323
|
if (isNodeOfType(unwrappedChild, "AssignmentExpression")) {
|
|
19301
19324
|
if (isNodeOfType(stripParenExpression(unwrappedChild.left), "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedChild.left, state)) mutations.push({ node: unwrappedChild });
|
|
19302
19325
|
return;
|
|
@@ -19401,7 +19424,7 @@ const updateReducerStateIdentityForIdentifierAssignment = (assignment, state) =>
|
|
|
19401
19424
|
if (isExpressionReachableFromOriginalReducerState(assignment.right, state)) state.mutableStateSourceNames.add(name);
|
|
19402
19425
|
};
|
|
19403
19426
|
const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, reportedNodes, options) => {
|
|
19404
|
-
if (!isFunctionLike$
|
|
19427
|
+
if (!isFunctionLike$3(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
|
|
19405
19428
|
const firstParam = functionNode.params?.[0];
|
|
19406
19429
|
const stateName = isNodeOfType(firstParam, "Identifier") ? firstParam.name : isNodeOfType(firstParam, "AssignmentPattern") && isNodeOfType(firstParam.left, "Identifier") ? firstParam.left.name : null;
|
|
19407
19430
|
if (!stateName) return;
|
|
@@ -20179,6 +20202,12 @@ const getReactDoctorStringSetting = (settings, settingName) => {
|
|
|
20179
20202
|
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
20180
20203
|
return typeof settingValue === "string" ? settingValue : void 0;
|
|
20181
20204
|
};
|
|
20205
|
+
const getReactDoctorNumberSetting = (settings, settingName) => {
|
|
20206
|
+
const bag = readReactDoctorSettingsBag(settings);
|
|
20207
|
+
if (!bag) return void 0;
|
|
20208
|
+
const settingValue = readOwnPropertyValue(bag, settingName);
|
|
20209
|
+
return typeof settingValue === "number" && Number.isFinite(settingValue) ? settingValue : void 0;
|
|
20210
|
+
};
|
|
20182
20211
|
const getReactDoctorStringArraySetting = (settings, settingName) => {
|
|
20183
20212
|
const bag = readReactDoctorSettingsBag(settings);
|
|
20184
20213
|
if (!bag) return [];
|
|
@@ -21244,7 +21273,7 @@ const isTanStackServerFnHandler = (node) => {
|
|
|
21244
21273
|
const isInsideServerOnlyScope = (node) => {
|
|
21245
21274
|
let currentNode = node.parent ?? null;
|
|
21246
21275
|
while (currentNode) {
|
|
21247
|
-
if (isFunctionLike$
|
|
21276
|
+
if (isFunctionLike$3(currentNode)) {
|
|
21248
21277
|
if (hasUseServerDirective(currentNode) || isTanStackServerFnHandler(currentNode)) return true;
|
|
21249
21278
|
}
|
|
21250
21279
|
currentNode = currentNode.parent ?? null;
|
|
@@ -23399,7 +23428,7 @@ const isReactClassComponent = (classNode) => {
|
|
|
23399
23428
|
const findEnclosingComponent = (node) => {
|
|
23400
23429
|
let walker = node.parent;
|
|
23401
23430
|
while (walker) {
|
|
23402
|
-
if (isFunctionLike$
|
|
23431
|
+
if (isFunctionLike$3(walker)) {
|
|
23403
23432
|
const componentName = inferFunctionLikeName(walker);
|
|
23404
23433
|
if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
|
|
23405
23434
|
component: walker,
|
|
@@ -23651,6 +23680,35 @@ const noUsememoSimpleExpression = defineRule({
|
|
|
23651
23680
|
});
|
|
23652
23681
|
//#endregion
|
|
23653
23682
|
//#region src/plugin/rules/design/no-wide-letter-spacing.ts
|
|
23683
|
+
const getJsxAttributeStringValue = (attributeValue) => {
|
|
23684
|
+
if (!attributeValue) return null;
|
|
23685
|
+
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") return attributeValue.value;
|
|
23686
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
23687
|
+
const expression = attributeValue.expression;
|
|
23688
|
+
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
|
|
23689
|
+
}
|
|
23690
|
+
return null;
|
|
23691
|
+
};
|
|
23692
|
+
const isTruthyBooleanJsxAttribute = (attributeValue) => {
|
|
23693
|
+
if (attributeValue === null) return true;
|
|
23694
|
+
if (isNodeOfType(attributeValue, "JSXExpressionContainer")) {
|
|
23695
|
+
const expression = attributeValue.expression;
|
|
23696
|
+
return isNodeOfType(expression, "Literal") && expression.value === true;
|
|
23697
|
+
}
|
|
23698
|
+
return false;
|
|
23699
|
+
};
|
|
23700
|
+
const hasUppercaseSiblingProp = (styleAttribute) => {
|
|
23701
|
+
const openingElement = styleAttribute.parent;
|
|
23702
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
23703
|
+
for (const attribute of openingElement.attributes ?? []) {
|
|
23704
|
+
if (!isNodeOfType(attribute, "JSXAttribute")) continue;
|
|
23705
|
+
if (!isNodeOfType(attribute.name, "JSXIdentifier")) continue;
|
|
23706
|
+
const attributeName = attribute.name.name;
|
|
23707
|
+
if (attributeName === "uppercase" && isTruthyBooleanJsxAttribute(attribute.value)) return true;
|
|
23708
|
+
if (attributeName === "textTransform" && getJsxAttributeStringValue(attribute.value) === "uppercase") return true;
|
|
23709
|
+
}
|
|
23710
|
+
return false;
|
|
23711
|
+
};
|
|
23654
23712
|
const noWideLetterSpacing = defineRule({
|
|
23655
23713
|
id: "no-wide-letter-spacing",
|
|
23656
23714
|
title: "Wide letter spacing on body text",
|
|
@@ -23660,6 +23718,7 @@ const noWideLetterSpacing = defineRule({
|
|
|
23660
23718
|
create: (context) => ({ JSXAttribute(node) {
|
|
23661
23719
|
const expression = getInlineStyleExpression(node);
|
|
23662
23720
|
if (!expression) return;
|
|
23721
|
+
if (hasUppercaseSiblingProp(node)) return;
|
|
23663
23722
|
let isUppercase = false;
|
|
23664
23723
|
let letterSpacingProperty = null;
|
|
23665
23724
|
let letterSpacingEm = null;
|
|
@@ -24822,6 +24881,7 @@ const preferModuleScopeStaticValue = defineRule({
|
|
|
24822
24881
|
tags: ["test-noise"],
|
|
24823
24882
|
severity: "warn",
|
|
24824
24883
|
category: "Architecture",
|
|
24884
|
+
disabledBy: ["react-compiler"],
|
|
24825
24885
|
recommendation: "Move the value above the component, at the top of the file. It doesn't use local state, so rebuilding it each update is wasted and makes it look new every time.",
|
|
24826
24886
|
create: (context) => ({ VariableDeclarator(node) {
|
|
24827
24887
|
if (!isNodeOfType(node.id, "Identifier")) return;
|
|
@@ -25634,7 +25694,7 @@ const reduxUseselectorInlineDerivation = defineRule({
|
|
|
25634
25694
|
if (!body) return;
|
|
25635
25695
|
const returnedExpressions = [];
|
|
25636
25696
|
if (isNodeOfType(body, "BlockStatement")) walkAst(body, (node) => {
|
|
25637
|
-
if (node !== body && isFunctionLike$
|
|
25697
|
+
if (node !== body && isFunctionLike$3(node)) return false;
|
|
25638
25698
|
if (isNodeOfType(node, "ReturnStatement")) {
|
|
25639
25699
|
if (node.argument) returnedExpressions.push(node.argument);
|
|
25640
25700
|
return false;
|
|
@@ -25977,7 +26037,7 @@ const hasOwnAwait = (functionBody) => {
|
|
|
25977
26037
|
let found = false;
|
|
25978
26038
|
walkAst(functionBody, (child) => {
|
|
25979
26039
|
if (found) return;
|
|
25980
|
-
if (child !== functionBody && isFunctionLike$
|
|
26040
|
+
if (child !== functionBody && isFunctionLike$3(child)) return false;
|
|
25981
26041
|
if (isNodeOfType(child, "AwaitExpression")) found = true;
|
|
25982
26042
|
});
|
|
25983
26043
|
return found;
|
|
@@ -25996,7 +26056,7 @@ const setterIsCalledInAsyncContext = (componentBody, setterName) => {
|
|
|
25996
26056
|
let found = false;
|
|
25997
26057
|
walkAst(componentBody, (child) => {
|
|
25998
26058
|
if (found) return;
|
|
25999
|
-
if (!isFunctionLike$
|
|
26059
|
+
if (!isFunctionLike$3(child)) return;
|
|
26000
26060
|
const functionBody = child.body;
|
|
26001
26061
|
if (!(Boolean(child.async) || hasOwnAwait(functionBody))) return;
|
|
26002
26062
|
if (callsIdentifier(functionBody, setterName)) found = true;
|
|
@@ -26779,7 +26839,8 @@ const rerenderTransitionsScroll = defineRule({
|
|
|
26779
26839
|
});
|
|
26780
26840
|
//#endregion
|
|
26781
26841
|
//#region src/plugin/rules/react-native/rn-animate-layout-property.ts
|
|
26782
|
-
const
|
|
26842
|
+
const REANIMATED_MODULE = "react-native-reanimated";
|
|
26843
|
+
const REANIMATED_LAYOUT_STYLE_PROPERTIES = new Set([
|
|
26783
26844
|
"width",
|
|
26784
26845
|
"height",
|
|
26785
26846
|
"top",
|
|
@@ -26817,12 +26878,91 @@ const REANIMATED_LAYOUT_KEYS = new Set([
|
|
|
26817
26878
|
"lineHeight",
|
|
26818
26879
|
"letterSpacing"
|
|
26819
26880
|
]);
|
|
26881
|
+
const REANIMATED_ANIMATION_HELPERS = new Set([
|
|
26882
|
+
"withTiming",
|
|
26883
|
+
"withSpring",
|
|
26884
|
+
"withDecay",
|
|
26885
|
+
"withDelay",
|
|
26886
|
+
"withRepeat",
|
|
26887
|
+
"withSequence",
|
|
26888
|
+
"withClamp"
|
|
26889
|
+
]);
|
|
26890
|
+
const findImportDeclaration = (symbol) => {
|
|
26891
|
+
let currentNode = symbol.declarationNode.parent;
|
|
26892
|
+
while (currentNode && !isNodeOfType(currentNode, "ImportDeclaration")) currentNode = currentNode.parent ?? null;
|
|
26893
|
+
return currentNode && isNodeOfType(currentNode, "ImportDeclaration") ? currentNode : null;
|
|
26894
|
+
};
|
|
26895
|
+
const isReanimatedImport = (symbol) => {
|
|
26896
|
+
const importDeclaration = findImportDeclaration(symbol);
|
|
26897
|
+
if (!importDeclaration) return false;
|
|
26898
|
+
return importDeclaration.source.value === REANIMATED_MODULE;
|
|
26899
|
+
};
|
|
26900
|
+
const getReanimatedNamedImport = (node, context) => {
|
|
26901
|
+
if (!isNodeOfType(node, "Identifier")) return null;
|
|
26902
|
+
const symbol = context.scopes.symbolFor(node);
|
|
26903
|
+
if (!symbol || symbol.kind !== "import") return null;
|
|
26904
|
+
if (!isReanimatedImport(symbol)) return null;
|
|
26905
|
+
const declarationNode = symbol.declarationNode;
|
|
26906
|
+
if (!isNodeOfType(declarationNode, "ImportSpecifier")) return null;
|
|
26907
|
+
const importedName = declarationNode.imported;
|
|
26908
|
+
if (isNodeOfType(importedName, "Identifier")) return importedName.name;
|
|
26909
|
+
if (isNodeOfType(importedName, "Literal") && typeof importedName.value === "string") return importedName.value;
|
|
26910
|
+
return null;
|
|
26911
|
+
};
|
|
26912
|
+
const isReanimatedNamespace = (node, context) => {
|
|
26913
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
26914
|
+
const symbol = context.scopes.symbolFor(node);
|
|
26915
|
+
if (!symbol || symbol.kind !== "import") return false;
|
|
26916
|
+
if (!isReanimatedImport(symbol)) return false;
|
|
26917
|
+
return isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier");
|
|
26918
|
+
};
|
|
26919
|
+
const isUseAnimatedStyleCallee = (callee, context) => {
|
|
26920
|
+
if (isNodeOfType(callee, "Identifier")) return getReanimatedNamedImport(callee, context) === "useAnimatedStyle";
|
|
26921
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
|
|
26922
|
+
if (!isNodeOfType(callee.property, "Identifier")) return false;
|
|
26923
|
+
if (callee.property.name !== "useAnimatedStyle") return false;
|
|
26924
|
+
return isReanimatedNamespace(callee.object, context);
|
|
26925
|
+
};
|
|
26926
|
+
const isReanimatedAnimationHelperCallee = (callee, context) => {
|
|
26927
|
+
if (isNodeOfType(callee, "Identifier")) {
|
|
26928
|
+
const importedName = getReanimatedNamedImport(callee, context);
|
|
26929
|
+
return importedName !== null && REANIMATED_ANIMATION_HELPERS.has(importedName);
|
|
26930
|
+
}
|
|
26931
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
|
|
26932
|
+
if (!isNodeOfType(callee.property, "Identifier")) return false;
|
|
26933
|
+
if (!REANIMATED_ANIMATION_HELPERS.has(callee.property.name)) return false;
|
|
26934
|
+
return isReanimatedNamespace(callee.object, context);
|
|
26935
|
+
};
|
|
26936
|
+
const isFunctionLike = (node) => isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration");
|
|
26937
|
+
const containsReanimatedAnimationHelperCall = (expression, context) => {
|
|
26938
|
+
const rootExpression = stripParenExpression(expression);
|
|
26939
|
+
let didFindAnimationHelper = false;
|
|
26940
|
+
walkAst(rootExpression, (node) => {
|
|
26941
|
+
if (didFindAnimationHelper) return false;
|
|
26942
|
+
if (node !== rootExpression && isFunctionLike(node)) return false;
|
|
26943
|
+
if (isNodeOfType(node, "CallExpression") && isReanimatedAnimationHelperCallee(node.callee, context)) {
|
|
26944
|
+
didFindAnimationHelper = true;
|
|
26945
|
+
return false;
|
|
26946
|
+
}
|
|
26947
|
+
});
|
|
26948
|
+
return didFindAnimationHelper;
|
|
26949
|
+
};
|
|
26820
26950
|
const findReturnedObject = (callback) => {
|
|
26821
26951
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return null;
|
|
26822
|
-
const body = callback.body;
|
|
26952
|
+
const body = stripParenExpression(callback.body);
|
|
26823
26953
|
if (isNodeOfType(body, "ObjectExpression")) return body;
|
|
26824
26954
|
if (!isNodeOfType(body, "BlockStatement")) return null;
|
|
26825
|
-
for (const
|
|
26955
|
+
for (const statement of body.body ?? []) {
|
|
26956
|
+
if (!isNodeOfType(statement, "ReturnStatement")) continue;
|
|
26957
|
+
if (!statement.argument) continue;
|
|
26958
|
+
const returnArgument = stripParenExpression(statement.argument);
|
|
26959
|
+
if (isNodeOfType(returnArgument, "ObjectExpression")) return returnArgument;
|
|
26960
|
+
}
|
|
26961
|
+
return null;
|
|
26962
|
+
};
|
|
26963
|
+
const getStaticPropertyName$1 = (property) => {
|
|
26964
|
+
if (!property.computed && isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
26965
|
+
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
26826
26966
|
return null;
|
|
26827
26967
|
};
|
|
26828
26968
|
const rnAnimateLayoutProperty = defineRule({
|
|
@@ -26830,21 +26970,25 @@ const rnAnimateLayoutProperty = defineRule({
|
|
|
26830
26970
|
title: "Animating a layout property",
|
|
26831
26971
|
tags: ["test-noise"],
|
|
26832
26972
|
requires: ["react-native"],
|
|
26833
|
-
severity: "
|
|
26834
|
-
|
|
26973
|
+
severity: "warn",
|
|
26974
|
+
category: "Performance",
|
|
26975
|
+
recommendation: "Prefer transform or opacity when a Reanimated animation helper drives purely visual motion; use layout-affecting styles only when the layout itself must change.",
|
|
26835
26976
|
create: (context) => ({ CallExpression(node) {
|
|
26836
|
-
if (!
|
|
26977
|
+
if (!isUseAnimatedStyleCallee(node.callee, context)) return;
|
|
26837
26978
|
const callback = node.arguments?.[0];
|
|
26838
26979
|
if (!callback) return;
|
|
26839
26980
|
const returnedObject = findReturnedObject(callback);
|
|
26840
26981
|
if (!returnedObject) return;
|
|
26841
26982
|
for (const property of returnedObject.properties ?? []) {
|
|
26842
26983
|
if (!isNodeOfType(property, "Property")) continue;
|
|
26843
|
-
|
|
26844
|
-
if (
|
|
26984
|
+
const propertyName = getStaticPropertyName$1(property);
|
|
26985
|
+
if (propertyName === null) continue;
|
|
26986
|
+
if (!REANIMATED_LAYOUT_STYLE_PROPERTIES.has(propertyName)) continue;
|
|
26987
|
+
const propertyValue = property.value;
|
|
26988
|
+
if (!containsReanimatedAnimationHelperCall(propertyValue, context)) continue;
|
|
26845
26989
|
context.report({
|
|
26846
26990
|
node: property,
|
|
26847
|
-
message: `
|
|
26991
|
+
message: `Reanimated can animate "${propertyName}", but this layout-affecting style recalculates layout while the animation runs; prefer transform or opacity when the motion is only visual.`
|
|
26848
26992
|
});
|
|
26849
26993
|
}
|
|
26850
26994
|
} })
|
|
@@ -27052,7 +27196,6 @@ const LEGACY_EXPO_PACKAGE_REPLACEMENTS = new Map([
|
|
|
27052
27196
|
["expo-av", "expo-audio for audio and expo-video for video"],
|
|
27053
27197
|
["expo-permissions", "the permissions API in each module (e.g. Camera.requestPermissionsAsync())"],
|
|
27054
27198
|
["expo-app-loading", "expo-splash-screen"],
|
|
27055
|
-
["expo-linear-gradient", "the `backgroundImage` CSS gradient style prop (New Architecture) or expo-linear-gradient's successor"],
|
|
27056
27199
|
["react-native-fast-image", "expo-image (drop-in with caching, placeholders, and crossfades)"]
|
|
27057
27200
|
]);
|
|
27058
27201
|
const EXPO_UI_MODULE_SOURCES = new Set([
|
|
@@ -27207,6 +27350,10 @@ const RECYCLABLE_LIST_PACKAGES = {
|
|
|
27207
27350
|
LegendList: ["@legendapp/list"]
|
|
27208
27351
|
};
|
|
27209
27352
|
const SIZING_HINT_ATTRIBUTE_NAMES = new Set(["estimatedItemSize", "estimatedListSize"]);
|
|
27353
|
+
const isFlashListV2OrNewer = (context) => {
|
|
27354
|
+
const flashListMajorVersion = getReactDoctorNumberSetting(context.settings, "shopifyFlashListMajorVersion");
|
|
27355
|
+
return flashListMajorVersion !== void 0 && flashListMajorVersion >= 2;
|
|
27356
|
+
};
|
|
27210
27357
|
const isEmptyArrayLiteral = (node) => {
|
|
27211
27358
|
if (!isNodeOfType(node.value, "JSXExpressionContainer")) return false;
|
|
27212
27359
|
const expression = node.value.expression;
|
|
@@ -27228,6 +27375,7 @@ const rnListMissingEstimatedItemSize = defineRule({
|
|
|
27228
27375
|
break;
|
|
27229
27376
|
}
|
|
27230
27377
|
if (!canonicalRecyclerName) return;
|
|
27378
|
+
if (canonicalRecyclerName === "FlashList" && isFlashListV2OrNewer(context)) return;
|
|
27231
27379
|
let hasSizingHint = false;
|
|
27232
27380
|
let dataIsEmptyLiteral = false;
|
|
27233
27381
|
let hasDataProp = false;
|
|
@@ -28170,37 +28318,22 @@ const rnNoSingleElementStyleArray = defineRule({
|
|
|
28170
28318
|
} })
|
|
28171
28319
|
});
|
|
28172
28320
|
//#endregion
|
|
28173
|
-
//#region src/plugin/
|
|
28174
|
-
const
|
|
28175
|
-
|
|
28176
|
-
|
|
28177
|
-
"
|
|
28178
|
-
|
|
28179
|
-
|
|
28180
|
-
]);
|
|
28321
|
+
//#region src/plugin/utils/define-retired-rule.ts
|
|
28322
|
+
const defineRetiredRule = (rule) => defineRule({
|
|
28323
|
+
...rule,
|
|
28324
|
+
defaultEnabled: false,
|
|
28325
|
+
lifecycle: "retired",
|
|
28326
|
+
create: () => ({})
|
|
28327
|
+
});
|
|
28181
28328
|
//#endregion
|
|
28182
28329
|
//#region src/plugin/rules/react-native/rn-prefer-content-inset-adjustment.ts
|
|
28183
|
-
const rnPreferContentInsetAdjustment =
|
|
28330
|
+
const rnPreferContentInsetAdjustment = defineRetiredRule({
|
|
28184
28331
|
id: "rn-prefer-content-inset-adjustment",
|
|
28185
|
-
title: "
|
|
28332
|
+
title: "Manual safe-area inset adjustment",
|
|
28186
28333
|
tags: ["test-noise"],
|
|
28187
28334
|
requires: ["react-native"],
|
|
28188
28335
|
severity: "warn",
|
|
28189
|
-
recommendation: "
|
|
28190
|
-
create: (context) => ({ JSXElement(node) {
|
|
28191
|
-
if (resolveJsxElementName(node.openingElement) !== "SafeAreaView") return;
|
|
28192
|
-
for (const child of node.children ?? []) {
|
|
28193
|
-
if (!isNodeOfType(child, "JSXElement")) continue;
|
|
28194
|
-
const childName = resolveJsxElementName(child.openingElement);
|
|
28195
|
-
if (!childName || !SCROLLVIEW_NAMES.has(childName)) continue;
|
|
28196
|
-
if (childName === "KeyboardAwareScrollView") continue;
|
|
28197
|
-
context.report({
|
|
28198
|
-
node,
|
|
28199
|
-
message: `Your users render an extra wrapper view from <SafeAreaView> around <${childName}>.`
|
|
28200
|
-
});
|
|
28201
|
-
return;
|
|
28202
|
-
}
|
|
28203
|
-
} })
|
|
28336
|
+
recommendation: "Prefer native content inset adjustment only when it replaces manual inset plumbing; SafeAreaView wrappers are valid and intentionally ignored."
|
|
28204
28337
|
});
|
|
28205
28338
|
//#endregion
|
|
28206
28339
|
//#region src/react-native-dependency-names.ts
|
|
@@ -28586,6 +28719,15 @@ const rnPressableSharedValueMutation = defineRule({
|
|
|
28586
28719
|
}
|
|
28587
28720
|
});
|
|
28588
28721
|
//#endregion
|
|
28722
|
+
//#region src/plugin/rules/react-native/utils/scrollview_names.ts
|
|
28723
|
+
const SCROLLVIEW_NAMES = new Set([
|
|
28724
|
+
"ScrollView",
|
|
28725
|
+
"FlatList",
|
|
28726
|
+
"SectionList",
|
|
28727
|
+
"VirtualizedList",
|
|
28728
|
+
"KeyboardAwareScrollView"
|
|
28729
|
+
]);
|
|
28730
|
+
//#endregion
|
|
28589
28731
|
//#region src/plugin/rules/react-native/rn-scrollview-dynamic-padding.ts
|
|
28590
28732
|
const rnScrollviewDynamicPadding = defineRule({
|
|
28591
28733
|
id: "rn-scrollview-dynamic-padding",
|
|
@@ -32281,7 +32423,7 @@ const isUseEffectEventSymbol = (symbol) => {
|
|
|
32281
32423
|
const findEnclosingComponentOrHookFunction = (node) => {
|
|
32282
32424
|
let current = node.parent;
|
|
32283
32425
|
while (current) {
|
|
32284
|
-
if (isFunctionLike$
|
|
32426
|
+
if (isFunctionLike$3(current)) {
|
|
32285
32427
|
const resolvedName = inferFunctionName(current);
|
|
32286
32428
|
if (resolvedName !== null && isReactComponentOrHookName(resolvedName)) return current;
|
|
32287
32429
|
}
|
|
@@ -32302,7 +32444,7 @@ const isCallbackArgumentForAllowedEffectEventHook = (functionNode, additionalEff
|
|
|
32302
32444
|
const isInsideAllowedEffectEventCallback = (node, additionalEffectHooksRegex) => {
|
|
32303
32445
|
let current = node.parent;
|
|
32304
32446
|
while (current) {
|
|
32305
|
-
if (isFunctionLike$
|
|
32447
|
+
if (isFunctionLike$3(current) && isCallbackArgumentForAllowedEffectEventHook(current, additionalEffectHooksRegex)) return true;
|
|
32306
32448
|
current = current.parent ?? null;
|
|
32307
32449
|
}
|
|
32308
32450
|
return false;
|
|
@@ -32640,7 +32782,7 @@ const containsAuthCheck = (rootNodes, allowedFunctionNames, genericMethodNames)
|
|
|
32640
32782
|
let foundAuthCall = false;
|
|
32641
32783
|
for (const rootNode of rootNodes) walkAst(rootNode, (child) => {
|
|
32642
32784
|
if (foundAuthCall) return;
|
|
32643
|
-
if (isFunctionLike$
|
|
32785
|
+
if (isFunctionLike$3(child)) return false;
|
|
32644
32786
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
32645
32787
|
if (getAuthCallName(child, allowedFunctionNames, genericMethodNames)) foundAuthCall = true;
|
|
32646
32788
|
});
|
|
@@ -37254,7 +37396,7 @@ const reactDoctorRules = [
|
|
|
37254
37396
|
rule: {
|
|
37255
37397
|
...rnAnimateLayoutProperty,
|
|
37256
37398
|
framework: "react-native",
|
|
37257
|
-
category: "
|
|
37399
|
+
category: "Performance",
|
|
37258
37400
|
tags: [...new Set(["react-native", ...rnAnimateLayoutProperty.tags ?? []])]
|
|
37259
37401
|
}
|
|
37260
37402
|
},
|
|
@@ -38119,7 +38261,7 @@ const appendNode = (builder, block, node) => {
|
|
|
38119
38261
|
};
|
|
38120
38262
|
const mapDescendantsToBlock = (builder, node, block) => {
|
|
38121
38263
|
builder.nodeBlock.set(node, block);
|
|
38122
|
-
if (isFunctionLike$
|
|
38264
|
+
if (isFunctionLike$3(node)) return;
|
|
38123
38265
|
const record = node;
|
|
38124
38266
|
for (const key of Object.keys(record)) {
|
|
38125
38267
|
if (key === "parent") continue;
|
|
@@ -38457,7 +38599,7 @@ const analyzeControlFlow = (program) => {
|
|
|
38457
38599
|
body: program.body
|
|
38458
38600
|
});
|
|
38459
38601
|
const visit = (node) => {
|
|
38460
|
-
if (isFunctionLike$
|
|
38602
|
+
if (isFunctionLike$3(node)) {
|
|
38461
38603
|
const body = node.body;
|
|
38462
38604
|
if (body) buildFor(node, body);
|
|
38463
38605
|
}
|
|
@@ -38474,7 +38616,7 @@ const analyzeControlFlow = (program) => {
|
|
|
38474
38616
|
const enclosingFunction = (node) => {
|
|
38475
38617
|
let current = node;
|
|
38476
38618
|
while (current) {
|
|
38477
|
-
if (isFunctionLike$
|
|
38619
|
+
if (isFunctionLike$3(current)) return current;
|
|
38478
38620
|
if (isNodeOfType(current, "Program")) return current;
|
|
38479
38621
|
current = current.parent ?? null;
|
|
38480
38622
|
}
|