Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
246155b
feat(plugins/hooks-extra): add 'no-use-in-try-catch' rule
Rel1cx Feb 23, 2025
c2dbfd1
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 23, 2025
0b2082a
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 23, 2025
ae70202
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 23, 2025
6fa8517
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 24, 2025
90fff97
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 24, 2025
46e426e
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 24, 2025
5f5523d
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
b9a8752
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
c57d2a2
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
58e2e32
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
271a150
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
c67097d
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
6df0d47
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
560cc2d
fix: correct argument order in isUseCall function
Rel1cx Feb 25, 2025
0e2d851
docs: add isUseCall function documentation and update related references
Rel1cx Feb 25, 2025
4f0be28
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
3ce6e36
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 25, 2025
19b6bde
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 26, 2025
da59b14
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 26, 2025
3c5339e
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 26, 2025
f86defe
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 26, 2025
2f9779f
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 27, 2025
7d96ec8
Merge branch 'main' into no-use-in-try-catch
Rel1cx Feb 27, 2025
dea47eb
Merge branch 'main' into no-use-in-try-catch
Rel1cx Mar 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/website/content/docs/rules/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
"hooks-extra-no-direct-set-state-in-use-layout-effect",
"hooks-extra-no-unnecessary-use-callback",
"hooks-extra-no-unnecessary-use-memo",
"hooks-extra-no-use-in-try-catch",
"hooks-extra-no-useless-custom-hooks",
"hooks-extra-prefer-use-state-lazy-initialization",
"---Naming Convention Rules---",
Expand Down
1 change: 1 addition & 0 deletions apps/website/content/docs/rules/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ full: true
| [`no-direct-set-state-in-use-layout-effect`](./hooks-extra-no-direct-set-state-in-use-layout-effect) | 0️⃣ | `🔍` | Disallow direct calls to the `set` function of `useState` in `useLayoutEffect`. |
| [`no-unnecessary-use-callback`](./hooks-extra-no-unnecessary-use-callback) | 0️⃣ | `🔍` | Disallow unnecessary usage of `useCallback`. |
| [`no-unnecessary-use-memo`](./hooks-extra-no-unnecessary-use-memo) | 0️⃣ | `🔍` | Disallow unnecessary usage of `useMemo`. |
| [`no-use-in-try-catch`](./hooks-extra-no-use-in-try-catch) | 1️⃣ | `🔍` | Disallow `use` in try-catch block. |
| [`no-useless-custom-hooks`](./hooks-extra-no-useless-custom-hooks) | 1️⃣ | `🔍` | Enforces custom Hooks to use at least one other Hook inside. |
| [`prefer-use-state-lazy-initialization`](./hooks-extra-prefer-use-state-lazy-initialization) | 1️⃣ | `🔍` | Enforces function calls made inside `useState` to be wrapped in an `initializer function`. |

Expand Down
1 change: 1 addition & 0 deletions packages/core/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
- [isRenderMethodLike](functions/isRenderMethodLike.md)
- [isRenderPropLoose](functions/isRenderPropLoose.md)
- [isThisSetState](functions/isThisSetState.md)
- [isUseCall](functions/isUseCall.md)
- [isUseCallbackCall](functions/isUseCallbackCall.md)
- [isUseContextCall](functions/isUseContextCall.md)
- [isUseDebugValueCall](functions/isUseDebugValueCall.md)
Expand Down
19 changes: 19 additions & 0 deletions packages/core/docs/functions/isUseCall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[**@eslint-react/core**](../README.md)

***

[@eslint-react/core](../README.md) / isUseCall

# Function: isUseCall()

> **isUseCall**(...`a`): `boolean`

## Parameters

### a

...\[[`Readonly`](../-internal-/type-aliases/Readonly.md)\<[`RuleContext`](../-internal-/interfaces/RuleContext.md)\<`string`, readonly `unknown`[]\>\>, [`CallExpression`](../-internal-/interfaces/CallExpression.md)\]

## Returns

`boolean`
1 change: 1 addition & 0 deletions packages/core/src/hook/is.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export function isUseEffectCallLoose(node: TSESTree.Node | _) {
}
}

export const isUseCall = flip(isReactHookCallWithName)("use");
export const isUseCallbackCall = flip(isReactHookCallWithName)("useCallback");
export const isUseContextCall = flip(isReactHookCallWithName)("useContext");
export const isUseDebugValueCall = flip(isReactHookCallWithName)("useDebugValue");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export const name = "react-hooks-extra/recommended";

export const rules = {
"react-hooks-extra/no-direct-set-state-in-use-effect": "warn",
"react-hooks-extra/no-use-in-try-catch": "error",
"react-hooks-extra/no-useless-custom-hooks": "warn",
"react-hooks-extra/prefer-use-state-lazy-initialization": "warn",
} as const satisfies RulePreset;
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import noDirectSetStateInUseEffect from "./rules/no-direct-set-state-in-use-effe
import noDirectSetStateInUseLayoutEffect from "./rules/no-direct-set-state-in-use-layout-effect";
import noUnnecessaryUseCallback from "./rules/no-unnecessary-use-callback";
import noUnnecessaryUseMemo from "./rules/no-unnecessary-use-memo";
import noUseInTryCatch from "./rules/no-use-in-try-catch";
import noUselessCustomHooks from "./rules/no-useless-custom-hooks";
import preferUseStateLazyInitialization from "./rules/prefer-use-state-lazy-initialization";

Expand All @@ -16,6 +17,7 @@ export const plugin = {
"no-direct-set-state-in-use-layout-effect": noDirectSetStateInUseLayoutEffect,
"no-unnecessary-use-callback": noUnnecessaryUseCallback,
"no-unnecessary-use-memo": noUnnecessaryUseMemo,
"no-use-in-try-catch": noUseInTryCatch,
"no-useless-custom-hooks": noUselessCustomHooks,
"prefer-use-state-lazy-initialization": preferUseStateLazyInitialization,

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
title: no-use-in-try-catch
---

**Full Name in `eslint-plugin-react-hooks-extra`**

```plain copy
react-hooks-extra/no-use-in-try-catch
```

**Full Name in `@eslint-react/eslint-plugin`**

```plain copy
@eslint-react/hooks-extra/no-use-in-try-catch
```

**Features**

`🔍`

**Presets**

- `recommended`
- `recommended-typescript`
- `recommended-type-checked`

## What it does

This rule disallows the use of `use` in a try-catch block.

`use` cannot be called in a try-catch block. Instead of a try-catch block [wrap your component in an Error Boundary](https://react.dev/reference/react/use#displaying-an-error-to-users-with-error-boundary), or [provide an alternative value to use with the Promise’s `.catch` method](https://react.dev/reference/react/use#providing-an-alternative-value-with-promise-catch).

## Examples

### Failing

```tsx
function Message({ messagePromise }) {
try {
const content = use(messagePromise);
// ^^^
// - 'use' cannot be called in a try-catch block. Instead of a try-catch block wrap your component in an Error Boundary, or provide an alternative value to use with the Promise’s .catch method.
return <p>Here is the message: {content}</p>;
} catch (error) {
return <p>⚠️Something went wrong</p>;
}
}
```

## Implementation

- [Rule source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-use-in-try-catch.ts)
- [Test source](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-hooks-extra/src/rules/no-use-in-try-catch.spec.ts)

## Further Reading

- [React: use](https://react.dev/reference/react/use)
- [React: use#dealing-with-rejected-promises](https://react.dev/reference/react/use#dealing-with-rejected-promises)
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { allValid, ruleTester } from "../../../../../test";
import rule, { RULE_NAME } from "./no-use-in-try-catch";

ruleTester.run(RULE_NAME, rule, {
invalid: [
{
code: /* tsx */ `
function Message({ messagePromise }) {
try {
const content = use(messagePromise);
// ^^^
// - 'use' cannot be called in a try-catch block. Instead of a try-catch block wrap your component in an Error Boundary, or provide an alternative value to use with the Promise’s .catch method.
return <p>Here is the message: {content}</p>;
} catch (error) {
return <p>⚠️Something went wrong</p>;
}
}
`,
errors: [{ messageId: "noUseInTryCatch" }],
},
{
code: /* tsx */ `
function Message({ messagePromise }) {
try {
const content = React.use(messagePromise);
// ^^^
// - 'use' cannot be called in a try-catch block. Instead of a try-catch block wrap your component in an Error Boundary, or provide an alternative value to use with the Promise’s .catch method.
return <p>Here is the message: {content}</p>;
} catch (error) {
return <p>⚠️Something went wrong</p>;
}
}
`,
errors: [{ messageId: "noUseInTryCatch" }],
},
{
code: /* tsx */ `
import { use } from "react";

function Message({ messagePromise }) {
try {
const content = use(messagePromise);
// ^^^
// - 'use' cannot be called in a try-catch block. Instead of a try-catch block wrap your component in an Error Boundary, or provide an alternative value to use with the Promise’s .catch method.
return <p>Here is the message: {content}</p>;
} catch (error) {
return <p>⚠️Something went wrong</p>;
}
}
`,
errors: [{ messageId: "noUseInTryCatch" }],
},
{
code: /* tsx */ `
import * as React from "react";

function Message({ messagePromise }) {
try {
const content = React.use(messagePromise);
// ^^^
// - 'use' cannot be called in a try-catch block. Instead of a try-catch block wrap your component in an Error Boundary, or provide an alternative value to use with the Promise’s .catch method.
return <p>Here is the message: {content}</p>;
} catch (error) {
return <p>⚠️Something went wrong</p>;
}
}
`,
errors: [{ messageId: "noUseInTryCatch" }],
},
],
valid: [
...allValid,
],
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import * as AST from "@eslint-react/ast";
import { isUseCall } from "@eslint-react/core";
import type { RuleFeature } from "@eslint-react/shared";
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
import type { CamelCase } from "string-ts";

import { createRule } from "../utils";

export const RULE_NAME = "no-use-in-try-catch";

export const RULE_FEATURES = [
"CHK",
] as const satisfies RuleFeature[];

export type MessageID = CamelCase<typeof RULE_NAME>;

export default createRule<[], MessageID>({
meta: {
type: "problem",
docs: {
description: "disallow 'use' in try-catch block",
[Symbol.for("rule_features")]: RULE_FEATURES,
},
messages: {
noUseInTryCatch:
"'use' cannot be called in a try-catch block. Instead of a try-catch block wrap your component in an Error Boundary, or provide an alternative value to use with the Promise’s '.catch' method.",
},
schema: [],
},
name: RULE_NAME,
create(context) {
if (!context.sourceCode.text.includes("try")) return {};
return {
CallExpression(node) {
if (!isUseCall(context, node)) return;
const tryCatchOrFunction = AST.findParentNode(node, (n) => {
return n.type === T.TryStatement || AST.isFunction(n);
});
if (tryCatchOrFunction?.type === T.TryStatement) {
context.report({
messageId: "noUseInTryCatch",
node,
});
}
},
};
},
defaultOptions: [],
});
1 change: 1 addition & 0 deletions packages/plugins/eslint-plugin/src/configs/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export const rules = {
"@eslint-react/hooks-extra/no-direct-set-state-in-use-layout-effect": "warn",
"@eslint-react/hooks-extra/no-unnecessary-use-callback": "warn",
"@eslint-react/hooks-extra/no-unnecessary-use-memo": "warn",
"@eslint-react/hooks-extra/no-use-in-try-catch": "error",
"@eslint-react/hooks-extra/no-useless-custom-hooks": "warn",
"@eslint-react/hooks-extra/prefer-use-state-lazy-initialization": "warn",

Expand Down
1 change: 1 addition & 0 deletions packages/plugins/eslint-plugin/src/configs/recommended.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const rules = {
...dom.rules,
...webApi.rules,
"@eslint-react/hooks-extra/no-direct-set-state-in-use-effect": "warn",
"@eslint-react/hooks-extra/no-use-in-try-catch": "error",
"@eslint-react/hooks-extra/no-useless-custom-hooks": "warn",
"@eslint-react/hooks-extra/prefer-use-state-lazy-initialization": "warn",
} as const satisfies RulePreset;
Expand Down