eslint-plugin-react-jsx 5.15.0 → 5.16.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 +81 -144
  2. package/package.json +6 -6
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { DEFAULT_ESLINT_REACT_SETTINGS } from "@eslint-react/shared";
2
+ import { Check, Extract } from "@eslint-react/ast";
3
+ import { AST_NODE_TYPES } from "@typescript-eslint/types";
2
4
  import { ESLintUtils } from "@typescript-eslint/utils";
3
5
  import * as core from "@eslint-react/core";
4
6
  import "@eslint-react/eslint";
5
7
  import { collapseMultilineText, findAttribute, getChildren, getElementFullType, hasAnyAttribute, hasChildren, isFragmentElement, isHostElement, isWhitespaceText } from "@eslint-react/jsx";
6
- import { AST_NODE_TYPES } from "@typescript-eslint/types";
7
- import { Check, Extract } from "@eslint-react/ast";
8
8
  import ts from "typescript";
9
9
 
10
10
  //#region \0rolldown/runtime.js
@@ -26,58 +26,53 @@ var __exportAll = (all, no_symbols) => {
26
26
  //#endregion
27
27
  //#region package.json
28
28
  var name$2 = "eslint-plugin-react-jsx";
29
- var version = "5.15.0";
29
+ var version = "5.16.1";
30
30
 
31
31
  //#endregion
32
- //#region src/utils/create-rule.ts
33
- function getDocsUrl(ruleName) {
34
- return `https://eslint-react.xyz/docs/rules/jsx-${ruleName}`;
35
- }
36
- const createRule = ESLintUtils.RuleCreator(getDocsUrl);
37
-
38
- //#endregion
39
- //#region src/rules/no-children-prop-with-children/lib.ts
40
- /**
41
- * Find a `children` property inside an ObjectExpression.
42
- * @param node The object expression.
43
- * @returns The Property node whose key is "children", or null.
44
- */
45
- function findChildrenProperty$1(node) {
46
- for (const prop of node.properties) {
47
- if (prop.type !== AST_NODE_TYPES.Property) continue;
48
- const key = Extract.unwrap(prop.key);
49
- if (key.type === AST_NODE_TYPES.Identifier && key.name === "children") return prop;
50
- if (key.type === AST_NODE_TYPES.Literal && key.value === "children") return prop;
51
- }
32
+ //#region src/utils/common.ts
33
+ function findChildrenProperty(node) {
34
+ for (const prop of node.properties) if (prop.type === AST_NODE_TYPES.Property && Extract.getStaticPropertyName(prop) === "children") return prop;
52
35
  return null;
53
36
  }
54
- /**
55
- * Compute the removal range for a JSX attribute, consuming any leading
56
- * whitespace (spaces, tabs, newlines) so the resulting markup stays clean.
57
- * @param context The rule context.
58
- * @param prop The JSX attribute.
59
- */
60
- function getPropRemovalRange$1(context, prop) {
37
+ function getChildrenContentRange(node) {
38
+ const first = node.children.at(0);
39
+ const last = node.children.at(-1);
40
+ if (first == null || last == null) return null;
41
+ return [first.range[0], last.range[1]];
42
+ }
43
+ function getChildrenPropText(context, prop) {
44
+ const { sourceCode } = context;
45
+ const { value } = prop;
46
+ if (value == null) return null;
47
+ if (value.type === AST_NODE_TYPES.Literal) return escapeJsxText(String(value.value));
48
+ if (value.type !== AST_NODE_TYPES.JSXExpressionContainer) return null;
49
+ const { expression } = value;
50
+ if (expression.type === AST_NODE_TYPES.JSXEmptyExpression) return null;
51
+ const exprText = sourceCode.getText(expression);
52
+ if (Check.isJSXElementOrFragment(expression)) return exprText;
53
+ return `{${exprText}}`;
54
+ }
55
+ function escapeJsxText(text) {
56
+ return text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll("{", "&#123;").replaceAll("}", "&#125;");
57
+ }
58
+ function getPropRemovalRange(context, prop) {
61
59
  const { sourceCode } = context;
62
60
  let start = prop.range[0];
63
61
  const end = prop.range[1];
64
- while (start > 0 && /\s/.test(sourceCode.text[start - 1])) start--;
62
+ while (start > 0 && /\s/.test(sourceCode.text[start - 1] ?? "")) start--;
65
63
  return [start, end];
66
64
  }
67
- /**
68
- * Compute the range covering **all** children content of a JSX element or
69
- * fragment (from the start of the first child to the end of the last child).
70
- *
71
- * Returns `null` when there are no children at all.
72
- * @param node The JSX element or fragment.
73
- */
74
- function getChildrenContentRange(node) {
75
- if (node.children.length === 0) return null;
76
- const first = node.children[0];
77
- const last = node.children[node.children.length - 1];
78
- return [first.range[0], last.range[1]];
65
+ function isDirectJsxChild(node) {
66
+ return Check.isJSXElementOrFragment(node.parent);
79
67
  }
80
68
 
69
+ //#endregion
70
+ //#region src/utils/create-rule.ts
71
+ function getDocsUrl(ruleName) {
72
+ return `https://eslint-react.xyz/docs/rules/jsx-${ruleName}`;
73
+ }
74
+ const createRule = ESLintUtils.RuleCreator(getDocsUrl);
75
+
81
76
  //#endregion
82
77
  //#region src/rules/no-children-prop-with-children/no-children-prop-with-children.ts
83
78
  const RULE_NAME$7 = "no-children-prop-with-children";
@@ -103,8 +98,10 @@ function create$7(context) {
103
98
  CallExpression(node) {
104
99
  if (!core.isCreateElementCall(context, node)) return;
105
100
  const [, propsArg, firstExtra] = node.arguments;
106
- if (propsArg == null || propsArg.type !== AST_NODE_TYPES.ObjectExpression) return;
107
- const childrenProp = findChildrenProperty$1(propsArg);
101
+ if (propsArg == null) return;
102
+ const propsObject = Extract.unwrap(propsArg);
103
+ if (propsObject.type !== AST_NODE_TYPES.ObjectExpression) return;
104
+ const childrenProp = findChildrenProperty(propsObject);
108
105
  if (childrenProp == null) return;
109
106
  if (firstExtra == null) return;
110
107
  context.report({
@@ -128,7 +125,7 @@ function create$7(context) {
128
125
  node: childrenProp,
129
126
  suggest: [{
130
127
  fix(fixer) {
131
- const [start, end] = getPropRemovalRange$1(context, childrenProp);
128
+ const [start, end] = getPropRemovalRange(context, childrenProp);
132
129
  return fixer.removeRange([start, end]);
133
130
  },
134
131
  messageId: "removeChildrenProp"
@@ -145,61 +142,6 @@ function create$7(context) {
145
142
  };
146
143
  }
147
144
 
148
- //#endregion
149
- //#region src/rules/no-children-prop/lib.ts
150
- /**
151
- * Find a `children` property inside an ObjectExpression.
152
- * @param node The object expression.
153
- * @returns The Property node whose key is "children", or null.
154
- */
155
- function findChildrenProperty(node) {
156
- for (const prop of node.properties) {
157
- if (prop.type !== AST_NODE_TYPES.Property) continue;
158
- const key = Extract.unwrap(prop.key);
159
- if (key.type === AST_NODE_TYPES.Identifier && key.name === "children") return prop;
160
- if (key.type === AST_NODE_TYPES.Literal && key.value === "children") return prop;
161
- }
162
- return null;
163
- }
164
- /**
165
- * Compute the removal range for a JSX attribute, consuming any leading
166
- * whitespace (spaces, tabs, newlines) so the resulting markup stays clean.
167
- * @param context The rule context.
168
- * @param prop The JSX attribute.
169
- */
170
- function getPropRemovalRange(context, prop) {
171
- const { sourceCode } = context;
172
- let start = prop.range[0];
173
- const end = prop.range[1];
174
- while (start > 0 && /\s/.test(sourceCode.text[start - 1])) start--;
175
- return [start, end];
176
- }
177
- /**
178
- * Extract the text to use as JSX children content from a `children` prop.
179
- *
180
- * - `children="text"` -> `text` (raw string, no quotes)
181
- * - `children={<div />}` -> `<div />` (JSX element, no braces)
182
- * - `children={<>…</>}` -> `<>…</>` (JSX fragment, no braces)
183
- * - `children={expression}` -> `{expression}` (wrapped in braces)
184
- * - `children` -> `null` (boolean shorthand, cannot extract)
185
- * @param context The rule context.
186
- * @param prop The JSX attribute.
187
- */
188
- function getChildrenPropText(context, prop) {
189
- const { sourceCode } = context;
190
- const { value } = prop;
191
- if (value == null) return null;
192
- if (value.type === AST_NODE_TYPES.Literal) return String(value.value);
193
- if (value.type === AST_NODE_TYPES.JSXExpressionContainer) {
194
- const { expression } = value;
195
- if (expression.type === AST_NODE_TYPES.JSXEmptyExpression) return null;
196
- const exprText = sourceCode.getText(expression);
197
- if (Check.isJSXElementOrFragment(expression)) return exprText;
198
- return `{${exprText}}`;
199
- }
200
- return null;
201
- }
202
-
203
145
  //#endregion
204
146
  //#region src/rules/no-children-prop/no-children-prop.ts
205
147
  const RULE_NAME$6 = "no-children-prop";
@@ -224,8 +166,10 @@ function create$6(context) {
224
166
  CallExpression(node) {
225
167
  if (!core.isCreateElementCall(context, node)) return;
226
168
  const [, propsArg] = node.arguments;
227
- if (propsArg == null || propsArg.type !== AST_NODE_TYPES.ObjectExpression) return;
228
- const childrenProp = findChildrenProperty(propsArg);
169
+ if (propsArg == null) return;
170
+ const propsObject = Extract.unwrap(propsArg);
171
+ if (propsObject.type !== AST_NODE_TYPES.ObjectExpression) return;
172
+ const childrenProp = findChildrenProperty(propsObject);
229
173
  if (childrenProp == null) return;
230
174
  context.report({
231
175
  messageId: "default",
@@ -291,18 +235,14 @@ var no_comment_textnodes_default = createRule({
291
235
  defaultOptions: []
292
236
  });
293
237
  function create$5(context) {
294
- function hasCommentLike(node) {
295
- if (Check.isOneOf([AST_NODE_TYPES.JSXAttribute, AST_NODE_TYPES.JSXExpressionContainer])(node.parent)) return false;
296
- return /^\s*\/(?:\/|\*)/mu.test(context.sourceCode.getText(node));
297
- }
298
- const visit = (node) => {
299
- if (!Check.isJSXElementOrFragment(node.parent)) return;
300
- if (!hasCommentLike(node)) return;
238
+ function visit(node) {
239
+ if (!isDirectJsxChild(node)) return;
240
+ if (!/^\s*\/(?:\/|\*)/mu.test(context.sourceCode.getText(node))) return;
301
241
  context.report({
302
242
  messageId: "default",
303
243
  node
304
244
  });
305
- };
245
+ }
306
246
  return {
307
247
  JSXText: visit,
308
248
  Literal: visit
@@ -366,34 +306,30 @@ function create$3(context) {
366
306
  * Visitor function for JSXElement and JSXFragment nodes
367
307
  * @param node The JSXElement or JSXFragment node to be checked
368
308
  */
369
- const visit = (node) => {
309
+ function visit(node) {
370
310
  for (const [index, child] of node.children.entries()) {
371
311
  if (child.type !== AST_NODE_TYPES.JSXText || !child.value.endsWith("$")) continue;
372
312
  if (node.children[index + 1]?.type !== AST_NODE_TYPES.JSXExpressionContainer) continue;
373
313
  if (child.value === "$" && node.children.length === 2) continue;
374
- const pos = child.loc.end;
314
+ if (!context.sourceCode.getText(child).endsWith("$")) continue;
315
+ const dollarStart = child.range[1] - 1;
316
+ const dollarEnd = child.range[1];
375
317
  context.report({
376
318
  loc: {
377
- end: {
378
- column: pos.column,
379
- line: pos.line
380
- },
381
- start: {
382
- column: pos.column - 1,
383
- line: pos.line
384
- }
319
+ end: context.sourceCode.getLocFromIndex(dollarEnd),
320
+ start: context.sourceCode.getLocFromIndex(dollarStart)
385
321
  },
386
322
  messageId: "default",
387
323
  node: child,
388
324
  suggest: [{
389
325
  fix(fixer) {
390
- return fixer.removeRange([child.range[1] - 1, child.range[1]]);
326
+ return fixer.removeRange([dollarStart, dollarEnd]);
391
327
  },
392
328
  messageId: "removeDollarSign"
393
329
  }]
394
330
  });
395
331
  }
396
- };
332
+ }
397
333
  return {
398
334
  JSXElement: visit,
399
335
  JSXFragment: visit
@@ -419,23 +355,25 @@ var no_leaked_semicolon_default = createRule({
419
355
  defaultOptions: []
420
356
  });
421
357
  function hasLeakedSemicolon(text) {
422
- return text.startsWith(";\n") || text.startsWith(";\r");
358
+ return /^;[ \t]*(?:\r\n|\r|\n)/u.test(text);
423
359
  }
424
360
  function create$2(context) {
425
- const visit = (node) => {
426
- if (!Check.isJSXElementOrFragment(node.parent)) return;
361
+ function visit(node) {
362
+ if (!isDirectJsxChild(node)) return;
427
363
  if (!hasLeakedSemicolon(context.sourceCode.getText(node))) return;
364
+ const semicolonStart = node.range[0];
365
+ const semicolonEnd = node.range[0] + 1;
428
366
  context.report({
429
367
  messageId: "default",
430
368
  node,
431
369
  suggest: [{
432
370
  fix(fixer) {
433
- return fixer.removeRange([node.range[0], node.range[0] + 1]);
371
+ return fixer.removeRange([semicolonStart, semicolonEnd]);
434
372
  },
435
373
  messageId: "removeSemicolon"
436
374
  }]
437
375
  });
438
- };
376
+ }
439
377
  return {
440
378
  JSXText: visit,
441
379
  Literal: visit
@@ -459,7 +397,7 @@ var no_namespace_default = createRule({
459
397
  function create$1(context) {
460
398
  return { JSXElement(node) {
461
399
  const name = getElementFullType(node);
462
- if (typeof name !== "string" || !name.includes(":")) return;
400
+ if (!name.includes(":")) return;
463
401
  context.report({
464
402
  data: { name },
465
403
  messageId: "noNamespace",
@@ -513,30 +451,24 @@ function create(context, [option]) {
513
451
  function isContentUseless(node) {
514
452
  if (node.children.length === 0) return !allowEmptyFragment;
515
453
  const insideJsx = Check.isJSXElementOrFragment(node.parent);
516
- if (!allowExpressions) {
517
- if (insideJsx) return true;
518
- if (node.children.length === 1) return true;
519
- }
520
- if (allowExpressions && !insideJsx && node.children.length === 1) {
521
- const child = node.children[0];
522
- if (child != null && child.type === AST_NODE_TYPES.JSXText) return false;
523
- }
454
+ if (allowExpressions && !insideJsx && node.children.length === 1 && node.children[0]?.type === AST_NODE_TYPES.JSXText) return false;
524
455
  const meaningful = getChildren(node);
525
456
  if (meaningful.length === 0) return true;
526
- if (meaningful.length === 1 && meaningful[0].type !== AST_NODE_TYPES.JSXExpressionContainer) return true;
527
- return false;
457
+ if (meaningful.length > 1) return false;
458
+ if (meaningful[0]?.type === AST_NODE_TYPES.JSXExpressionContainer) return !allowExpressions;
459
+ return true;
528
460
  }
529
461
  /**
530
462
  * Whether it is safe to auto-fix the fragment by unwrapping it.
531
463
  * @param node The fragment node to check.
532
464
  */
533
465
  function isSafeToFix(node) {
466
+ if (node.type === AST_NODE_TYPES.JSXElement && node.openingElement.attributes.some((attr) => attr.type === AST_NODE_TYPES.JSXSpreadAttribute)) return false;
534
467
  if (Check.isJSXElementOrFragment(node.parent)) return isHostElement(node.parent);
535
468
  if (node.children.length === 0) return false;
536
- return !node.children.some((child) => {
537
- if (child.type === AST_NODE_TYPES.JSXExpressionContainer) return true;
538
- if (child.type === AST_NODE_TYPES.JSXText) return !isWhitespaceText(child);
539
- return false;
469
+ return node.children.every((child) => {
470
+ if (Check.isJSXElementOrFragment(child)) return true;
471
+ return child.type === AST_NODE_TYPES.JSXText && isWhitespaceText(child);
540
472
  });
541
473
  }
542
474
  /**
@@ -552,7 +484,12 @@ function create(context, [option]) {
552
484
  const cleaned = collapseMultilineText(child.value);
553
485
  if (cleaned != null) text += cleaned;
554
486
  } else text += context.sourceCode.getText(child);
555
- return fixer.replaceText(node, text);
487
+ let start = node.range[0];
488
+ if (text === "" && node.children.length > 0) {
489
+ const lineStart = Math.max(context.sourceCode.text.lastIndexOf("\n", start - 1), context.sourceCode.text.lastIndexOf("\r", start - 1)) + 1;
490
+ if (/^[ \t]*$/u.test(context.sourceCode.text.slice(lineStart, start))) start = lineStart;
491
+ }
492
+ return fixer.replaceTextRange([start, node.range[1]], text);
556
493
  };
557
494
  }
558
495
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-react-jsx",
3
- "version": "5.15.0",
3
+ "version": "5.16.1",
4
4
  "description": "ESLint React's ESLint plugin for React Flavored JSX rules.",
5
5
  "keywords": [
6
6
  "react",
@@ -39,11 +39,11 @@
39
39
  "dependencies": {
40
40
  "@typescript-eslint/types": "^8.64.0",
41
41
  "@typescript-eslint/utils": "^8.64.0",
42
- "@eslint-react/eslint": "5.15.0",
43
- "@eslint-react/core": "5.15.0",
44
- "@eslint-react/jsx": "5.15.0",
45
- "@eslint-react/ast": "5.15.0",
46
- "@eslint-react/shared": "5.15.0"
42
+ "@eslint-react/ast": "5.16.1",
43
+ "@eslint-react/eslint": "5.16.1",
44
+ "@eslint-react/jsx": "5.16.1",
45
+ "@eslint-react/shared": "5.16.1",
46
+ "@eslint-react/core": "5.16.1"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/react": "^19.2.17",