oxlint-plugin-react-doctor 0.2.18-dev.5d7b36b → 0.2.18-dev.68a0bef
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 +2 -2
- package/dist/index.js +57 -141
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -5047,7 +5047,7 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
5047
5047
|
readonly originallyExternal: false;
|
|
5048
5048
|
readonly rule: {
|
|
5049
5049
|
readonly framework: "react-native";
|
|
5050
|
-
readonly category: "
|
|
5050
|
+
readonly category: "Bugs";
|
|
5051
5051
|
readonly tags: readonly string[];
|
|
5052
5052
|
readonly id: string;
|
|
5053
5053
|
readonly title?: string;
|
|
@@ -11345,7 +11345,7 @@ declare const RULES: readonly [{
|
|
|
11345
11345
|
readonly originallyExternal: false;
|
|
11346
11346
|
readonly rule: {
|
|
11347
11347
|
readonly framework: "react-native";
|
|
11348
|
-
readonly category: "
|
|
11348
|
+
readonly category: "Bugs";
|
|
11349
11349
|
readonly tags: readonly string[];
|
|
11350
11350
|
readonly id: string;
|
|
11351
11351
|
readonly title?: string;
|
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$2 = (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$2(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$2(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$2(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$2(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$2(child) && !isIteratorCallbackArgument(child)) return false;
|
|
4601
4601
|
if (isReleaseLikeCall(child, knownCleanupFunctionNames, knownBoundSubscriptionNames)) {
|
|
4602
4602
|
didFindRelease = true;
|
|
4603
4603
|
return false;
|
|
@@ -4606,7 +4606,7 @@ const containsReleaseLikeCall = (node, knownCleanupFunctionNames, knownBoundSubs
|
|
|
4606
4606
|
return didFindRelease;
|
|
4607
4607
|
};
|
|
4608
4608
|
const isCleanupFunctionLike = (node, knownCleanupFunctionNames, knownBoundSubscriptionNames) => {
|
|
4609
|
-
if (!isFunctionLike$
|
|
4609
|
+
if (!isFunctionLike$2(node)) return false;
|
|
4610
4610
|
return containsReleaseLikeCall(node.body, knownCleanupFunctionNames, knownBoundSubscriptionNames);
|
|
4611
4611
|
};
|
|
4612
4612
|
const isCleanupReturn = (returnedValue, knownCleanupFunctionNames, knownBoundSubscriptionNames, options = {}) => {
|
|
@@ -4870,7 +4870,7 @@ const recordReference = (state, identifier, flag) => {
|
|
|
4870
4870
|
};
|
|
4871
4871
|
const isFunctionBodyBlock = (block) => {
|
|
4872
4872
|
if (!block.parent) return false;
|
|
4873
|
-
return isFunctionLike$
|
|
4873
|
+
return isFunctionLike$2(block.parent);
|
|
4874
4874
|
};
|
|
4875
4875
|
const isCatchClauseBlock = (block) => block.parent !== null && block.parent !== void 0 && block.parent.type === "CatchClause";
|
|
4876
4876
|
const handleVariableDeclaration = (declaration, state) => {
|
|
@@ -5025,7 +5025,7 @@ const walkParameterReferences = (pattern, state) => {
|
|
|
5025
5025
|
if (isNodeOfType(pattern, "RestElement")) walkParameterReferences(pattern.argument, state);
|
|
5026
5026
|
};
|
|
5027
5027
|
const walk = (node, state) => {
|
|
5028
|
-
if (isFunctionLike$
|
|
5028
|
+
if (isFunctionLike$2(node)) {
|
|
5029
5029
|
if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
|
|
5030
5030
|
setNodeScope(node, state);
|
|
5031
5031
|
const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
|
|
@@ -5271,7 +5271,7 @@ const closureCaptures = (functionNode, scopes) => {
|
|
|
5271
5271
|
const out = [];
|
|
5272
5272
|
const seen = /* @__PURE__ */ new Set();
|
|
5273
5273
|
const visit = (node) => {
|
|
5274
|
-
if (node !== functionNode && isFunctionLike$
|
|
5274
|
+
if (node !== functionNode && isFunctionLike$2(node)) {
|
|
5275
5275
|
const innerCaptures = closureCaptures(node, scopes);
|
|
5276
5276
|
for (const reference of innerCaptures) if (reference.resolvedSymbol && !isDescendantScope(reference.resolvedSymbol.scope, functionScope)) {
|
|
5277
5277
|
if (!seen.has(reference.id)) {
|
|
@@ -7391,7 +7391,7 @@ const isAtomFromJotai = (callExpression) => {
|
|
|
7391
7391
|
if (!isImportedFromModule(callExpression, localName, "jotai")) return false;
|
|
7392
7392
|
return getImportedNameFromModule(callExpression, localName, "jotai") === "atom";
|
|
7393
7393
|
};
|
|
7394
|
-
const isFunctionLike$
|
|
7394
|
+
const isFunctionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
|
|
7395
7395
|
const getFirstParameterName = (fn) => {
|
|
7396
7396
|
const parameters = fn.params ?? [];
|
|
7397
7397
|
if (parameters.length !== 1) return null;
|
|
@@ -7510,7 +7510,7 @@ const jotaiDerivedAtomReturnsFreshObject = defineRule({
|
|
|
7510
7510
|
const args = node.arguments ?? [];
|
|
7511
7511
|
if (args.length === 0) return;
|
|
7512
7512
|
const reader = args[0];
|
|
7513
|
-
if (!isFunctionLike$
|
|
7513
|
+
if (!isFunctionLike$1(reader)) return;
|
|
7514
7514
|
const getParameterName = getFirstParameterName(reader);
|
|
7515
7515
|
if (!getParameterName) return;
|
|
7516
7516
|
const freshReturn = getFreshReturnForFunction(reader);
|
|
@@ -7766,7 +7766,7 @@ const isInsideLoopContext = (node) => {
|
|
|
7766
7766
|
let current = node.parent;
|
|
7767
7767
|
while (current) {
|
|
7768
7768
|
if (isNodeOfType(current, "ForStatement") || isNodeOfType(current, "ForInStatement") || isNodeOfType(current, "ForOfStatement") || isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return true;
|
|
7769
|
-
if (isFunctionLike$
|
|
7769
|
+
if (isFunctionLike$2(current)) {
|
|
7770
7770
|
if (isIteratorCallback(current)) return true;
|
|
7771
7771
|
return false;
|
|
7772
7772
|
}
|
|
@@ -8127,7 +8127,7 @@ const jsHoistIntl = defineRule({
|
|
|
8127
8127
|
let cursor = node.parent ?? null;
|
|
8128
8128
|
let inFunctionBody = false;
|
|
8129
8129
|
while (cursor) {
|
|
8130
|
-
if (isFunctionLike$
|
|
8130
|
+
if (isFunctionLike$2(cursor)) {
|
|
8131
8131
|
inFunctionBody = true;
|
|
8132
8132
|
const fnParent = cursor.parent;
|
|
8133
8133
|
if (fnParent && isNodeOfType(fnParent, "CallExpression") && fnParent.arguments?.[0] === cursor) {
|
|
@@ -13082,7 +13082,7 @@ const collectChainedGetHandlerBodies = (initNode) => {
|
|
|
13082
13082
|
};
|
|
13083
13083
|
const resolveBodiesFromExpression = (expression, resolveBinding, remainingDepth) => {
|
|
13084
13084
|
if (remainingDepth <= 0) return [];
|
|
13085
|
-
if (isFunctionLike$
|
|
13085
|
+
if (isFunctionLike$2(expression)) return expression.body ? [expression.body] : [];
|
|
13086
13086
|
if (isNodeOfType(expression, "CallExpression")) {
|
|
13087
13087
|
for (const callArgument of expression.arguments ?? []) {
|
|
13088
13088
|
if (isNodeOfType(callArgument, "ArrowFunctionExpression") || isNodeOfType(callArgument, "FunctionExpression")) {
|
|
@@ -13552,7 +13552,7 @@ const getEffectFn = (analysis, node) => {
|
|
|
13552
13552
|
if (isNodeOfType(fn, "ArrowFunctionExpression") || isNodeOfType(fn, "FunctionExpression")) return fn;
|
|
13553
13553
|
if (isNodeOfType(fn, "Identifier")) {
|
|
13554
13554
|
const definitionNode = getRef(analysis, fn)?.resolved?.defs[0]?.node;
|
|
13555
|
-
if (definitionNode && isFunctionLike$
|
|
13555
|
+
if (definitionNode && isFunctionLike$2(definitionNode)) return definitionNode;
|
|
13556
13556
|
if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
13557
13557
|
const initializer = definitionNode.init;
|
|
13558
13558
|
if (isNodeOfType(initializer, "ArrowFunctionExpression") || isNodeOfType(initializer, "FunctionExpression")) return initializer;
|
|
@@ -13645,14 +13645,14 @@ const getUseStateDecl = (analysis, ref) => {
|
|
|
13645
13645
|
return node ?? null;
|
|
13646
13646
|
};
|
|
13647
13647
|
const isCleanupReturnArgument = (analysis, node) => {
|
|
13648
|
-
if (isFunctionLike$
|
|
13648
|
+
if (isFunctionLike$2(node)) return true;
|
|
13649
13649
|
if (isNodeOfType(node, "MemberExpression")) return true;
|
|
13650
13650
|
if (isNodeOfType(node, "Identifier")) {
|
|
13651
13651
|
const definitionNode = getRef(analysis, node)?.resolved?.defs[0]?.node;
|
|
13652
|
-
if (definitionNode && isFunctionLike$
|
|
13652
|
+
if (definitionNode && isFunctionLike$2(definitionNode)) return true;
|
|
13653
13653
|
if (definitionNode && isNodeOfType(definitionNode, "VariableDeclarator")) {
|
|
13654
13654
|
const initializer = definitionNode.init;
|
|
13655
|
-
return isFunctionLike$
|
|
13655
|
+
return isFunctionLike$2(initializer);
|
|
13656
13656
|
}
|
|
13657
13657
|
}
|
|
13658
13658
|
if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
|
|
@@ -13662,7 +13662,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
|
|
|
13662
13662
|
if (visited.has(node)) return false;
|
|
13663
13663
|
visited.add(node);
|
|
13664
13664
|
if (isNodeOfType(node, "ReturnStatement") && node.argument != null) return isCleanupReturnArgument(analysis, node.argument);
|
|
13665
|
-
if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$
|
|
13665
|
+
if (!isNodeOfType(node, "BlockStatement") && isFunctionLike$2(node)) return false;
|
|
13666
13666
|
const record = node;
|
|
13667
13667
|
for (const [key, value] of Object.entries(record)) {
|
|
13668
13668
|
if (key === "parent") continue;
|
|
@@ -13674,7 +13674,7 @@ const hasCleanupReturn = (analysis, node, visited = /* @__PURE__ */ new WeakSet(
|
|
|
13674
13674
|
};
|
|
13675
13675
|
const hasCleanup = (analysis, node) => {
|
|
13676
13676
|
const fn = getEffectFn(analysis, node);
|
|
13677
|
-
if (!isFunctionLike$
|
|
13677
|
+
if (!isFunctionLike$2(fn)) return false;
|
|
13678
13678
|
if (!isNodeOfType(fn.body, "BlockStatement")) return false;
|
|
13679
13679
|
return hasCleanupReturn(analysis, fn.body);
|
|
13680
13680
|
};
|
|
@@ -14024,7 +14024,7 @@ const isInsideStaticPlaceholderMap = (node) => {
|
|
|
14024
14024
|
let current = node;
|
|
14025
14025
|
while (current.parent) {
|
|
14026
14026
|
const parent = current.parent;
|
|
14027
|
-
if (isFunctionLike$
|
|
14027
|
+
if (isFunctionLike$2(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
|
|
14028
14028
|
const callee = parent.callee;
|
|
14029
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);
|
|
14030
14030
|
if (isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current) return isArrayFromLengthObjectCall(parent);
|
|
@@ -14043,7 +14043,7 @@ const findIteratorItemName$1 = (node) => {
|
|
|
14043
14043
|
let current = node;
|
|
14044
14044
|
while (current.parent) {
|
|
14045
14045
|
const parent = current.parent;
|
|
14046
|
-
if (isFunctionLike$
|
|
14046
|
+
if (isFunctionLike$2(current) && isNodeOfType(parent, "CallExpression") && parent.arguments.includes(current)) {
|
|
14047
14047
|
const callee = parent.callee;
|
|
14048
14048
|
const isIteratorMethodCall = isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && (callee.property.name === "map" || callee.property.name === "flatMap" || callee.property.name === "forEach");
|
|
14049
14049
|
const isArrayFromCallback = isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current;
|
|
@@ -14988,7 +14988,7 @@ const componentOrHookDisplayNameForFunction = (functionNode) => {
|
|
|
14988
14988
|
const nearestEnclosingFunction = (node) => {
|
|
14989
14989
|
let cursor = node.parent;
|
|
14990
14990
|
while (cursor) {
|
|
14991
|
-
if (isFunctionLike$
|
|
14991
|
+
if (isFunctionLike$2(cursor)) return cursor;
|
|
14992
14992
|
cursor = cursor.parent ?? null;
|
|
14993
14993
|
}
|
|
14994
14994
|
return null;
|
|
@@ -15671,7 +15671,7 @@ const extractDestructuredPropNames = (params) => {
|
|
|
15671
15671
|
};
|
|
15672
15672
|
const getInlineFunctionNode = (node) => {
|
|
15673
15673
|
if (!node) return null;
|
|
15674
|
-
if (isFunctionLike$
|
|
15674
|
+
if (isFunctionLike$2(node)) return node;
|
|
15675
15675
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
15676
15676
|
for (const argument of node.arguments ?? []) {
|
|
15677
15677
|
const inlineFunctionNode = getInlineFunctionNode(argument);
|
|
@@ -15682,7 +15682,7 @@ const getInlineFunctionNode = (node) => {
|
|
|
15682
15682
|
const getNearestComponentFunction = (node) => {
|
|
15683
15683
|
let cursor = node.parent ?? null;
|
|
15684
15684
|
while (cursor) {
|
|
15685
|
-
if (isFunctionLike$
|
|
15685
|
+
if (isFunctionLike$2(cursor)) return cursor;
|
|
15686
15686
|
cursor = cursor.parent ?? null;
|
|
15687
15687
|
}
|
|
15688
15688
|
return null;
|
|
@@ -16012,7 +16012,7 @@ const collectFunctionLocalBindings = (functionNode) => {
|
|
|
16012
16012
|
const walkComponentRespectingShadows = (node, shadowedStateNames, visit) => {
|
|
16013
16013
|
if (!node || typeof node !== "object") return;
|
|
16014
16014
|
let nextShadowedStateNames = shadowedStateNames;
|
|
16015
|
-
if (isFunctionLike$
|
|
16015
|
+
if (isFunctionLike$2(node)) {
|
|
16016
16016
|
const localBindings = collectFunctionLocalBindings(node);
|
|
16017
16017
|
if (localBindings.size > 0) {
|
|
16018
16018
|
const merged = new Set(shadowedStateNames);
|
|
@@ -18269,7 +18269,7 @@ const isInsideClassBody = (node) => {
|
|
|
18269
18269
|
let current = node.parent;
|
|
18270
18270
|
while (current) {
|
|
18271
18271
|
if (isNodeOfType(current, "ClassBody")) return true;
|
|
18272
|
-
if (isFunctionLike$
|
|
18272
|
+
if (isFunctionLike$2(current)) return false;
|
|
18273
18273
|
current = current.parent;
|
|
18274
18274
|
}
|
|
18275
18275
|
return false;
|
|
@@ -18973,7 +18973,7 @@ const isLodashMutatorCall = (callExpression) => {
|
|
|
18973
18973
|
};
|
|
18974
18974
|
//#endregion
|
|
18975
18975
|
//#region src/plugin/utils/find-exported-function-body.ts
|
|
18976
|
-
const isFunctionLike
|
|
18976
|
+
const isFunctionLike = (node) => {
|
|
18977
18977
|
if (!node) return false;
|
|
18978
18978
|
return isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
|
|
18979
18979
|
};
|
|
@@ -18988,7 +18988,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
|
|
|
18988
18988
|
if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
|
|
18989
18989
|
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
18990
18990
|
const initializer = declarator.init ? stripParenExpression(declarator.init) : null;
|
|
18991
|
-
if (initializer && isFunctionLike
|
|
18991
|
+
if (initializer && isFunctionLike(initializer)) localBindings.set(declarator.id.name, initializer);
|
|
18992
18992
|
}
|
|
18993
18993
|
};
|
|
18994
18994
|
for (const statement of programRoot.body ?? []) {
|
|
@@ -19032,7 +19032,7 @@ const findExportedFunctionBody = (programRoot, exportedName) => {
|
|
|
19032
19032
|
defaultExport = declaration;
|
|
19033
19033
|
continue;
|
|
19034
19034
|
}
|
|
19035
|
-
if (isFunctionLike
|
|
19035
|
+
if (isFunctionLike(declaration)) {
|
|
19036
19036
|
defaultExport = declaration;
|
|
19037
19037
|
continue;
|
|
19038
19038
|
}
|
|
@@ -19193,7 +19193,7 @@ const resolveCrossFileFunctionExport = (fromFilename, source, exportedName) => {
|
|
|
19193
19193
|
const resolveReducerFunction = (node, currentFilename) => {
|
|
19194
19194
|
if (!node) return null;
|
|
19195
19195
|
const unwrappedNode = stripParenExpression(node);
|
|
19196
|
-
if (isFunctionLike$
|
|
19196
|
+
if (isFunctionLike$2(unwrappedNode)) return {
|
|
19197
19197
|
functionNode: unwrappedNode,
|
|
19198
19198
|
crossFileSourceDisplay: null
|
|
19199
19199
|
};
|
|
@@ -19201,7 +19201,7 @@ const resolveReducerFunction = (node, currentFilename) => {
|
|
|
19201
19201
|
const initializer = findVariableInitializer(unwrappedNode, unwrappedNode.name)?.initializer;
|
|
19202
19202
|
if (!initializer) return null;
|
|
19203
19203
|
const unwrappedInitializer = stripParenExpression(initializer);
|
|
19204
|
-
if (isFunctionLike$
|
|
19204
|
+
if (isFunctionLike$2(unwrappedInitializer)) return {
|
|
19205
19205
|
functionNode: unwrappedInitializer,
|
|
19206
19206
|
crossFileSourceDisplay: null
|
|
19207
19207
|
};
|
|
@@ -19315,11 +19315,11 @@ const canExpressionReturnOriginalReducerStateReference = (node, state) => {
|
|
|
19315
19315
|
return false;
|
|
19316
19316
|
};
|
|
19317
19317
|
const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
|
|
19318
|
-
if (isFunctionLike$
|
|
19318
|
+
if (isFunctionLike$2(node)) return [];
|
|
19319
19319
|
const mutations = [];
|
|
19320
19320
|
walkAst(node, (child) => {
|
|
19321
19321
|
const unwrappedChild = stripParenExpression(child);
|
|
19322
|
-
if (child !== node && isFunctionLike$
|
|
19322
|
+
if (child !== node && isFunctionLike$2(unwrappedChild)) return false;
|
|
19323
19323
|
if (isNodeOfType(unwrappedChild, "AssignmentExpression")) {
|
|
19324
19324
|
if (isNodeOfType(stripParenExpression(unwrappedChild.left), "MemberExpression") && isExpressionRootedInMutableReducerStateSource(unwrappedChild.left, state)) mutations.push({ node: unwrappedChild });
|
|
19325
19325
|
return;
|
|
@@ -19424,7 +19424,7 @@ const updateReducerStateIdentityForIdentifierAssignment = (assignment, state) =>
|
|
|
19424
19424
|
if (isExpressionReachableFromOriginalReducerState(assignment.right, state)) state.mutableStateSourceNames.add(name);
|
|
19425
19425
|
};
|
|
19426
19426
|
const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, reportedNodes, options) => {
|
|
19427
|
-
if (!isFunctionLike$
|
|
19427
|
+
if (!isFunctionLike$2(functionNode) || !isNodeOfType(functionNode.body, "BlockStatement")) return;
|
|
19428
19428
|
const firstParam = functionNode.params?.[0];
|
|
19429
19429
|
const stateName = isNodeOfType(firstParam, "Identifier") ? firstParam.name : isNodeOfType(firstParam, "AssignmentPattern") && isNodeOfType(firstParam.left, "Identifier") ? firstParam.left.name : null;
|
|
19430
19430
|
if (!stateName) return;
|
|
@@ -21273,7 +21273,7 @@ const isTanStackServerFnHandler = (node) => {
|
|
|
21273
21273
|
const isInsideServerOnlyScope = (node) => {
|
|
21274
21274
|
let currentNode = node.parent ?? null;
|
|
21275
21275
|
while (currentNode) {
|
|
21276
|
-
if (isFunctionLike$
|
|
21276
|
+
if (isFunctionLike$2(currentNode)) {
|
|
21277
21277
|
if (hasUseServerDirective(currentNode) || isTanStackServerFnHandler(currentNode)) return true;
|
|
21278
21278
|
}
|
|
21279
21279
|
currentNode = currentNode.parent ?? null;
|
|
@@ -23428,7 +23428,7 @@ const isReactClassComponent = (classNode) => {
|
|
|
23428
23428
|
const findEnclosingComponent = (node) => {
|
|
23429
23429
|
let walker = node.parent;
|
|
23430
23430
|
while (walker) {
|
|
23431
|
-
if (isFunctionLike$
|
|
23431
|
+
if (isFunctionLike$2(walker)) {
|
|
23432
23432
|
const componentName = inferFunctionLikeName(walker);
|
|
23433
23433
|
if (componentName && isReactComponentName(componentName) && expressionContainsJsxOrCreateElement(walker)) return {
|
|
23434
23434
|
component: walker,
|
|
@@ -25694,7 +25694,7 @@ const reduxUseselectorInlineDerivation = defineRule({
|
|
|
25694
25694
|
if (!body) return;
|
|
25695
25695
|
const returnedExpressions = [];
|
|
25696
25696
|
if (isNodeOfType(body, "BlockStatement")) walkAst(body, (node) => {
|
|
25697
|
-
if (node !== body && isFunctionLike$
|
|
25697
|
+
if (node !== body && isFunctionLike$2(node)) return false;
|
|
25698
25698
|
if (isNodeOfType(node, "ReturnStatement")) {
|
|
25699
25699
|
if (node.argument) returnedExpressions.push(node.argument);
|
|
25700
25700
|
return false;
|
|
@@ -26037,7 +26037,7 @@ const hasOwnAwait = (functionBody) => {
|
|
|
26037
26037
|
let found = false;
|
|
26038
26038
|
walkAst(functionBody, (child) => {
|
|
26039
26039
|
if (found) return;
|
|
26040
|
-
if (child !== functionBody && isFunctionLike$
|
|
26040
|
+
if (child !== functionBody && isFunctionLike$2(child)) return false;
|
|
26041
26041
|
if (isNodeOfType(child, "AwaitExpression")) found = true;
|
|
26042
26042
|
});
|
|
26043
26043
|
return found;
|
|
@@ -26056,7 +26056,7 @@ const setterIsCalledInAsyncContext = (componentBody, setterName) => {
|
|
|
26056
26056
|
let found = false;
|
|
26057
26057
|
walkAst(componentBody, (child) => {
|
|
26058
26058
|
if (found) return;
|
|
26059
|
-
if (!isFunctionLike$
|
|
26059
|
+
if (!isFunctionLike$2(child)) return;
|
|
26060
26060
|
const functionBody = child.body;
|
|
26061
26061
|
if (!(Boolean(child.async) || hasOwnAwait(functionBody))) return;
|
|
26062
26062
|
if (callsIdentifier(functionBody, setterName)) found = true;
|
|
@@ -26839,8 +26839,7 @@ const rerenderTransitionsScroll = defineRule({
|
|
|
26839
26839
|
});
|
|
26840
26840
|
//#endregion
|
|
26841
26841
|
//#region src/plugin/rules/react-native/rn-animate-layout-property.ts
|
|
26842
|
-
const
|
|
26843
|
-
const REANIMATED_LAYOUT_STYLE_PROPERTIES = new Set([
|
|
26842
|
+
const REANIMATED_LAYOUT_KEYS = new Set([
|
|
26844
26843
|
"width",
|
|
26845
26844
|
"height",
|
|
26846
26845
|
"top",
|
|
@@ -26878,91 +26877,12 @@ const REANIMATED_LAYOUT_STYLE_PROPERTIES = new Set([
|
|
|
26878
26877
|
"lineHeight",
|
|
26879
26878
|
"letterSpacing"
|
|
26880
26879
|
]);
|
|
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
|
-
};
|
|
26950
26880
|
const findReturnedObject = (callback) => {
|
|
26951
26881
|
if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return null;
|
|
26952
|
-
const body =
|
|
26882
|
+
const body = callback.body;
|
|
26953
26883
|
if (isNodeOfType(body, "ObjectExpression")) return body;
|
|
26954
26884
|
if (!isNodeOfType(body, "BlockStatement")) return null;
|
|
26955
|
-
for (const
|
|
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;
|
|
26885
|
+
for (const stmt of body.body ?? []) if (isNodeOfType(stmt, "ReturnStatement") && isNodeOfType(stmt.argument, "ObjectExpression")) return stmt.argument;
|
|
26966
26886
|
return null;
|
|
26967
26887
|
};
|
|
26968
26888
|
const rnAnimateLayoutProperty = defineRule({
|
|
@@ -26970,25 +26890,21 @@ const rnAnimateLayoutProperty = defineRule({
|
|
|
26970
26890
|
title: "Animating a layout property",
|
|
26971
26891
|
tags: ["test-noise"],
|
|
26972
26892
|
requires: ["react-native"],
|
|
26973
|
-
severity: "
|
|
26974
|
-
|
|
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.",
|
|
26893
|
+
severity: "error",
|
|
26894
|
+
recommendation: "Animating size or position props runs on the JS thread and can stutter. Animate `transform: [{ translateX/Y }, { scale }]` or `opacity` instead, which run on the GPU.",
|
|
26976
26895
|
create: (context) => ({ CallExpression(node) {
|
|
26977
|
-
if (!
|
|
26896
|
+
if (!isNodeOfType(node.callee, "Identifier") || node.callee.name !== "useAnimatedStyle") return;
|
|
26978
26897
|
const callback = node.arguments?.[0];
|
|
26979
26898
|
if (!callback) return;
|
|
26980
26899
|
const returnedObject = findReturnedObject(callback);
|
|
26981
26900
|
if (!returnedObject) return;
|
|
26982
26901
|
for (const property of returnedObject.properties ?? []) {
|
|
26983
26902
|
if (!isNodeOfType(property, "Property")) continue;
|
|
26984
|
-
|
|
26985
|
-
if (
|
|
26986
|
-
if (!REANIMATED_LAYOUT_STYLE_PROPERTIES.has(propertyName)) continue;
|
|
26987
|
-
const propertyValue = property.value;
|
|
26988
|
-
if (!containsReanimatedAnimationHelperCall(propertyValue, context)) continue;
|
|
26903
|
+
if (!isNodeOfType(property.key, "Identifier")) continue;
|
|
26904
|
+
if (!REANIMATED_LAYOUT_KEYS.has(property.key.name)) continue;
|
|
26989
26905
|
context.report({
|
|
26990
26906
|
node: property,
|
|
26991
|
-
message: `
|
|
26907
|
+
message: `Your users see stutter when useAnimatedStyle animates "${property.key.name}" on the layout thread.`
|
|
26992
26908
|
});
|
|
26993
26909
|
}
|
|
26994
26910
|
} })
|
|
@@ -32423,7 +32339,7 @@ const isUseEffectEventSymbol = (symbol) => {
|
|
|
32423
32339
|
const findEnclosingComponentOrHookFunction = (node) => {
|
|
32424
32340
|
let current = node.parent;
|
|
32425
32341
|
while (current) {
|
|
32426
|
-
if (isFunctionLike$
|
|
32342
|
+
if (isFunctionLike$2(current)) {
|
|
32427
32343
|
const resolvedName = inferFunctionName(current);
|
|
32428
32344
|
if (resolvedName !== null && isReactComponentOrHookName(resolvedName)) return current;
|
|
32429
32345
|
}
|
|
@@ -32444,7 +32360,7 @@ const isCallbackArgumentForAllowedEffectEventHook = (functionNode, additionalEff
|
|
|
32444
32360
|
const isInsideAllowedEffectEventCallback = (node, additionalEffectHooksRegex) => {
|
|
32445
32361
|
let current = node.parent;
|
|
32446
32362
|
while (current) {
|
|
32447
|
-
if (isFunctionLike$
|
|
32363
|
+
if (isFunctionLike$2(current) && isCallbackArgumentForAllowedEffectEventHook(current, additionalEffectHooksRegex)) return true;
|
|
32448
32364
|
current = current.parent ?? null;
|
|
32449
32365
|
}
|
|
32450
32366
|
return false;
|
|
@@ -32782,7 +32698,7 @@ const containsAuthCheck = (rootNodes, allowedFunctionNames, genericMethodNames)
|
|
|
32782
32698
|
let foundAuthCall = false;
|
|
32783
32699
|
for (const rootNode of rootNodes) walkAst(rootNode, (child) => {
|
|
32784
32700
|
if (foundAuthCall) return;
|
|
32785
|
-
if (isFunctionLike$
|
|
32701
|
+
if (isFunctionLike$2(child)) return false;
|
|
32786
32702
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
32787
32703
|
if (getAuthCallName(child, allowedFunctionNames, genericMethodNames)) foundAuthCall = true;
|
|
32788
32704
|
});
|
|
@@ -37396,7 +37312,7 @@ const reactDoctorRules = [
|
|
|
37396
37312
|
rule: {
|
|
37397
37313
|
...rnAnimateLayoutProperty,
|
|
37398
37314
|
framework: "react-native",
|
|
37399
|
-
category: "
|
|
37315
|
+
category: "Bugs",
|
|
37400
37316
|
tags: [...new Set(["react-native", ...rnAnimateLayoutProperty.tags ?? []])]
|
|
37401
37317
|
}
|
|
37402
37318
|
},
|
|
@@ -38261,7 +38177,7 @@ const appendNode = (builder, block, node) => {
|
|
|
38261
38177
|
};
|
|
38262
38178
|
const mapDescendantsToBlock = (builder, node, block) => {
|
|
38263
38179
|
builder.nodeBlock.set(node, block);
|
|
38264
|
-
if (isFunctionLike$
|
|
38180
|
+
if (isFunctionLike$2(node)) return;
|
|
38265
38181
|
const record = node;
|
|
38266
38182
|
for (const key of Object.keys(record)) {
|
|
38267
38183
|
if (key === "parent") continue;
|
|
@@ -38599,7 +38515,7 @@ const analyzeControlFlow = (program) => {
|
|
|
38599
38515
|
body: program.body
|
|
38600
38516
|
});
|
|
38601
38517
|
const visit = (node) => {
|
|
38602
|
-
if (isFunctionLike$
|
|
38518
|
+
if (isFunctionLike$2(node)) {
|
|
38603
38519
|
const body = node.body;
|
|
38604
38520
|
if (body) buildFor(node, body);
|
|
38605
38521
|
}
|
|
@@ -38616,7 +38532,7 @@ const analyzeControlFlow = (program) => {
|
|
|
38616
38532
|
const enclosingFunction = (node) => {
|
|
38617
38533
|
let current = node;
|
|
38618
38534
|
while (current) {
|
|
38619
|
-
if (isFunctionLike$
|
|
38535
|
+
if (isFunctionLike$2(current)) return current;
|
|
38620
38536
|
if (isNodeOfType(current, "Program")) return current;
|
|
38621
38537
|
current = current.parent ?? null;
|
|
38622
38538
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.2.18-dev.
|
|
3
|
+
"version": "0.2.18-dev.68a0bef",
|
|
4
4
|
"description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|