Skip to content

Commit 00d83ad

Browse files
authored
feat: add rule naming-convention/use-state, closes #205 (#206)
1 parent b5c193c commit 00d83ad

File tree

18 files changed

+595
-73
lines changed

18 files changed

+595
-73
lines changed

CHANGELOG.md

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,22 @@
44

55
#### Add rule `react/no-direct-mutation-state`
66

7+
#### Add rule `naming-convention/use-state`
8+
9+
#### Update `recommended` and `recommended-legacy` presets
10+
711
---
812

913
#### 🏠 Internal
1014

1115
- `@eslint-react/eslint-plugin-react`
1216
- Add rule `react/no-direct-mutation-state`.
17+
- Add rule `naming-convention/use-state`.
18+
19+
#### 📝 Documentation
20+
21+
- `@eslint-react/website`
22+
- Improve rules overview page.
1323

1424
#### Authors: 1
1525

@@ -80,14 +90,6 @@
8090
- `@eslint-react/monorepo`
8191
- Update `eslint-config-with-tsconfig` to `2.9.120`.
8292

83-
#### 📝 Documentation
84-
85-
- `@eslint-react/eslint-plugin`
86-
- Improve README.md
87-
88-
- `@eslint-react/monorepo`
89-
- Improve README.md
90-
9193
#### Authors: 1
9294

9395
- Eva1ent ([@Rel1cx](https://github.com/Rel1cx))

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ module.exports = {
4444
### Linting with type information
4545

4646
> [!NOTE]\
47-
> Rules that require type information are not enabled by default. To enable them, you need to set the `project` option in `parserOptions` to the path of your `tsconfig.json` file.
47+
> Rules that require type information are not enabled by default.
48+
>
49+
> To enable them, you need to set the `project` option in `parserOptions` to the path of your `tsconfig.json` file.
4850
>
4951
> Then replace `plugin:@eslint-react/recommended-legacy` with `plugin:@eslint-react/recommended-type-checked-legacy`.
5052

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@
8989
"markdownlint": "0.32.1",
9090
"pathe": "1.1.1",
9191
"publint": "0.2.6",
92-
"rollup": "4.6.1",
92+
"rollup": "4.7.0",
9393
"rollup-plugin-dts": "6.1.0",
9494
"rollup-plugin-swc3": "0.11.0",
9595
"rollup-plugin-visualizer": "5.10.0",

packages/core/docs/README.md

Lines changed: 15 additions & 13 deletions
Large diffs are not rendered by default.

packages/core/src/component/component-collector.ts

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { M, MutList, MutRef, O } from "@eslint-react/tools";
1212
import { type TSESTree } from "@typescript-eslint/types";
1313
import type { ESLintUtils } from "@typescript-eslint/utils";
1414

15+
import { unsafeIsReactHookCall } from "../hook";
1516
import type { ExRFunctionComponent } from "./component";
1617
import { DEFAULT_COMPONENT_COLLECTOR_HINT, ExRComponentCollectorHint } from "./component-collector-hint";
1718
import { ExRFunctionComponentFlag } from "./component-flag";
@@ -60,9 +61,9 @@ export function componentCollector(
6061
pragma = getPragmaFromContext(context),
6162
) {
6263
const components = new Map<string, ExRFunctionComponent>();
63-
const functionStack = MutList.make<[TSESTreeFunction, boolean]>();
64+
const functionStack = MutList.make<[TSESTreeFunction, boolean, TSESTree.CallExpression[]]>();
6465
const getCurrentFunction = () => O.fromNullable(MutList.tail(functionStack));
65-
const onFunctionEnter = (node: TSESTreeFunction) => MutList.append(functionStack, [node, false]);
66+
const onFunctionEnter = (node: TSESTreeFunction) => MutList.append(functionStack, [node, false, []]);
6667
const onFunctionExit = () => MutList.pop(functionStack);
6768

6869
const ctx = {
@@ -88,7 +89,7 @@ export function componentCollector(
8889
return;
8990
}
9091

91-
const [currentFn, isComponent] = maybeCurrentFn.value;
92+
const [currentFn, isComponent, hookCalls] = maybeCurrentFn.value;
9293

9394
if (isComponent) {
9495
return;
@@ -103,7 +104,7 @@ export function componentCollector(
103104
}
104105

105106
MutList.pop(functionStack);
106-
MutList.append(functionStack, [currentFn, true]);
107+
MutList.append(functionStack, [currentFn, true, []]);
107108

108109
const id = getFunctionComponentIdentifier(currentFn, context);
109110
const key = uid.rnd();
@@ -121,29 +122,38 @@ export function componentCollector(
121122
displayName: O.none(),
122123
flag: getComponentFlag(initPath, pragma),
123124
hint,
125+
hookCalls,
124126
initPath,
125127
node: currentFn,
126128
});
127129
},
128130
// eslint-disable-next-line perfectionist/sort-objects
129-
"ArrowFunctionExpression[body.type!='BlockStatement']"(node: TSESTree.ArrowFunctionExpression) {
130-
const { body } = node;
131+
"ArrowFunctionExpression[body.type!='BlockStatement']"() {
132+
const maybeCurrentFn = getCurrentFunction();
133+
134+
if (O.isNone(maybeCurrentFn)) {
135+
return;
136+
}
137+
138+
const [currentFn, _, hookCalls] = maybeCurrentFn.value;
139+
140+
const { body } = currentFn;
131141

132142
if (
133-
!hasNoneOrValidComponentName(node)
143+
!hasNoneOrValidComponentName(currentFn)
134144
|| !isJSXValue(body, context, hint)
135-
|| !hasValidHierarchy(node, context, hint)
145+
|| !hasValidHierarchy(currentFn, context, hint)
136146
) {
137147
return;
138148
}
139149

140-
const id = getFunctionComponentIdentifier(node, context);
150+
const id = getFunctionComponentIdentifier(currentFn, context);
141151
const key = uid.rnd();
142152
const name = O.flatMapNullable(
143153
id,
144154
getComponentNameFromIdentifier,
145155
);
146-
const initPath = getComponentInitPath(node);
156+
const initPath = getComponentInitPath(currentFn);
147157

148158
components.set(key, {
149159
_: key,
@@ -153,10 +163,28 @@ export function componentCollector(
153163
displayName: O.none(),
154164
flag: getComponentFlag(initPath, pragma),
155165
hint,
166+
hookCalls,
156167
initPath,
157-
node,
168+
node: currentFn,
158169
});
159170
},
171+
"CallExpression:exit"(node: TSESTree.CallExpression) {
172+
if (!unsafeIsReactHookCall(node)) {
173+
return;
174+
}
175+
176+
const maybeCurrentFn = getCurrentFunction();
177+
178+
if (O.isNone(maybeCurrentFn)) {
179+
return;
180+
}
181+
182+
const [currentFn, IsComponent, hookCalls] = maybeCurrentFn.value;
183+
184+
MutList.pop(functionStack);
185+
MutList.append(functionStack, [currentFn, IsComponent, [...hookCalls, node]]);
186+
},
187+
// eslint-disable-next-line perfectionist/sort-objects
160188
"AssignmentExpression[operator='='][left.type='MemberExpression'][left.property.name='displayName']"(
161189
node: TSESTree.AssignmentExpression,
162190
) {

packages/core/src/component/component.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type ExRFunctionComponent = {
1515
flag: ExRFunctionComponentFlag;
1616
hint: ExRComponentCollectorHint;
1717
initPath: O.Option<ExRComponentInitPath>;
18+
hookCalls: TSESTree.CallExpression[];
1819
displayName: O.Option<TSESTree.Expression>;
1920
};
2021

0 commit comments

Comments
 (0)