@eslint-react/jsx 5.20.0 → 5.20.2

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 (3) hide show
  1. package/dist/index.d.ts +83 -147
  2. package/dist/index.js +77 -139
  3. package/package.json +6 -6
package/dist/index.d.ts CHANGED
@@ -3,15 +3,12 @@ import { TSESTree } from "@typescript-eslint/types";
3
3
  import { RuleContext } from "@eslint-react/eslint";
4
4
  //#region src/attribute-find.d.ts
5
5
  /**
6
- * Find a JSX attribute (or spread attribute containing the property) by name on a given element.
7
- *
8
- * Returns the last matching attribute to mirror React's behavior where later props win,
9
- * or `undefined` when the attribute is not present.
6
+ * Find a JSX attribute (or a spread attribute containing the property) by name.
10
7
  *
8
+ * Returns the last matching attribute to mirror React's behavior where later props win.
11
9
  * Spread attributes are resolved when possible: if the spread argument is an identifier
12
- * that resolves to an object expression, the object's properties are searched for a matching key.
13
- * Nested object expressions and nested spread identifiers are also resolved
14
- * (see {@link findSpreadProperty}).
10
+ * that resolves to an object expression, the object's properties are searched for a
11
+ * matching key (see {@link findSpreadProperty}).
15
12
  * @param context The ESLint rule context (needed for variable resolution in spread attributes).
16
13
  * @param element The `JSXElement` node to search.
17
14
  * @param name The attribute name to look for (ex: "className").
@@ -19,14 +16,10 @@ import { RuleContext } from "@eslint-react/eslint";
19
16
  */
20
17
  export declare function findAttribute(context: RuleContext, element: TSESTree.JSXElement, name: string): TSESTreeJSXAttributeLike | undefined;
21
18
  /**
22
- * Walk up the AST from `node` to find the nearest ancestor that is a `JSXAttribute`
23
- * and (optionally) passes a predicate.
24
- *
25
- * This is useful when a rule visitor enters a deeply nested node (ex: a `Literal`
26
- * inside an expression container) and needs to know which JSX attribute it belongs to.
19
+ * Walk up the AST from `node` to find the nearest `JSXAttribute` ancestor, optionally matching a predicate.
27
20
  * @param node The starting node for the upward search.
28
- * @param test Optional predicate to filter candidate `JSXAttribute` nodes. When omitted every `JSXAttribute` ancestor matches.
29
- * @returns The first matching `JSXAttribute` ancestor, or `undefined` if none is found before reaching the root.
21
+ * @param test Optional predicate to filter candidate `JSXAttribute` nodes.
22
+ * @returns The first matching `JSXAttribute` ancestor, or `undefined` when none is found.
30
23
  */
31
24
  export declare function findParentAttribute(node: TSESTree.Node, test?: (node: TSESTree.JSXAttribute) => boolean): TSESTree.JSXAttribute | undefined;
32
25
  /**
@@ -55,43 +48,30 @@ export declare function findSpreadProperty(context: RuleContext, argument: TSEST
55
48
  //#endregion
56
49
  //#region src/attribute-has.d.ts
57
50
  /**
58
- * Check whether a JSX element carries a given attribute (prop).
59
- *
60
- * This is a thin convenience wrapper around {@link findAttribute} for the
61
- * common case where you only need a boolean answer.
51
+ * Check if the element has an attribute with the given name.
62
52
  *
63
53
  * Spread attributes are taken into account: `<Comp {...{ disabled: true }} />`
64
- * will report `true` for `"disabled"`.
54
+ * reports `true` for `"disabled"` (see {@link findAttribute}).
65
55
  * @param context The ESLint rule context (needed for variable resolution in spread attributes).
66
- * @param element The `JSXElement` node to inspect.
56
+ * @param element The `JSXElement` node to check.
67
57
  * @param name The attribute name to look for (ex: "className").
68
- * @returns `true` when the attribute is present on the element.
58
+ * @returns `true` if the attribute is present on the element.
69
59
  */
70
60
  export declare function hasAttribute(context: RuleContext, element: TSESTree.JSXElement, name: string): boolean;
71
61
  /**
72
- * Check whether a JSX element carries at least one of the given attributes.
73
- *
74
- * This is a batch variant of {@link hasAttribute} for the common pattern of
75
- * short-circuiting on multiple prop names.
76
- *
77
- * Spread attributes are taken into account (see {@link findAttribute}).
62
+ * Check if the element has at least one of the given attributes.
78
63
  * @param context The ESLint rule context (needed for variable resolution in spread attributes).
79
- * @param element The `JSXElement` node to inspect.
64
+ * @param element The `JSXElement` node to check.
80
65
  * @param names The attribute names to look for.
81
- * @returns `true` when at least one of the attributes is present.
66
+ * @returns `true` if at least one of the attributes is present.
82
67
  */
83
68
  export declare function hasAnyAttribute(context: RuleContext, element: TSESTree.JSXElement, names: string[]): boolean;
84
69
  /**
85
- * Check whether a JSX element carries all of the given attributes (props).
86
- *
87
- * This is a batch variant of {@link hasAttribute} for the common pattern
88
- * where a rule needs to verify that a set of required props are all present.
89
- *
90
- * Spread attributes are taken into account (see {@link findAttribute}).
70
+ * Check if the element has all of the given attributes.
91
71
  * @param context The ESLint rule context (needed for variable resolution in spread attributes).
92
- * @param element The `JSXElement` node to inspect.
72
+ * @param element The `JSXElement` node to check.
93
73
  * @param names The attribute names to look for.
94
- * @returns `true` when every name in `names` is present on the element.
74
+ * @returns `true` if every attribute is present on the element.
95
75
  */
96
76
  export declare function hasEveryAttribute(context: RuleContext, element: TSESTree.JSXElement, names: string[]): boolean;
97
77
  //#endregion
@@ -99,16 +79,15 @@ export declare function hasEveryAttribute(context: RuleContext, element: TSESTre
99
79
  /**
100
80
  * Get the stringified name of a `JSXAttribute` node.
101
81
  *
102
- * Handles both simple identifiers and namespaced names:
103
82
  * - `className` -> `"className"`
104
83
  * - `aria-label` -> `"aria-label"`
105
84
  * - `xml:space` -> `"xml:space"`.
106
- * @param node A `JSXAttribute` AST node.
85
+ * @param node The `JSXAttribute` node to get the name from.
107
86
  * @returns The attribute name as a plain string.
108
87
  */
109
88
  export declare function getAttributeName(node: TSESTree.JSXAttribute): string;
110
89
  /**
111
- * Check whether a node is a `JSXAttribute` with the given name.
90
+ * Check if the node is a `JSXAttribute` with the given name.
112
91
  *
113
92
  * Only plain identifier names are matched (ex: `className`); namespaced
114
93
  * attributes (ex: `xml:space`) do not match.
@@ -116,9 +95,9 @@ export declare function getAttributeName(node: TSESTree.JSXAttribute): string;
116
95
  * Supports both data-first and data-last (curried) call styles:
117
96
  * - `isAttribute(node, "className")`
118
97
  * - `isAttribute("className")(node)`.
119
- * @param node The AST node to test.
98
+ * @param node The node to check.
120
99
  * @param name The attribute name to match (ex: "className").
121
- * @returns `true` when the node is a `JSXAttribute` named `name`.
100
+ * @returns `true` if the node is a `JSXAttribute` named `name`.
122
101
  */
123
102
  export declare const isAttribute: {
124
103
  (name: string): (node: TSESTree.Node) => node is TSESTree.JSXAttribute;
@@ -129,13 +108,11 @@ export declare const isAttribute: {
129
108
  /**
130
109
  * Discriminated union representing the resolved value of a JSX attribute.
131
110
  *
132
- * Each variant carries the original AST `node` (where applicable — the
133
- * `boolean` variant has no value node and reports `null`) and a `toStatic()`
134
- * helper that attempts to collapse the value into a plain JavaScript value
135
- * at analysis time.
136
- *
137
- * `toStatic()` returns `undefined` whenever no static value is available;
138
- * structural information is carried by `kind`, value information by `toStatic()`.
111
+ * Each variant carries the original AST `node` (the `boolean` variant has no value
112
+ * node and reports `null`) and a `toStatic()` helper that attempts to collapse the
113
+ * value into a plain JavaScript value at analysis time. `toStatic()` returns
114
+ * `undefined` when no static value is available; structural information is carried
115
+ * by `kind`, value information by `toStatic()`.
139
116
  */
140
117
  type AttributeValue = {
141
118
  readonly kind: "boolean";
@@ -168,17 +145,11 @@ type AttributeValue = {
168
145
  toStatic(): unknown;
169
146
  };
170
147
  /**
171
- * Resolve the value of a JSX attribute (or spread attribute) into an
172
- * {@link AttributeValue} descriptor that can be inspected further.
173
- *
174
- * This is the low-level building block; it operates on a single attribute
175
- * node that the caller has already located. For the higher-level "find by
176
- * name and resolve" combo, see {@link getAttributeValue}.
148
+ * Resolve the value of a JSX attribute (or spread attribute) into an {@link AttributeValue} descriptor.
177
149
  *
178
- * When the attribute is a `JSXSpreadAttribute`, passing `name` (typically the
179
- * same name the attribute was found by) makes `toStatic()` return the static
180
- * value of that named property, eliminating the need to branch on
181
- * `kind === "spreadProps"` at the call site.
150
+ * When the attribute is a `JSXSpreadAttribute`, passing `name` (typically the name
151
+ * the attribute was found by) makes `toStatic()` return the static value of that
152
+ * named property. For the higher-level "find by name and resolve" combo, see {@link getAttributeValue}.
182
153
  * @param context The ESLint rule context (needed for scope look-ups).
183
154
  * @param attribute A `JSXAttribute` or `JSXSpreadAttribute` node.
184
155
  * @param name Optional property name used to resolve `toStatic()` for spread attributes.
@@ -188,28 +159,20 @@ export declare function resolveAttributeValue(context: RuleContext, attribute: T
188
159
  /**
189
160
  * Find an attribute by name on a JSX element and resolve its value in a single call.
190
161
  *
191
- * This is a convenience composition of {@link findAttribute} and
192
- * {@link resolveAttributeValue} that eliminates the most common two-step
193
- * pattern in lint rules.
162
+ * Convenience composition of {@link findAttribute} and {@link resolveAttributeValue}.
194
163
  * @param context The ESLint rule context.
195
164
  * @param element The `JSXElement` node to search.
196
165
  * @param name The attribute name to look up (ex: "className").
197
- * @returns An {@link AttributeValue} descriptor, or `undefined` when the attribute is not present on the element.
166
+ * @returns An {@link AttributeValue} descriptor, or `undefined` when the attribute is not present.
198
167
  */
199
168
  export declare function getAttributeValue(context: RuleContext, element: TSESTree.JSXElement, name: string): AttributeValue | undefined;
200
169
  /**
201
- * Find an attribute by name on a JSX element and collapse its value to a plain
202
- * JavaScript value in a single step.
170
+ * Find an attribute by name on a JSX element and collapse its value to a plain JavaScript value.
203
171
  *
204
- * This is a convenience composition of {@link findAttribute} ->
205
- * {@link resolveAttributeValue} -> `toStatic()`, with automatic handling of the
206
- * `spreadProps` case (extracts the named property from the spread object).
207
- *
208
- * Returns `undefined` both when the attribute is absent and when its value
209
- * cannot be statically determined; use {@link findAttribute} or
210
- * {@link hasAttribute} when presence itself matters.
172
+ * Returns `undefined` both when the attribute is absent and when its value cannot
173
+ * be statically determined; use {@link hasAttribute} when presence itself matters.
211
174
  * @param context The ESLint rule context.
212
- * @param element The `JSXElement` node to inspect.
175
+ * @param element The `JSXElement` node to check.
213
176
  * @param name The attribute name to look up (ex: "className").
214
177
  * @returns The static value of the attribute, or `undefined` when absent or indeterminate.
215
178
  */
@@ -226,30 +189,18 @@ export declare function getAttributeStaticValue(context: RuleContext, element: T
226
189
  * 4. Skip empty string expressions (`{""}`), which produce no DOM node.
227
190
  * 5. Collect everything else.
228
191
  * @param element A `JSXElement` or `JSXFragment` node.
229
- * @returns An array of children nodes that contribute to rendered output.
192
+ * @returns The children nodes that contribute to rendered output.
230
193
  */
231
194
  export declare function getChildren(element: TSESTreeJSXElementLike): TSESTree.JSXChild[];
232
195
  /**
233
- * Check whether a JSX element (or fragment) has meaningful children, that is,
234
- * at least one child that is not purely whitespace text or an empty string expression.
235
- *
236
- * A `JSXText` child whose `raw` content is empty after trimming is considered
237
- * non-meaningful because it is typically a code-formatting artifact
238
- * (indentation between tags). While React's client renderer preserves these
239
- * nodes as text nodes, they rarely represent intentionally rendered content.
196
+ * Check if the element has at least one meaningful child, that is, a child that
197
+ * is not purely whitespace text or an empty string expression (`{""}`).
240
198
  *
241
- * An empty string expression (`children={""}`) is also considered
242
- * non-meaningful because React's reconciler and SSR renderer explicitly skip
243
- * empty strings, producing no DOM node.
244
- *
245
- * Unlike {@link getChildren} (which only filters whitespace that contains a
246
- * newline) this check treats any whitespace-only text as non-meaningful
247
- * (see {@link isWhitespaceText}). As a result `hasChildren(node)` is not
248
- * always equal to `getChildren(node).length > 0`: they differ for
249
- * whitespace-only children that have no newline, such as `<div> </div>` or
250
- * `<div>\t\t</div>`. Choose the API that matches your rule's intent.
199
+ * Unlike {@link getChildren} (which only filters whitespace containing a newline),
200
+ * this check treats any whitespace-only text as non-meaningful, so `hasChildren(node)`
201
+ * is not always equal to `getChildren(node).length > 0` (ex: `<div> </div>`).
251
202
  * @param element A `JSXElement` or `JSXFragment` node.
252
- * @returns `true` when the element has at least one meaningful child.
203
+ * @returns `true` if the element has at least one meaningful child.
253
204
  */
254
205
  export declare function hasChildren(element: TSESTreeJSXElementLike): boolean;
255
206
  //#endregion
@@ -263,42 +214,34 @@ export declare function hasChildren(element: TSESTreeJSXElementLike): boolean;
263
214
  */
264
215
  type ElementTest = string | readonly string[] | ((elementType: string, node: TSESTreeJSXElementLike) => boolean);
265
216
  /**
266
- * Check whether a node is a `JSXElement` (or `JSXFragment`) and optionally
267
- * matches a given test.
268
- *
269
- * Modelled after
270
- * [`hast-util-is-element`](https://github.com/syntax-tree/hast-util-is-element):
271
- * the `test` parameter controls what counts as a match.
217
+ * Check if the node is a `JSXElement` (or `JSXFragment`), optionally matching a given test.
272
218
  *
273
- * When called without a test, the function acts as a simple type-guard
274
- * for `JSXElement | JSXFragment`.
275
- * @param node The AST node to test.
219
+ * Modelled after [`hast-util-is-element`](https://github.com/syntax-tree/hast-util-is-element):
220
+ * the `test` parameter controls what counts as a match. When called without a test,
221
+ * the function acts as a simple type guard for `JSXElement | JSXFragment`.
222
+ * @param node The node to check.
276
223
  * @param test Optional test to match the element type against.
277
- * @returns `true` when the node is a matching JSX element.
224
+ * @returns `true` if the node is a matching JSX element.
278
225
  */
279
226
  export declare function isElement(node: TSESTree.Node | null | undefined, test?: ElementTest): node is TSESTreeJSXElementLike;
280
227
  /**
281
- * Check whether a node is a React Fragment element.
282
- *
283
- * Recognizes both the shorthand `<>...</>` syntax (`JSXFragment`) and the
284
- * explicit `<Fragment>` / `<React.Fragment>` form (`JSXElement`).
285
- *
286
- * The comparison is performed against the self name (last dot-separated
287
- * segment) of both the node and the configured factory, so `<React.Fragment>`
288
- * matches `"React.Fragment"` and `<Fragment>` matches `"Fragment"`.
289
- * @param node The AST node to test.
228
+ * Check if the node is a React Fragment element.
229
+ *
230
+ * Recognizes both the shorthand `<>...</>` syntax (`JSXFragment`) and the explicit
231
+ * `<Fragment>` / `<React.Fragment>` form (`JSXElement`). The comparison is performed
232
+ * against the self name (last dot-separated segment) of both the node and the
233
+ * configured factory, so `<React.Fragment>` matches `"React.Fragment"` and
234
+ * `<Fragment>` matches `"Fragment"`.
235
+ * @param node The node to check.
290
236
  * @param jsxFragmentFactory The configured fragment factory string (ex: "React.Fragment").
291
- * @returns `true` when the node represents a React Fragment.
237
+ * @returns `true` if the node represents a React Fragment.
292
238
  */
293
239
  export declare function isFragmentElement(node: TSESTree.Node, jsxFragmentFactory?: string): node is TSESTreeJSXElementLike;
294
240
  /**
295
- * Check whether a node is a host (intrinsic / DOM) element.
296
- *
297
- * A host element is a `JSXElement` whose tag name is a plain `JSXIdentifier`
298
- * starting with a lowercase letter, the same heuristic React uses to
299
- * distinguish `<div>` from `<MyComponent>`.
300
- * @param node The AST node to test.
301
- * @returns `true` when the node is a `JSXElement` with a lowercase tag name.
241
+ * Check if the node is a host (intrinsic / DOM) element, that is, a `JSXElement`
242
+ * whose tag name starts with a lowercase letter (ex: `<div>` vs `<MyComponent>`).
243
+ * @param node The node to check.
244
+ * @returns `true` if the node is a `JSXElement` with a lowercase tag name.
302
245
  */
303
246
  export declare function isHostElement(node: TSESTree.Node): node is TSESTree.JSXElement;
304
247
  //#endregion
@@ -311,8 +254,8 @@ export declare function isHostElement(node: TSESTree.Node): node is TSESTree.JSX
311
254
  * - `<React.Fragment>` -> `"React.Fragment"`
312
255
  * - `<xml:space>` -> `"xml:space"`
313
256
  * - `<></>` -> `""`.
314
- * @param node A `JSXElement` or `JSXFragment` node.
315
- * @returns The fully-qualified element type string.
257
+ * @param node The `JSXElement` or `JSXFragment` node.
258
+ * @returns The fully qualified element type string.
316
259
  */
317
260
  export declare function getElementFullType(node: TSESTreeJSXElementLike): string;
318
261
  /**
@@ -321,7 +264,7 @@ export declare function getElementFullType(node: TSESTreeJSXElementLike): string
321
264
  * - `<Foo.Bar.Baz>` -> `"Baz"`
322
265
  * - `<div>` -> `"div"`
323
266
  * - `<></>` -> `""`.
324
- * @param node A `JSXElement` or `JSXFragment` node.
267
+ * @param node The `JSXElement` or `JSXFragment` node.
325
268
  * @returns The last segment of the element type, or `""` for fragments.
326
269
  */
327
270
  export declare function getElementSelfType(node: TSESTreeJSXElementLike): string;
@@ -330,51 +273,44 @@ export declare function getElementSelfType(node: TSESTreeJSXElementLike): string
330
273
  /**
331
274
  * Collapse a multiline JSX text string following React's whitespace rules.
332
275
  *
333
- * This mirrors Babel's `cleanJSXElementLiteralChild` algorithm:
276
+ * Mirrors Babel's `cleanJSXElementLiteralChild` algorithm:
334
277
  * 1. Split the raw text into lines.
335
278
  * 2. Find the last non-empty line.
336
279
  * 3. Trim leading spaces on non-first lines and trailing spaces on non-last lines.
337
280
  * 4. Collapse tabs into spaces.
338
281
  * 5. Append a single space after each non-last non-empty line.
339
282
  * @param text The raw JSX text string to collapse.
340
- * @returns The collapsed string, or `null` if the text contains only whitespace.
283
+ * @returns The collapsed string, or `null` when the text contains only whitespace.
341
284
  * @see https://github.com/babel/babel/blob/main/packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts
342
285
  */
343
286
  export declare function collapseMultilineText(text: string): string | null;
344
287
  /**
345
- * Check whether a JSX child node is whitespace padding that React would
346
- * trim away during rendering.
347
- *
348
- * A child is considered whitespace padding when it is a `JSXText` node whose
349
- * content is empty after applying React's whitespace normalization
350
- * (see {@link collapseMultilineText}, modelled after Babel's
351
- * `cleanJSXElementLiteralChild`) **and** it contains a newline. This is the
352
- * whitespace that appears between JSX tags purely for formatting.
288
+ * Check if the node is whitespace padding that React would trim away during
289
+ * rendering, that is, a `JSXText` node that cleans to nothing (see
290
+ * {@link collapseMultilineText}) and contains a newline.
353
291
  *
354
292
  * For the looser "any whitespace-only text" check, see {@link isWhitespaceText}.
355
- * @param node A JSX child node.
356
- * @returns `true` when the node is purely formatting whitespace.
293
+ * @param node The JSX child node to check.
294
+ * @returns `true` if the node is purely formatting whitespace.
357
295
  */
358
296
  export declare function isPaddingWhitespace(node: TSESTree.JSXChild): boolean;
359
297
  /**
360
- * Check whether a JSX child node is any whitespace-only text.
298
+ * Check if the node is whitespace-only text.
361
299
  *
362
- * This is a looser variant of {@link isPaddingWhitespace}; it matches every
363
- * `JSXText` node whose raw content is empty after trimming, regardless of
364
- * whether it contains a newline.
365
- * @param node A JSX child node.
366
- * @returns `true` when the node is a whitespace-only `JSXText`.
300
+ * Looser variant of {@link isPaddingWhitespace}; matches every `JSXText` node
301
+ * whose raw content is empty after trimming, regardless of newlines.
302
+ * @param node The JSX child node to check.
303
+ * @returns `true` if the node is a whitespace-only `JSXText`.
367
304
  */
368
305
  export declare function isWhitespaceText(node: TSESTree.JSXChild): boolean;
369
306
  /**
370
- * Check whether a JSX child node is an empty string expression (`{""}`).
307
+ * Check if the node is an empty string expression (`{""}`).
371
308
  *
372
- * React's reconciler and SSR renderer explicitly skip empty strings,
373
- * producing no DOM node (see `ReactChildFiber.js` and `ReactFizzConfigDOM.js`).
374
- * Such expressions are therefore treated as non-rendered children, in the same
375
- * way as whitespace padding.
376
- * @param node A JSX child node.
377
- * @returns `true` when the node is a `{""}` expression container.
309
+ * React's reconciler and SSR renderer explicitly skip empty strings, producing no
310
+ * DOM node, so such expressions are treated as non-rendered children, same as
311
+ * whitespace padding.
312
+ * @param node The JSX child node to check.
313
+ * @returns `true` if the node is a `{""}` expression container.
378
314
  */
379
315
  export declare function isEmptyStringExpression(node: TSESTree.JSXChild): boolean;
380
316
  //#endregion
package/dist/index.js CHANGED
@@ -481,11 +481,10 @@ const takeWhile = dual(2, (xs, pred) => {
481
481
  /**
482
482
  * Get the stringified name of a `JSXAttribute` node.
483
483
  *
484
- * Handles both simple identifiers and namespaced names:
485
484
  * - `className` -> `"className"`
486
485
  * - `aria-label` -> `"aria-label"`
487
486
  * - `xml:space` -> `"xml:space"`.
488
- * @param node A `JSXAttribute` AST node.
487
+ * @param node The `JSXAttribute` node to get the name from.
489
488
  * @returns The attribute name as a plain string.
490
489
  */
491
490
  function getAttributeName(node) {
@@ -493,7 +492,7 @@ function getAttributeName(node) {
493
492
  return node.name.namespace.name + ":" + node.name.name.name;
494
493
  }
495
494
  /**
496
- * Check whether a node is a `JSXAttribute` with the given name.
495
+ * Check if the node is a `JSXAttribute` with the given name.
497
496
  *
498
497
  * Only plain identifier names are matched (ex: `className`); namespaced
499
498
  * attributes (ex: `xml:space`) do not match.
@@ -501,9 +500,9 @@ function getAttributeName(node) {
501
500
  * Supports both data-first and data-last (curried) call styles:
502
501
  * - `isAttribute(node, "className")`
503
502
  * - `isAttribute("className")(node)`.
504
- * @param node The AST node to test.
503
+ * @param node The node to check.
505
504
  * @param name The attribute name to match (ex: "className").
506
- * @returns `true` when the node is a `JSXAttribute` named `name`.
505
+ * @returns `true` if the node is a `JSXAttribute` named `name`.
507
506
  */
508
507
  const isAttribute = dual(2, (node, name) => {
509
508
  return node.type === AST_NODE_TYPES.JSXAttribute && node.name.type === AST_NODE_TYPES.JSXIdentifier && node.name.name === name;
@@ -512,15 +511,12 @@ const isAttribute = dual(2, (node, name) => {
512
511
  //#endregion
513
512
  //#region src/attribute-find.ts
514
513
  /**
515
- * Find a JSX attribute (or spread attribute containing the property) by name on a given element.
516
- *
517
- * Returns the last matching attribute to mirror React's behavior where later props win,
518
- * or `undefined` when the attribute is not present.
514
+ * Find a JSX attribute (or a spread attribute containing the property) by name.
519
515
  *
516
+ * Returns the last matching attribute to mirror React's behavior where later props win.
520
517
  * Spread attributes are resolved when possible: if the spread argument is an identifier
521
- * that resolves to an object expression, the object's properties are searched for a matching key.
522
- * Nested object expressions and nested spread identifiers are also resolved
523
- * (see {@link findSpreadProperty}).
518
+ * that resolves to an object expression, the object's properties are searched for a
519
+ * matching key (see {@link findSpreadProperty}).
524
520
  * @param context The ESLint rule context (needed for variable resolution in spread attributes).
525
521
  * @param element The `JSXElement` node to search.
526
522
  * @param name The attribute name to look for (ex: "className").
@@ -533,14 +529,10 @@ function findAttribute(context, element, name) {
533
529
  });
534
530
  }
535
531
  /**
536
- * Walk up the AST from `node` to find the nearest ancestor that is a `JSXAttribute`
537
- * and (optionally) passes a predicate.
538
- *
539
- * This is useful when a rule visitor enters a deeply nested node (ex: a `Literal`
540
- * inside an expression container) and needs to know which JSX attribute it belongs to.
532
+ * Walk up the AST from `node` to find the nearest `JSXAttribute` ancestor, optionally matching a predicate.
541
533
  * @param node The starting node for the upward search.
542
- * @param test Optional predicate to filter candidate `JSXAttribute` nodes. When omitted every `JSXAttribute` ancestor matches.
543
- * @returns The first matching `JSXAttribute` ancestor, or `undefined` if none is found before reaching the root.
534
+ * @param test Optional predicate to filter candidate `JSXAttribute` nodes.
535
+ * @returns The first matching `JSXAttribute` ancestor, or `undefined` when none is found.
544
536
  */
545
537
  function findParentAttribute(node, test = () => true) {
546
538
  const guard = (n) => {
@@ -605,47 +597,34 @@ function findSpreadProperty(context, argument, name, seen = /* @__PURE__ */ new
605
597
  //#endregion
606
598
  //#region src/attribute-has.ts
607
599
  /**
608
- * Check whether a JSX element carries a given attribute (prop).
609
- *
610
- * This is a thin convenience wrapper around {@link findAttribute} for the
611
- * common case where you only need a boolean answer.
600
+ * Check if the element has an attribute with the given name.
612
601
  *
613
602
  * Spread attributes are taken into account: `<Comp {...{ disabled: true }} />`
614
- * will report `true` for `"disabled"`.
603
+ * reports `true` for `"disabled"` (see {@link findAttribute}).
615
604
  * @param context The ESLint rule context (needed for variable resolution in spread attributes).
616
- * @param element The `JSXElement` node to inspect.
605
+ * @param element The `JSXElement` node to check.
617
606
  * @param name The attribute name to look for (ex: "className").
618
- * @returns `true` when the attribute is present on the element.
607
+ * @returns `true` if the attribute is present on the element.
619
608
  */
620
609
  function hasAttribute(context, element, name) {
621
610
  return findAttribute(context, element, name) != null;
622
611
  }
623
612
  /**
624
- * Check whether a JSX element carries at least one of the given attributes.
625
- *
626
- * This is a batch variant of {@link hasAttribute} for the common pattern of
627
- * short-circuiting on multiple prop names.
628
- *
629
- * Spread attributes are taken into account (see {@link findAttribute}).
613
+ * Check if the element has at least one of the given attributes.
630
614
  * @param context The ESLint rule context (needed for variable resolution in spread attributes).
631
- * @param element The `JSXElement` node to inspect.
615
+ * @param element The `JSXElement` node to check.
632
616
  * @param names The attribute names to look for.
633
- * @returns `true` when at least one of the attributes is present.
617
+ * @returns `true` if at least one of the attributes is present.
634
618
  */
635
619
  function hasAnyAttribute(context, element, names) {
636
620
  return names.some((name) => findAttribute(context, element, name) != null);
637
621
  }
638
622
  /**
639
- * Check whether a JSX element carries all of the given attributes (props).
640
- *
641
- * This is a batch variant of {@link hasAttribute} for the common pattern
642
- * where a rule needs to verify that a set of required props are all present.
643
- *
644
- * Spread attributes are taken into account (see {@link findAttribute}).
623
+ * Check if the element has all of the given attributes.
645
624
  * @param context The ESLint rule context (needed for variable resolution in spread attributes).
646
- * @param element The `JSXElement` node to inspect.
625
+ * @param element The `JSXElement` node to check.
647
626
  * @param names The attribute names to look for.
648
- * @returns `true` when every name in `names` is present on the element.
627
+ * @returns `true` if every attribute is present on the element.
649
628
  */
650
629
  function hasEveryAttribute(context, element, names) {
651
630
  return names.every((name) => findAttribute(context, element, name) != null);
@@ -654,17 +633,11 @@ function hasEveryAttribute(context, element, names) {
654
633
  //#endregion
655
634
  //#region src/attribute-value.ts
656
635
  /**
657
- * Resolve the value of a JSX attribute (or spread attribute) into an
658
- * {@link AttributeValue} descriptor that can be inspected further.
659
- *
660
- * This is the low-level building block; it operates on a single attribute
661
- * node that the caller has already located. For the higher-level "find by
662
- * name and resolve" combo, see {@link getAttributeValue}.
636
+ * Resolve the value of a JSX attribute (or spread attribute) into an {@link AttributeValue} descriptor.
663
637
  *
664
- * When the attribute is a `JSXSpreadAttribute`, passing `name` (typically the
665
- * same name the attribute was found by) makes `toStatic()` return the static
666
- * value of that named property, eliminating the need to branch on
667
- * `kind === "spreadProps"` at the call site.
638
+ * When the attribute is a `JSXSpreadAttribute`, passing `name` (typically the name
639
+ * the attribute was found by) makes `toStatic()` return the static value of that
640
+ * named property. For the higher-level "find by name and resolve" combo, see {@link getAttributeValue}.
668
641
  * @param context The ESLint rule context (needed for scope look-ups).
669
642
  * @param attribute A `JSXAttribute` or `JSXSpreadAttribute` node.
670
643
  * @param name Optional property name used to resolve `toStatic()` for spread attributes.
@@ -677,13 +650,11 @@ function resolveAttributeValue(context, attribute, name) {
677
650
  /**
678
651
  * Find an attribute by name on a JSX element and resolve its value in a single call.
679
652
  *
680
- * This is a convenience composition of {@link findAttribute} and
681
- * {@link resolveAttributeValue} that eliminates the most common two-step
682
- * pattern in lint rules.
653
+ * Convenience composition of {@link findAttribute} and {@link resolveAttributeValue}.
683
654
  * @param context The ESLint rule context.
684
655
  * @param element The `JSXElement` node to search.
685
656
  * @param name The attribute name to look up (ex: "className").
686
- * @returns An {@link AttributeValue} descriptor, or `undefined` when the attribute is not present on the element.
657
+ * @returns An {@link AttributeValue} descriptor, or `undefined` when the attribute is not present.
687
658
  */
688
659
  function getAttributeValue(context, element, name) {
689
660
  const attr = findAttribute(context, element, name);
@@ -691,18 +662,12 @@ function getAttributeValue(context, element, name) {
691
662
  return resolveAttributeValue(context, attr, name);
692
663
  }
693
664
  /**
694
- * Find an attribute by name on a JSX element and collapse its value to a plain
695
- * JavaScript value in a single step.
665
+ * Find an attribute by name on a JSX element and collapse its value to a plain JavaScript value.
696
666
  *
697
- * This is a convenience composition of {@link findAttribute} ->
698
- * {@link resolveAttributeValue} -> `toStatic()`, with automatic handling of the
699
- * `spreadProps` case (extracts the named property from the spread object).
700
- *
701
- * Returns `undefined` both when the attribute is absent and when its value
702
- * cannot be statically determined; use {@link findAttribute} or
703
- * {@link hasAttribute} when presence itself matters.
667
+ * Returns `undefined` both when the attribute is absent and when its value cannot
668
+ * be statically determined; use {@link hasAttribute} when presence itself matters.
704
669
  * @param context The ESLint rule context.
705
- * @param element The `JSXElement` node to inspect.
670
+ * @param element The `JSXElement` node to check.
706
671
  * @param name The attribute name to look up (ex: "className").
707
672
  * @returns The static value of the attribute, or `undefined` when absent or indeterminate.
708
673
  */
@@ -778,14 +743,14 @@ function resolveJsxSpreadAttribute(context, node, name) {
778
743
  /**
779
744
  * Collapse a multiline JSX text string following React's whitespace rules.
780
745
  *
781
- * This mirrors Babel's `cleanJSXElementLiteralChild` algorithm:
746
+ * Mirrors Babel's `cleanJSXElementLiteralChild` algorithm:
782
747
  * 1. Split the raw text into lines.
783
748
  * 2. Find the last non-empty line.
784
749
  * 3. Trim leading spaces on non-first lines and trailing spaces on non-last lines.
785
750
  * 4. Collapse tabs into spaces.
786
751
  * 5. Append a single space after each non-last non-empty line.
787
752
  * @param text The raw JSX text string to collapse.
788
- * @returns The collapsed string, or `null` if the text contains only whitespace.
753
+ * @returns The collapsed string, or `null` when the text contains only whitespace.
789
754
  * @see https://github.com/babel/babel/blob/main/packages/babel-types/src/utils/react/cleanJSXElementLiteralChild.ts
790
755
  */
791
756
  function collapseMultilineText(text) {
@@ -809,45 +774,38 @@ function collapseMultilineText(text) {
809
774
  return str === "" ? null : str;
810
775
  }
811
776
  /**
812
- * Check whether a JSX child node is whitespace padding that React would
813
- * trim away during rendering.
814
- *
815
- * A child is considered whitespace padding when it is a `JSXText` node whose
816
- * content is empty after applying React's whitespace normalization
817
- * (see {@link collapseMultilineText}, modelled after Babel's
818
- * `cleanJSXElementLiteralChild`) **and** it contains a newline. This is the
819
- * whitespace that appears between JSX tags purely for formatting.
777
+ * Check if the node is whitespace padding that React would trim away during
778
+ * rendering, that is, a `JSXText` node that cleans to nothing (see
779
+ * {@link collapseMultilineText}) and contains a newline.
820
780
  *
821
781
  * For the looser "any whitespace-only text" check, see {@link isWhitespaceText}.
822
- * @param node A JSX child node.
823
- * @returns `true` when the node is purely formatting whitespace.
782
+ * @param node The JSX child node to check.
783
+ * @returns `true` if the node is purely formatting whitespace.
824
784
  */
825
785
  function isPaddingWhitespace(node) {
826
786
  if (node.type !== AST_NODE_TYPES.JSXText) return false;
827
787
  return collapseMultilineText(node.value) == null && node.value.includes("\n");
828
788
  }
829
789
  /**
830
- * Check whether a JSX child node is any whitespace-only text.
790
+ * Check if the node is whitespace-only text.
831
791
  *
832
- * This is a looser variant of {@link isPaddingWhitespace}; it matches every
833
- * `JSXText` node whose raw content is empty after trimming, regardless of
834
- * whether it contains a newline.
835
- * @param node A JSX child node.
836
- * @returns `true` when the node is a whitespace-only `JSXText`.
792
+ * Looser variant of {@link isPaddingWhitespace}; matches every `JSXText` node
793
+ * whose raw content is empty after trimming, regardless of newlines.
794
+ * @param node The JSX child node to check.
795
+ * @returns `true` if the node is a whitespace-only `JSXText`.
837
796
  */
838
797
  function isWhitespaceText(node) {
839
798
  if (node.type !== AST_NODE_TYPES.JSXText) return false;
840
799
  return node.raw.trim() === "";
841
800
  }
842
801
  /**
843
- * Check whether a JSX child node is an empty string expression (`{""}`).
802
+ * Check if the node is an empty string expression (`{""}`).
844
803
  *
845
- * React's reconciler and SSR renderer explicitly skip empty strings,
846
- * producing no DOM node (see `ReactChildFiber.js` and `ReactFizzConfigDOM.js`).
847
- * Such expressions are therefore treated as non-rendered children, in the same
848
- * way as whitespace padding.
849
- * @param node A JSX child node.
850
- * @returns `true` when the node is a `{""}` expression container.
804
+ * React's reconciler and SSR renderer explicitly skip empty strings, producing no
805
+ * DOM node, so such expressions are treated as non-rendered children, same as
806
+ * whitespace padding.
807
+ * @param node The JSX child node to check.
808
+ * @returns `true` if the node is a `{""}` expression container.
851
809
  */
852
810
  function isEmptyStringExpression(node) {
853
811
  if (node.type !== AST_NODE_TYPES.JSXExpressionContainer) return false;
@@ -868,7 +826,7 @@ function isEmptyStringExpression(node) {
868
826
  * 4. Skip empty string expressions (`{""}`), which produce no DOM node.
869
827
  * 5. Collect everything else.
870
828
  * @param element A `JSXElement` or `JSXFragment` node.
871
- * @returns An array of children nodes that contribute to rendered output.
829
+ * @returns The children nodes that contribute to rendered output.
872
830
  */
873
831
  function getChildren(element) {
874
832
  const children = [];
@@ -883,26 +841,14 @@ function getChildren(element) {
883
841
  return children;
884
842
  }
885
843
  /**
886
- * Check whether a JSX element (or fragment) has meaningful children, that is,
887
- * at least one child that is not purely whitespace text or an empty string expression.
888
- *
889
- * A `JSXText` child whose `raw` content is empty after trimming is considered
890
- * non-meaningful because it is typically a code-formatting artifact
891
- * (indentation between tags). While React's client renderer preserves these
892
- * nodes as text nodes, they rarely represent intentionally rendered content.
893
- *
894
- * An empty string expression (`children={""}`) is also considered
895
- * non-meaningful because React's reconciler and SSR renderer explicitly skip
896
- * empty strings, producing no DOM node.
844
+ * Check if the element has at least one meaningful child, that is, a child that
845
+ * is not purely whitespace text or an empty string expression (`{""}`).
897
846
  *
898
- * Unlike {@link getChildren} (which only filters whitespace that contains a
899
- * newline) this check treats any whitespace-only text as non-meaningful
900
- * (see {@link isWhitespaceText}). As a result `hasChildren(node)` is not
901
- * always equal to `getChildren(node).length > 0`: they differ for
902
- * whitespace-only children that have no newline, such as `<div> </div>` or
903
- * `<div>\t\t</div>`. Choose the API that matches your rule's intent.
847
+ * Unlike {@link getChildren} (which only filters whitespace containing a newline),
848
+ * this check treats any whitespace-only text as non-meaningful, so `hasChildren(node)`
849
+ * is not always equal to `getChildren(node).length > 0` (ex: `<div> </div>`).
904
850
  * @param element A `JSXElement` or `JSXFragment` node.
905
- * @returns `true` when the element has at least one meaningful child.
851
+ * @returns `true` if the element has at least one meaningful child.
906
852
  */
907
853
  function hasChildren(element) {
908
854
  if (element.children.length === 0) return false;
@@ -919,8 +865,8 @@ function hasChildren(element) {
919
865
  * - `<React.Fragment>` -> `"React.Fragment"`
920
866
  * - `<xml:space>` -> `"xml:space"`
921
867
  * - `<></>` -> `""`.
922
- * @param node A `JSXElement` or `JSXFragment` node.
923
- * @returns The fully-qualified element type string.
868
+ * @param node The `JSXElement` or `JSXFragment` node.
869
+ * @returns The fully qualified element type string.
924
870
  */
925
871
  function getElementFullType(node) {
926
872
  if (node.type === AST_NODE_TYPES.JSXFragment) return "";
@@ -939,7 +885,7 @@ function getElementFullType(node) {
939
885
  * - `<Foo.Bar.Baz>` -> `"Baz"`
940
886
  * - `<div>` -> `"div"`
941
887
  * - `<></>` -> `""`.
942
- * @param node A `JSXElement` or `JSXFragment` node.
888
+ * @param node The `JSXElement` or `JSXFragment` node.
943
889
  * @returns The last segment of the element type, or `""` for fragments.
944
890
  */
945
891
  function getElementSelfType(node) {
@@ -949,18 +895,14 @@ function getElementSelfType(node) {
949
895
  //#endregion
950
896
  //#region src/element-is.ts
951
897
  /**
952
- * Check whether a node is a `JSXElement` (or `JSXFragment`) and optionally
953
- * matches a given test.
898
+ * Check if the node is a `JSXElement` (or `JSXFragment`), optionally matching a given test.
954
899
  *
955
- * Modelled after
956
- * [`hast-util-is-element`](https://github.com/syntax-tree/hast-util-is-element):
957
- * the `test` parameter controls what counts as a match.
958
- *
959
- * When called without a test, the function acts as a simple type-guard
960
- * for `JSXElement | JSXFragment`.
961
- * @param node The AST node to test.
900
+ * Modelled after [`hast-util-is-element`](https://github.com/syntax-tree/hast-util-is-element):
901
+ * the `test` parameter controls what counts as a match. When called without a test,
902
+ * the function acts as a simple type guard for `JSXElement | JSXFragment`.
903
+ * @param node The node to check.
962
904
  * @param test Optional test to match the element type against.
963
- * @returns `true` when the node is a matching JSX element.
905
+ * @returns `true` if the node is a matching JSX element.
964
906
  */
965
907
  function isElement(node, test) {
966
908
  if (node == null) return false;
@@ -974,17 +916,16 @@ function isElement(node, test) {
974
916
  }
975
917
  }
976
918
  /**
977
- * Check whether a node is a React Fragment element.
978
- *
979
- * Recognizes both the shorthand `<>...</>` syntax (`JSXFragment`) and the
980
- * explicit `<Fragment>` / `<React.Fragment>` form (`JSXElement`).
919
+ * Check if the node is a React Fragment element.
981
920
  *
982
- * The comparison is performed against the self name (last dot-separated
983
- * segment) of both the node and the configured factory, so `<React.Fragment>`
984
- * matches `"React.Fragment"` and `<Fragment>` matches `"Fragment"`.
985
- * @param node The AST node to test.
921
+ * Recognizes both the shorthand `<>...</>` syntax (`JSXFragment`) and the explicit
922
+ * `<Fragment>` / `<React.Fragment>` form (`JSXElement`). The comparison is performed
923
+ * against the self name (last dot-separated segment) of both the node and the
924
+ * configured factory, so `<React.Fragment>` matches `"React.Fragment"` and
925
+ * `<Fragment>` matches `"Fragment"`.
926
+ * @param node The node to check.
986
927
  * @param jsxFragmentFactory The configured fragment factory string (ex: "React.Fragment").
987
- * @returns `true` when the node represents a React Fragment.
928
+ * @returns `true` if the node represents a React Fragment.
988
929
  */
989
930
  function isFragmentElement(node, jsxFragmentFactory = "React.Fragment") {
990
931
  if (node.type === AST_NODE_TYPES.JSXFragment) return true;
@@ -993,13 +934,10 @@ function isFragmentElement(node, jsxFragmentFactory = "React.Fragment") {
993
934
  return getElementFullType(node).split(".").at(-1) === fragment;
994
935
  }
995
936
  /**
996
- * Check whether a node is a host (intrinsic / DOM) element.
997
- *
998
- * A host element is a `JSXElement` whose tag name is a plain `JSXIdentifier`
999
- * starting with a lowercase letter, the same heuristic React uses to
1000
- * distinguish `<div>` from `<MyComponent>`.
1001
- * @param node The AST node to test.
1002
- * @returns `true` when the node is a `JSXElement` with a lowercase tag name.
937
+ * Check if the node is a host (intrinsic / DOM) element, that is, a `JSXElement`
938
+ * whose tag name starts with a lowercase letter (ex: `<div>` vs `<MyComponent>`).
939
+ * @param node The node to check.
940
+ * @returns `true` if the node is a `JSXElement` with a lowercase tag name.
1003
941
  */
1004
942
  function isHostElement(node) {
1005
943
  if (node.type !== AST_NODE_TYPES.JSXElement) return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-react/jsx",
3
- "version": "5.20.0",
3
+ "version": "5.20.2",
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": {
@@ -29,10 +29,10 @@
29
29
  "dist"
30
30
  ],
31
31
  "dependencies": {
32
- "@eslint-react/ast": "5.20.0",
33
- "@eslint-react/eslint": "5.20.0",
34
- "@eslint-react/shared": "5.20.0",
35
- "@eslint-react/var": "5.20.0",
32
+ "@eslint-react/ast": "5.20.2",
33
+ "@eslint-react/eslint": "5.20.2",
34
+ "@eslint-react/shared": "5.20.2",
35
+ "@eslint-react/var": "5.20.2",
36
36
  "@typescript-eslint/types": "^8.70.0",
37
37
  "@typescript-eslint/utils": "^8.70.0",
38
38
  "ts-pattern": "^5.9.0"
@@ -41,7 +41,7 @@
41
41
  "@local/configs": "0.0.0",
42
42
  "@local/eff": "0.0.0",
43
43
  "@local/testkit": "0.0.0",
44
- "eslint": "^10.10.0",
44
+ "eslint": "^10.11.0",
45
45
  "tsdown": "^0.23.0",
46
46
  "typescript": "6.0.3"
47
47
  },