@eslint-react/jsx 1.46.1-next.0 → 4.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,296 +1,873 @@
1
- 'use strict';
1
+ import * as ast from "@eslint-react/ast";
2
+ import { findParent } from "@eslint-react/ast";
3
+ import { resolve } from "@eslint-react/var";
4
+ import { AST_NODE_TYPES, TSESTree } from "@typescript-eslint/types";
5
+ import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
6
+ import { P, match } from "ts-pattern";
7
+ import { RE_ANNOTATION_JSX, RE_ANNOTATION_JSX_FRAG, RE_ANNOTATION_JSX_IMPORT_SOURCE, RE_ANNOTATION_JSX_RUNTIME } from "@eslint-react/shared";
2
8
 
3
- var VAR = require('@eslint-react/var');
4
- var types = require('@typescript-eslint/types');
5
- var tsPattern = require('ts-pattern');
6
- var AST2 = require('@eslint-react/ast');
7
- var eff = require('@eslint-react/eff');
9
+ //#region src/get-attribute-name.ts
10
+ /**
11
+ * Get the stringified name of a `JSXAttribute` node.
12
+ *
13
+ * Handles both simple identifiers and namespaced names:
14
+ *
15
+ * - `className` -> `"className"`
16
+ * - `aria-label` -> `"aria-label"`
17
+ * - `xml:space` -> `"xml:space"`
18
+ *
19
+ * @param node - A `JSXAttribute` AST node.
20
+ * @returns The attribute name as a plain string.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { getAttributeName } from "@eslint-react/jsx";
25
+ *
26
+ * // Inside a rule visitor:
27
+ * JSXAttribute(node) {
28
+ * const name = getAttributeName(node); // "className"
29
+ * }
30
+ * ```
31
+ */
32
+ function getAttributeName(node) {
33
+ if (node.name.type === "JSXIdentifier") return node.name.name;
34
+ return node.name.namespace.name + ":" + node.name.name.name;
35
+ }
36
+
37
+ //#endregion
38
+ //#region src/find-attribute.ts
39
+ /**
40
+ * Find a JSX attribute (or spread attribute containing the property) by name
41
+ * on a given element.
42
+ *
43
+ * Returns the **last** matching attribute to mirror React's behaviour where
44
+ * later props win, or `undefined` when the attribute is not present.
45
+ *
46
+ * Spread attributes are resolved when possible: if the spread argument is an
47
+ * identifier that resolves to an object expression, the object's properties
48
+ * are searched for a matching key.
49
+ *
50
+ * @param context - The ESLint rule context (needed for variable resolution in
51
+ * spread attributes).
52
+ * @param element - The `JSXElement` node to search.
53
+ * @param name - The attribute name to look for (e.g. `"className"`).
54
+ * @returns The matching `JSXAttribute` or `JSXSpreadAttribute`, or
55
+ * `undefined` when not found.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * const attr = findAttribute(context, node, "sandbox");
60
+ * if (attr != null) {
61
+ * // attribute (or spread containing it) exists on the element
62
+ * }
63
+ * ```
64
+ */
65
+ function findAttribute(context, element, name) {
66
+ return element.openingElement.attributes.findLast((attr) => {
67
+ if (attr.type === AST_NODE_TYPES.JSXAttribute) return getAttributeName(attr) === name;
68
+ switch (attr.argument.type) {
69
+ case AST_NODE_TYPES.Identifier: {
70
+ const initNode = resolve(context, attr.argument);
71
+ if (initNode?.type === AST_NODE_TYPES.ObjectExpression) return ast.findProperty(initNode.properties, name) != null;
72
+ return false;
73
+ }
74
+ case AST_NODE_TYPES.ObjectExpression: return ast.findProperty(attr.argument.properties, name) != null;
75
+ }
76
+ return false;
77
+ });
78
+ }
8
79
 
9
- function _interopNamespace(e) {
10
- if (e && e.__esModule) return e;
11
- var n = Object.create(null);
12
- if (e) {
13
- Object.keys(e).forEach(function (k) {
14
- if (k !== 'default') {
15
- var d = Object.getOwnPropertyDescriptor(e, k);
16
- Object.defineProperty(n, k, d.get ? d : {
17
- enumerable: true,
18
- get: function () { return e[k]; }
19
- });
20
- }
21
- });
22
- }
23
- n.default = e;
24
- return Object.freeze(n);
80
+ //#endregion
81
+ //#region src/find-parent-attribute.ts
82
+ /**
83
+ * Walk **up** the AST from `node` to find the nearest ancestor that is a
84
+ * `JSXAttribute` and (optionally) passes a predicate.
85
+ *
86
+ * This is useful when a rule visitor enters a deeply‑nested node (e.g. a
87
+ * `Literal` inside an expression container) and needs to know which JSX
88
+ * attribute it belongs to.
89
+ *
90
+ * @param node - The starting node for the upward search.
91
+ * @param test - Optional predicate to filter candidate `JSXAttribute` nodes.
92
+ * When omitted every `JSXAttribute` ancestor matches.
93
+ * @returns The first matching `JSXAttribute` ancestor, or `null` if none is
94
+ * found before reaching the root.
95
+ *
96
+ * @example
97
+ * ```ts
98
+ * // Inside a Literal visitor, find the owning attribute:
99
+ * const attr = findParentAttribute(literalNode);
100
+ * if (attr != null) {
101
+ * console.log(getAttributeName(attr));
102
+ * }
103
+ * ```
104
+ */
105
+ function findParentAttribute(node, test = () => true) {
106
+ const guard = (n) => {
107
+ return n.type === AST_NODE_TYPES.JSXAttribute && test(n);
108
+ };
109
+ return findParent(node, guard);
25
110
  }
26
111
 
27
- var VAR__namespace = /*#__PURE__*/_interopNamespace(VAR);
28
- var AST2__namespace = /*#__PURE__*/_interopNamespace(AST2);
112
+ //#endregion
113
+ //#region src/resolve-attribute-value.ts
114
+ /**
115
+ * Resolve the value of a JSX attribute (or spread attribute) into a
116
+ * {@link JsxAttributeValue} descriptor that can be inspected further.
117
+ *
118
+ * This is the low‑level building block – it operates on a single attribute
119
+ * node that the caller has already located. For the higher‑level "find by
120
+ * name **and** resolve" combo, see {@link getAttributeValue}.
121
+ *
122
+ * @param context - The ESLint rule context (needed for scope look‑ups).
123
+ * @param attribute - A `JSXAttribute` or `JSXSpreadAttribute` node.
124
+ * @returns A discriminated‑union descriptor of the attribute's value.
125
+ *
126
+ * @example
127
+ * ```ts
128
+ * import { findAttribute, resolveAttributeValue } from "@eslint-react/jsx";
129
+ *
130
+ * const attr = findAttribute(context, element, "sandbox");
131
+ * if (attr != null) {
132
+ * const value = resolveAttributeValue(context, attr);
133
+ * if (value.kind === "literal") {
134
+ * console.log(value.toStatic());
135
+ * }
136
+ * }
137
+ * ```
138
+ */
139
+ function resolveAttributeValue(context, attribute) {
140
+ if (attribute.type === AST_NODE_TYPES.JSXAttribute) return resolveJsxAttribute(context, attribute);
141
+ return resolveJsxSpreadAttribute(context, attribute);
142
+ }
143
+ function resolveJsxAttribute(context, node) {
144
+ const scope = context.sourceCode.getScope(node);
145
+ if (node.value == null) return {
146
+ kind: "boolean",
147
+ node: null,
148
+ toStatic() {
149
+ return true;
150
+ }
151
+ };
152
+ switch (node.value.type) {
153
+ case AST_NODE_TYPES.Literal: {
154
+ const staticValue = node.value.value;
155
+ return {
156
+ kind: "literal",
157
+ node: node.value,
158
+ toStatic() {
159
+ return staticValue;
160
+ }
161
+ };
162
+ }
163
+ case AST_NODE_TYPES.JSXExpressionContainer: {
164
+ const expr = node.value.expression;
165
+ if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return {
166
+ kind: "missing",
167
+ node: expr,
168
+ toStatic() {
169
+ return null;
170
+ }
171
+ };
172
+ return {
173
+ kind: "unknown",
174
+ node: expr,
175
+ toStatic() {
176
+ return getStaticValue(expr, scope)?.value;
177
+ }
178
+ };
179
+ }
180
+ case AST_NODE_TYPES.JSXElement: return {
181
+ kind: "element",
182
+ node: node.value,
183
+ toStatic() {
184
+ return null;
185
+ }
186
+ };
187
+ case AST_NODE_TYPES.JSXSpreadChild: return {
188
+ kind: "spreadChild",
189
+ getChildren(_at) {
190
+ return null;
191
+ },
192
+ node: node.value.expression,
193
+ toStatic() {
194
+ return null;
195
+ }
196
+ };
197
+ }
198
+ }
199
+ function resolveJsxSpreadAttribute(context, node) {
200
+ const scope = context.sourceCode.getScope(node);
201
+ return {
202
+ kind: "spreadProps",
203
+ getProperty(name) {
204
+ return match(getStaticValue(node.argument, scope)?.value).with({ [name]: P.select(P.unknown) }, (v) => v).otherwise(() => null);
205
+ },
206
+ node: node.argument,
207
+ toStatic() {
208
+ return null;
209
+ }
210
+ };
211
+ }
29
212
 
30
- // src/attribute/attribute.ts
31
- function toString(node) {
32
- switch (node.type) {
33
- case types.AST_NODE_TYPES.JSXIdentifier:
34
- return node.name;
35
- case types.AST_NODE_TYPES.JSXNamespacedName:
36
- return `${node.namespace.name}:${node.name.name}`;
37
- case types.AST_NODE_TYPES.JSXMemberExpression:
38
- return `${toString(node.object)}.${toString(node.property)}`;
39
- case types.AST_NODE_TYPES.JSXText:
40
- return node.value;
41
- case types.AST_NODE_TYPES.JSXOpeningElement:
42
- return `<${toString(node.name)}>`;
43
- case types.AST_NODE_TYPES.JSXClosingElement:
44
- return `</${toString(node.name)}>`;
45
- case types.AST_NODE_TYPES.JSXOpeningFragment:
46
- return "<>";
47
- case types.AST_NODE_TYPES.JSXClosingFragment:
48
- return "</>";
49
- }
213
+ //#endregion
214
+ //#region src/get-attribute-static-value.ts
215
+ /**
216
+ * Find an attribute by name on a JSX element and collapse its value to a
217
+ * plain JavaScript value in a single step.
218
+ *
219
+ * This is a convenience composition of {@link findAttribute} ->
220
+ * {@link resolveAttributeValue} -> `toStatic()`, with automatic handling
221
+ * of the `spreadProps` case (extracts the named property from the spread
222
+ * object).
223
+ *
224
+ * Returns `undefined` when the attribute is absent **or** when its value
225
+ * cannot be statically determined.
226
+ *
227
+ * @param context - The ESLint rule context.
228
+ * @param element - The `JSXElement` node to inspect.
229
+ * @param name - The attribute name to look up (e.g. `"className"`).
230
+ * @returns The static value of the attribute, or `undefined`.
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * // <iframe sandbox="allow-scripts" />
235
+ * const sandbox = getAttributeStaticValue(context, node, "sandbox");
236
+ * // -> "allow-scripts"
237
+ *
238
+ * // <button type={dynamicVar} />
239
+ * const type = getAttributeStaticValue(context, node, "type");
240
+ * // -> undefined (cannot be resolved statically)
241
+ * ```
242
+ */
243
+ function getAttributeStaticValue(context, element, name) {
244
+ const attr = findAttribute(context, element, name);
245
+ if (attr == null) return void 0;
246
+ const resolved = resolveAttributeValue(context, attr);
247
+ if (resolved.kind === "spreadProps") return resolved.getProperty(name);
248
+ return resolved.toStatic();
50
249
  }
51
250
 
52
- // src/attribute/attribute-name.ts
53
- function getAttributeName(node) {
54
- return toString(node.name);
251
+ //#endregion
252
+ //#region src/get-attribute-value.ts
253
+ /**
254
+ * Find an attribute by name on a JSX element **and** resolve its value in a
255
+ * single call.
256
+ *
257
+ * This is a convenience composition of {@link findAttribute} and
258
+ * {@link resolveAttributeValue} that eliminates the most common two-step
259
+ * pattern in lint rules:
260
+ *
261
+ * ```ts
262
+ * const attr = findAttribute(context, element, name);
263
+ * if (attr == null) return;
264
+ * const value = resolveAttributeValue(context, attr);
265
+ * ```
266
+ *
267
+ * @param context - The ESLint rule context.
268
+ * @param element - The `JSXElement` node to search.
269
+ * @param name - The attribute name to look up (e.g. `"className"`).
270
+ * @returns A {@link JsxAttributeValue} descriptor, or `undefined` when the
271
+ * attribute is not present on the element.
272
+ *
273
+ * @example
274
+ * ```ts
275
+ * const value = getAttributeValue(context, node, "sandbox");
276
+ * if (value?.kind === "literal") {
277
+ * console.log(value.toStatic()); // the literal value
278
+ * }
279
+ * ```
280
+ */
281
+ function getAttributeValue(context, element, name) {
282
+ const attr = findAttribute(context, element, name);
283
+ if (attr == null) return void 0;
284
+ return resolveAttributeValue(context, attr);
55
285
  }
56
286
 
57
- // src/attribute/attribute.ts
58
- function getAttribute(name, attributes, initialScope) {
59
- return attributes.findLast((attr) => {
60
- if (attr.type === types.AST_NODE_TYPES.JSXAttribute) {
61
- return getAttributeName(attr) === name;
62
- }
63
- if (initialScope == null) return false;
64
- switch (attr.argument.type) {
65
- case types.AST_NODE_TYPES.Identifier: {
66
- const variable = VAR__namespace.findVariable(attr.argument.name, initialScope);
67
- const variableNode = VAR__namespace.getVariableInitNode(variable, 0);
68
- if (variableNode?.type === types.AST_NODE_TYPES.ObjectExpression) {
69
- return VAR__namespace.findPropertyInProperties(name, variableNode.properties, initialScope) != null;
70
- }
71
- return false;
72
- }
73
- case types.AST_NODE_TYPES.ObjectExpression:
74
- return VAR__namespace.findPropertyInProperties(name, attr.argument.properties, initialScope) != null;
75
- }
76
- return false;
77
- });
287
+ //#endregion
288
+ //#region src/get-children.ts
289
+ /**
290
+ * Get the **meaningful** children of a JSX element or fragment.
291
+ *
292
+ * Filters out "padding spaces" — `JSXText` nodes that consist entirely of
293
+ * whitespace and contain at least one newline. These nodes are artefacts of
294
+ * source formatting that React trims away during rendering and are therefore
295
+ * not considered meaningful content.
296
+ *
297
+ * @param element - A `JSXElement` or `JSXFragment` node.
298
+ * @returns An array of children nodes that contribute to rendered output.
299
+ *
300
+ * @example
301
+ * ```ts
302
+ * import { getChildren } from "@eslint-react/jsx";
303
+ *
304
+ * // <div>
305
+ * // <span />
306
+ * // </div>
307
+ * //
308
+ * // Raw children: [JSXText("\n "), JSXElement(<span />), JSXText("\n")]
309
+ * // getChildren: [JSXElement(<span />)]
310
+ *
311
+ * const meaningful = getChildren(node);
312
+ * ```
313
+ */
314
+ function getChildren(element) {
315
+ return element.children.filter((child) => !isPaddingSpaces(child));
78
316
  }
79
- function getAttributeValue(node, name, initialScope) {
80
- switch (node.type) {
81
- case types.AST_NODE_TYPES.JSXAttribute:
82
- if (node.value?.type === types.AST_NODE_TYPES.Literal) {
83
- return {
84
- kind: "some",
85
- node: node.value,
86
- initialScope,
87
- value: node.value.value
88
- };
89
- }
90
- if (node.value?.type === types.AST_NODE_TYPES.JSXExpressionContainer) {
91
- return VAR__namespace.toStaticValue({
92
- kind: "lazy",
93
- node: node.value.expression,
94
- initialScope
95
- });
96
- }
97
- return { kind: "none", node, initialScope };
98
- case types.AST_NODE_TYPES.JSXSpreadAttribute: {
99
- const staticValue = VAR__namespace.toStaticValue({
100
- kind: "lazy",
101
- node: node.argument,
102
- initialScope
103
- });
104
- if (staticValue.kind === "none") {
105
- return staticValue;
106
- }
107
- return tsPattern.match(staticValue.value).with({ [name]: tsPattern.P.select(tsPattern.P.any) }, (value) => ({
108
- kind: "some",
109
- node: node.argument,
110
- initialScope,
111
- value
112
- })).otherwise(() => ({ kind: "none", node, initialScope }));
113
- }
114
- default:
115
- return { kind: "none", node, initialScope };
116
- }
317
+ /**
318
+ * A `JSXText` node is considered **padding spaces** when it is purely
319
+ * whitespace *and* contains at least one newline character.
320
+ *
321
+ * These nodes are formatting artefacts (indentation between JSX tags) that
322
+ * React discards at render time.
323
+ *
324
+ * @param node
325
+ * @internal
326
+ */
327
+ function isPaddingSpaces(node) {
328
+ if (node.type !== AST_NODE_TYPES.JSXText) return false;
329
+ return node.raw.trim() === "" && node.raw.includes("\n");
117
330
  }
118
331
 
119
- // src/attribute/has.ts
120
- function hasAttribute(name, attributes, initialScope) {
121
- return getAttribute(name, attributes, initialScope) != null;
332
+ //#endregion
333
+ //#region src/get-element-type.ts
334
+ /**
335
+ * Get the string representation of a JSX element's type.
336
+ *
337
+ * - `<div>` -> `"div"`
338
+ * - `<Foo.Bar>` -> `"Foo.Bar"`
339
+ * - `<React.Fragment>` -> `"React.Fragment"`
340
+ * - `<></>` -> `""`
341
+ *
342
+ * @param node - A `JSXElement` or `JSXFragment` node.
343
+ * @returns The fully-qualified element type string.
344
+ */
345
+ function getElementFullType(node) {
346
+ if (node.type === AST_NODE_TYPES.JSXFragment) return "";
347
+ function getQualifiedName(node) {
348
+ if (node.type === AST_NODE_TYPES.JSXIdentifier) return node.name;
349
+ if (node.type === AST_NODE_TYPES.JSXNamespacedName) return node.namespace.name + ":" + node.name.name;
350
+ return getQualifiedName(node.object) + "." + getQualifiedName(node.property);
351
+ }
352
+ return getQualifiedName(node.openingElement.name);
122
353
  }
123
- function hasAnyAttribute(names, attributes, initialScope) {
124
- return names.some((n) => hasAttribute(n, attributes, initialScope));
354
+ /**
355
+ * Get the **self name** (last dot-separated segment) of a JSX element type.
356
+ *
357
+ * - `<Foo.Bar.Baz>` -> `"Baz"`
358
+ * - `<div>` -> `"div"`
359
+ * - `<></>` -> `""`
360
+ *
361
+ * @param node - A `JSXElement` or `JSXFragment` node.
362
+ * @returns The last segment of the element type, or `""` for fragments.
363
+ */
364
+ function getElementSelfType(node) {
365
+ return getElementFullType(node).split(".").at(-1) ?? "";
125
366
  }
126
- function hasEveryAttribute(names, attributes, initialScope) {
127
- return names.every((n) => hasAttribute(n, attributes, initialScope));
367
+
368
+ //#endregion
369
+ //#region src/has-any-attribute.ts
370
+ /**
371
+ * Check whether a JSX element carries **at least one** of the given attributes.
372
+ *
373
+ * This is a batch variant of {@link hasAttribute} for the common pattern of
374
+ * short-circuiting on multiple prop names:
375
+ *
376
+ * ```ts
377
+ * // before
378
+ * if (hasAttribute(ctx, el, "key")) return;
379
+ * if (hasAttribute(ctx, el, "ref")) return;
380
+ *
381
+ * // after
382
+ * if (hasAnyAttribute(ctx, el, ["key", "ref"])) return;
383
+ * ```
384
+ *
385
+ * Spread attributes are taken into account (see {@link findAttribute}).
386
+ *
387
+ * @param context - The ESLint rule context (needed for variable resolution in
388
+ * spread attributes).
389
+ * @param element - The `JSXElement` node to inspect.
390
+ * @param names - The attribute names to look for.
391
+ * @returns `true` when **at least one** of the attributes is present.
392
+ */
393
+ function hasAnyAttribute(context, element, names) {
394
+ return names.some((name) => findAttribute(context, element, name) != null);
128
395
  }
129
- function findParentAttribute(node, test = eff.constTrue) {
130
- const guard = (node2) => {
131
- return node2.type === types.AST_NODE_TYPES.JSXAttribute && test(node2);
132
- };
133
- return AST2__namespace.findParentNode(node, guard);
396
+
397
+ //#endregion
398
+ //#region src/has-attribute.ts
399
+ /**
400
+ * Check whether a JSX element carries a given attribute (prop).
401
+ *
402
+ * This is a thin convenience wrapper around {@link findAttribute} for the
403
+ * common case where you only need a boolean answer.
404
+ *
405
+ * Spread attributes are taken into account: `<Comp {...{ disabled: true }} />`
406
+ * will report `true` for `"disabled"`.
407
+ *
408
+ * @param context - The ESLint rule context (needed for variable resolution in
409
+ * spread attributes).
410
+ * @param element - The `JSXElement` node to inspect.
411
+ * @param name - The attribute name to look for (e.g. `"className"`).
412
+ * @returns `true` when the attribute is present on the element.
413
+ *
414
+ * @example
415
+ * ```ts
416
+ * import { hasAttribute } from "@eslint-react/jsx";
417
+ *
418
+ * if (hasAttribute(context, node, "key")) {
419
+ * // element has a `key` prop
420
+ * }
421
+ * ```
422
+ */
423
+ function hasAttribute(context, element, name) {
424
+ return findAttribute(context, element, name) != null;
134
425
  }
135
- function getElementType(node) {
136
- if (node.type === types.AST_NODE_TYPES.JSXFragment) {
137
- return "";
138
- }
139
- return toString(node.openingElement.name);
426
+
427
+ //#endregion
428
+ //#region src/has-children.ts
429
+ /**
430
+ * Check whether a JSX element (or fragment) has **meaningful** children —
431
+ * that is, at least one child that is not purely whitespace text.
432
+ *
433
+ * A `JSXText` child whose `raw` content is empty after trimming is
434
+ * considered non-meaningful regardless of whether it contains a line break.
435
+ * This matches React's rendering behaviour where whitespace-only text nodes
436
+ * do not produce visible output.
437
+ *
438
+ * @param element - A `JSXElement` or `JSXFragment` node.
439
+ * @returns `true` when the element has at least one meaningful child.
440
+ *
441
+ * @example
442
+ * ```ts
443
+ * import { hasChildren } from "@eslint-react/jsx";
444
+ *
445
+ * // <div>hello</div> -> true
446
+ * // <div> {expr} </div> -> true
447
+ * // <div> </div> -> false (whitespace-only)
448
+ * // <div> -> false (whitespace-only, with newlines)
449
+ * // </div>
450
+ * // <div></div> -> false (no children at all)
451
+ *
452
+ * if (hasChildren(node)) {
453
+ * // element renders visible content
454
+ * }
455
+ * ```
456
+ */
457
+ function hasChildren(element) {
458
+ if (element.children.length === 0) return false;
459
+ return !element.children.every((child) => isWhitespaceText$1(child));
140
460
  }
141
- function isHostElement(node) {
142
- return node.type === types.AST_NODE_TYPES.JSXElement && node.openingElement.name.type === types.AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
461
+ /**
462
+ * Whether a JSX child node is a whitespace-only `JSXText` node.
463
+ *
464
+ * Any `JSXText` whose raw content consists entirely of whitespace characters
465
+ * (spaces, tabs, newlines, etc.) is considered whitespace text. Non-text
466
+ * nodes always return `false`.
467
+ * @param node
468
+ */
469
+ function isWhitespaceText$1(node) {
470
+ if (node.type !== AST_NODE_TYPES.JSXText) return false;
471
+ return node.raw.trim() === "";
143
472
  }
144
- function isKeyedElement(node, initialScope) {
145
- return node.type === types.AST_NODE_TYPES.JSXElement && hasAttribute("key", node.openingElement.attributes, initialScope);
473
+
474
+ //#endregion
475
+ //#region src/has-every-attribute.ts
476
+ /**
477
+ * Check whether a JSX element carries **all** of the given attributes (props).
478
+ *
479
+ * This is a batch variant of {@link hasAttribute} for the common pattern
480
+ * where a rule needs to verify that a set of required props are all present.
481
+ *
482
+ * Spread attributes are taken into account (see {@link findAttribute}).
483
+ *
484
+ * @param context - The ESLint rule context (needed for variable resolution in
485
+ * spread attributes).
486
+ * @param element - The `JSXElement` node to inspect.
487
+ * @param names - The attribute names to look for.
488
+ * @returns `true` when **every** name in `names` is present on the element.
489
+ *
490
+ * @example
491
+ * ```ts
492
+ * import { hasEveryAttribute } from "@eslint-react/jsx";
493
+ *
494
+ * // Ensure both `alt` and `src` are provided on an <img>
495
+ * if (hasEveryAttribute(context, node, ["alt", "src"])) {
496
+ * // element has both props
497
+ * }
498
+ * ```
499
+ */
500
+ function hasEveryAttribute(context, element, names) {
501
+ return names.every((name) => findAttribute(context, element, name) != null);
146
502
  }
147
- function isFragmentElement(node, allowJSXFragment = false) {
148
- if (node == null) return false;
149
- if (node.type !== types.AST_NODE_TYPES.JSXElement && node.type !== types.AST_NODE_TYPES.JSXFragment) return false;
150
- if (node.type === types.AST_NODE_TYPES.JSXFragment) return allowJSXFragment;
151
- return getElementType(node).split(".").at(-1) === "Fragment";
503
+
504
+ //#endregion
505
+ //#region src/is-element.ts
506
+ /**
507
+ * Check whether a node is a `JSXElement` (or `JSXFragment`) and optionally
508
+ * matches a given test.
509
+ *
510
+ * Modelled after
511
+ * [`hast-util-is-element`](https://github.com/syntax-tree/hast-util-is-element):
512
+ * the `test` parameter controls what counts as a match.
513
+ *
514
+ * When called **without** a test, the function acts as a simple type-guard
515
+ * for `JSXElement | JSXFragment`.
516
+ *
517
+ * @param node - The AST node to test.
518
+ * @param test - Optional test to match the element type against.
519
+ * @returns `true` when the node is a matching JSX element.
520
+ *
521
+ * @example
522
+ * ```ts
523
+ * import { isElement } from "@eslint-react/jsx";
524
+ *
525
+ * // Type-guard only — any JSX element or fragment
526
+ * if (isElement(node)) { … }
527
+ *
528
+ * // Match a single tag name
529
+ * if (isElement(node, "iframe")) { … }
530
+ *
531
+ * // Match one of several tag names
532
+ * if (isElement(node, ["button", "input", "select"])) { … }
533
+ *
534
+ * // Custom predicate
535
+ * if (isElement(node, (type) => type.endsWith(".Provider"))) { … }
536
+ * ```
537
+ */
538
+ function isElement(node, test) {
539
+ if (node == null) return false;
540
+ if (node.type !== AST_NODE_TYPES.JSXElement && node.type !== AST_NODE_TYPES.JSXFragment) return false;
541
+ if (test == null) return true;
542
+ const elementType = getElementFullType(node);
543
+ if (typeof test === "string") return elementType === test;
544
+ if (typeof test === "function") return test(elementType, node);
545
+ return test.includes(elementType);
152
546
  }
153
547
 
154
- // src/jsx-detection-hint.ts
155
- var JSXDetectionHint = {
156
- None: 0n,
157
- SkipUndefined: 1n << 0n,
158
- SkipNullLiteral: 1n << 1n,
159
- SkipBooleanLiteral: 1n << 2n,
160
- SkipStringLiteral: 1n << 3n,
161
- SkipNumberLiteral: 1n << 4n,
162
- SkipBigIntLiteral: 1n << 5n,
163
- SkipEmptyArray: 1n << 6n,
164
- SkipCreateElement: 1n << 7n,
165
- StrictArray: 1n << 8n,
166
- StrictLogical: 1n << 9n,
167
- StrictConditional: 1n << 10n
548
+ //#endregion
549
+ //#region src/is-fragment-element.ts
550
+ /**
551
+ * Check whether a node is a React **Fragment** element.
552
+ *
553
+ * Recognises both the shorthand `<>…</>` syntax (`JSXFragment`) and the
554
+ * explicit `<Fragment>` / `<React.Fragment>` form (`JSXElement`).
555
+ *
556
+ * The comparison is performed against the **self name** (last dot‑separated
557
+ * segment) of both the node and the configured factory, so
558
+ * `<React.Fragment>` matches `"React.Fragment"` and `<Fragment>` matches
559
+ * `"Fragment"`.
560
+ *
561
+ * @param node - The AST node to test.
562
+ * @param jsxFragmentFactory - The configured fragment factory string
563
+ * (e.g. `"React.Fragment"`). Defaults to
564
+ * `"React.Fragment"`.
565
+ * @returns `true` when the node represents a React Fragment.
566
+ *
567
+ * @example
568
+ * ```ts
569
+ * // Using the default factory
570
+ * if (isFragmentElement(node)) { … }
571
+ *
572
+ * // With a custom factory from jsxConfig
573
+ * const config = getJsxConfig(context);
574
+ * if (isFragmentElement(node, config.jsxFragmentFactory)) { … }
575
+ * ```
576
+ */
577
+ function isFragmentElement(node, jsxFragmentFactory = "React.Fragment") {
578
+ if (node.type === AST_NODE_TYPES.JSXFragment) return true;
579
+ if (node.type !== AST_NODE_TYPES.JSXElement) return false;
580
+ const fragment = jsxFragmentFactory.split(".").at(-1) ?? "Fragment";
581
+ return getElementFullType(node).split(".").at(-1) === fragment;
582
+ }
583
+
584
+ //#endregion
585
+ //#region src/is-host-element.ts
586
+ /**
587
+ * Check whether a node is a **host** (intrinsic / DOM) element.
588
+ *
589
+ * A host element is a `JSXElement` whose tag name is a plain `JSXIdentifier`
590
+ * starting with a lowercase letter – the same heuristic React uses to
591
+ * distinguish `<div>` from `<MyComponent>`.
592
+ *
593
+ * @param node - The AST node to test.
594
+ * @returns `true` when the node is a `JSXElement` with a lowercase tag name.
595
+ *
596
+ * @example
597
+ * ```ts
598
+ * // <div className="box" /> -> true
599
+ * // <span /> -> true
600
+ * // <MyComponent /> -> false
601
+ * // <Foo.Bar /> -> false
602
+ * isHostElement(node);
603
+ * ```
604
+ */
605
+ function isHostElement(node) {
606
+ return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
607
+ }
608
+
609
+ //#endregion
610
+ //#region src/jsx-detection-hint.ts
611
+ const JsxDetectionHint = {
612
+ None: 0n,
613
+ DoNotIncludeJsxWithNullValue: 1n << 0n,
614
+ DoNotIncludeJsxWithNumberValue: 1n << 1n,
615
+ DoNotIncludeJsxWithBigIntValue: 1n << 2n,
616
+ DoNotIncludeJsxWithStringValue: 1n << 3n,
617
+ DoNotIncludeJsxWithBooleanValue: 1n << 4n,
618
+ DoNotIncludeJsxWithUndefinedValue: 1n << 5n,
619
+ DoNotIncludeJsxWithEmptyArrayValue: 1n << 6n,
620
+ DoNotIncludeJsxWithCreateElementValue: 1n << 7n,
621
+ RequireAllArrayElementsToBeJsx: 1n << 8n,
622
+ RequireBothSidesOfLogicalExpressionToBeJsx: 1n << 9n,
623
+ RequireBothBranchesOfConditionalExpressionToBeJsx: 1n << 10n
168
624
  };
169
- var DEFAULT_JSX_DETECTION_HINT = 0n | JSXDetectionHint.SkipUndefined | JSXDetectionHint.SkipBooleanLiteral;
625
+ /**
626
+ * Default JSX detection configuration.
627
+ *
628
+ * Skips number, bigint, boolean, string, and undefined literals –
629
+ * the value types that are commonly returned alongside JSX in React
630
+ * components but are not themselves renderable elements.
631
+ */
632
+ const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
633
+
634
+ //#endregion
635
+ //#region src/is-jsx-like.ts
636
+ /**
637
+ * Determine whether a node represents JSX-like content based on heuristics.
638
+ *
639
+ * The detection behaviour is configurable through {@link JsxDetectionHint}
640
+ * bit-flags so that callers can opt individual value kinds in or out.
641
+ *
642
+ * @param context - The ESLint rule context (needed for variable resolution).
643
+ * @param node - The AST node to analyse.
644
+ * @param hint - Optional bit-flags to adjust detection behaviour.
645
+ * Defaults to {@link DEFAULT_JSX_DETECTION_HINT}.
646
+ * @returns Whether the node is considered JSX-like.
647
+ *
648
+ * @example
649
+ * ```ts
650
+ * import { isJsxLike } from "@eslint-react/jsx";
651
+ *
652
+ * if (isJsxLike(context, node)) {
653
+ * // node looks like it evaluates to a React element
654
+ * }
655
+ * ```
656
+ */
657
+ function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
658
+ if (node == null) return false;
659
+ if (ast.isJSX(node)) return true;
660
+ switch (node.type) {
661
+ case AST_NODE_TYPES.Literal:
662
+ switch (typeof node.value) {
663
+ case "boolean": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithBooleanValue);
664
+ case "string": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue);
665
+ case "number": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithNumberValue);
666
+ case "bigint": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithBigIntValue);
667
+ }
668
+ if (node.value == null) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithNullValue);
669
+ return false;
670
+ case AST_NODE_TYPES.TemplateLiteral: return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue);
671
+ case AST_NODE_TYPES.ArrayExpression:
672
+ if (node.elements.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
673
+ if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(context, n, hint));
674
+ return node.elements.some((n) => isJsxLike(context, n, hint));
675
+ case AST_NODE_TYPES.LogicalExpression:
676
+ if (hint & JsxDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx) return isJsxLike(context, node.left, hint) && isJsxLike(context, node.right, hint);
677
+ return isJsxLike(context, node.left, hint) || isJsxLike(context, node.right, hint);
678
+ case AST_NODE_TYPES.ConditionalExpression: {
679
+ const consequentIsJsx = Array.isArray(node.consequent) ? checkArray(context, node.consequent, hint) : isJsxLike(context, node.consequent, hint);
680
+ const alternateIsJsx = isJsxLike(context, node.alternate, hint);
681
+ if (hint & JsxDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx) return consequentIsJsx && alternateIsJsx;
682
+ return consequentIsJsx || alternateIsJsx;
683
+ }
684
+ case AST_NODE_TYPES.SequenceExpression: return isJsxLike(context, node.expressions.at(-1) ?? null, hint);
685
+ case AST_NODE_TYPES.CallExpression:
686
+ if (hint & JsxDetectionHint.DoNotIncludeJsxWithCreateElementValue) return false;
687
+ switch (node.callee.type) {
688
+ case AST_NODE_TYPES.Identifier: return node.callee.name === "createElement";
689
+ case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement";
690
+ }
691
+ return false;
692
+ case AST_NODE_TYPES.Identifier:
693
+ if (node.name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
694
+ if (ast.isJSXTagNameExpression(node)) return true;
695
+ return isJsxLike(context, resolve(context, node), hint);
696
+ }
697
+ return false;
698
+ }
699
+ function checkArray(context, elements, hint) {
700
+ if (elements.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
701
+ if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return elements.every((n) => isJsxLike(context, n, hint));
702
+ return elements.some((n) => isJsxLike(context, n, hint));
703
+ }
170
704
 
171
- // src/jsx-detection.ts
172
- var isJSX = AST2__namespace.isOneOf([
173
- types.AST_NODE_TYPES.JSXAttribute,
174
- types.AST_NODE_TYPES.JSXClosingElement,
175
- types.AST_NODE_TYPES.JSXClosingFragment,
176
- types.AST_NODE_TYPES.JSXElement,
177
- types.AST_NODE_TYPES.JSXEmptyExpression,
178
- types.AST_NODE_TYPES.JSXExpressionContainer,
179
- types.AST_NODE_TYPES.JSXFragment,
180
- types.AST_NODE_TYPES.JSXIdentifier,
181
- types.AST_NODE_TYPES.JSXMemberExpression,
182
- types.AST_NODE_TYPES.JSXNamespacedName,
183
- types.AST_NODE_TYPES.JSXOpeningElement,
184
- types.AST_NODE_TYPES.JSXOpeningFragment,
185
- types.AST_NODE_TYPES.JSXSpreadAttribute,
186
- types.AST_NODE_TYPES.JSXSpreadChild,
187
- types.AST_NODE_TYPES.JSXText
188
- ]);
705
+ //#endregion
706
+ //#region src/is-jsx-text.ts
707
+ /**
708
+ * Check whether a node is a JSX text node.
709
+ *
710
+ * Returns `true` for both `JSXText` nodes and `Literal` nodes that appear
711
+ * as direct children of a JSX element (the parser may represent inline text
712
+ * with either node type depending on context).
713
+ *
714
+ * @param node - The AST node to test.
715
+ * @returns `true` when `node` is a `JSXText` or `Literal`.
716
+ */
189
717
  function isJsxText(node) {
190
- if (node == null) return false;
191
- return node.type === types.AST_NODE_TYPES.JSXText || node.type === types.AST_NODE_TYPES.Literal;
718
+ if (node == null) return false;
719
+ return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
720
+ }
721
+
722
+ //#endregion
723
+ //#region src/is-whitespace.ts
724
+ /**
725
+ * Check whether a JSX child node is **whitespace padding** that React would
726
+ * trim away during rendering.
727
+ *
728
+ * A child is considered whitespace padding when it is a `JSXText` node whose
729
+ * raw content is empty after trimming **and** contains at least one newline.
730
+ * This is the whitespace that appears between JSX tags purely for formatting:
731
+ *
732
+ * ```jsx
733
+ * <div>
734
+ * <span /> ← the text between </span> and the next tag is padding
735
+ * <span />
736
+ * </div>
737
+ * ```
738
+ *
739
+ * Use {@link isWhitespaceText} for a looser check that also matches
740
+ * whitespace‑only text that does **not** contain a newline.
741
+ *
742
+ * @param node - A JSX child node.
743
+ * @returns `true` when the node is purely formatting whitespace.
744
+ *
745
+ * @example
746
+ * ```ts
747
+ * import { isWhitespace } from "@eslint-react/jsx";
748
+ *
749
+ * const meaningful = element.children.filter(
750
+ * (child) => !isWhitespace(child),
751
+ * );
752
+ * ```
753
+ */
754
+ function isWhitespace(node) {
755
+ if (node.type !== AST_NODE_TYPES.JSXText) return false;
756
+ return node.raw.trim() === "" && node.raw.includes("\n");
757
+ }
758
+ /**
759
+ * Check whether a JSX child node is **any** whitespace‑only text.
760
+ *
761
+ * This is a looser variant of {@link isWhitespace} — it matches every
762
+ * `JSXText` node whose raw content is empty after trimming, regardless of
763
+ * whether it contains a newline.
764
+ *
765
+ * @param node - A JSX child node.
766
+ * @returns `true` when the node is a whitespace‑only `JSXText`.
767
+ */
768
+ function isWhitespaceText(node) {
769
+ if (node.type !== AST_NODE_TYPES.JSXText) return false;
770
+ return node.raw.trim() === "";
771
+ }
772
+
773
+ //#endregion
774
+ //#region src/jsx-config.ts
775
+ /**
776
+ * TypeScript `jsx` compiler option values.
777
+ *
778
+ * Mirrors `ts.JsxEmit` so that consumers do not need a direct dependency on
779
+ * the TypeScript compiler.
780
+ */
781
+ const JsxEmit = {
782
+ None: 0,
783
+ Preserve: 1,
784
+ React: 2,
785
+ ReactNative: 3,
786
+ ReactJSX: 4,
787
+ ReactJSXDev: 5
788
+ };
789
+ /**
790
+ * Weak‑map cache keyed by `sourceCode` so that the (potentially expensive)
791
+ * pragma‑scanning pass runs at most once per file.
792
+ */
793
+ const annotationCache = /* @__PURE__ */ new WeakMap();
794
+ /**
795
+ * Weak‑map cache for the fully‑merged config (compiler options + annotation).
796
+ */
797
+ const mergedCache = /* @__PURE__ */ new WeakMap();
798
+ /**
799
+ * Read JSX configuration from the TypeScript compiler options exposed by the
800
+ * parser services.
801
+ *
802
+ * Falls back to sensible React defaults when no compiler options are
803
+ * available (e.g. when the file is parsed without type information).
804
+ *
805
+ * @param context - The ESLint rule context.
806
+ * @returns Fully‑populated `JsxConfig` derived from compiler options.
807
+ */
808
+ function getJsxConfigFromCompilerOptions(context) {
809
+ const options = context.sourceCode.parserServices?.program?.getCompilerOptions() ?? {};
810
+ return {
811
+ jsx: options.jsx ?? JsxEmit.ReactJSX,
812
+ jsxFactory: options.jsxFactory ?? "React.createElement",
813
+ jsxFragmentFactory: options.jsxFragmentFactory ?? "React.Fragment",
814
+ jsxImportSource: options.jsxImportSource ?? "react"
815
+ };
816
+ }
817
+ /**
818
+ * Extract JSX configuration from `@jsx`, `@jsxFrag`, `@jsxRuntime` and
819
+ * `@jsxImportSource` pragma comments in the source file.
820
+ *
821
+ * The result is cached per `sourceCode` instance via a `WeakMap` so that
822
+ * repeated calls from different rules analysing the same file are free.
823
+ *
824
+ * @param context - The ESLint rule context.
825
+ * @returns Partial `JsxConfig` containing only the values found in pragmas.
826
+ */
827
+ function getJsxConfigFromAnnotation(context) {
828
+ const cached = annotationCache.get(context.sourceCode);
829
+ if (cached != null) return cached;
830
+ const options = {};
831
+ if (!context.sourceCode.text.includes("@jsx")) {
832
+ annotationCache.set(context.sourceCode, options);
833
+ return options;
834
+ }
835
+ let jsx, jsxFrag, jsxRuntime, jsxImportSource;
836
+ for (const comment of context.sourceCode.getAllComments().reverse()) {
837
+ const value = comment.value;
838
+ jsx ??= value.match(RE_ANNOTATION_JSX)?.[1];
839
+ jsxFrag ??= value.match(RE_ANNOTATION_JSX_FRAG)?.[1];
840
+ jsxRuntime ??= value.match(RE_ANNOTATION_JSX_RUNTIME)?.[1];
841
+ jsxImportSource ??= value.match(RE_ANNOTATION_JSX_IMPORT_SOURCE)?.[1];
842
+ }
843
+ if (jsx != null) options.jsxFactory = jsx;
844
+ if (jsxFrag != null) options.jsxFragmentFactory = jsxFrag;
845
+ if (jsxRuntime != null) options.jsx = jsxRuntime === "classic" ? JsxEmit.React : JsxEmit.ReactJSX;
846
+ if (jsxImportSource != null) options.jsxImportSource = jsxImportSource;
847
+ annotationCache.set(context.sourceCode, options);
848
+ return options;
192
849
  }
193
- function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
194
- if (node == null) return false;
195
- if (isJSX(node)) return true;
196
- switch (node.type) {
197
- case types.AST_NODE_TYPES.Literal: {
198
- switch (typeof node.value) {
199
- case "boolean":
200
- return !(hint & JSXDetectionHint.SkipBooleanLiteral);
201
- case "string":
202
- return !(hint & JSXDetectionHint.SkipStringLiteral);
203
- case "number":
204
- return !(hint & JSXDetectionHint.SkipNumberLiteral);
205
- case "bigint":
206
- return !(hint & JSXDetectionHint.SkipBigIntLiteral);
207
- }
208
- if (node.value == null) {
209
- return !(hint & JSXDetectionHint.SkipNullLiteral);
210
- }
211
- return false;
212
- }
213
- case types.AST_NODE_TYPES.TemplateLiteral: {
214
- return !(hint & JSXDetectionHint.SkipStringLiteral);
215
- }
216
- case types.AST_NODE_TYPES.ArrayExpression: {
217
- if (hint & JSXDetectionHint.StrictArray) {
218
- return node.elements.every((n) => isJsxLike(code, n, hint));
219
- }
220
- return node.elements.some((n) => isJsxLike(code, n, hint));
221
- }
222
- case types.AST_NODE_TYPES.LogicalExpression: {
223
- if (hint & JSXDetectionHint.StrictLogical) {
224
- return isJsxLike(code, node.left, hint) && isJsxLike(code, node.right, hint);
225
- }
226
- return isJsxLike(code, node.left, hint) || isJsxLike(code, node.right, hint);
227
- }
228
- case types.AST_NODE_TYPES.ConditionalExpression: {
229
- let leftHasJSX2 = function(node2) {
230
- if (Array.isArray(node2.consequent)) {
231
- if (node2.consequent.length === 0) {
232
- return !(hint & JSXDetectionHint.SkipEmptyArray);
233
- }
234
- if (hint & JSXDetectionHint.StrictArray) {
235
- return node2.consequent.every((n) => isJsxLike(code, n, hint));
236
- }
237
- return node2.consequent.some((n) => isJsxLike(code, n, hint));
238
- }
239
- return isJsxLike(code, node2.consequent, hint);
240
- }, rightHasJSX2 = function(node2) {
241
- return isJsxLike(code, node2.alternate, hint);
242
- };
243
- if (hint & JSXDetectionHint.StrictConditional) {
244
- return leftHasJSX2(node) && rightHasJSX2(node);
245
- }
246
- return leftHasJSX2(node) || rightHasJSX2(node);
247
- }
248
- case types.AST_NODE_TYPES.SequenceExpression: {
249
- const exp = node.expressions.at(-1);
250
- return isJsxLike(code, exp, hint);
251
- }
252
- case types.AST_NODE_TYPES.CallExpression: {
253
- if (hint & JSXDetectionHint.SkipCreateElement) {
254
- return false;
255
- }
256
- switch (node.callee.type) {
257
- case types.AST_NODE_TYPES.Identifier:
258
- return node.callee.name === "createElement";
259
- case types.AST_NODE_TYPES.MemberExpression:
260
- return node.callee.property.type === types.AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement";
261
- }
262
- return false;
263
- }
264
- case types.AST_NODE_TYPES.Identifier: {
265
- const { name } = node;
266
- if (name === "undefined") {
267
- return !(hint & JSXDetectionHint.SkipUndefined);
268
- }
269
- if (AST2__namespace.isJSXTagNameExpression(node)) {
270
- return true;
271
- }
272
- const variable = VAR__namespace.findVariable(name, code.getScope(node));
273
- const variableNode = variable && VAR__namespace.getVariableInitNode(variable, 0);
274
- return !!variableNode && isJsxLike(code, variableNode, hint);
275
- }
276
- }
277
- return false;
850
+ /**
851
+ * Get the fully‑merged JSX configuration for the current file.
852
+ *
853
+ * Compiler options provide the base values; pragma annotations found in the
854
+ * source override them where present. The result is cached per `sourceCode`.
855
+ *
856
+ * This is the main entry‑point most consumers should use.
857
+ *
858
+ * @param context - The ESLint rule context.
859
+ * @returns Fully‑populated, merged `JsxConfig`.
860
+ */
861
+ function getJsxConfig(context) {
862
+ const cached = mergedCache.get(context.sourceCode);
863
+ if (cached != null) return cached;
864
+ const merged = {
865
+ ...getJsxConfigFromCompilerOptions(context),
866
+ ...getJsxConfigFromAnnotation(context)
867
+ };
868
+ mergedCache.set(context.sourceCode, merged);
869
+ return merged;
278
870
  }
279
871
 
280
- exports.DEFAULT_JSX_DETECTION_HINT = DEFAULT_JSX_DETECTION_HINT;
281
- exports.JSXDetectionHint = JSXDetectionHint;
282
- exports.findParentAttribute = findParentAttribute;
283
- exports.getAttribute = getAttribute;
284
- exports.getAttributeName = getAttributeName;
285
- exports.getAttributeValue = getAttributeValue;
286
- exports.getElementType = getElementType;
287
- exports.hasAnyAttribute = hasAnyAttribute;
288
- exports.hasAttribute = hasAttribute;
289
- exports.hasEveryAttribute = hasEveryAttribute;
290
- exports.isFragmentElement = isFragmentElement;
291
- exports.isHostElement = isHostElement;
292
- exports.isJSX = isJSX;
293
- exports.isJsxLike = isJsxLike;
294
- exports.isJsxText = isJsxText;
295
- exports.isKeyedElement = isKeyedElement;
296
- exports.toString = toString;
872
+ //#endregion
873
+ export { DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, findAttribute, findParentAttribute, getAttributeName, getAttributeStaticValue, getAttributeValue, getChildren, getElementFullType, getElementSelfType, getJsxConfig, getJsxConfigFromAnnotation, getJsxConfigFromCompilerOptions, hasAnyAttribute, hasAttribute, hasChildren, hasEveryAttribute, isElement, isFragmentElement, isHostElement, isJsxLike, isJsxText, isWhitespace, isWhitespaceText, resolveAttributeValue };