Skip to content

Commit

Permalink
[New] async-server-action: Add rule to require that server actions …
Browse files Browse the repository at this point in the history
…be async
  • Loading branch information
jorgezreik authored and ljharb committed Apr 8, 2024
1 parent a944aa5 commit 4d1e087
Show file tree
Hide file tree
Showing 6 changed files with 677 additions and 0 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange

## Unreleased

### Added

- [`async-server-action`]: add rule ([#3729][] @jorgezreik)

[#3729]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3729

### Fixed
* [`prop-types`]: null-check rootNode before calling getScope ([#3762][] @crnhrv)
* [`boolean-prop-naming`]: avoid a crash with a spread prop ([#3733][] @ljharb)
Expand Down Expand Up @@ -40,6 +46,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
* [`no-unknown-property`]: support `popover`, `popovertarget`, `popovertargetaction` attributes ([#3707][] @ljharb)
* [`no-unknown-property`]: only match `data-*` attributes containing `-` ([#3713][] @silverwind)
* [`checked-requires-onchange-or-readonly`]: correct options that were behaving opposite ([#3715][] @jaesoekjjang)
* [`boolean-prop-naming`]: avoid a crash with a non-TSTypeReference type ([#3718][] @developer-bandi)

### Changed
* [`boolean-prop-naming`]: improve error message (@ljharb)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@ module.exports = [

| Name                                  | Description | 💼 | 🚫 | 🔧 | 💡 ||
| :------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- | :- | :- |
| [async-server-action](docs/rules/async-server-action.md) | Require functions with the `use server` directive to be async | | | | 💡 | |
| [boolean-prop-naming](docs/rules/boolean-prop-naming.md) | Enforces consistent naming for boolean props | | | | | |
| [button-has-type](docs/rules/button-has-type.md) | Disallow usage of `button` elements without an explicit `type` attribute | | | | | |
| [checked-requires-onchange-or-readonly](docs/rules/checked-requires-onchange-or-readonly.md) | Enforce using `onChange` or `readonly` attribute when `checked` is used | | | | | |
Expand Down
55 changes: 55 additions & 0 deletions docs/rules/async-server-action.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Require functions with the `use server` directive to be async (`react/async-server-action`)

💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).

<!-- end auto-generated rule header -->

Require Server Actions (functions with the `use server` directive) to be async, as mandated by the `use server` [spec](https://react.dev/reference/react/use-server).

This must be the case even if the function does not use `await` or `return` a promise.

## Rule Details

Examples of **incorrect** code for this rule:

```jsx
<form
action={() => {
'use server';
...
}}
>
...
</form>
```

```jsx
function action() {
'use server';
...
}
```

Examples of **correct** code for this rule:

```jsx
<form
action={async () => {
'use server';
...
}}
>
...
</form>
```

```jsx
async function action() {
'use server';
...
}
```

## When Not To Use It

If you are not using React Server Components.
59 changes: 59 additions & 0 deletions lib/rules/async-server-action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @fileoverview Require functions with the `use server` directive to be async
* @author Jorge Zreik
*/

'use strict';

const docsUrl = require('../util/docsUrl');
const report = require('../util/report');

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

const messages = {
asyncServerAction: 'Server Actions must be async',
suggestAsync: 'Make {{functionName}} async',
};

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {
description: 'Require functions with the `use server` directive to be async',
category: 'Possible Errors',
recommended: false,
url: docsUrl('async-server-action'),
},

messages,

type: 'suggestion',
hasSuggestions: true,

schema: [],
},

create(context) {
return {
':function[async=false][generator=false]>BlockStatement>:first-child[expression.value="use server"]'(node) {
const currentFunction = node.parent.parent;
const functionName = currentFunction.id ? `\`${currentFunction.id.name}\`` : 'this function';

const data = { functionName };
report(context, messages.asyncServerAction, 'asyncServerAction', {
node: currentFunction,
data,
suggest: [{
desc: messages.suggestAsync,
data,
fix(fixer) {
return fixer.insertTextBefore(currentFunction, 'async ');
},
}],
});
},
};
},
};
1 change: 1 addition & 0 deletions lib/rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

/** @type {Record<string, import('eslint').Rule.RuleModule>} */
module.exports = {
'async-server-action': require('./async-server-action'),
'boolean-prop-naming': require('./boolean-prop-naming'),
'button-has-type': require('./button-has-type'),
'checked-requires-onchange-or-readonly': require('./checked-requires-onchange-or-readonly'),
Expand Down
Loading

0 comments on commit 4d1e087

Please sign in to comment.