oxlint-plugin-react-doctor 0.2.18-dev.eba20ae → 0.3.0-dev.6130583
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 +45 -7
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import path from "node:path";
|
|
1
|
+
import * as path from "node:path";
|
|
2
2
|
import { analyze } from "eslint-scope";
|
|
3
3
|
import * as eslintVisitorKeys from "eslint-visitor-keys";
|
|
4
|
-
import fs from "node:fs";
|
|
4
|
+
import * as fs from "node:fs";
|
|
5
5
|
import { parseSync } from "oxc-parser";
|
|
6
6
|
//#region src/plugin/utils/is-testlike-filename.ts
|
|
7
7
|
const NON_PRODUCTION_PATH_SEGMENTS = [
|
|
@@ -14786,6 +14786,37 @@ const isSetterIdentifier = (name) => SETTER_PATTERN.test(name);
|
|
|
14786
14786
|
//#region src/plugin/utils/is-setter-call.ts
|
|
14787
14787
|
const isSetterCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && isSetterIdentifier(node.callee.name);
|
|
14788
14788
|
//#endregion
|
|
14789
|
+
//#region src/plugin/utils/is-hook-binding-in-scope.ts
|
|
14790
|
+
const isHookBindingInScope = (node, query) => {
|
|
14791
|
+
const { bindingName, hookName, destructureIndex } = query;
|
|
14792
|
+
let cursor = node;
|
|
14793
|
+
while (cursor) {
|
|
14794
|
+
if (isNodeOfType(cursor, "BlockStatement") || isNodeOfType(cursor, "Program")) for (const statement of cursor.body ?? []) {
|
|
14795
|
+
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
14796
|
+
for (const declarator of statement.declarations ?? []) {
|
|
14797
|
+
if (!isNodeOfType(declarator.init, "CallExpression")) continue;
|
|
14798
|
+
if (!isHookCall$1(declarator.init, hookName)) continue;
|
|
14799
|
+
if (destructureIndex !== void 0) {
|
|
14800
|
+
if (!isNodeOfType(declarator.id, "ArrayPattern")) continue;
|
|
14801
|
+
const elements = declarator.id.elements ?? [];
|
|
14802
|
+
if (elements.length <= destructureIndex) continue;
|
|
14803
|
+
const element = elements[destructureIndex];
|
|
14804
|
+
if (isNodeOfType(element, "Identifier") && element.name === bindingName) return true;
|
|
14805
|
+
} else if (isNodeOfType(declarator.id, "Identifier") && declarator.id.name === bindingName) return true;
|
|
14806
|
+
}
|
|
14807
|
+
}
|
|
14808
|
+
cursor = cursor.parent ?? null;
|
|
14809
|
+
}
|
|
14810
|
+
return false;
|
|
14811
|
+
};
|
|
14812
|
+
//#endregion
|
|
14813
|
+
//#region src/plugin/utils/is-use-state-setter-in-scope.ts
|
|
14814
|
+
const isUseStateSetterInScope = (node, setterName) => isHookBindingInScope(node, {
|
|
14815
|
+
bindingName: setterName,
|
|
14816
|
+
hookName: "useState",
|
|
14817
|
+
destructureIndex: 1
|
|
14818
|
+
});
|
|
14819
|
+
//#endregion
|
|
14789
14820
|
//#region src/plugin/rules/state-and-effects/no-cascading-set-state.ts
|
|
14790
14821
|
const isAsyncFunctionLike = (node) => {
|
|
14791
14822
|
if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) return Boolean(node.async);
|
|
@@ -14827,7 +14858,7 @@ const countMaxPathSetStateCalls = (node) => {
|
|
|
14827
14858
|
const finallyCount = node.finalizer ? countMaxPathSetStateCalls(node.finalizer) : 0;
|
|
14828
14859
|
return Math.max(tryCount, catchCount) + finallyCount;
|
|
14829
14860
|
}
|
|
14830
|
-
if (isSetterCall(node)) {
|
|
14861
|
+
if (isNodeOfType(node, "CallExpression") && isSetterCall(node) && isNodeOfType(node.callee, "Identifier") && isUseStateSetterInScope(node, node.callee.name)) {
|
|
14831
14862
|
let nestedSettersInArgs = 0;
|
|
14832
14863
|
for (const argument of node.arguments ?? []) nestedSettersInArgs += countMaxPathSetStateCalls(argument);
|
|
14833
14864
|
return 1 + nestedSettersInArgs;
|
|
@@ -15628,7 +15659,11 @@ const noDerivedStateEffect = defineRule({
|
|
|
15628
15659
|
if (statements.length === 0) return;
|
|
15629
15660
|
if (!statements.every((statement) => {
|
|
15630
15661
|
if (!isNodeOfType(statement, "ExpressionStatement")) return false;
|
|
15631
|
-
|
|
15662
|
+
const expression = statement.expression;
|
|
15663
|
+
if (!isSetterCall(expression)) return false;
|
|
15664
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
15665
|
+
if (!isNodeOfType(expression.callee, "Identifier")) return false;
|
|
15666
|
+
return isUseStateSetterInScope(expression, expression.callee.name);
|
|
15632
15667
|
})) return;
|
|
15633
15668
|
let allArgumentsDeriveFromDeps = true;
|
|
15634
15669
|
let hasAnyDependencyReference = false;
|
|
@@ -25968,7 +26003,9 @@ const renderingHydrationNoFlicker = defineRule({
|
|
|
25968
26003
|
const bodyStatements = isNodeOfType(callback.body, "BlockStatement") ? callback.body.body : [callback.body];
|
|
25969
26004
|
if (!bodyStatements || bodyStatements.length !== 1) return;
|
|
25970
26005
|
const soleStatement = bodyStatements[0];
|
|
25971
|
-
if (isNodeOfType(soleStatement, "ExpressionStatement")
|
|
26006
|
+
if (!isNodeOfType(soleStatement, "ExpressionStatement")) return;
|
|
26007
|
+
const expression = soleStatement.expression;
|
|
26008
|
+
if (isSetterCall(expression) && isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && isUseStateSetterInScope(expression, expression.callee.name)) context.report({
|
|
25972
26009
|
node,
|
|
25973
26010
|
message: "This flashes for your users because useEffect(setState, []) runs after the first paint, so use useSyncExternalStore, or add suppressHydrationWarning"
|
|
25974
26011
|
});
|
|
@@ -26491,6 +26528,7 @@ const rerenderFunctionalSetstate = defineRule({
|
|
|
26491
26528
|
if (!isSetterCall(node)) return;
|
|
26492
26529
|
if (!node.arguments?.length) return;
|
|
26493
26530
|
if (!isNodeOfType(node.callee, "Identifier")) return;
|
|
26531
|
+
if (!isUseStateSetterInScope(node, node.callee.name)) return;
|
|
26494
26532
|
const calleeName = node.callee.name;
|
|
26495
26533
|
const argument = node.arguments[0];
|
|
26496
26534
|
const expectedStateName = deriveStateVariableName(calleeName);
|
|
@@ -26825,7 +26863,7 @@ const handlerCallsSetState = (handler) => {
|
|
|
26825
26863
|
let setStateCall = null;
|
|
26826
26864
|
walkAst(handler.body, (child) => {
|
|
26827
26865
|
if (setStateCall) return;
|
|
26828
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && /^set[A-Z]/.test(child.callee.name)) setStateCall = child;
|
|
26866
|
+
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && /^set[A-Z]/.test(child.callee.name) && isUseStateSetterInScope(child, child.callee.name)) setStateCall = child;
|
|
26829
26867
|
});
|
|
26830
26868
|
return setStateCall;
|
|
26831
26869
|
};
|
|
@@ -28073,7 +28111,7 @@ const findSetStateInBody = (body) => {
|
|
|
28073
28111
|
let setStateCallNode = null;
|
|
28074
28112
|
walkAst(body, (child) => {
|
|
28075
28113
|
if (setStateCallNode) return;
|
|
28076
|
-
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && SET_STATE_PATTERN.test(child.callee.name)) setStateCallNode = child;
|
|
28114
|
+
if (isNodeOfType(child, "CallExpression") && isNodeOfType(child.callee, "Identifier") && SET_STATE_PATTERN.test(child.callee.name) && isUseStateSetterInScope(child, child.callee.name)) setStateCallNode = child;
|
|
28077
28115
|
});
|
|
28078
28116
|
return setStateCallNode;
|
|
28079
28117
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-dev.6130583",
|
|
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",
|