@eslint-react/jsx 5.8.9 → 5.8.10

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
@@ -218,15 +218,21 @@ declare function getAttributeValue(context: RuleContext, element: TSESTree.JSXEl
218
218
  * Filters out nodes that React will not render into the DOM:
219
219
  *
220
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.
221
+ * and contain at least one newline (see {@link isWhitespace}). These are
222
+ * code-formatting artefacts (indentation between tags). While React's client
223
+ * renderer preserves them as text nodes, browser HTML parsers may discard
224
+ * them during hydration, causing hydration mismatches.
225
225
  *
226
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).
227
+ * is a string literal with value `""` (see {@link isEmptyStringExpression}).
228
+ * React's reconciler and SSR renderer explicitly skip empty strings,
229
+ * producing no DOM node.
230
+ *
231
+ * Whitespace-only text **without** a newline (e.g. the single space in
232
+ * `<div> </div>`) is intentionally **kept**, because React renders it. For
233
+ * this reason `getChildren(node).length > 0` is **not** equivalent to
234
+ * {@link hasChildren}, which applies a stricter "any whitespace-only text is
235
+ * non-meaningful" heuristic. Pick the one that matches your rule's intent.
230
236
  *
231
237
  * @param element - A `JSXElement` or `JSXFragment` node.
232
238
  * @returns An array of children nodes that contribute to rendered output.
@@ -340,6 +346,13 @@ declare function hasAttribute(context: RuleContext, element: TSESTree.JSXElement
340
346
  * non-meaningful because React's reconciler and SSR renderer explicitly skip
341
347
  * empty strings, producing no DOM node.
342
348
  *
349
+ * Unlike {@link getChildren} — which only filters whitespace that contains a
350
+ * newline — this check treats **any** whitespace-only text as non-meaningful
351
+ * (see {@link isWhitespaceText}). As a result `hasChildren(node)` is **not**
352
+ * always equal to `getChildren(node).length > 0`: they differ for
353
+ * whitespace-only children that have no newline, such as `<div> </div>` or
354
+ * `<div>\t\t</div>`. Choose the API that matches your rule's intent.
355
+ *
343
356
  * @param element - A `JSXElement` or `JSXFragment` node.
344
357
  * @returns `true` when the element has at least one meaningful child.
345
358
  *
@@ -529,6 +542,28 @@ declare function isWhitespace(node: TSESTree.JSXChild): boolean;
529
542
  * @returns `true` when the node is a whitespace‑only `JSXText`.
530
543
  */
531
544
  declare function isWhitespaceText(node: TSESTree.JSXChild): boolean;
545
+ /**
546
+ * Check whether a JSX child node is an **empty string expression** (`{""}`).
547
+ *
548
+ * React's reconciler and SSR renderer explicitly skip empty strings,
549
+ * producing no DOM node (see `ReactChildFiber.js` and `ReactFizzConfigDOM.js`).
550
+ * Such expressions are therefore treated as non-rendered children, in the same
551
+ * way as whitespace padding.
552
+ *
553
+ * @param node - A JSX child node.
554
+ * @returns `true` when the node is a `{""}` expression container.
555
+ *
556
+ * @example
557
+ * ```ts
558
+ * import { isEmptyStringExpression } from "@eslint-react/jsx";
559
+ *
560
+ * // <div>{""}</div> -> the expression container is an empty string expression
561
+ * const meaningful = element.children.filter(
562
+ * (child) => !isEmptyStringExpression(child),
563
+ * );
564
+ * ```
565
+ */
566
+ declare function isEmptyStringExpression(node: TSESTree.JSXChild): boolean;
532
567
  //#endregion
533
568
  //#region src/jsx-detection-hint.d.ts
534
569
  /**
@@ -597,4 +632,4 @@ declare const DEFAULT_JSX_DETECTION_HINT: JsxDetectionHint;
597
632
  */
598
633
  declare function resolveAttributeValue(context: RuleContext, attribute: TSESTreeJSXAttributeLike): JsxAttributeValue;
599
634
  //#endregion
600
- export { DEFAULT_JSX_DETECTION_HINT, ElementTest, JsxAttributeValue, JsxDetectionHint, findAttribute, findParentAttribute, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isElement, isFragmentElement, isHostElement, isWhitespace, isWhitespaceText, resolveAttributeValue };
635
+ export { DEFAULT_JSX_DETECTION_HINT, ElementTest, JsxAttributeValue, JsxDetectionHint, findAttribute, findParentAttribute, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isElement, isEmptyStringExpression, isFragmentElement, isHostElement, isWhitespace, isWhitespaceText, resolveAttributeValue };
package/dist/index.js CHANGED
@@ -292,6 +292,84 @@ function getAttributeValue(context, element, name) {
292
292
  return resolveAttributeValue(context, attr);
293
293
  }
294
294
 
295
+ //#endregion
296
+ //#region src/is-whitespace.ts
297
+ /**
298
+ * Check whether a JSX child node is **whitespace padding** that React would
299
+ * trim away during rendering.
300
+ *
301
+ * A child is considered whitespace padding when it is a `JSXText` node whose
302
+ * raw content is empty after trimming **and** contains at least one newline.
303
+ * This is the whitespace that appears between JSX tags purely for formatting:
304
+ *
305
+ * ```jsx
306
+ * <div>
307
+ * <span /> ← the text between </span> and the next tag is padding
308
+ * <span />
309
+ * </div>
310
+ * ```
311
+ *
312
+ * Use {@link isWhitespaceText} for a looser check that also matches
313
+ * whitespace‑only text that does **not** contain a newline.
314
+ *
315
+ * @param node - A JSX child node.
316
+ * @returns `true` when the node is purely formatting whitespace.
317
+ *
318
+ * @example
319
+ * ```ts
320
+ * import { isWhitespace } from "@eslint-react/jsx";
321
+ *
322
+ * const meaningful = element.children.filter(
323
+ * (child) => !isWhitespace(child),
324
+ * );
325
+ * ```
326
+ */
327
+ function isWhitespace(node) {
328
+ if (node.type !== AST_NODE_TYPES.JSXText) return false;
329
+ return node.raw.trim() === "" && node.raw.includes("\n");
330
+ }
331
+ /**
332
+ * Check whether a JSX child node is **any** whitespace‑only text.
333
+ *
334
+ * This is a looser variant of {@link isWhitespace} — it matches every
335
+ * `JSXText` node whose raw content is empty after trimming, regardless of
336
+ * whether it contains a newline.
337
+ *
338
+ * @param node - A JSX child node.
339
+ * @returns `true` when the node is a whitespace‑only `JSXText`.
340
+ */
341
+ function isWhitespaceText(node) {
342
+ if (node.type !== AST_NODE_TYPES.JSXText) return false;
343
+ return node.raw.trim() === "";
344
+ }
345
+ /**
346
+ * Check whether a JSX child node is an **empty string expression** (`{""}`).
347
+ *
348
+ * React's reconciler and SSR renderer explicitly skip empty strings,
349
+ * producing no DOM node (see `ReactChildFiber.js` and `ReactFizzConfigDOM.js`).
350
+ * Such expressions are therefore treated as non-rendered children, in the same
351
+ * way as whitespace padding.
352
+ *
353
+ * @param node - A JSX child node.
354
+ * @returns `true` when the node is a `{""}` expression container.
355
+ *
356
+ * @example
357
+ * ```ts
358
+ * import { isEmptyStringExpression } from "@eslint-react/jsx";
359
+ *
360
+ * // <div>{""}</div> -> the expression container is an empty string expression
361
+ * const meaningful = element.children.filter(
362
+ * (child) => !isEmptyStringExpression(child),
363
+ * );
364
+ * ```
365
+ */
366
+ function isEmptyStringExpression(node) {
367
+ if (node.type !== AST_NODE_TYPES.JSXExpressionContainer) return false;
368
+ const expr = node.expression;
369
+ if (expr.type !== AST_NODE_TYPES.Literal) return false;
370
+ return expr.value === "";
371
+ }
372
+
295
373
  //#endregion
296
374
  //#region src/get-children.ts
297
375
  /**
@@ -300,15 +378,21 @@ function getAttributeValue(context, element, name) {
300
378
  * Filters out nodes that React will not render into the DOM:
301
379
  *
302
380
  * 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.
381
+ * and contain at least one newline (see {@link isWhitespace}). These are
382
+ * code-formatting artefacts (indentation between tags). While React's client
383
+ * renderer preserves them as text nodes, browser HTML parsers may discard
384
+ * them during hydration, causing hydration mismatches.
307
385
  *
308
386
  * 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).
387
+ * is a string literal with value `""` (see {@link isEmptyStringExpression}).
388
+ * React's reconciler and SSR renderer explicitly skip empty strings,
389
+ * producing no DOM node.
390
+ *
391
+ * Whitespace-only text **without** a newline (e.g. the single space in
392
+ * `<div> </div>`) is intentionally **kept**, because React renders it. For
393
+ * this reason `getChildren(node).length > 0` is **not** equivalent to
394
+ * {@link hasChildren}, which applies a stricter "any whitespace-only text is
395
+ * non-meaningful" heuristic. Pick the one that matches your rule's intent.
312
396
  *
313
397
  * @param element - A `JSXElement` or `JSXFragment` node.
314
398
  * @returns An array of children nodes that contribute to rendered output.
@@ -329,44 +413,11 @@ function getAttributeValue(context, element, name) {
329
413
  */
330
414
  function getChildren(element) {
331
415
  return element.children.filter((child) => {
332
- if (isPaddingSpaces(child)) return false;
333
- if (isEmptyStringExpression$1(child)) return false;
416
+ if (isWhitespace(child)) return false;
417
+ if (isEmptyStringExpression(child)) return false;
334
418
  return true;
335
419
  });
336
420
  }
337
- /**
338
- * A `JSXText` node is considered **padding spaces** when it is purely
339
- * whitespace *and* contains at least one newline character.
340
- *
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.
344
- *
345
- * @param node The JSX child node to check.
346
- * @internal
347
- */
348
- function isPaddingSpaces(node) {
349
- if (node.type !== AST_NODE_TYPES.JSXText) return false;
350
- return node.raw.trim() === "" && node.raw.includes("\n");
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
- }
370
421
 
371
422
  //#endregion
372
423
  //#region src/get-element-type.ts
@@ -479,6 +530,13 @@ function hasAttribute(context, element, name) {
479
530
  * non-meaningful because React's reconciler and SSR renderer explicitly skip
480
531
  * empty strings, producing no DOM node.
481
532
  *
533
+ * Unlike {@link getChildren} — which only filters whitespace that contains a
534
+ * newline — this check treats **any** whitespace-only text as non-meaningful
535
+ * (see {@link isWhitespaceText}). As a result `hasChildren(node)` is **not**
536
+ * always equal to `getChildren(node).length > 0`: they differ for
537
+ * whitespace-only children that have no newline, such as `<div> </div>` or
538
+ * `<div>\t\t</div>`. Choose the API that matches your rule's intent.
539
+ *
482
540
  * @param element - A `JSXElement` or `JSXFragment` node.
483
541
  * @returns `true` when the element has at least one meaningful child.
484
542
  *
@@ -501,33 +559,7 @@ function hasAttribute(context, element, name) {
501
559
  */
502
560
  function hasChildren(element) {
503
561
  if (element.children.length === 0) return false;
504
- return !element.children.every((child) => isWhitespaceText$1(child) || isEmptyStringExpression(child));
505
- }
506
- /**
507
- * Whether a JSX child node is a whitespace-only `JSXText` node.
508
- *
509
- * Any `JSXText` whose raw content consists entirely of whitespace characters
510
- * (spaces, tabs, newlines, etc.) is considered whitespace text. Non-text
511
- * nodes always return `false`.
512
- * @param node The JSX child node to check.
513
- */
514
- function isWhitespaceText$1(node) {
515
- if (node.type !== AST_NODE_TYPES.JSXText) return false;
516
- return node.raw.trim() === "";
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 === "";
562
+ return !element.children.every((child) => isWhitespaceText(child) || isEmptyStringExpression(child));
531
563
  }
532
564
 
533
565
  //#endregion
@@ -665,57 +697,6 @@ function isHostElement(node) {
665
697
  return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
666
698
  }
667
699
 
668
- //#endregion
669
- //#region src/is-whitespace.ts
670
- /**
671
- * Check whether a JSX child node is **whitespace padding** that React would
672
- * trim away during rendering.
673
- *
674
- * A child is considered whitespace padding when it is a `JSXText` node whose
675
- * raw content is empty after trimming **and** contains at least one newline.
676
- * This is the whitespace that appears between JSX tags purely for formatting:
677
- *
678
- * ```jsx
679
- * <div>
680
- * <span /> ← the text between </span> and the next tag is padding
681
- * <span />
682
- * </div>
683
- * ```
684
- *
685
- * Use {@link isWhitespaceText} for a looser check that also matches
686
- * whitespace‑only text that does **not** contain a newline.
687
- *
688
- * @param node - A JSX child node.
689
- * @returns `true` when the node is purely formatting whitespace.
690
- *
691
- * @example
692
- * ```ts
693
- * import { isWhitespace } from "@eslint-react/jsx";
694
- *
695
- * const meaningful = element.children.filter(
696
- * (child) => !isWhitespace(child),
697
- * );
698
- * ```
699
- */
700
- function isWhitespace(node) {
701
- if (node.type !== AST_NODE_TYPES.JSXText) return false;
702
- return node.raw.trim() === "" && node.raw.includes("\n");
703
- }
704
- /**
705
- * Check whether a JSX child node is **any** whitespace‑only text.
706
- *
707
- * This is a looser variant of {@link isWhitespace} — it matches every
708
- * `JSXText` node whose raw content is empty after trimming, regardless of
709
- * whether it contains a newline.
710
- *
711
- * @param node - A JSX child node.
712
- * @returns `true` when the node is a whitespace‑only `JSXText`.
713
- */
714
- function isWhitespaceText(node) {
715
- if (node.type !== AST_NODE_TYPES.JSXText) return false;
716
- return node.raw.trim() === "";
717
- }
718
-
719
700
  //#endregion
720
701
  //#region src/jsx-detection-hint.ts
721
702
  const JsxDetectionHint = {
@@ -742,4 +723,4 @@ const JsxDetectionHint = {
742
723
  const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
743
724
 
744
725
  //#endregion
745
- export { DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, findAttribute, findParentAttribute, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isElement, isFragmentElement, isHostElement, isWhitespace, isWhitespaceText, resolveAttributeValue };
726
+ export { DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, findAttribute, findParentAttribute, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isElement, isEmptyStringExpression, isFragmentElement, isHostElement, isWhitespace, isWhitespaceText, resolveAttributeValue };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-react/jsx",
3
- "version": "5.8.9",
3
+ "version": "5.8.10",
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.9",
36
- "@eslint-react/shared": "5.8.9",
37
- "@eslint-react/var": "5.8.9",
38
- "@eslint-react/eslint": "5.8.9"
35
+ "@eslint-react/ast": "5.8.10",
36
+ "@eslint-react/eslint": "5.8.10",
37
+ "@eslint-react/var": "5.8.10",
38
+ "@eslint-react/shared": "5.8.10"
39
39
  },
40
40
  "devDependencies": {
41
41
  "eslint": "^10.4.1",
42
42
  "tsdown": "^0.22.1",
43
43
  "typescript": "5.9.3",
44
44
  "@local/configs": "0.0.0",
45
- "@local/eff": "3.0.0-beta.72"
45
+ "@local/eff": "0.0.0"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "eslint": "^10.3.0",