add noAccessStateInSetState rule and tests
MODIFY
packages/@romejs/compiler/lint/rules/index.ts
packages/@romejs/compiler/lint/rules/index.ts
@@ -81,6 +81,7 @@ import jsxFragments from "./react/jsxFragments";
import jsxKey from "./react/jsxKey";
import jsxNoCommentText from "./react/jsxNoCommentText";
import jsxNoDuplicateProps from "./react/jsxNoDuplicateProps";
+import noAccessStateInSetState from "./react/noAccessStateInSetState";
import noChildrenProp from "./react/noChildrenProp";
import noDanger from "./react/noDanger";
import noDangerWithChildren from "./react/noDangerWithChildren";
@@ -173,6 +174,7 @@ export const lintTransforms = [
jsxKey,
jsxNoCommentText,
jsxNoDuplicateProps,
+ noAccessStateInSetState,
noChildrenProp,
noDanger,
noDangerWithChildren,

ADD
packages/@romejs/compiler/lint/rules/react/noAccessStateInSetState.test.ts
+import {test} from "rome";
+import {testLint} from "../testHelpers";
+
+test(
+ "react no access state in set state",
+ async (t) => {
+ await testLint(
+ t,
+ {
+ // INVALID
+ invalid: [
+ `
+ function increment() {
+ this.setState({value: this.state.value + 1});
+ }
+ `,
+ `
+ function increment() {
+ this.setState({value: 1 + this.state.value});
+ }
+ `,
+ `
+ this.setState({
+ value: this.state.value + 1
+ });
+ `,
+ `
+ this.setState({
+ value: 1 + this.state.value
+ });
+ `,
+ `
+ this.setState({
+ value: !this.state.value
+ });
+ `,
+ `
+ this.setState({
+ value: !!this.state.value
+ });
+ `,
+ `
+ this.setState({
+ foo: bar,
+ value: 1 + this.state.value
+ });
+ `,
+ `
+ this.setState({
+ foo: bar,
+ value: this.state.value + 1
+ });
+ `,
+ `
+ this.setState({
+ value: this.state.value
+ });
+ `,
+ `
+ this.setState({
+ foo: bar,
+ value: this.state.value
+ });
+ `,
+ ],
+ valid: [
+ `
+ this.setState({
+ foo: bar
+ });
+ `,
+ `
+ function increment() {
+ this.setState(prevState => ({value: prevState.value + 1}));
+ }
+ `,
+ ],
+ },
+ {category: "lint/react/noAccessStateInSetState"},
+ );
+ },
+);

ADD
packages/@romejs/compiler/lint/rules/react/noAccessStateInSetState.ts
+import {Path, TransformExitResult} from "@romejs/compiler";
+import {descriptions} from "@romejs/diagnostics";
+import {doesNodeMatchPattern} from "@romejs/js-ast-utils";
+
+export default {
+ name: "reactNoAccessStateInSetState",
+ enter(path: Path): TransformExitResult {
+ const {node} = path;
+ if (
+ node.type === "JSCallExpression" &&
+ doesNodeMatchPattern(node.callee, "this.setState") &&
+ node.arguments[0].type === "JSObjectExpression"
+ ) {
+ const hasThisState = node.arguments[0].properties.some((arg) => {
+ if (arg.type === "JSObjectProperty") {
+ if (
+ arg.value.type === "JSMemberExpression" &&
+ doesNodeMatchPattern(arg.value.object, "this.state")
+ ) {
+ return true;
+ }
+ if (
+ (arg.value.type === "JSUnaryExpression" &&
+ arg.value.argument.type === "JSMemberExpression" &&
+ doesNodeMatchPattern(arg.value.argument.object, "this.state")) ||
+ (arg.value.type === "JSUnaryExpression" &&
+ arg.value.argument.type === "JSUnaryExpression" &&
+ arg.value.argument.argument.type === "JSMemberExpression" &&
+ doesNodeMatchPattern(
+ arg.value.argument.argument.object,
+ "this.state",
+ ))
+ ) {
+ return true;
+ }
+ if (arg.value.type === "JSBinaryExpression") {
+ if (
+ arg.value.left.type === "JSMemberExpression" &&
+ doesNodeMatchPattern(arg.value.left.object, "this.state")
+ ) {
+ return true;
+ }
+ if (
+ arg.value.right.type === "JSMemberExpression" &&
+ doesNodeMatchPattern(arg.value.right.object, "this.state")
+ ) {
+ return true;
+ }
+ }
+ }
+ return false;
+ });
+ if (hasThisState) {
+ path.context.addNodeDiagnostic(
+ node,
+ descriptions.LINT.REACT_NO_ACCESS_STATE_IN_SET_STATE,
+ );
+ }
+ }
+
+ return node;
+ },
+};

MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -167,6 +167,7 @@ type LintDiagnosticCategory =
| "lint/react/jsxKey"
| "lint/react/jsxNoCommentText"
| "lint/react/jsxNoDuplicateProps"
+ | "lint/react/noAccessStateInSetState"
| "lint/react/noChildrenProp"
| "lint/react/noDanger"
| "lint/react/noDangerWithChildren"

MODIFY
packages/@romejs/diagnostics/descriptions/lint.ts
packages/@romejs/diagnostics/descriptions/lint.ts
@@ -27,6 +27,17 @@ export const lint = createDiagnosticsCategory({
},
],
},
+ REACT_NO_ACCESS_STATE_IN_SET_STATE: {
+ category: "lint/react/noAccessStateInSetState",
+ message: "Avoid using <emphasis>this.state</emphasis> within a <emphasis>this.setState</emphasis> call.",
+ advice: [
+ {
+ type: "log",
+ category: "info",
+ text: "Batched state calls could result in unexpected errors due to stale state data.",
+ },
+ ],
+ },
JSX_A11Y_ANCHOR_IS_VALID: (message: string) => ({
category: "lint/jsx-a11y/anchorIsValid",
message,

add noAccessStateInSetState rule and tests (#538)