@eslint-react/jsx 5.8.6 → 5.8.7-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.
package/dist/index.d.ts CHANGED
@@ -215,10 +215,18 @@ declare function getAttributeValue(context: RuleContext, element: TSESTree.JSXEl
215
215
  /**
216
216
  * Get the **meaningful** children of a JSX element or fragment.
217
217
  *
218
- * Filters out "padding spaces" `JSXText` nodes that consist entirely of
219
- * whitespace and contain at least one newline. These nodes are artefacts of
220
- * source formatting that React trims away during rendering and are therefore
221
- * not considered meaningful content.
218
+ * Filters out nodes that React will not render into the DOM:
219
+ *
220
+ * 1. "Padding spaces" `JSXText` nodes that consist entirely of whitespace
221
+ * and contain at least one newline. These are code-formatting artefacts
222
+ * (indentation between tags). While React's client renderer preserves them
223
+ * as text nodes, browser HTML parsers may discard them during hydration,
224
+ * causing hydration mismatches.
225
+ *
226
+ * 2. Empty string expressions — `JSXExpressionContainer` nodes whose expression
227
+ * is a string literal with value `""`. React's reconciler and SSR renderer
228
+ * explicitly skip empty strings, producing no DOM node
229
+ * (see ReactChildFiber.js and ReactFizzConfigDOM.js).
222
230
  *
223
231
  * @param element - A `JSXElement` or `JSXFragment` node.
224
232
  * @returns An array of children nodes that contribute to rendered output.
@@ -320,12 +328,17 @@ declare function hasAttribute(context: RuleContext, element: TSESTree.JSXElement
320
328
  //#region src/has-children.d.ts
321
329
  /**
322
330
  * Check whether a JSX element (or fragment) has **meaningful** children —
323
- * that is, at least one child that is not purely whitespace text.
331
+ * that is, at least one child that is not purely whitespace text or an empty
332
+ * string expression.
333
+ *
334
+ * A `JSXText` child whose `raw` content is empty after trimming is considered
335
+ * non-meaningful because it is typically a code-formatting artefact
336
+ * (indentation between tags). While React's client renderer preserves these
337
+ * nodes as text nodes, they rarely represent intentionally rendered content.
324
338
  *
325
- * A `JSXText` child whose `raw` content is empty after trimming is
326
- * considered non-meaningful regardless of whether it contains a line break.
327
- * This matches React's rendering behaviour where whitespace-only text nodes
328
- * do not produce visible output.
339
+ * An empty string expression (`children={""}`) is also considered
340
+ * non-meaningful because React's reconciler and SSR renderer explicitly skip
341
+ * empty strings, producing no DOM node.
329
342
  *
330
343
  * @param element - A `JSXElement` or `JSXFragment` node.
331
344
  * @returns `true` when the element has at least one meaningful child.
@@ -340,6 +353,7 @@ declare function hasAttribute(context: RuleContext, element: TSESTree.JSXElement
340
353
  * // <div> -> false (whitespace-only, with newlines)
341
354
  * // </div>
342
355
  * // <div></div> -> false (no children at all)
356
+ * // <div>{""}</div> -> false (empty string expression)
343
357
  *
344
358
  * if (hasChildren(node)) {
345
359
  * // element renders visible content
package/dist/index.js CHANGED
@@ -297,10 +297,18 @@ function getAttributeValue(context, element, name) {
297
297
  /**
298
298
  * Get the **meaningful** children of a JSX element or fragment.
299
299
  *
300
- * Filters out "padding spaces" `JSXText` nodes that consist entirely of
301
- * whitespace and contain at least one newline. These nodes are artefacts of
302
- * source formatting that React trims away during rendering and are therefore
303
- * not considered meaningful content.
300
+ * Filters out nodes that React will not render into the DOM:
301
+ *
302
+ * 1. "Padding spaces" `JSXText` nodes that consist entirely of whitespace
303
+ * and contain at least one newline. These are code-formatting artefacts
304
+ * (indentation between tags). While React's client renderer preserves them
305
+ * as text nodes, browser HTML parsers may discard them during hydration,
306
+ * causing hydration mismatches.
307
+ *
308
+ * 2. Empty string expressions — `JSXExpressionContainer` nodes whose expression
309
+ * is a string literal with value `""`. React's reconciler and SSR renderer
310
+ * explicitly skip empty strings, producing no DOM node
311
+ * (see ReactChildFiber.js and ReactFizzConfigDOM.js).
304
312
  *
305
313
  * @param element - A `JSXElement` or `JSXFragment` node.
306
314
  * @returns An array of children nodes that contribute to rendered output.
@@ -320,14 +328,19 @@ function getAttributeValue(context, element, name) {
320
328
  * ```
321
329
  */
322
330
  function getChildren(element) {
323
- return element.children.filter((child) => !isPaddingSpaces(child));
331
+ return element.children.filter((child) => {
332
+ if (isPaddingSpaces(child)) return false;
333
+ if (isEmptyStringExpression$1(child)) return false;
334
+ return true;
335
+ });
324
336
  }
325
337
  /**
326
338
  * A `JSXText` node is considered **padding spaces** when it is purely
327
339
  * whitespace *and* contains at least one newline character.
328
340
  *
329
- * These nodes are formatting artefacts (indentation between JSX tags) that
330
- * React discards at render time.
341
+ * These nodes are code-formatting artefacts (indentation between JSX tags).
342
+ * While React's client renderer preserves them as text nodes, browser HTML
343
+ * parsers may discard them during hydration, leading to hydration mismatches.
331
344
  *
332
345
  * @param node The JSX child node to check.
333
346
  * @internal
@@ -336,6 +349,24 @@ function isPaddingSpaces(node) {
336
349
  if (node.type !== AST_NODE_TYPES.JSXText) return false;
337
350
  return node.raw.trim() === "" && node.raw.includes("\n");
338
351
  }
352
+ /**
353
+ * A `JSXExpressionContainer` node is considered an empty string expression
354
+ * when it wraps a string literal with value `""`.
355
+ *
356
+ * React's reconciler explicitly ignores empty strings
357
+ * (`typeof newChild === 'string' && newChild !== ''` in ReactChildFiber.js),
358
+ * and the SSR renderer skips them as well (`if (text === '') { return; }`
359
+ * in ReactFizzConfigDOM.js). They produce no DOM node.
360
+ *
361
+ * @param node The JSX child node to check.
362
+ * @internal
363
+ */
364
+ function isEmptyStringExpression$1(node) {
365
+ if (node.type !== AST_NODE_TYPES.JSXExpressionContainer) return false;
366
+ const expr = node.expression;
367
+ if (expr.type !== AST_NODE_TYPES.Literal) return false;
368
+ return expr.value === "";
369
+ }
339
370
 
340
371
  //#endregion
341
372
  //#region src/get-element-type.ts
@@ -436,12 +467,17 @@ function hasAttribute(context, element, name) {
436
467
  //#region src/has-children.ts
437
468
  /**
438
469
  * Check whether a JSX element (or fragment) has **meaningful** children —
439
- * that is, at least one child that is not purely whitespace text.
470
+ * that is, at least one child that is not purely whitespace text or an empty
471
+ * string expression.
472
+ *
473
+ * A `JSXText` child whose `raw` content is empty after trimming is considered
474
+ * non-meaningful because it is typically a code-formatting artefact
475
+ * (indentation between tags). While React's client renderer preserves these
476
+ * nodes as text nodes, they rarely represent intentionally rendered content.
440
477
  *
441
- * A `JSXText` child whose `raw` content is empty after trimming is
442
- * considered non-meaningful regardless of whether it contains a line break.
443
- * This matches React's rendering behaviour where whitespace-only text nodes
444
- * do not produce visible output.
478
+ * An empty string expression (`children={""}`) is also considered
479
+ * non-meaningful because React's reconciler and SSR renderer explicitly skip
480
+ * empty strings, producing no DOM node.
445
481
  *
446
482
  * @param element - A `JSXElement` or `JSXFragment` node.
447
483
  * @returns `true` when the element has at least one meaningful child.
@@ -456,6 +492,7 @@ function hasAttribute(context, element, name) {
456
492
  * // <div> -> false (whitespace-only, with newlines)
457
493
  * // </div>
458
494
  * // <div></div> -> false (no children at all)
495
+ * // <div>{""}</div> -> false (empty string expression)
459
496
  *
460
497
  * if (hasChildren(node)) {
461
498
  * // element renders visible content
@@ -464,7 +501,7 @@ function hasAttribute(context, element, name) {
464
501
  */
465
502
  function hasChildren(element) {
466
503
  if (element.children.length === 0) return false;
467
- return !element.children.every((child) => isWhitespaceText$1(child));
504
+ return !element.children.every((child) => isWhitespaceText$1(child) || isEmptyStringExpression(child));
468
505
  }
469
506
  /**
470
507
  * Whether a JSX child node is a whitespace-only `JSXText` node.
@@ -478,6 +515,20 @@ function isWhitespaceText$1(node) {
478
515
  if (node.type !== AST_NODE_TYPES.JSXText) return false;
479
516
  return node.raw.trim() === "";
480
517
  }
518
+ /**
519
+ * Whether a JSX child node is an empty string expression (`{""}`).
520
+ *
521
+ * React's reconciler and SSR renderer skip empty strings, producing no DOM
522
+ * node. These expressions are therefore considered non-meaningful children.
523
+ *
524
+ * @param node The JSX child node to check.
525
+ */
526
+ function isEmptyStringExpression(node) {
527
+ if (node.type !== AST_NODE_TYPES.JSXExpressionContainer) return false;
528
+ const expr = node.expression;
529
+ if (expr.type !== AST_NODE_TYPES.Literal) return false;
530
+ return expr.value === "";
531
+ }
481
532
 
482
533
  //#endregion
483
534
  //#region src/has-every-attribute.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-react/jsx",
3
- "version": "5.8.6",
3
+ "version": "5.8.7-beta.1",
4
4
  "description": "ESLint React's TSESTree JSX utility module for static analysis of JSX patterns.",
5
5
  "homepage": "https://github.com/Rel1cx/eslint-react",
6
6
  "bugs": {
@@ -32,17 +32,17 @@
32
32
  "@typescript-eslint/types": "^8.60.0",
33
33
  "@typescript-eslint/utils": "^8.60.0",
34
34
  "ts-pattern": "^5.9.0",
35
- "@eslint-react/ast": "5.8.6",
36
- "@eslint-react/eslint": "5.8.6",
37
- "@eslint-react/var": "5.8.6",
38
- "@eslint-react/shared": "5.8.6"
35
+ "@eslint-react/ast": "5.8.7-beta.1",
36
+ "@eslint-react/eslint": "5.8.7-beta.1",
37
+ "@eslint-react/shared": "5.8.7-beta.1",
38
+ "@eslint-react/var": "5.8.7-beta.1"
39
39
  },
40
40
  "devDependencies": {
41
41
  "eslint": "^10.4.0",
42
42
  "tsdown": "^0.22.0",
43
43
  "typescript": "5.9.3",
44
- "@local/eff": "3.0.0-beta.72",
45
- "@local/configs": "0.0.0"
44
+ "@local/configs": "0.0.0",
45
+ "@local/eff": "3.0.0-beta.72"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "eslint": "^10.3.0",