@eslint-react/jsx 5.8.11 → 5.8.13
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 +29 -33
- package/dist/index.js +67 -39
- package/package.json +6 -6
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,24 @@ import { TSESTreeJSXAttributeLike, TSESTreeJSXElementLike } from "@eslint-react/
|
|
|
2
2
|
import { TSESTree } from "@typescript-eslint/types";
|
|
3
3
|
import { RuleContext } from "@eslint-react/eslint";
|
|
4
4
|
|
|
5
|
+
//#region src/collapse-multiline-text.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* Collapse a multiline JSX text string following React's whitespace rules.
|
|
8
|
+
*
|
|
9
|
+
* This mirrors Babel's `cleanJSXElementLiteralChild` algorithm:
|
|
10
|
+
* 1. Split the raw text into lines.
|
|
11
|
+
* 2. Find the last non-empty line.
|
|
12
|
+
* 3. Trim leading spaces on non-first lines and trailing spaces on non-last lines.
|
|
13
|
+
* 4. Collapse tabs into spaces.
|
|
14
|
+
* 5. Append a single space after each non-last non-empty line.
|
|
15
|
+
*
|
|
16
|
+
* @param text - The raw JSX text string to collapse.
|
|
17
|
+
* @returns The collapsed string, or `null` if the text contains only whitespace.
|
|
18
|
+
*
|
|
19
|
+
* @see https://github.com/babel/babel/blob/main/packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts
|
|
20
|
+
*/
|
|
21
|
+
declare function collapseMultilineText(text: string): string | null;
|
|
22
|
+
//#endregion
|
|
5
23
|
//#region src/find-attribute.d.ts
|
|
6
24
|
/**
|
|
7
25
|
* Find a JSX attribute (or spread attribute containing the property) by name
|
|
@@ -215,24 +233,12 @@ declare function getAttributeValue(context: RuleContext, element: TSESTree.JSXEl
|
|
|
215
233
|
/**
|
|
216
234
|
* Get the **meaningful** children of a JSX element or fragment.
|
|
217
235
|
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
* them during hydration, causing hydration mismatches.
|
|
225
|
-
*
|
|
226
|
-
* 2. Empty string expressions — `JSXExpressionContainer` nodes whose expression
|
|
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.
|
|
236
|
+
* Mirrors Babel's `buildChildren` helper:
|
|
237
|
+
* 1. Iterate over `element.children`.
|
|
238
|
+
* 2. Skip `JSXText` nodes that clean to nothing (padding whitespace).
|
|
239
|
+
* 3. Skip `JSXExpressionContainer` nodes whose expression is empty.
|
|
240
|
+
* 4. Skip `JSXEmptyExpression` nodes.
|
|
241
|
+
* 5. Collect everything else.
|
|
236
242
|
*
|
|
237
243
|
* @param element - A `JSXElement` or `JSXFragment` node.
|
|
238
244
|
* @returns An array of children nodes that contribute to rendered output.
|
|
@@ -505,8 +511,10 @@ declare function isHostElement(node: TSESTree.Node): node is TSESTree.JSXElement
|
|
|
505
511
|
* trim away during rendering.
|
|
506
512
|
*
|
|
507
513
|
* A child is considered whitespace padding when it is a `JSXText` node whose
|
|
508
|
-
*
|
|
509
|
-
*
|
|
514
|
+
* content is empty after applying React's whitespace normalization
|
|
515
|
+
* (see {@link collapseMultilineText}, modelled after Babel's
|
|
516
|
+
* `cleanJSXElementLiteralChild`). This is the whitespace that appears between
|
|
517
|
+
* JSX tags purely for formatting:
|
|
510
518
|
*
|
|
511
519
|
* ```jsx
|
|
512
520
|
* <div>
|
|
@@ -515,20 +523,8 @@ declare function isHostElement(node: TSESTree.Node): node is TSESTree.JSXElement
|
|
|
515
523
|
* </div>
|
|
516
524
|
* ```
|
|
517
525
|
*
|
|
518
|
-
* Use {@link isWhitespaceText} for a looser check that also matches
|
|
519
|
-
* whitespace‑only text that does **not** contain a newline.
|
|
520
|
-
*
|
|
521
526
|
* @param node - A JSX child node.
|
|
522
527
|
* @returns `true` when the node is purely formatting whitespace.
|
|
523
|
-
*
|
|
524
|
-
* @example
|
|
525
|
-
* ```ts
|
|
526
|
-
* import { isWhitespace } from "@eslint-react/jsx";
|
|
527
|
-
*
|
|
528
|
-
* const meaningful = element.children.filter(
|
|
529
|
-
* (child) => !isWhitespace(child),
|
|
530
|
-
* );
|
|
531
|
-
* ```
|
|
532
528
|
*/
|
|
533
529
|
declare function isWhitespace(node: TSESTree.JSXChild): boolean;
|
|
534
530
|
/**
|
|
@@ -632,4 +628,4 @@ declare const DEFAULT_JSX_DETECTION_HINT: JsxDetectionHint;
|
|
|
632
628
|
*/
|
|
633
629
|
declare function resolveAttributeValue(context: RuleContext, attribute: TSESTreeJSXAttributeLike): JsxAttributeValue;
|
|
634
630
|
//#endregion
|
|
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 };
|
|
631
|
+
export { DEFAULT_JSX_DETECTION_HINT, ElementTest, JsxAttributeValue, JsxDetectionHint, collapseMultilineText, findAttribute, findParentAttribute, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isElement, isEmptyStringExpression, isFragmentElement, isHostElement, isWhitespace, isWhitespaceText, resolveAttributeValue };
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,44 @@ import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
|
4
4
|
import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
5
5
|
import { P, match } from "ts-pattern";
|
|
6
6
|
|
|
7
|
+
//#region src/collapse-multiline-text.ts
|
|
8
|
+
/**
|
|
9
|
+
* Collapse a multiline JSX text string following React's whitespace rules.
|
|
10
|
+
*
|
|
11
|
+
* This mirrors Babel's `cleanJSXElementLiteralChild` algorithm:
|
|
12
|
+
* 1. Split the raw text into lines.
|
|
13
|
+
* 2. Find the last non-empty line.
|
|
14
|
+
* 3. Trim leading spaces on non-first lines and trailing spaces on non-last lines.
|
|
15
|
+
* 4. Collapse tabs into spaces.
|
|
16
|
+
* 5. Append a single space after each non-last non-empty line.
|
|
17
|
+
*
|
|
18
|
+
* @param text - The raw JSX text string to collapse.
|
|
19
|
+
* @returns The collapsed string, or `null` if the text contains only whitespace.
|
|
20
|
+
*
|
|
21
|
+
* @see https://github.com/babel/babel/blob/main/packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts
|
|
22
|
+
*/
|
|
23
|
+
function collapseMultilineText(text) {
|
|
24
|
+
const lines = text.split(/\r\n|\n|\r/);
|
|
25
|
+
let lastNonEmptyLine = 0;
|
|
26
|
+
for (let i = 0; i < lines.length; i++) if (/[^ \t]/.exec(lines[i]) != null) lastNonEmptyLine = i;
|
|
27
|
+
let str = "";
|
|
28
|
+
for (let i = 0; i < lines.length; i++) {
|
|
29
|
+
const line = lines[i];
|
|
30
|
+
const isFirstLine = i === 0;
|
|
31
|
+
const isLastLine = i === lines.length - 1;
|
|
32
|
+
const isLastNonEmptyLine = i === lastNonEmptyLine;
|
|
33
|
+
let trimmedLine = line.replace(/\t/g, " ");
|
|
34
|
+
if (!isFirstLine) trimmedLine = trimmedLine.replace(/^ +/, "");
|
|
35
|
+
if (!isLastLine) trimmedLine = trimmedLine.replace(/ +$/, "");
|
|
36
|
+
if (trimmedLine.length > 0) {
|
|
37
|
+
if (!isLastNonEmptyLine) trimmedLine += " ";
|
|
38
|
+
str += trimmedLine;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return str || null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
//#endregion
|
|
7
45
|
//#region src/get-attribute-name.ts
|
|
8
46
|
/**
|
|
9
47
|
* Get the stringified name of a `JSXAttribute` node.
|
|
@@ -299,8 +337,10 @@ function getAttributeValue(context, element, name) {
|
|
|
299
337
|
* trim away during rendering.
|
|
300
338
|
*
|
|
301
339
|
* A child is considered whitespace padding when it is a `JSXText` node whose
|
|
302
|
-
*
|
|
303
|
-
*
|
|
340
|
+
* content is empty after applying React's whitespace normalization
|
|
341
|
+
* (see {@link collapseMultilineText}, modelled after Babel's
|
|
342
|
+
* `cleanJSXElementLiteralChild`). This is the whitespace that appears between
|
|
343
|
+
* JSX tags purely for formatting:
|
|
304
344
|
*
|
|
305
345
|
* ```jsx
|
|
306
346
|
* <div>
|
|
@@ -309,24 +349,12 @@ function getAttributeValue(context, element, name) {
|
|
|
309
349
|
* </div>
|
|
310
350
|
* ```
|
|
311
351
|
*
|
|
312
|
-
* Use {@link isWhitespaceText} for a looser check that also matches
|
|
313
|
-
* whitespace‑only text that does **not** contain a newline.
|
|
314
|
-
*
|
|
315
352
|
* @param node - A JSX child node.
|
|
316
353
|
* @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
354
|
*/
|
|
327
355
|
function isWhitespace(node) {
|
|
328
356
|
if (node.type !== AST_NODE_TYPES.JSXText) return false;
|
|
329
|
-
return node.
|
|
357
|
+
return collapseMultilineText(node.value) == null && node.value.includes("\n");
|
|
330
358
|
}
|
|
331
359
|
/**
|
|
332
360
|
* Check whether a JSX child node is **any** whitespace‑only text.
|
|
@@ -375,24 +403,12 @@ function isEmptyStringExpression(node) {
|
|
|
375
403
|
/**
|
|
376
404
|
* Get the **meaningful** children of a JSX element or fragment.
|
|
377
405
|
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
* them during hydration, causing hydration mismatches.
|
|
385
|
-
*
|
|
386
|
-
* 2. Empty string expressions — `JSXExpressionContainer` nodes whose expression
|
|
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.
|
|
406
|
+
* Mirrors Babel's `buildChildren` helper:
|
|
407
|
+
* 1. Iterate over `element.children`.
|
|
408
|
+
* 2. Skip `JSXText` nodes that clean to nothing (padding whitespace).
|
|
409
|
+
* 3. Skip `JSXExpressionContainer` nodes whose expression is empty.
|
|
410
|
+
* 4. Skip `JSXEmptyExpression` nodes.
|
|
411
|
+
* 5. Collect everything else.
|
|
396
412
|
*
|
|
397
413
|
* @param element - A `JSXElement` or `JSXFragment` node.
|
|
398
414
|
* @returns An array of children nodes that contribute to rendered output.
|
|
@@ -412,11 +428,23 @@ function isEmptyStringExpression(node) {
|
|
|
412
428
|
* ```
|
|
413
429
|
*/
|
|
414
430
|
function getChildren(element) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
if (
|
|
418
|
-
|
|
419
|
-
|
|
431
|
+
const elements = [];
|
|
432
|
+
for (const child of element.children) {
|
|
433
|
+
if (child.type === AST_NODE_TYPES.JSXText) {
|
|
434
|
+
if (collapseMultilineText(child.value) == null && child.value.includes("\n")) continue;
|
|
435
|
+
elements.push(child);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (child.type === AST_NODE_TYPES.JSXExpressionContainer) {
|
|
439
|
+
const { expression } = child;
|
|
440
|
+
if (expression.type === AST_NODE_TYPES.JSXEmptyExpression) continue;
|
|
441
|
+
if (isEmptyStringExpression(child)) continue;
|
|
442
|
+
elements.push(child);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
elements.push(child);
|
|
446
|
+
}
|
|
447
|
+
return elements;
|
|
420
448
|
}
|
|
421
449
|
|
|
422
450
|
//#endregion
|
|
@@ -723,4 +751,4 @@ const JsxDetectionHint = {
|
|
|
723
751
|
const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
|
|
724
752
|
|
|
725
753
|
//#endregion
|
|
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 };
|
|
754
|
+
export { DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, collapseMultilineText, 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.
|
|
3
|
+
"version": "5.8.13",
|
|
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,14 +32,14 @@
|
|
|
32
32
|
"@typescript-eslint/types": "^8.60.1",
|
|
33
33
|
"@typescript-eslint/utils": "^8.60.1",
|
|
34
34
|
"ts-pattern": "^5.9.0",
|
|
35
|
-
"@eslint-react/ast": "5.8.
|
|
36
|
-
"@eslint-react/
|
|
37
|
-
"@eslint-react/
|
|
38
|
-
"@eslint-react/
|
|
35
|
+
"@eslint-react/ast": "5.8.13",
|
|
36
|
+
"@eslint-react/shared": "5.8.13",
|
|
37
|
+
"@eslint-react/var": "5.8.13",
|
|
38
|
+
"@eslint-react/eslint": "5.8.13"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"eslint": "^10.4.1",
|
|
42
|
-
"tsdown": "^0.22.
|
|
42
|
+
"tsdown": "^0.22.2",
|
|
43
43
|
"typescript": "6.0.3",
|
|
44
44
|
"@local/configs": "0.0.0",
|
|
45
45
|
"@local/eff": "0.0.0"
|