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 committed Apr 8, 2024
1 parent e4ecbcf commit 561edf4
Show file tree
Hide file tree
Showing 6 changed files with 2,363 additions and 1,439 deletions.
3,387 changes: 1,948 additions & 1,439 deletions CHANGELOG.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions configs/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const all = require('./all');
module.exports = Object.assign({}, all, {
languageOptions: all.languageOptions,
rules: {
'react/async-server-action': 2,
'react/display-name': 2,
'react/jsx-key': 2,
'react/jsx-no-comment-textnodes': 2,
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 enabled in the ☑️ `recommended` [config](https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs).

<!-- 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.
85 changes: 85 additions & 0 deletions lib/rules/async-server-action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* @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: 'Your server action should be async',
};

/**
* Detects a `use server` directive in a given AST node
* @param {ASTNode} node The node to search.
* @returns {boolean} Whether the node given has a `use server` directive.
*/
function hasUseServerDirective(node) {
if (node.body.type !== 'BlockStatement') return false;

const functionBody = node.body.body;
if (functionBody.length === 0) return false;

const potentialDirectiveStatement = functionBody[0];
if (potentialDirectiveStatement.type !== 'ExpressionStatement') return false;

const potentialDirectiveExpression = potentialDirectiveStatement.expression;
if (potentialDirectiveExpression.type !== 'Literal') return false;

return potentialDirectiveExpression.value === 'use server';
}

module.exports = {
meta: {
docs: {
description:
'Require functions with the `use server` directive to be async',
category: 'Possible Errors',
recommended: true,
url: docsUrl('async-server-action'),
},

messages,

fixable: 'code',

schema: [],
},

create(context) {
/**
* Validates that given AST node is async if it has the `use server` directive
* @param {ASTNode} node The node to search.
* @returns {void}
*/
function validate(node) {
if (hasUseServerDirective(node) && !node.async) {
report(context, messages.asyncServerAction, 'asyncServerAction', {
node,
fix(fixer) {
return fixer.insertTextBefore(node, 'async ');
},
});
}
}

return {
FunctionDeclaration(node) {
validate(node);
},
FunctionExpression(node) {
validate(node);
},
ArrowFunctionExpression(node) {
validate(node);
},
};
},
};
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 561edf4

Please sign in to comment.