eslint-plugin-react-rsc 5.3.3-next.0 → 5.3.4-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +78 -7
  2. package/package.json +10 -10
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ const rules$4 = { "react-rsc/function-definition": "off" };
34
34
  //#endregion
35
35
  //#region package.json
36
36
  var name$4 = "eslint-plugin-react-rsc";
37
- var version = "5.3.3-next.0";
37
+ var version = "5.3.4-beta.0";
38
38
 
39
39
  //#endregion
40
40
  //#region src/utils/create-rule.ts
@@ -53,7 +53,12 @@ var function_definition_default = createRule({
53
53
  fixable: "code",
54
54
  messages: {
55
55
  file: "Functions exported from files with `use server` directive are React Server Functions and therefore must be async.",
56
- local: "Functions with `use server` directive are React Server Functions and therefore must be async."
56
+ fileDirectivePosition: "The '{{name}}' directive must be at the very beginning of the file, before any imports or other code.",
57
+ fileDirectiveQuote: "The '{{name}}' directive must be written with single or double quotes, not backticks.",
58
+ local: "Functions with `use server` directive are React Server Functions and therefore must be async.",
59
+ localDirectivePosition: "The '{{name}}' directive must be at the very beginning of the function body.",
60
+ localDirectiveQuote: "The '{{name}}' directive must be written with single or double quotes, not backticks.",
61
+ localDirectiveUnexpected: "The '{{name}}' directive can only be used at the top of a file, not inside a function body."
57
62
  },
58
63
  schema: []
59
64
  },
@@ -62,7 +67,9 @@ var function_definition_default = createRule({
62
67
  defaultOptions: []
63
68
  });
64
69
  function create(context) {
65
- if (!context.sourceCode.text.includes("use server")) return {};
70
+ const hasUseServer = context.sourceCode.text.includes("use server");
71
+ const hasUseClient = context.sourceCode.text.includes("use client");
72
+ if (!hasUseServer && !hasUseClient) return {};
66
73
  const hasFileLevelUseServerDirective = context.sourceCode.ast.body.some(Check.isDirective("use server"));
67
74
  /**
68
75
  * Check if `node` is an async function, and report if not
@@ -102,24 +109,83 @@ function create(context) {
102
109
  /**
103
110
  * Find function declarations from exports and check them
104
111
  * @param id The identifier of the exported function
105
- * @param node The export declaration node
106
112
  */
107
- function findAndCheckExportedFunctionDeclarations(id, node) {
113
+ function findAndCheckExportedFunctionDeclarations(id) {
108
114
  const initNode = resolve(context, id);
109
115
  if (initNode == null) return;
110
116
  const unwrapped = Extract.unwrap(initNode);
111
117
  if (!Check.isFunction(unwrapped)) return;
112
118
  reportNonAsyncFunction(unwrapped, "file");
113
119
  }
120
+ /**
121
+ * Check file-level directives for correct position and quote style.
122
+ * Well-formed directives at the beginning of the file will have a `directive` property.
123
+ * If they appear after other code, the parser will not set `directive`.
124
+ */
125
+ function checkFileLevelDirectives() {
126
+ for (const node of context.sourceCode.ast.body) {
127
+ if (node.type !== AST_NODE_TYPES.ExpressionStatement) continue;
128
+ if (Check.isLiteral("string")(node.expression)) {
129
+ const value = node.expression.value;
130
+ if ((value === "use server" || value === "use client") && node.directive == null) context.report({
131
+ data: { name: value },
132
+ messageId: "fileDirectivePosition",
133
+ node
134
+ });
135
+ continue;
136
+ }
137
+ if (node.expression.type === AST_NODE_TYPES.TemplateLiteral && node.expression.quasis.length === 1 && node.expression.expressions.length === 0) {
138
+ const value = node.expression.quasis[0]?.value.cooked;
139
+ if (value === "use server" || value === "use client") context.report({
140
+ data: { name: value },
141
+ messageId: "fileDirectiveQuote",
142
+ node
143
+ });
144
+ }
145
+ }
146
+ }
147
+ /**
148
+ * Check function-level directives for correct position and quote style.
149
+ * @param node The function node to check
150
+ */
151
+ function checkFunctionDirectives(node) {
152
+ if (node.body.type !== AST_NODE_TYPES.BlockStatement) return;
153
+ for (const stmt of node.body.body) {
154
+ if (stmt.type !== AST_NODE_TYPES.ExpressionStatement) continue;
155
+ if (Check.isLiteral("string")(stmt.expression)) {
156
+ const value = stmt.expression.value;
157
+ if (value === "use server" && stmt.directive == null) context.report({
158
+ data: { name: value },
159
+ messageId: "localDirectivePosition",
160
+ node: stmt
161
+ });
162
+ if (value === "use client") context.report({
163
+ data: { name: value },
164
+ messageId: "localDirectiveUnexpected",
165
+ node: stmt
166
+ });
167
+ continue;
168
+ }
169
+ if (stmt.expression.type === AST_NODE_TYPES.TemplateLiteral && stmt.expression.quasis.length === 1 && stmt.expression.expressions.length === 0) {
170
+ const value = stmt.expression.quasis[0]?.value.cooked;
171
+ if (value === "use server" || value === "use client") context.report({
172
+ data: { name: value },
173
+ messageId: "localDirectiveQuote",
174
+ node: stmt
175
+ });
176
+ }
177
+ }
178
+ }
114
179
  return merge({
115
180
  ArrowFunctionExpression(node) {
181
+ checkFunctionDirectives(node);
116
182
  checkLocalServerFunction(node);
117
183
  },
118
184
  ExportDefaultDeclaration(node) {
119
185
  if (!hasFileLevelUseServerDirective) return;
120
186
  const decl = node.declaration;
121
187
  if (reportNonAsyncFunction(decl, "file")) return;
122
- if (decl.type === AST_NODE_TYPES.Identifier) findAndCheckExportedFunctionDeclarations(decl, node);
188
+ if (decl.type === AST_NODE_TYPES.Identifier) findAndCheckExportedFunctionDeclarations(decl);
123
189
  },
124
190
  ExportNamedDeclaration(node) {
125
191
  if (!hasFileLevelUseServerDirective) return;
@@ -129,13 +195,18 @@ function create(context) {
129
195
  if (decl.type === AST_NODE_TYPES.VariableDeclaration) for (const declarator of decl.declarations) reportNonAsyncFunction(declarator.init, "file");
130
196
  return;
131
197
  }
132
- if (node.source == null && node.specifiers.length > 0) for (const spec of node.specifiers) findAndCheckExportedFunctionDeclarations(spec.local, node);
198
+ if (node.source == null && node.specifiers.length > 0) for (const spec of node.specifiers) findAndCheckExportedFunctionDeclarations(spec.local);
133
199
  },
134
200
  FunctionDeclaration(node) {
201
+ checkFunctionDirectives(node);
135
202
  checkLocalServerFunction(node);
136
203
  },
137
204
  FunctionExpression(node) {
205
+ checkFunctionDirectives(node);
138
206
  checkLocalServerFunction(node);
207
+ },
208
+ Program() {
209
+ checkFileLevelDirectives();
139
210
  }
140
211
  });
141
212
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-rsc",
3
- "version": "5.3.3-next.0",
3
+ "version": "5.3.4-beta.0",
4
4
  "description": "ESLint React's ESLint plugin for RSC related rules.",
5
5
  "keywords": [
6
6
  "react",
@@ -37,16 +37,16 @@
37
37
  "./package.json"
38
38
  ],
39
39
  "dependencies": {
40
- "@typescript-eslint/scope-manager": "^8.58.2",
41
- "@typescript-eslint/type-utils": "^8.58.2",
42
- "@typescript-eslint/types": "^8.58.2",
43
- "@typescript-eslint/utils": "^8.58.2",
40
+ "@typescript-eslint/scope-manager": "^8.59.0",
41
+ "@typescript-eslint/type-utils": "^8.59.0",
42
+ "@typescript-eslint/types": "^8.59.0",
43
+ "@typescript-eslint/utils": "^8.59.0",
44
44
  "ts-pattern": "^5.9.0",
45
- "@eslint-react/ast": "5.3.3-next.0",
46
- "@eslint-react/core": "5.3.3-next.0",
47
- "@eslint-react/eslint": "5.3.3-next.0",
48
- "@eslint-react/shared": "5.3.3-next.0",
49
- "@eslint-react/var": "5.3.3-next.0"
45
+ "@eslint-react/ast": "5.3.4-beta.0",
46
+ "@eslint-react/core": "5.3.4-beta.0",
47
+ "@eslint-react/var": "5.3.4-beta.0",
48
+ "@eslint-react/eslint": "5.3.4-beta.0",
49
+ "@eslint-react/shared": "5.3.4-beta.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/react": "^19.2.14",