eslint-plugin-react-rsc 5.3.3-next.2 → 5.3.4-beta.1

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 +90 -3
  2. package/package.json +7 -7
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.2";
37
+ var version = "5.3.4-beta.1";
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
@@ -110,8 +117,68 @@ function create(context) {
110
117
  if (!Check.isFunction(unwrapped)) return;
111
118
  reportNonAsyncFunction(unwrapped, "file");
112
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
+ }
113
179
  return merge({
114
180
  ArrowFunctionExpression(node) {
181
+ checkFunctionDirectives(node);
115
182
  checkLocalServerFunction(node);
116
183
  },
117
184
  ExportDefaultDeclaration(node) {
@@ -131,10 +198,15 @@ function create(context) {
131
198
  if (node.source == null && node.specifiers.length > 0) for (const spec of node.specifiers) findAndCheckExportedFunctionDeclarations(spec.local);
132
199
  },
133
200
  FunctionDeclaration(node) {
201
+ checkFunctionDirectives(node);
134
202
  checkLocalServerFunction(node);
135
203
  },
136
204
  FunctionExpression(node) {
205
+ checkFunctionDirectives(node);
137
206
  checkLocalServerFunction(node);
207
+ },
208
+ Program() {
209
+ checkFileLevelDirectives();
138
210
  }
139
211
  });
140
212
  }
@@ -206,10 +278,25 @@ const settings = { ...settings$1 };
206
278
  const finalPlugin = {
207
279
  ...plugin,
208
280
  configs: {
281
+ /**
282
+ * Disable experimental rules that might be subject to change in the future
283
+ */
209
284
  ["disable-experimental"]: disable_experimental_exports,
285
+ /**
286
+ * Enforce rules that are recommended by ESLint React for general purpose React + React DOM projects
287
+ */
210
288
  ["recommended"]: recommended_exports,
289
+ /**
290
+ * Same as the `recommended` preset but disables rules that can be enforced by TypeScript
291
+ */
211
292
  ["recommended-typescript"]: recommended_typescript_exports,
293
+ /**
294
+ * More strict version of the `recommended` preset
295
+ */
212
296
  ["strict"]: strict_exports,
297
+ /**
298
+ * Same as the `strict` preset but disables rules that can be enforced by TypeScript
299
+ */
213
300
  ["strict-typescript"]: strict_typescript_exports
214
301
  }
215
302
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-rsc",
3
- "version": "5.3.3-next.2",
3
+ "version": "5.3.4-beta.1",
4
4
  "description": "ESLint React's ESLint plugin for RSC related rules.",
5
5
  "keywords": [
6
6
  "react",
@@ -42,17 +42,17 @@
42
42
  "@typescript-eslint/types": "^8.59.0",
43
43
  "@typescript-eslint/utils": "^8.59.0",
44
44
  "ts-pattern": "^5.9.0",
45
- "@eslint-react/ast": "5.3.3-next.2",
46
- "@eslint-react/core": "5.3.3-next.2",
47
- "@eslint-react/shared": "5.3.3-next.2",
48
- "@eslint-react/var": "5.3.3-next.2",
49
- "@eslint-react/eslint": "5.3.3-next.2"
45
+ "@eslint-react/ast": "5.3.4-beta.1",
46
+ "@eslint-react/core": "5.3.4-beta.1",
47
+ "@eslint-react/shared": "5.3.4-beta.1",
48
+ "@eslint-react/var": "5.3.4-beta.1",
49
+ "@eslint-react/eslint": "5.3.4-beta.1"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/react": "^19.2.14",
53
53
  "@types/react-dom": "^19.2.3",
54
54
  "eslint": "^10.2.1",
55
- "tsdown": "^0.21.9",
55
+ "tsdown": "^0.21.10",
56
56
  "@local/configs": "0.0.0",
57
57
  "@local/eff": "3.0.0-beta.72"
58
58
  },