Skip to content

Commit c340d73

Browse files
committed
refactor(naming-convention/use-state): enhance error messaging and docs, closes #980
1 parent 0c9d2a4 commit c340d73

File tree

3 files changed

+48
-15
lines changed

3 files changed

+48
-15
lines changed

packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.md

+6-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,12 @@ react-naming-convention/use-state
2020

2121
## Description
2222

23-
Enforces destructuring and symmetric naming of `useState` hook value and setter
23+
Enforces destructuring and symmetric naming of `useState` hook value and setter.
24+
25+
This rule ensures two things:
26+
27+
1. The `useState` hook is destructured into a value and setter pair.
28+
2. The value and setter are named symmetrically (e.g. `count` and `setCount`).
2429

2530
## Examples
2631

packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.spec.ts

+31-7
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,30 @@ import rule, { RULE_NAME } from "./use-state";
55

66
ruleTester.run(RULE_NAME, rule, {
77
invalid: [
8+
{
9+
code: tsx`
10+
function Component() {
11+
useState(0);
12+
13+
return <div />;
14+
}
15+
`,
16+
errors: [{
17+
messageId: "missingDestructuring",
18+
}],
19+
},
20+
{
21+
code: tsx`
22+
function Component() {
23+
const data = useState(0);
24+
25+
return <div />;
26+
}
27+
`,
28+
errors: [{
29+
messageId: "missingDestructuring",
30+
}],
31+
},
832
{
933
code: tsx`
1034
function Component() {
@@ -14,7 +38,7 @@ ruleTester.run(RULE_NAME, rule, {
1438
}
1539
`,
1640
errors: [{
17-
messageId: "invalid",
41+
messageId: "invalidSetterNaming",
1842
}],
1943
},
2044
{
@@ -26,7 +50,7 @@ ruleTester.run(RULE_NAME, rule, {
2650
}
2751
`,
2852
errors: [{
29-
messageId: "invalid",
53+
messageId: "invalidSetterNaming",
3054
}],
3155
},
3256
{
@@ -40,7 +64,7 @@ ruleTester.run(RULE_NAME, rule, {
4064
}
4165
`,
4266
errors: [{
43-
messageId: "invalid",
67+
messageId: "invalidSetterNaming",
4468
}],
4569
},
4670
{
@@ -54,7 +78,7 @@ ruleTester.run(RULE_NAME, rule, {
5478
}
5579
`,
5680
errors: [{
57-
messageId: "invalid",
81+
messageId: "invalidSetterNaming",
5882
}],
5983
},
6084
{
@@ -68,7 +92,7 @@ ruleTester.run(RULE_NAME, rule, {
6892
}
6993
`,
7094
errors: [{
71-
messageId: "invalid",
95+
messageId: "invalidSetterNaming",
7296
}],
7397
},
7498
{
@@ -82,7 +106,7 @@ ruleTester.run(RULE_NAME, rule, {
82106
}
83107
`,
84108
errors: [{
85-
messageId: "invalid",
109+
messageId: "invalidSetterNaming",
86110
}],
87111
},
88112
{
@@ -95,7 +119,7 @@ ruleTester.run(RULE_NAME, rule, {
95119
}
96120
`,
97121
errors: [{
98-
messageId: "invalid",
122+
messageId: "invalidSetterNaming",
99123
}],
100124
},
101125
],

packages/plugins/eslint-plugin-react-naming-convention/src/rules/use-state.ts

+11-7
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const RULE_FEATURES = [
1515
"CHK",
1616
] as const satisfies RuleFeature[];
1717

18-
export type MessageID = "invalid";
18+
export type MessageID = "missingDestructuring" | "invalidSetterNaming";
1919

2020
export default createRule<[], MessageID>({
2121
meta: {
@@ -25,7 +25,10 @@ export default createRule<[], MessageID>({
2525
[Symbol.for("rule_features")]: RULE_FEATURES,
2626
},
2727
messages: {
28-
invalid: "An useState call is not destructured into value + setter pair.",
28+
invalidSetterNaming:
29+
"The setter should be named 'set' followed by the capitalized state variable name, e.g., 'setState' for 'state'.",
30+
missingDestructuring:
31+
"useState should be destructured into a value and setter pair, e.g., const [state, setState] = useState(...).",
2932
},
3033
schema: [],
3134
},
@@ -38,23 +41,24 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
3841
return {
3942
"CallExpression[callee.name='useState']"(node: TSESTree.CallExpression) {
4043
if (node.parent.type !== T.VariableDeclarator) {
41-
context.report({ messageId: "invalid", node });
44+
context.report({ messageId: "missingDestructuring", node });
45+
return;
4246
}
4347
const id = getInstanceId(node);
4448
if (id?.type !== T.ArrayPattern) {
45-
context.report({ messageId: "invalid", node });
49+
context.report({ messageId: "missingDestructuring", node });
4650
return;
4751
}
4852
const [value, setter] = id.elements;
4953
if (value == null || setter == null) {
50-
context.report({ messageId: "invalid", node });
54+
context.report({ messageId: "missingDestructuring", node });
5155
return;
5256
}
5357
const setterName = match(setter)
5458
.with({ type: T.Identifier }, (id) => id.name)
5559
.otherwise(() => _);
5660
if (setterName == null || !setterName.startsWith("set")) {
57-
context.report({ messageId: "invalid", node });
61+
context.report({ messageId: "invalidSetterNaming", node });
5862
return;
5963
}
6064
const valueName = match(value)
@@ -70,7 +74,7 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
7074
})
7175
.otherwise(() => _);
7276
if (valueName == null || `set_${valueName}` !== snakeCase(setterName)) {
73-
context.report({ messageId: "invalid", node });
77+
context.report({ messageId: "invalidSetterNaming", node });
7478
return;
7579
}
7680
},

0 commit comments

Comments
 (0)