Skip to content

Commit 3b2a929

Browse files
authored
Add debug/is-from-ref rule (#1433)
1 parent 9f660ee commit 3b2a929

14 files changed

Lines changed: 280 additions & 20 deletions

File tree

apps/website/content/docs/rules/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"debug-function-component",
104104
"debug-hook",
105105
"debug-is-from-react",
106+
"debug-is-from-ref",
106107
"debug-jsx"
107108
]
108109
}

apps/website/content/docs/rules/overview.mdx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ full: true
177177
- [`function-component`](./debug-function-component) - Reports all function components in JSON format
178178
- [`hook`](./debug-hook) - Reports all React Hooks in JSON format
179179
- [`is-from-react`](./debug-is-from-react) - Reports all identifiers initialized from React in JSON format
180+
- [`is-from-ref`](./debug-is-from-ref) - Reports all identifiers initialized or derived from refs in JSON format
180181
- [`jsx`](./debug-jsx) - Reports all JSX elements and fragments in JSON format
181182
</Tab>
182183

@@ -328,13 +329,14 @@ full: true
328329
These rules are useful for code metrics, code transformation, issue reporting, or when building custom tooling that needs to identify specific patterns.
329330
</Callout>
330331

331-
| Rule | 🌟 | Description |
332-
| :----------------------------------------------- | :--: | :------------------------------------------------------------ |
333-
| [`class-component`](debug-class-component) | `🐞` | Reports all class components in JSON format |
334-
| [`function-component`](debug-function-component) | `🐞` | Reports all function components in JSON format |
335-
| [`hook`](debug-hook) | `🐞` | Reports all React Hooks in JSON format |
336-
| [`is-from-react`](debug-is-from-react) | `🐞` | Reports all identifiers initialized from React in JSON format |
337-
| [`jsx`](debug-jsx) | `🐞` | Reports all JSX elements and fragments in JSON format |
332+
| Rule | 🌟 | Description |
333+
| :----------------------------------------------- | :--: | :---------------------------------------------------------------------- |
334+
| [`class-component`](debug-class-component) | `🐞` | Reports all class components in JSON format |
335+
| [`function-component`](debug-function-component) | `🐞` | Reports all function components in JSON format |
336+
| [`hook`](debug-hook) | `🐞` | Reports all React Hooks in JSON format |
337+
| [`is-from-react`](debug-is-from-react) | `🐞` | Reports all identifiers initialized from React in JSON format |
338+
| [`is-from-ref`](debug-is-from-ref) | `🐞` | Reports all identifiers initialized or derived from refs in JSON format |
339+
| [`jsx`](debug-jsx) | `🐞` | Reports all JSX elements and fragments in JSON format |
338340

339341
{/* VERIFY_RULES_METAS_END */}
340342
</Tab>

packages/core/docs/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,9 @@
139139
| [isComponentWrapperCallbackLoose](functions/isComponentWrapperCallbackLoose.md) | Check if the node is a callback function passed to a component wrapper loosely |
140140
| [isComponentWrapperCallLoose](functions/isComponentWrapperCallLoose.md) | Check if the node is a call expression for a component wrapper loosely |
141141
| [isDeclaredInRenderPropLoose](functions/isDeclaredInRenderPropLoose.md) | Unsafe check whether given node is declared inside a render prop `_ = <Component renderRow={"node"} /> ` ^^^^^^ ` _ = <Component rows={ [{ render: "node" }] } /> ` ^^^^^^ `` |
142-
| [isInitializedFromReact](functions/isInitializedFromReact.md) | Checks if a variable is initialized from React import |
142+
| [isInitializedFromReact](functions/isInitializedFromReact.md) | Checks if a variable is initialized or derived from React import |
143143
| [isInitializedFromReactNative](functions/isInitializedFromReactNative.md) | Checks if a variable is initialized from React Native import |
144+
| [isInitializedFromRef](functions/isInitializedFromRef.md) | Checks if the variable with the given name is initialized or derived from a ref |
144145
| [isInsideComponentOrHook](functions/isInsideComponentOrHook.md) | Checks if a given AST node is inside a React component or hook |
145146
| [isJsxFragmentElement](functions/isJsxFragmentElement.md) | Determines if a JSX element is a React Fragment Fragments can be imported from React and used like <Fragment> or <React.Fragment> |
146147
| [isJsxHostElement](functions/isJsxHostElement.md) | Determines if a JSX element is a host element Host elements in React start with lowercase letters (e.g., div, span) |
@@ -154,6 +155,7 @@
154155
| [isReactHookCallWithName](functions/isReactHookCallWithName.md) | Checks if a node is a call to a specific React hook. Returns a function that accepts a hook name to check against. |
155156
| [isReactHookId](functions/isReactHookId.md) | - |
156157
| [isReactHookName](functions/isReactHookName.md) | Catch all identifiers that begin with "use" followed by an uppercase Latin character to exclude identifiers like "user". |
158+
| [isRefName](functions/isRefName.md) | Checks if a given name corresponds to a ref name |
157159
| [isRenderFunctionLoose](functions/isRenderFunctionLoose.md) | Unsafe check whether given node is a render function `const renderRow = () => <div /> ` ^^^^^^^^^^^^` _ = <Component renderRow={() => <div />} /> ` ^^^^^^^^^^^^^ `` |
158160
| [isRenderMethodLike](functions/isRenderMethodLike.md) | Check whether given node is a render method of a class component |
159161
| [isRenderPropLoose](functions/isRenderPropLoose.md) | Unsafe check whether given JSXAttribute is a render prop `_ = <Component renderRow={() => <div />} /> ` ^^^^^^^^^^^^^^^^^^^^^^^^^ `` |

packages/core/docs/functions/isInitializedFromReact.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function isInitializedFromReact(
99
importSource: string): boolean;
1010
```
1111

12-
Checks if a variable is initialized from React import
12+
Checks if a variable is initialized or derived from React import
1313

1414
## Parameters
1515

@@ -23,4 +23,4 @@ Checks if a variable is initialized from React import
2323

2424
`boolean`
2525

26-
True if the variable is initialized from React import
26+
True if the variable is initialized or derived from React import
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[@eslint-react/core](../README.md) / isInitializedFromRef
2+
3+
# Function: isInitializedFromRef()
4+
5+
```ts
6+
function isInitializedFromRef(name: string, initialScope: Scope): boolean;
7+
```
8+
9+
Checks if the variable with the given name is initialized or derived from a ref
10+
11+
## Parameters
12+
13+
| Parameter | Type | Description |
14+
| ------ | ------ | ------ |
15+
| `name` | `string` | The variable name |
16+
| `initialScope` | `Scope` | The initial scope |
17+
18+
## Returns
19+
20+
`boolean`
21+
22+
True if the variable is derived from a ref, false otherwise
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
[@eslint-react/core](../README.md) / isRefName
2+
3+
# Function: isRefName()
4+
5+
```ts
6+
function isRefName(name: string): boolean;
7+
```
8+
9+
Checks if a given name corresponds to a ref name
10+
11+
## Parameters
12+
13+
| Parameter | Type | Description |
14+
| ------ | ------ | ------ |
15+
| `name` | `string` | The name to check |
16+
17+
## Returns
18+
19+
`boolean`
20+
21+
True if the name is "ref" or ends with "Ref"

packages/core/src/api/is-from-react.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { findImportSource } from "@eslint-react/var";
22
import type { Scope } from "@typescript-eslint/scope-manager";
33

44
/**
5-
* Checks if a variable is initialized from React import
5+
* Checks if a variable is initialized or derived from React import
66
* @param name The variable name
77
* @param initialScope The initial scope
88
* @param importSource Alternative import source of React (e.g., "preact/compat")
9-
* @returns True if the variable is initialized from React import
9+
* @returns True if the variable is initialized or derived from React import
1010
*/
1111
export function isInitializedFromReact(
1212
name: string,

packages/core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export * from "./component";
33
export * from "./hierarchy";
44
export * from "./hook";
55
export * from "./jsx";
6+
export * from "./ref";
67
export type * from "./semantic";
Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
1-
/* eslint-disable jsdoc/require-returns-check */
1+
import { findVariable } from "@eslint-react/var";
22
import type { Scope } from "@typescript-eslint/scope-manager";
3-
import type { TSESTree } from "@typescript-eslint/types";
3+
import { AST_NODE_TYPES as T } from "@typescript-eslint/utils";
4+
5+
import { isUseRefCall } from "../hook";
6+
import { isRefName } from "./ref-name";
47

58
/**
6-
* Checks whether a given node is derived from a ref.
7-
* @todo Implement the function logic
8-
* @param node The AST node to check
9-
* @param initialScope The initial scope to start the search from
10-
* @returns True if the node is derived from a ref, false otherwise
9+
* Checks if the variable with the given name is initialized or derived from a ref
10+
* @param name The variable name
11+
* @param initialScope The initial scope
12+
* @returns True if the variable is derived from a ref, false otherwise
1113
*/
12-
export function isFromRef(node: TSESTree.Node, initialScope: Scope) {}
14+
export function isInitializedFromRef(name: string, initialScope: Scope) {
15+
for (const { node } of findVariable(initialScope)(name)?.defs ?? []) {
16+
if (node.type !== T.VariableDeclarator) continue;
17+
const init = node.init;
18+
if (init == null) continue;
19+
switch (true) {
20+
// const identifier = anotherRef.current;
21+
case init.type === T.MemberExpression
22+
&& init.object.type === T.Identifier
23+
&& isRefName(init.object.name):
24+
return true;
25+
// const identifier = useRef();
26+
case init.type === T.CallExpression
27+
&& isUseRefCall(init):
28+
return true;
29+
}
30+
}
31+
return false;
32+
}

packages/plugins/eslint-plugin-react-debug/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export default defineConfig(
4545
- [`function-component`](./debug-function-component) - Reports all function components
4646
- [`hook`](./debug-hook) - Reports all `React` hooks
4747
- [`is-from-react`](./debug-is-from-react) - Reports identifiers initialized from `React`
48+
- [`is-from-ref`](./debug-is-from-ref) - Reports identifiers initialized or derived from refs
4849
- [`jsx`](./debug-jsx) - Reports all `JSX` elements and fragments
4950

5051
## Rules

0 commit comments

Comments
 (0)