@eslint-react/core 3.0.0-next.8 → 3.0.0-next.81

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 +348 -326
  2. package/dist/index.js +715 -550
  3. package/package.json +10 -10
package/dist/index.js CHANGED
@@ -1,18 +1,219 @@
1
- import { findImportSource, findProperty, findVariable, getVariableDefinitionNode } from "@eslint-react/var";
2
1
  import * as ast from "@eslint-react/ast";
3
- import { constFalse, constTrue, dual, flip, getOrElseUpdate, identity, unit } from "@eslint-react/eff";
4
2
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
5
- import { IdGenerator, RE_ANNOTATION_JSX, RE_ANNOTATION_JSX_FRAG, RE_ANNOTATION_JSX_IMPORT_SOURCE, RE_ANNOTATION_JSX_RUNTIME, RE_COMPONENT_NAME, RE_COMPONENT_NAME_LOOSE } from "@eslint-react/shared";
6
- import { getStaticValue } from "@typescript-eslint/utils/ast-utils";
3
+ import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
7
4
  import { P, match } from "ts-pattern";
5
+ import { IdGenerator, RE_ANNOTATION_JSX, RE_ANNOTATION_JSX_FRAG, RE_ANNOTATION_JSX_IMPORT_SOURCE, RE_ANNOTATION_JSX_RUNTIME, RE_COMPONENT_NAME, RE_COMPONENT_NAME_LOOSE } from "@eslint-react/shared";
6
+ import { resolve } from "@eslint-react/var";
8
7
  import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils";
9
8
 
9
+ //#region ../../.pkgs/eff/dist/index.js
10
+ /**
11
+ * Returns its argument.
12
+ *
13
+ * @param x - The value to return.
14
+ * @returns The input value unchanged.
15
+ */
16
+ function identity(x) {
17
+ return x;
18
+ }
19
+ /**
20
+ * Creates a function that can be used in a data-last (aka `pipe`able) or
21
+ * data-first style.
22
+ *
23
+ * The first parameter to `dual` is either the arity of the uncurried function
24
+ * or a predicate that determines if the function is being used in a data-first
25
+ * or data-last style.
26
+ *
27
+ * Using the arity is the most common use case, but there are some cases where
28
+ * you may want to use a predicate. For example, if you have a function that
29
+ * takes an optional argument, you can use a predicate to determine if the
30
+ * function is being used in a data-first or data-last style.
31
+ *
32
+ * You can pass either the arity of the uncurried function or a predicate
33
+ * which determines if the function is being used in a data-first or
34
+ * data-last style.
35
+ *
36
+ * **Example** (Using arity to determine data-first or data-last style)
37
+ *
38
+ * ```ts
39
+ * import { dual, pipe } from "effect/Function"
40
+ *
41
+ * const sum = dual<
42
+ * (that: number) => (self: number) => number,
43
+ * (self: number, that: number) => number
44
+ * >(2, (self, that) => self + that)
45
+ *
46
+ * console.log(sum(2, 3)) // 5
47
+ * console.log(pipe(2, sum(3))) // 5
48
+ * ```
49
+ *
50
+ * **Example** (Using call signatures to define the overloads)
51
+ *
52
+ * ```ts
53
+ * import { dual, pipe } from "effect/Function"
54
+ *
55
+ * const sum: {
56
+ * (that: number): (self: number) => number
57
+ * (self: number, that: number): number
58
+ * } = dual(2, (self: number, that: number): number => self + that)
59
+ *
60
+ * console.log(sum(2, 3)) // 5
61
+ * console.log(pipe(2, sum(3))) // 5
62
+ * ```
63
+ *
64
+ * **Example** (Using a predicate to determine data-first or data-last style)
65
+ *
66
+ * ```ts
67
+ * import { dual, pipe } from "effect/Function"
68
+ *
69
+ * const sum = dual<
70
+ * (that: number) => (self: number) => number,
71
+ * (self: number, that: number) => number
72
+ * >(
73
+ * (args) => args.length === 2,
74
+ * (self, that) => self + that
75
+ * )
76
+ *
77
+ * console.log(sum(2, 3)) // 5
78
+ * console.log(pipe(2, sum(3))) // 5
79
+ * ```
80
+ *
81
+ * @param arity - The arity of the uncurried function or a predicate that determines if the function is being used in a data-first or data-last style.
82
+ * @param body - The function to be curried.
83
+ * @since 1.0.0
84
+ */
85
+ const dual = function(arity, body) {
86
+ if (typeof arity === "function") return function() {
87
+ return arity(arguments) ? body.apply(this, arguments) : ((self) => body(self, ...arguments));
88
+ };
89
+ switch (arity) {
90
+ case 0:
91
+ case 1: throw new RangeError(`Invalid arity ${arity}`);
92
+ case 2: return function(a, b) {
93
+ if (arguments.length >= 2) return body(a, b);
94
+ return function(self) {
95
+ return body(self, a);
96
+ };
97
+ };
98
+ case 3: return function(a, b, c) {
99
+ if (arguments.length >= 3) return body(a, b, c);
100
+ return function(self) {
101
+ return body(self, a, b);
102
+ };
103
+ };
104
+ default: return function() {
105
+ if (arguments.length >= arity) return body.apply(this, arguments);
106
+ const args = arguments;
107
+ return function(self) {
108
+ return body(self, ...args);
109
+ };
110
+ };
111
+ }
112
+ };
113
+ /**
114
+ * Do nothing and return `false`.
115
+ *
116
+ * @returns false
117
+ */
118
+ function constFalse() {
119
+ return false;
120
+ }
121
+ /**
122
+ * Reverses the order of arguments for a curried function.
123
+ *
124
+ * @param f - The function to flip.
125
+ * @returns A new function with the argument order reversed.
126
+ * @example
127
+ * ```ts
128
+ * import * as assert from "node:assert"
129
+ * import { flip } from "effect/Function"
130
+ *
131
+ * const f = (a: number) => (b: string) => a - b.length
132
+ *
133
+ * assert.deepStrictEqual(flip(f)('aaa')(2), -1)
134
+ * ```
135
+ *
136
+ * @since 1.0.0
137
+ */
138
+ const flip = (f) => (...b) => (...a) => f(...a)(...b);
139
+ /**
140
+ * Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`.
141
+ * The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
142
+ *
143
+ * @param self - The first function to apply (or the composed function in data-last style).
144
+ * @param bc - The second function to apply.
145
+ * @returns A composed function that applies both functions in sequence.
146
+ * @example
147
+ * ```ts
148
+ * import * as assert from "node:assert"
149
+ * import { compose } from "effect/Function"
150
+ *
151
+ * const increment = (n: number) => n + 1;
152
+ * const square = (n: number) => n * n;
153
+ *
154
+ * assert.strictEqual(compose(increment, square)(2), 9);
155
+ * ```
156
+ *
157
+ * @since 1.0.0
158
+ */
159
+ const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
160
+ function getOrElseUpdate(map, key, callback) {
161
+ if (map.has(key)) return map.get(key);
162
+ const value = callback();
163
+ map.set(key, value);
164
+ return value;
165
+ }
166
+
167
+ //#endregion
168
+ //#region src/api/find-import-source.ts
169
+ /**
170
+ * Get the arguments of a require expression
171
+ * @param node The node to match
172
+ * @returns The require expression arguments or null if the node is not a require expression
173
+ * @internal
174
+ */
175
+ function getRequireExpressionArguments(node) {
176
+ return match(node).with({
177
+ type: AST_NODE_TYPES.CallExpression,
178
+ arguments: P.select(),
179
+ callee: {
180
+ type: AST_NODE_TYPES.Identifier,
181
+ name: "require"
182
+ }
183
+ }, identity).with({
184
+ type: AST_NODE_TYPES.MemberExpression,
185
+ object: P.select()
186
+ }, getRequireExpressionArguments).otherwise(() => null);
187
+ }
188
+ /**
189
+ * Find the import source of a variable
190
+ * @param name The variable name
191
+ * @param initialScope The initial scope to search
192
+ * @returns The import source or null if not found
193
+ */
194
+ function findImportSource(name, initialScope) {
195
+ const latestDef = findVariable(initialScope, name)?.defs.at(-1);
196
+ if (latestDef == null) return null;
197
+ const { node, parent } = latestDef;
198
+ if (node.type === AST_NODE_TYPES.VariableDeclarator && node.init != null) {
199
+ const { init } = node;
200
+ if (init.type === AST_NODE_TYPES.MemberExpression && init.object.type === AST_NODE_TYPES.Identifier) return findImportSource(init.object.name, initialScope);
201
+ if (init.type === AST_NODE_TYPES.Identifier) return findImportSource(init.name, initialScope);
202
+ const arg0 = getRequireExpressionArguments(init)?.[0];
203
+ if (arg0 == null || !ast.isLiteral(arg0, "string")) return null;
204
+ return arg0.value;
205
+ }
206
+ if (parent?.type === AST_NODE_TYPES.ImportDeclaration) return parent.source.value;
207
+ return null;
208
+ }
209
+
210
+ //#endregion
10
211
  //#region src/api/is-from-react.ts
11
212
  /**
12
213
  * Check if a variable is initialized from React import
13
214
  * @param name The variable name
14
215
  * @param initialScope The initial scope
15
- * @param importSource Alternative import source of React (e.g., "preact/compat")
216
+ * @param importSource Alternative import source of React (ex: "preact/compat")
16
217
  * @returns True if the variable is initialized or derived from React import
17
218
  */
18
219
  function isInitializedFromReact(name, initialScope, importSource = "react") {
@@ -25,7 +226,7 @@ function isInitializedFromReact(name, initialScope, importSource = "react") {
25
226
  * if a variable is initialized from React Native import
26
227
  * @param name The variable name
27
228
  * @param initialScope The initial scope
28
- * @param importSource Alternative import source of React Native (e.g., "react-native-web")
229
+ * @param importSource Alternative import source of React Native (ex: "react-native-web")
29
230
  * @returns True if the variable is initialized from React Native import
30
231
  */
31
232
  function isInitializedFromReactNative(name, initialScope, importSource = "react-native") {
@@ -40,7 +241,7 @@ function isInitializedFromReactNative(name, initialScope, importSource = "react-
40
241
  //#region src/api/is-react-api.ts
41
242
  /**
42
243
  * Check if the node is a React API identifier or member expression
43
- * @param api The React API name to check against (e.g., "useState", "React.memo")
244
+ * @param api The React API name to check against (ex: "useState", "React.memo")
44
245
  * @returns A predicate function to check if a node matches the API
45
246
  */
46
247
  function isReactAPI(api) {
@@ -195,11 +396,11 @@ function isUseEffectLikeCall(node, additionalEffectHooks = { test: constFalse })
195
396
  function isUseStateLikeCall(node, additionalStateHooks = { test: constFalse }) {
196
397
  if (node == null) return false;
197
398
  if (node.type !== AST_NODE_TYPES.CallExpression) return false;
198
- return [/^use\w*State$/u, additionalStateHooks].some((regexp) => {
199
- if (node.callee.type === AST_NODE_TYPES.Identifier) return regexp.test(node.callee.name);
200
- if (node.callee.type === AST_NODE_TYPES.MemberExpression) return node.callee.property.type === AST_NODE_TYPES.Identifier && regexp.test(node.callee.property.name);
201
- return false;
202
- });
399
+ switch (true) {
400
+ case node.callee.type === AST_NODE_TYPES.Identifier: return node.callee.name === "useState" || additionalStateHooks.test(node.callee.name);
401
+ case node.callee.type === AST_NODE_TYPES.MemberExpression && node.callee.property.type === AST_NODE_TYPES.Identifier: return ast.getPropertyName(node.callee.property) === "useState" || additionalStateHooks.test(node.callee.property.name);
402
+ }
403
+ return false;
203
404
  }
204
405
  const isUseCall = flip(isHookCallWithName)("use");
205
406
  const isUseActionStateCall = flip(isHookCallWithName)("useActionState");
@@ -260,7 +461,7 @@ function isHookId(id) {
260
461
 
261
462
  //#endregion
262
463
  //#region src/hook/hook-collector.ts
263
- const idGen$2 = new IdGenerator("hook_");
464
+ const idGen$2 = new IdGenerator("hook:");
264
465
  /**
265
466
  * Get a ctx and visitor object for the rule to collect hooks
266
467
  * @param context The ESLint rule context
@@ -270,7 +471,7 @@ function useHookCollector(context) {
270
471
  const hooks = /* @__PURE__ */ new Map();
271
472
  const functionEntries = [];
272
473
  const getText = (n) => context.sourceCode.getText(n);
273
- const getCurrentEntry = () => functionEntries.at(-1);
474
+ const getCurrentEntry = () => functionEntries.at(-1) ?? null;
274
475
  const onFunctionEnter = (node) => {
275
476
  const id = ast.getFunctionId(node);
276
477
  const key = idGen$2.next();
@@ -284,11 +485,11 @@ function useHookCollector(context) {
284
485
  key,
285
486
  kind: "function",
286
487
  name: ast.getFullyQualifiedName(id, getText),
287
- node,
288
488
  directives: [],
289
489
  flag: 0n,
290
490
  hint: 0n,
291
- hookCalls: []
491
+ hookCalls: [],
492
+ node
292
493
  });
293
494
  };
294
495
  const onFunctionExit = () => {
@@ -315,150 +516,6 @@ function useHookCollector(context) {
315
516
  };
316
517
  }
317
518
 
318
- //#endregion
319
- //#region src/jsx/jsx-stringify.ts
320
- /**
321
- * Incomplete but sufficient stringification of JSX nodes for common use cases
322
- *
323
- * @param node JSX node from TypeScript ESTree
324
- * @returns String representation of the JSX node
325
- */
326
- function stringifyJsx(node) {
327
- switch (node.type) {
328
- case AST_NODE_TYPES.JSXIdentifier: return node.name;
329
- case AST_NODE_TYPES.JSXNamespacedName: return `${node.namespace.name}:${node.name.name}`;
330
- case AST_NODE_TYPES.JSXMemberExpression: return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`;
331
- case AST_NODE_TYPES.JSXText: return node.value;
332
- case AST_NODE_TYPES.JSXOpeningElement: return `<${stringifyJsx(node.name)}>`;
333
- case AST_NODE_TYPES.JSXClosingElement: return `</${stringifyJsx(node.name)}>`;
334
- case AST_NODE_TYPES.JSXOpeningFragment: return "<>";
335
- case AST_NODE_TYPES.JSXClosingFragment: return "</>";
336
- }
337
- }
338
-
339
- //#endregion
340
- //#region src/jsx/jsx-attribute-name.ts
341
- /**
342
- * Get the stringified name of a JSX attribute
343
- * @param context The ESLint rule context
344
- * @param node The JSX attribute node
345
- * @returns The name of the attribute
346
- */
347
- function getJsxAttributeName(context, node) {
348
- return stringifyJsx(node.name);
349
- }
350
-
351
- //#endregion
352
- //#region src/jsx/jsx-attribute.ts
353
- /**
354
- * Creates a helper function to find a specific JSX attribute by name
355
- * Handles direct attributes and spread attributes (variables or object literals)
356
- * @param context The ESLint rule context
357
- * @param node The JSX element node
358
- * @param initialScope (Optional) The initial scope to use for variable resolution
359
- */
360
- function getJsxAttribute(context, node, initialScope) {
361
- const scope = initialScope ?? context.sourceCode.getScope(node);
362
- const attributes = node.openingElement.attributes;
363
- /**
364
- * Finds the last occurrence of a specific attribute
365
- * @param name The attribute name to search for
366
- */
367
- return (name) => {
368
- return attributes.findLast((attr) => {
369
- if (attr.type === AST_NODE_TYPES.JSXAttribute) return getJsxAttributeName(context, attr) === name;
370
- switch (attr.argument.type) {
371
- case AST_NODE_TYPES.Identifier: {
372
- const variableNode = getVariableDefinitionNode(findVariable(attr.argument.name, scope), 0);
373
- if (variableNode?.type === AST_NODE_TYPES.ObjectExpression) return findProperty(name, variableNode.properties, scope) != null;
374
- return false;
375
- }
376
- case AST_NODE_TYPES.ObjectExpression: return findProperty(name, attr.argument.properties, scope) != null;
377
- }
378
- return false;
379
- });
380
- };
381
- }
382
-
383
- //#endregion
384
- //#region src/jsx/jsx-attribute-value.ts
385
- /**
386
- * Resolve the static value of a JSX attribute or spread attribute
387
- *
388
- * @param context - The ESLint rule context
389
- * @param attribute - The JSX attribute node to resolve
390
- * @returns An object containing the value kind, the node (if applicable), and a `toStatic` helper
391
- */
392
- function resolveJsxAttributeValue(context, attribute) {
393
- const initialScope = context.sourceCode.getScope(attribute);
394
- /**
395
- * Handles standard JSX attributes (e.g., prop="value", prop={value}, prop)
396
- * @param node The JSX attribute node
397
- */
398
- function handleJsxAttribute(node) {
399
- if (node.value == null) return {
400
- kind: "boolean",
401
- toStatic() {
402
- return true;
403
- }
404
- };
405
- switch (node.value.type) {
406
- case AST_NODE_TYPES.Literal: {
407
- const staticValue = node.value.value;
408
- return {
409
- kind: "literal",
410
- node: node.value,
411
- toStatic() {
412
- return staticValue;
413
- }
414
- };
415
- }
416
- case AST_NODE_TYPES.JSXExpressionContainer: {
417
- const expr = node.value.expression;
418
- return {
419
- kind: "expression",
420
- node: expr,
421
- toStatic() {
422
- return getStaticValue(expr, initialScope)?.value;
423
- }
424
- };
425
- }
426
- case AST_NODE_TYPES.JSXElement: return {
427
- kind: "element",
428
- node: node.value,
429
- toStatic() {
430
- return unit;
431
- }
432
- };
433
- case AST_NODE_TYPES.JSXSpreadChild: return {
434
- kind: "spreadChild",
435
- node: node.value.expression,
436
- toStatic() {
437
- return unit;
438
- }
439
- };
440
- }
441
- }
442
- /**
443
- * Handles JSX spread attributes (e.g., {...props})
444
- * @param node The JSX spread attribute node
445
- */
446
- function handleJsxSpreadAttribute(node) {
447
- return {
448
- kind: "spreadProps",
449
- node: node.argument,
450
- toStatic(name) {
451
- if (name == null) return unit;
452
- return match(getStaticValue(node.argument, initialScope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => unit);
453
- }
454
- };
455
- }
456
- switch (attribute.type) {
457
- case AST_NODE_TYPES.JSXAttribute: return handleJsxAttribute(attribute);
458
- case AST_NODE_TYPES.JSXSpreadAttribute: return handleJsxSpreadAttribute(attribute);
459
- }
460
- }
461
-
462
519
  //#endregion
463
520
  //#region src/jsx/jsx-config.ts
464
521
  const JsxEmit = {
@@ -531,25 +588,15 @@ const JsxDetectionHint = {
531
588
  */
532
589
  const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
533
590
  /**
534
- * Check if a node is a `JSXText` or a `Literal` node
535
- * @param node The AST node to check
536
- * @returns `true` if the node is a `JSXText` or a `Literal` node
537
- */
538
- function isJsxText(node) {
539
- if (node == null) return false;
540
- return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
541
- }
542
- /**
543
591
  * Determine if a node represents JSX-like content based on heuristics
544
592
  * Supports configuration through hint flags to customize detection behavior
545
593
  *
546
- * @param code The source code with scope lookup capability
547
- * @param code.getScope The function to get the scope of a node
594
+ * @param context The rule context with scope lookup capability
548
595
  * @param node The AST node to analyze
549
596
  * @param hint The configuration flags to adjust detection behavior
550
597
  * @returns boolean Whether the node is considered JSX-like
551
598
  */
552
- function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
599
+ function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
553
600
  if (node == null) return false;
554
601
  if (ast.isJSX(node)) return true;
555
602
  switch (node.type) {
@@ -565,27 +612,27 @@ function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
565
612
  case AST_NODE_TYPES.TemplateLiteral: return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue);
566
613
  case AST_NODE_TYPES.ArrayExpression:
567
614
  if (node.elements.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
568
- if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(code, n, hint));
569
- return node.elements.some((n) => isJsxLike(code, n, hint));
615
+ if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(context, n, hint));
616
+ return node.elements.some((n) => isJsxLike(context, n, hint));
570
617
  case AST_NODE_TYPES.LogicalExpression:
571
- if (hint & JsxDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx) return isJsxLike(code, node.left, hint) && isJsxLike(code, node.right, hint);
572
- return isJsxLike(code, node.left, hint) || isJsxLike(code, node.right, hint);
618
+ if (hint & JsxDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx) return isJsxLike(context, node.left, hint) && isJsxLike(context, node.right, hint);
619
+ return isJsxLike(context, node.left, hint) || isJsxLike(context, node.right, hint);
573
620
  case AST_NODE_TYPES.ConditionalExpression: {
574
621
  function leftHasJSX(node) {
575
622
  if (Array.isArray(node.consequent)) {
576
623
  if (node.consequent.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
577
- if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.consequent.every((n) => isJsxLike(code, n, hint));
578
- return node.consequent.some((n) => isJsxLike(code, n, hint));
624
+ if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.consequent.every((n) => isJsxLike(context, n, hint));
625
+ return node.consequent.some((n) => isJsxLike(context, n, hint));
579
626
  }
580
- return isJsxLike(code, node.consequent, hint);
627
+ return isJsxLike(context, node.consequent, hint);
581
628
  }
582
629
  function rightHasJSX(node) {
583
- return isJsxLike(code, node.alternate, hint);
630
+ return isJsxLike(context, node.alternate, hint);
584
631
  }
585
632
  if (hint & JsxDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx) return leftHasJSX(node) && rightHasJSX(node);
586
633
  return leftHasJSX(node) || rightHasJSX(node);
587
634
  }
588
- case AST_NODE_TYPES.SequenceExpression: return isJsxLike(code, node.expressions.at(-1), hint);
635
+ case AST_NODE_TYPES.SequenceExpression: return isJsxLike(context, node.expressions.at(-1) ?? null, hint);
589
636
  case AST_NODE_TYPES.CallExpression:
590
637
  if (hint & JsxDetectionHint.DoNotIncludeJsxWithCreateElementValue) return false;
591
638
  switch (node.callee.type) {
@@ -593,130 +640,444 @@ function isJsxLike(code, node, hint = DEFAULT_JSX_DETECTION_HINT) {
593
640
  case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement";
594
641
  }
595
642
  return false;
596
- case AST_NODE_TYPES.Identifier: {
597
- const { name } = node;
598
- if (name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
643
+ case AST_NODE_TYPES.Identifier:
644
+ if (node.name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
599
645
  if (ast.isJSXTagNameExpression(node)) return true;
600
- return isJsxLike(code, getVariableDefinitionNode(findVariable(name, code.getScope(node)), 0), hint);
601
- }
646
+ return isJsxLike(context, resolve(context, node), hint);
602
647
  }
603
648
  return false;
604
649
  }
605
650
 
606
651
  //#endregion
607
- //#region src/jsx/jsx-element-type.ts
652
+ //#region src/jsx/jsx-stringify.ts
608
653
  /**
609
- * Extracts the element type name from a JSX element or fragment
610
- * For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment")
611
- * For JSX fragments, returns an empty string
654
+ * Incomplete but sufficient stringification of JSX nodes for common use cases
612
655
  *
613
- * @param context ESLint rule context
614
- * @param node JSX element or fragment node
615
- * @returns String representation of the element type
656
+ * @param node JSX node from TypeScript ESTree
657
+ * @returns String representation of the JSX node
616
658
  */
617
- function getJsxElementType(context, node) {
618
- if (node.type === AST_NODE_TYPES.JSXFragment) return "";
619
- return stringifyJsx(node.openingElement.name);
659
+ function stringifyJsx(node) {
660
+ switch (node.type) {
661
+ case AST_NODE_TYPES.JSXIdentifier: return node.name;
662
+ case AST_NODE_TYPES.JSXNamespacedName: return `${node.namespace.name}:${node.name.name}`;
663
+ case AST_NODE_TYPES.JSXMemberExpression: return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`;
664
+ case AST_NODE_TYPES.JSXText: return node.value;
665
+ case AST_NODE_TYPES.JSXOpeningElement: return `<${stringifyJsx(node.name)}>`;
666
+ case AST_NODE_TYPES.JSXClosingElement: return `</${stringifyJsx(node.name)}>`;
667
+ case AST_NODE_TYPES.JSXOpeningFragment: return "<>";
668
+ case AST_NODE_TYPES.JSXClosingFragment: return "</>";
669
+ }
620
670
  }
621
671
 
622
672
  //#endregion
623
- //#region src/jsx/jsx-element-is.ts
673
+ //#region src/jsx/jsx-inspector.ts
624
674
  /**
625
- * Determine if a JSX element is a host element
626
- * Host elements in React start with lowercase letters (e.g., div, span)
675
+ * A stateful helper that binds an ESLint `RuleContext` once and exposes
676
+ * ergonomic methods for the most common JSX inspection tasks that rules need.
627
677
  *
628
- * @param context ESLint rule context
629
- * @param node AST node to check
630
- * @returns boolean indicating if the element is a host element
631
- */
632
- function isJsxHostElement(context, node) {
633
- return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
634
- }
635
- /**
636
- * Determine if a JSX element is a React Fragment
637
- * Fragments can be imported from React and used like <Fragment> or <React.Fragment>
678
+ * ### Typical usage inside a rule's `create` function
638
679
  *
639
- * @param context ESLint rule context
640
- * @param node AST node to check
641
- * @param jsxConfig Optional JSX configuration
642
- * @param jsxConfig.jsxFragmentFactory Name of the fragment factory (e.g., React.Fragment)
643
- * @returns boolean indicating if the element is a Fragment
644
- */
645
- function isJsxFragmentElement(context, node, jsxConfig) {
646
- if (node.type !== AST_NODE_TYPES.JSXElement) return false;
647
- const fragment = jsxConfig?.jsxFragmentFactory?.split(".").at(-1) ?? "Fragment";
648
- return getJsxElementType(context, node).split(".").at(-1) === fragment;
649
- }
650
-
651
- //#endregion
652
- //#region src/jsx/jsx-hierarchy.ts
653
- /**
654
- * Traverses up the AST to find a parent JSX attribute node that matches a given test
680
+ * ```ts
681
+ * export function create(context: RuleContext) {
682
+ * const jsx = JsxInspector.from(context);
655
683
  *
656
- * @param node The starting AST node
657
- * @param test Optional predicate function to test if the attribute meets criteria
658
- * Defaults to always returning true (matches any attribute)
659
- * @returns The first matching JSX attribute node found when traversing upwards, or undefined
660
- */
661
- function findParentJsxAttribute(node, test = constTrue) {
662
- const guard = (node) => {
663
- return node.type === AST_NODE_TYPES.JSXAttribute && test(node);
664
- };
665
- return ast.findParentNode(node, guard);
666
- }
667
-
668
- //#endregion
669
- //#region src/component/component-detection-hint.ts
670
- /**
671
- * Hints for component collector
672
- */
673
- const ComponentDetectionHint = {
674
- ...JsxDetectionHint,
675
- DoNotIncludeFunctionDefinedOnObjectMethod: 1n << 64n,
676
- DoNotIncludeFunctionDefinedOnClassMethod: 1n << 65n,
677
- DoNotIncludeFunctionDefinedOnClassProperty: 1n << 66n,
678
- DoNotIncludeFunctionDefinedInArrayPattern: 1n << 67n,
679
- DoNotIncludeFunctionDefinedInArrayExpression: 1n << 68n,
680
- DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 69n,
681
- DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 70n
682
- };
683
- /**
684
- * Default component detection hint
685
- */
686
- const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
687
-
688
- //#endregion
689
- //#region src/component/component-is.ts
690
- /**
691
- * Check if a node is a React class component
692
- * @param node The AST node to check
693
- * @returns `true` if the node is a class component, `false` otherwise
684
+ * return defineRuleListener({
685
+ * JSXElement(node) {
686
+ * // element type
687
+ * const type = jsx.getElementType(node); // "div" | "React.Fragment" |
688
+ *
689
+ * // attribute lookup + value resolution in one step
690
+ * const val = jsx.getAttributeValue(node, "sandbox");
691
+ * if (typeof val?.getStatic() === "string") { … }
692
+ *
693
+ * // simple boolean checks
694
+ * if (jsx.isHostElement(node)) { … }
695
+ * if (jsx.isFragmentElement(node)) { … }
696
+ * if (jsx.hasAttribute(node, "key")) { … }
697
+ * },
698
+ * });
699
+ * }
700
+ * ```
694
701
  */
695
- function isClassComponent(node) {
696
- if ("superClass" in node && node.superClass != null) {
697
- const re = /^(?:Pure)?Component$/u;
698
- switch (true) {
699
- case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
700
- case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
701
- }
702
+ var JsxInspector = class JsxInspector {
703
+ context;
704
+ /**
705
+ * Merged JSX configuration (tsconfig compiler options + pragma annotations).
706
+ * The result is lazily computed and cached for the lifetime of this inspector.
707
+ */
708
+ get jsxConfig() {
709
+ return this.#jsxConfig ??= {
710
+ ...getJsxConfigFromContext(this.context),
711
+ ...getJsxConfigFromAnnotation(this.context)
712
+ };
702
713
  }
703
- return false;
704
- }
705
- /**
706
- * Check if a node is a React PureComponent
707
- * @param node The AST node to check
708
- * @returns `true` if the node is a PureComponent, `false` otherwise
709
- */
710
- function isPureComponent(node) {
711
- if ("superClass" in node && node.superClass != null) {
712
- const re = /^PureComponent$/u;
713
- switch (true) {
714
- case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
714
+ /**
715
+ * Lazily resolved & cached JSX configuration (merged from tsconfig +
716
+ * pragma annotations). Use {@link jsxConfig} to access.
717
+ */
718
+ #jsxConfig;
719
+ constructor(context) {
720
+ this.context = context;
721
+ }
722
+ /**
723
+ * Walk **up** the AST from `node` to find the nearest ancestor that is a
724
+ * `JSXAttribute` and passes the optional `test` predicate.
725
+ * @param node The starting node for the search.
726
+ * @param test A predicate function to test each ancestor node.
727
+ */
728
+ static findParentAttribute(node, test = () => true) {
729
+ const guard = (n) => {
730
+ return n.type === AST_NODE_TYPES.JSXAttribute && test(n);
731
+ };
732
+ return ast.findParentNode(node, guard);
733
+ }
734
+ /**
735
+ * Create a new `JsxInspector` bound to the given rule context.
736
+ * @param context The ESLint rule context to bind to this inspector instance.
737
+ */
738
+ static from(context) {
739
+ return new JsxInspector(context);
740
+ }
741
+ /**
742
+ * Whether the node is a `JSXText` or a `Literal` node.
743
+ * @param node The node to check.
744
+ */
745
+ static isJsxText(node) {
746
+ if (node == null) return false;
747
+ return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
748
+ }
749
+ /**
750
+ * Find a JSX attribute (or spread attribute containing the property) by name
751
+ * on a given element.
752
+ *
753
+ * Returns the **last** matching attribute (to mirror React's behaviour where
754
+ * later props win), or `undefined` if not found.
755
+ * @param node The JSX element to search for the attribute.
756
+ * @param name The name of the attribute to find (ex: `"className"`).
757
+ */
758
+ findAttribute(node, name) {
759
+ return node.openingElement.attributes.findLast((attr) => {
760
+ if (attr.type === AST_NODE_TYPES.JSXAttribute) return stringifyJsx(attr.name) === name;
761
+ switch (attr.argument.type) {
762
+ case AST_NODE_TYPES.Identifier: {
763
+ const initNode = resolve(this.context, attr.argument);
764
+ if (initNode?.type === AST_NODE_TYPES.ObjectExpression) return ast.findProperty(initNode.properties, name) != null;
765
+ return false;
766
+ }
767
+ case AST_NODE_TYPES.ObjectExpression: return ast.findProperty(attr.argument.properties, name) != null;
768
+ }
769
+ return false;
770
+ });
771
+ }
772
+ /**
773
+ * Get the stringified name of a `JSXAttribute` node
774
+ * (ex: `"className"`, `"aria-label"`, `"xml:space"`).
775
+ * @param node The `JSXAttribute` node to extract the name from.
776
+ * @returns The stringified name of the attribute.
777
+ */
778
+ getAttributeName(node) {
779
+ return stringifyJsx(node.name);
780
+ }
781
+ /**
782
+ * Resolve the static value of an attribute, automatically handling the
783
+ * `spreadProps` case by extracting the named property.
784
+ *
785
+ * This eliminates the repetitive pattern:
786
+ * ```ts
787
+ * const v = core.resolveJsxAttributeValue(ctx, attr);
788
+ * const s = v.kind === "spreadProps" ? v.getProperty(name) : v.toStatic();
789
+ * ```
790
+ *
791
+ * Returns `undefined` when the attribute is not present or its value
792
+ * cannot be statically determined.
793
+ * @param node The JSX element to search for the attribute.
794
+ * @param name The name of the attribute to resolve (ex: `"className"`).
795
+ * @returns The static value of the attribute, or `undefined` if not found or not statically resolvable.
796
+ */
797
+ getAttributeStaticValue(node, name) {
798
+ const attr = this.findAttribute(node, name);
799
+ if (attr == null) return void 0;
800
+ const resolved = this.resolveAttributeValue(attr);
801
+ if (resolved.kind === "spreadProps") return resolved.getProperty(name);
802
+ return resolved.toStatic();
803
+ }
804
+ /**
805
+ * **All-in-one helper** – find an attribute by name on an element *and*
806
+ * resolve its value in a single call.
807
+ *
808
+ * Returns `undefined` when the attribute is not present.
809
+ * @param node The JSX element to search for the attribute.
810
+ * @param name The name of the attribute to find and resolve (ex: `"className"`).
811
+ * @returns A descriptor of the attribute's value that can be further inspected, or `undefined` if the attribute is not found.
812
+ */
813
+ getAttributeValue(node, name) {
814
+ const attr = this.findAttribute(node, name);
815
+ if (attr == null) return void 0;
816
+ return this.resolveAttributeValue(attr);
817
+ }
818
+ /**
819
+ * Get the **self name** (last segment) of a JSX element type.
820
+ *
821
+ * - `<Foo.Bar.Baz>` → `"Baz"`
822
+ * - `<div>` → `"div"`
823
+ * - `<></>` → `""`
824
+ * @param node The JSX element or fragment to extract the self name from.
825
+ */
826
+ getElementSelfName(node) {
827
+ return this.getElementType(node).split(".").at(-1) ?? "";
828
+ }
829
+ /**
830
+ * Get the string representation of a JSX element's type.
831
+ *
832
+ * - `<div>` → `"div"`
833
+ * - `<Foo.Bar>` → `"Foo.Bar"`
834
+ * - `<React.Fragment>` → `"React.Fragment"`
835
+ * - `<></>` (JSXFragment) → `""`
836
+ * @param node The JSX element or fragment to extract the type from.
837
+ */
838
+ getElementType(node) {
839
+ if (node.type === AST_NODE_TYPES.JSXFragment) return "";
840
+ return stringifyJsx(node.openingElement.name);
841
+ }
842
+ /**
843
+ * Shorthand: check whether an attribute exists on the element.
844
+ * @param node The JSX element to check for the attribute.
845
+ * @param name The name of the attribute to check for (ex: `"className"`).
846
+ * @returns `true` if the attribute exists on the element, `false` otherwise.
847
+ */
848
+ hasAttribute(node, name) {
849
+ return this.findAttribute(node, name) != null;
850
+ }
851
+ /**
852
+ * Whether the node is a React **Fragment** element (either `<Fragment>` /
853
+ * `<React.Fragment>` or the shorthand `<>` syntax).
854
+ *
855
+ * The check honours the configured `jsxFragmentFactory`.
856
+ * @param node The node to check.
857
+ */
858
+ isFragmentElement(node) {
859
+ if (node.type === AST_NODE_TYPES.JSXFragment) return true;
860
+ if (node.type !== AST_NODE_TYPES.JSXElement) return false;
861
+ const fragment = this.jsxConfig.jsxFragmentFactory.split(".").at(-1) ?? "Fragment";
862
+ return this.getElementType(node).split(".").at(-1) === fragment;
863
+ }
864
+ /**
865
+ * Whether the node is a **host** (intrinsic / DOM) element – i.e. its tag
866
+ * name starts with a lowercase letter.
867
+ * @param node The node to check.
868
+ */
869
+ isHostElement(node) {
870
+ return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
871
+ }
872
+ /**
873
+ * Resolve the *value* of a JSX attribute (or spread attribute) into a
874
+ * descriptor that can be inspected further.
875
+ *
876
+ * See {@link JsxAttributeValue} for the full set of `kind` discriminants.
877
+ * @param attribute The attribute node to resolve the value of.
878
+ * @returns A descriptor of the attribute's value that can be further inspected.
879
+ */
880
+ resolveAttributeValue(attribute) {
881
+ if (attribute.type === AST_NODE_TYPES.JSXAttribute) return this.#resolveJsxAttribute(attribute);
882
+ return this.#resolveJsxSpreadAttribute(attribute);
883
+ }
884
+ #resolveJsxAttribute(node) {
885
+ const scope = this.context.sourceCode.getScope(node);
886
+ if (node.value == null) return {
887
+ kind: "boolean",
888
+ toStatic() {
889
+ return true;
890
+ }
891
+ };
892
+ switch (node.value.type) {
893
+ case AST_NODE_TYPES.Literal: {
894
+ const staticValue = node.value.value;
895
+ return {
896
+ kind: "literal",
897
+ node: node.value,
898
+ toStatic() {
899
+ return staticValue;
900
+ }
901
+ };
902
+ }
903
+ case AST_NODE_TYPES.JSXExpressionContainer: {
904
+ const expr = node.value.expression;
905
+ if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return {
906
+ kind: "missing",
907
+ node: expr,
908
+ toStatic() {
909
+ return null;
910
+ }
911
+ };
912
+ return {
913
+ kind: "expression",
914
+ node: expr,
915
+ toStatic() {
916
+ return getStaticValue(expr, scope)?.value;
917
+ }
918
+ };
919
+ }
920
+ case AST_NODE_TYPES.JSXElement: return {
921
+ kind: "element",
922
+ node: node.value,
923
+ toStatic() {
924
+ return null;
925
+ }
926
+ };
927
+ case AST_NODE_TYPES.JSXSpreadChild:
928
+ node.value.expression;
929
+ return {
930
+ kind: "spreadChild",
931
+ node: node.value.expression,
932
+ toStatic() {
933
+ return null;
934
+ },
935
+ getChildren(_at) {
936
+ return null;
937
+ }
938
+ };
939
+ }
940
+ }
941
+ #resolveJsxSpreadAttribute(node) {
942
+ const scope = this.context.sourceCode.getScope(node);
943
+ return {
944
+ kind: "spreadProps",
945
+ node: node.argument,
946
+ toStatic() {
947
+ return null;
948
+ },
949
+ getProperty(name) {
950
+ return match(getStaticValue(node.argument, scope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => null);
951
+ }
952
+ };
953
+ }
954
+ };
955
+
956
+ //#endregion
957
+ //#region src/component/component-detection-legacy.ts
958
+ /**
959
+ * Check if a node is a React class component
960
+ * @param node The AST node to check
961
+ * @returns `true` if the node is a class component, `false` otherwise
962
+ */
963
+ function isClassComponent(node) {
964
+ if ("superClass" in node && node.superClass != null) {
965
+ const re = /^(?:Pure)?Component$/u;
966
+ switch (true) {
967
+ case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
715
968
  case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
716
969
  }
717
970
  }
718
971
  return false;
719
972
  }
973
+ /**
974
+ * Check if a node is a React PureComponent
975
+ * @param node The AST node to check
976
+ * @returns `true` if the node is a PureComponent, `false` otherwise
977
+ */
978
+ function isPureComponent(node) {
979
+ if ("superClass" in node && node.superClass != null) {
980
+ const re = /^PureComponent$/u;
981
+ switch (true) {
982
+ case node.superClass.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.name);
983
+ case node.superClass.type === AST_NODE_TYPES.MemberExpression && node.superClass.property.type === AST_NODE_TYPES.Identifier: return re.test(node.superClass.property.name);
984
+ }
985
+ }
986
+ return false;
987
+ }
988
+ /**
989
+ * Create a lifecycle method checker function
990
+ * @param methodName The lifecycle method name
991
+ * @param isStatic Whether the method is static
992
+ */
993
+ function createLifecycleChecker(methodName, isStatic = false) {
994
+ return (node) => ast.isMethodOrProperty(node) && node.static === isStatic && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === methodName;
995
+ }
996
+ const isRender = createLifecycleChecker("render");
997
+ const isComponentDidCatch = createLifecycleChecker("componentDidCatch");
998
+ const isComponentDidMount = createLifecycleChecker("componentDidMount");
999
+ const isComponentDidUpdate = createLifecycleChecker("componentDidUpdate");
1000
+ const isComponentWillMount = createLifecycleChecker("componentWillMount");
1001
+ const isComponentWillReceiveProps = createLifecycleChecker("componentWillReceiveProps");
1002
+ const isComponentWillUnmount = createLifecycleChecker("componentWillUnmount");
1003
+ const isComponentWillUpdate = createLifecycleChecker("componentWillUpdate");
1004
+ const isGetChildContext = createLifecycleChecker("getChildContext");
1005
+ const isGetInitialState = createLifecycleChecker("getInitialState");
1006
+ const isGetSnapshotBeforeUpdate = createLifecycleChecker("getSnapshotBeforeUpdate");
1007
+ const isShouldComponentUpdate = createLifecycleChecker("shouldComponentUpdate");
1008
+ const isUnsafeComponentWillMount = createLifecycleChecker("UNSAFE_componentWillMount");
1009
+ const isUnsafeComponentWillReceiveProps = createLifecycleChecker("UNSAFE_componentWillReceiveProps");
1010
+ const isUnsafeComponentWillUpdate = createLifecycleChecker("UNSAFE_componentWillUpdate");
1011
+ const isGetDefaultProps = createLifecycleChecker("getDefaultProps", true);
1012
+ const isGetDerivedStateFromProps = createLifecycleChecker("getDerivedStateFromProps", true);
1013
+ const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromError", true);
1014
+ /**
1015
+ * Check if the given node is a componentDidMount callback
1016
+ * @param node The node to check
1017
+ * @returns True if the node is a componentDidMount callback, false otherwise
1018
+ */
1019
+ function isComponentDidMountCallback(node) {
1020
+ return ast.isFunction(node) && isComponentDidMount(node.parent) && node.parent.value === node;
1021
+ }
1022
+ /**
1023
+ * Check if the given node is a componentWillUnmount callback
1024
+ * @param node The node to check
1025
+ * @returns True if the node is a componentWillUnmount callback, false otherwise
1026
+ */
1027
+ function isComponentWillUnmountCallback(node) {
1028
+ return ast.isFunction(node) && isComponentWillUnmount(node.parent) && node.parent.value === node;
1029
+ }
1030
+ /**
1031
+ * Check whether given node is a render method of a class component
1032
+ * @example
1033
+ * ```tsx
1034
+ * class Component extends React.Component {
1035
+ * renderHeader = () => <div />;
1036
+ * renderFooter = () => <div />;
1037
+ * }
1038
+ * ```
1039
+ * @param node The AST node to check
1040
+ * @returns `true` if node is a render function, `false` if not
1041
+ */
1042
+ function isRenderMethodLike(node) {
1043
+ return ast.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render") && node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration;
1044
+ }
1045
+ /**
1046
+ * Check if the given node is a function within a render method of a class component
1047
+ *
1048
+ * @param node The AST node to check
1049
+ * @returns `true` if the node is a render function inside a class component
1050
+ *
1051
+ * @example
1052
+ * ```tsx
1053
+ * class Component extends React.Component {
1054
+ * renderHeader = () => <div />; // Returns true
1055
+ * }
1056
+ * ```
1057
+ */
1058
+ function isRenderMethodCallback(node) {
1059
+ const parent = node.parent;
1060
+ const greatGrandparent = parent.parent?.parent;
1061
+ return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
1062
+ }
1063
+ /**
1064
+ * Check whether the given node is a this.setState() call
1065
+ * @param node The node to check
1066
+ * @internal
1067
+ */
1068
+ function isThisSetState(node) {
1069
+ const { callee } = node;
1070
+ return callee.type === AST_NODE_TYPES.MemberExpression && ast.isThisExpressionLoose(callee.object) && callee.property.type === AST_NODE_TYPES.Identifier && callee.property.name === "setState";
1071
+ }
1072
+ /**
1073
+ * Check whether the given node is an assignment to this.state
1074
+ * @param node The node to check
1075
+ * @internal
1076
+ */
1077
+ function isAssignmentToThisState(node) {
1078
+ const { left } = node;
1079
+ return left.type === AST_NODE_TYPES.MemberExpression && ast.isThisExpressionLoose(left.object) && ast.getPropertyName(left.property) === "state";
1080
+ }
720
1081
 
721
1082
  //#endregion
722
1083
  //#region src/component/component-wrapper.ts
@@ -771,7 +1132,7 @@ function isComponentWrapperCallbackLoose(context, node) {
771
1132
  * Get function component identifier from `const Component = memo(() => {});`
772
1133
  * @param context The rule context
773
1134
  * @param node The function node to analyze
774
- * @returns The function identifier or `unit` if not found
1135
+ * @returns The function identifier or `null` if not found
775
1136
  */
776
1137
  function getFunctionComponentId(context, node) {
777
1138
  const functionId = ast.getFunctionId(node);
@@ -779,7 +1140,7 @@ function getFunctionComponentId(context, node) {
779
1140
  const { parent } = node;
780
1141
  if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.id;
781
1142
  if (parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent) && parent.parent.type === AST_NODE_TYPES.CallExpression && isComponentWrapperCallLoose(context, parent.parent) && parent.parent.parent.type === AST_NODE_TYPES.VariableDeclarator) return parent.parent.parent.id;
782
- return unit;
1143
+ return null;
783
1144
  }
784
1145
 
785
1146
  //#endregion
@@ -814,75 +1175,24 @@ function isFunctionWithLooseComponentName(context, fn, allowNone = false) {
814
1175
  }
815
1176
 
816
1177
  //#endregion
817
- //#region src/component/component-render-method.ts
818
- /**
819
- * Check whether given node is a render method of a class component
820
- * @example
821
- * ```tsx
822
- * class Component extends React.Component {
823
- * renderHeader = () => <div />;
824
- * renderFooter = () => <div />;
825
- * }
826
- * ```
827
- * @param node The AST node to check
828
- * @returns `true` if node is a render function, `false` if not
829
- */
830
- function isRenderMethodLike(node) {
831
- return ast.isMethodOrProperty(node) && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render") && node.parent.parent.type === AST_NODE_TYPES.ClassDeclaration;
832
- }
833
-
834
- //#endregion
835
- //#region src/component/component-definition.ts
1178
+ //#region src/component/component-detection.ts
836
1179
  /**
837
- * Check if the given node is a function within a render method of a class component.
838
- *
839
- * @param node The AST node to check
840
- * @returns `true` if the node is a render function inside a class component
841
- *
842
- * @example
843
- * ```tsx
844
- * class Component extends React.Component {
845
- * renderHeader = () => <div />; // Returns true
846
- * }
847
- * ```
848
- */
849
- function isRenderMethodCallback(node) {
850
- const parent = node.parent;
851
- const greatGrandparent = parent.parent?.parent;
852
- return greatGrandparent != null && isRenderMethodLike(parent) && isClassComponent(greatGrandparent);
853
- }
854
- /**
855
- * Check if a function node should be excluded based on provided detection hints
856
- *
857
- * @param node The function node to check
858
- * @param hint Component detection hints as bit flags
859
- * @returns `true` if the function matches an exclusion hint
1180
+ * Hints for component collector
860
1181
  */
861
- function shouldExcludeBasedOnHint(node, hint) {
862
- switch (true) {
863
- case hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnObjectMethod && ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.Property && node.parent.parent.type === AST_NODE_TYPES.ObjectExpression: return true;
864
- case hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassMethod && ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.MethodDefinition: return true;
865
- case hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassProperty && ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.Property: return true;
866
- case hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern && node.parent.type === AST_NODE_TYPES.ArrayPattern: return true;
867
- case hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression && node.parent.type === AST_NODE_TYPES.ArrayExpression: return true;
868
- case hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback && node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee.type === AST_NODE_TYPES.MemberExpression && node.parent.callee.property.type === AST_NODE_TYPES.Identifier && node.parent.callee.property.name === "map": return true;
869
- case hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback && node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee.type === AST_NODE_TYPES.MemberExpression && node.parent.callee.property.type === AST_NODE_TYPES.Identifier && node.parent.callee.property.name === "flatMap": return true;
870
- }
871
- return false;
872
- }
1182
+ const ComponentDetectionHint = {
1183
+ ...JsxDetectionHint,
1184
+ DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 17n,
1185
+ DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 16n,
1186
+ DoNotIncludeFunctionDefinedInArrayExpression: 1n << 15n,
1187
+ DoNotIncludeFunctionDefinedInArrayPattern: 1n << 14n,
1188
+ DoNotIncludeFunctionDefinedOnClassMethod: 1n << 12n,
1189
+ DoNotIncludeFunctionDefinedOnClassProperty: 1n << 13n,
1190
+ DoNotIncludeFunctionDefinedOnObjectMethod: 1n << 11n
1191
+ };
873
1192
  /**
874
- * Determine if the node is an argument within `createElement`'s children list (3rd argument onwards)
875
- *
876
- * @param context The rule context
877
- * @param node The AST node to check
878
- * @returns `true` if the node is passed as a child to `createElement`
1193
+ * Default component detection hint
879
1194
  */
880
- function isChildrenOfCreateElement(context, node) {
881
- const parent = node.parent;
882
- if (parent?.type !== AST_NODE_TYPES.CallExpression) return false;
883
- if (!isCreateElementCall(context, parent)) return false;
884
- return parent.arguments.slice(2).some((arg) => arg === node);
885
- }
1195
+ const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
886
1196
  /**
887
1197
  * Determine if a function node represents a valid React component definition
888
1198
  *
@@ -893,8 +1203,33 @@ function isChildrenOfCreateElement(context, node) {
893
1203
  */
894
1204
  function isComponentDefinition(context, node, hint) {
895
1205
  if (!isFunctionWithLooseComponentName(context, node, true)) return false;
896
- if (isChildrenOfCreateElement(context, node) || isRenderMethodCallback(node)) return false;
897
- if (shouldExcludeBasedOnHint(node, hint)) return false;
1206
+ switch (true) {
1207
+ case node.parent.type === AST_NODE_TYPES.CallExpression && isCreateElementCall(context, node.parent) && node.parent.arguments.slice(2).some((arg) => arg === node): return false;
1208
+ case isRenderMethodCallback(node): return false;
1209
+ }
1210
+ switch (true) {
1211
+ case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.Property && node.parent.parent.type === AST_NODE_TYPES.ObjectExpression:
1212
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnObjectMethod) return false;
1213
+ break;
1214
+ case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.MethodDefinition:
1215
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassMethod) return false;
1216
+ break;
1217
+ case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && node.parent.type === AST_NODE_TYPES.Property:
1218
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassProperty) return false;
1219
+ break;
1220
+ case node.parent.type === AST_NODE_TYPES.ArrayPattern:
1221
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern) return false;
1222
+ break;
1223
+ case node.parent.type === AST_NODE_TYPES.ArrayExpression:
1224
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression) return false;
1225
+ break;
1226
+ case node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee.type === AST_NODE_TYPES.MemberExpression && node.parent.callee.property.type === AST_NODE_TYPES.Identifier && node.parent.callee.property.name === "map":
1227
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback) return false;
1228
+ break;
1229
+ case node.parent.type === AST_NODE_TYPES.CallExpression && node.parent.callee.type === AST_NODE_TYPES.MemberExpression && node.parent.callee.property.type === AST_NODE_TYPES.Identifier && node.parent.callee.property.name === "flatMap":
1230
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback) return false;
1231
+ break;
1232
+ }
898
1233
  const significantParent = ast.findParentNode(node, ast.isOneOf([
899
1234
  AST_NODE_TYPES.JSXExpressionContainer,
900
1235
  AST_NODE_TYPES.ArrowFunctionExpression,
@@ -913,15 +1248,12 @@ function isComponentDefinition(context, node, hint) {
913
1248
  * Component flag constants
914
1249
  */
915
1250
  const ComponentFlag = {
916
- None: 0n,
917
- PureComponent: 1n << 0n,
918
1251
  CreateElement: 1n << 1n,
1252
+ ForwardRef: 1n << 3n,
919
1253
  Memo: 1n << 2n,
920
- ForwardRef: 1n << 3n
1254
+ None: 0n,
1255
+ PureComponent: 1n << 0n
921
1256
  };
922
-
923
- //#endregion
924
- //#region src/component/component-init-path.ts
925
1257
  /**
926
1258
  * Get component flag from init path
927
1259
  * @param initPath The init path of the function component
@@ -936,7 +1268,7 @@ function getComponentFlagFromInitPath(initPath) {
936
1268
 
937
1269
  //#endregion
938
1270
  //#region src/component/component-collector.ts
939
- const idGen$1 = new IdGenerator("function_component_");
1271
+ const idGen$1 = new IdGenerator("function-component:");
940
1272
  /**
941
1273
  * Get a ctx and visitor object for the rule to collect function components
942
1274
  * @param context The ESLint rule context
@@ -948,14 +1280,14 @@ function useComponentCollector(context, options = {}) {
948
1280
  const functionEntries = [];
949
1281
  const components = /* @__PURE__ */ new Map();
950
1282
  const getText = (n) => context.sourceCode.getText(n);
951
- const getCurrentEntry = () => functionEntries.at(-1);
1283
+ const getCurrentEntry = () => functionEntries.at(-1) ?? null;
952
1284
  const onFunctionEnter = (node) => {
953
1285
  const key = idGen$1.next();
954
1286
  const exp = ast.findParentNode(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration);
955
1287
  const isExportDefault = exp != null;
956
1288
  const isExportDefaultDeclaration = exp != null && ast.getUnderlyingExpression(exp.declaration) === node;
957
1289
  const id = getFunctionComponentId(context, node);
958
- const name = id == null ? unit : ast.getFullyQualifiedName(id, getText);
1290
+ const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
959
1291
  const initPath = ast.getFunctionInitPath(node);
960
1292
  const directives = ast.getFunctionDirectives(node);
961
1293
  const entry = {
@@ -963,9 +1295,8 @@ function useComponentCollector(context, options = {}) {
963
1295
  key,
964
1296
  kind: "function-component",
965
1297
  name,
966
- node,
967
1298
  directives,
968
- displayName: unit,
1299
+ displayName: null,
969
1300
  flag: getComponentFlagFromInitPath(initPath),
970
1301
  hint,
971
1302
  hookCalls: [],
@@ -973,6 +1304,7 @@ function useComponentCollector(context, options = {}) {
973
1304
  isComponentDefinition: isComponentDefinition(context, node, hint),
974
1305
  isExportDefault,
975
1306
  isExportDefaultDeclaration,
1307
+ node,
976
1308
  rets: []
977
1309
  };
978
1310
  functionEntries.push(entry);
@@ -1002,13 +1334,13 @@ function useComponentCollector(context, options = {}) {
1002
1334
  if (body.type === AST_NODE_TYPES.BlockStatement) return;
1003
1335
  entry.rets.push(body);
1004
1336
  if (!entry.isComponentDefinition) return;
1005
- if (!components.has(entry.key) && !isJsxLike(context.sourceCode, body, hint)) return;
1337
+ if (!components.has(entry.key) && !isJsxLike(context, body, hint)) return;
1006
1338
  components.set(entry.key, entry);
1007
1339
  },
1008
1340
  ...collectDisplayName ? { [ast.SEL_DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node) {
1009
1341
  const { left, right } = node;
1010
1342
  if (left.type !== AST_NODE_TYPES.MemberExpression) return;
1011
- const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : unit;
1343
+ const componentName = left.object.type === AST_NODE_TYPES.Identifier ? left.object.name : null;
1012
1344
  const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
1013
1345
  if (component == null) return;
1014
1346
  component.displayName = right;
@@ -1027,7 +1359,7 @@ function useComponentCollector(context, options = {}) {
1027
1359
  entry.rets.push(node.argument);
1028
1360
  if (!entry.isComponentDefinition) return;
1029
1361
  const { argument } = node;
1030
- if (!components.has(entry.key) && !isJsxLike(context.sourceCode, argument, hint)) return;
1362
+ if (!components.has(entry.key) && !isJsxLike(context, argument, hint)) return;
1031
1363
  components.set(entry.key, entry);
1032
1364
  }
1033
1365
  }
@@ -1036,7 +1368,7 @@ function useComponentCollector(context, options = {}) {
1036
1368
 
1037
1369
  //#endregion
1038
1370
  //#region src/component/component-collector-legacy.ts
1039
- const idGen = new IdGenerator("class_component_");
1371
+ const idGen = new IdGenerator("class-component:");
1040
1372
  /**
1041
1373
  * Get a ctx and visitor object for the rule to collect class componentss
1042
1374
  * @param context The ESLint rule context
@@ -1052,18 +1384,18 @@ function useComponentCollectorLegacy(context) {
1052
1384
  if (!isClassComponent(node)) return;
1053
1385
  const id = ast.getClassId(node);
1054
1386
  const key = idGen.next();
1055
- const name = id == null ? unit : ast.getFullyQualifiedName(id, getText);
1387
+ const name = id == null ? null : ast.getFullyQualifiedName(id, getText);
1056
1388
  const flag = isPureComponent(node) ? ComponentFlag.PureComponent : ComponentFlag.None;
1057
1389
  components.set(key, {
1058
1390
  id,
1059
1391
  key,
1060
1392
  kind: "class-component",
1061
1393
  name,
1062
- node,
1063
- displayName: unit,
1394
+ displayName: null,
1064
1395
  flag,
1065
1396
  hint: 0n,
1066
- methods: []
1397
+ methods: [],
1398
+ node
1067
1399
  });
1068
1400
  };
1069
1401
  return {
@@ -1074,179 +1406,6 @@ function useComponentCollectorLegacy(context) {
1074
1406
  }
1075
1407
  };
1076
1408
  }
1077
- /**
1078
- * Check whether the given node is a this.setState() call
1079
- * @param node The node to check
1080
- * @internal
1081
- */
1082
- function isThisSetState(node) {
1083
- const { callee } = node;
1084
- return callee.type === AST_NODE_TYPES$1.MemberExpression && ast.isThisExpressionLoose(callee.object) && callee.property.type === AST_NODE_TYPES$1.Identifier && callee.property.name === "setState";
1085
- }
1086
- /**
1087
- * Check whether the given node is an assignment to this.state
1088
- * @param node The node to check
1089
- * @internal
1090
- */
1091
- function isAssignmentToThisState(node) {
1092
- const { left } = node;
1093
- return left.type === AST_NODE_TYPES$1.MemberExpression && ast.isThisExpressionLoose(left.object) && ast.getPropertyName(left.property) === "state";
1094
- }
1095
-
1096
- //#endregion
1097
- //#region src/component/component-method-is.ts
1098
- /**
1099
- * Create a lifecycle method checker function
1100
- * @param methodName The lifecycle method name
1101
- * @param isStatic Whether the method is static
1102
- */
1103
- function createLifecycleChecker(methodName, isStatic = false) {
1104
- return (node) => ast.isMethodOrProperty(node) && node.static === isStatic && node.key.type === AST_NODE_TYPES.Identifier && node.key.name === methodName;
1105
- }
1106
- const isRender = createLifecycleChecker("render");
1107
- const isComponentDidCatch = createLifecycleChecker("componentDidCatch");
1108
- const isComponentDidMount = createLifecycleChecker("componentDidMount");
1109
- const isComponentDidUpdate = createLifecycleChecker("componentDidUpdate");
1110
- const isComponentWillMount = createLifecycleChecker("componentWillMount");
1111
- const isComponentWillReceiveProps = createLifecycleChecker("componentWillReceiveProps");
1112
- const isComponentWillUnmount = createLifecycleChecker("componentWillUnmount");
1113
- const isComponentWillUpdate = createLifecycleChecker("componentWillUpdate");
1114
- const isGetChildContext = createLifecycleChecker("getChildContext");
1115
- const isGetInitialState = createLifecycleChecker("getInitialState");
1116
- const isGetSnapshotBeforeUpdate = createLifecycleChecker("getSnapshotBeforeUpdate");
1117
- const isShouldComponentUpdate = createLifecycleChecker("shouldComponentUpdate");
1118
- const isUnsafeComponentWillMount = createLifecycleChecker("UNSAFE_componentWillMount");
1119
- const isUnsafeComponentWillReceiveProps = createLifecycleChecker("UNSAFE_componentWillReceiveProps");
1120
- const isUnsafeComponentWillUpdate = createLifecycleChecker("UNSAFE_componentWillUpdate");
1121
- const isGetDefaultProps = createLifecycleChecker("getDefaultProps", true);
1122
- const isGetDerivedStateFromProps = createLifecycleChecker("getDerivedStateFromProps", true);
1123
- const isGetDerivedStateFromError = createLifecycleChecker("getDerivedStateFromError", true);
1124
-
1125
- //#endregion
1126
- //#region src/component/component-method-callback.ts
1127
- /**
1128
- * Check if the given node is a componentDidMount callback
1129
- * @param node The node to check
1130
- * @returns True if the node is a componentDidMount callback, false otherwise
1131
- */
1132
- function isComponentDidMountCallback(node) {
1133
- return ast.isFunction(node) && isComponentDidMount(node.parent) && node.parent.value === node;
1134
- }
1135
- /**
1136
- * Check if the given node is a componentWillUnmount callback
1137
- * @param node The node to check
1138
- * @returns True if the node is a componentWillUnmount callback, false otherwise
1139
- */
1140
- function isComponentWillUnmountCallback(node) {
1141
- return ast.isFunction(node) && isComponentWillUnmount(node.parent) && node.parent.value === node;
1142
- }
1143
-
1144
- //#endregion
1145
- //#region src/component/component-render-prop.ts
1146
- /**
1147
- * Unsafe check whether given node is a render function
1148
- * ```tsx
1149
- * const renderRow = () => <div />
1150
- * ` ^^^^^^^^^^^^`
1151
- * _ = <Component renderRow={() => <div />} />
1152
- * ` ^^^^^^^^^^^^^ `
1153
- * ```
1154
- * @param context The rule context
1155
- * @param node The AST node to check
1156
- * @returns `true` if node is a render function, `false` if not
1157
- */
1158
- function isRenderFunctionLoose(context, node) {
1159
- if (!ast.isFunction(node)) return false;
1160
- const id = ast.getFunctionId(node);
1161
- switch (true) {
1162
- case id?.type === AST_NODE_TYPES.Identifier: return id.name.startsWith("render");
1163
- case id?.type === AST_NODE_TYPES.MemberExpression && id.property.type === AST_NODE_TYPES.Identifier: return id.property.name.startsWith("render");
1164
- case node.parent.type === AST_NODE_TYPES.JSXExpressionContainer && node.parent.parent.type === AST_NODE_TYPES.JSXAttribute && node.parent.parent.name.type === AST_NODE_TYPES.JSXIdentifier: return node.parent.parent.name.name.startsWith("render");
1165
- }
1166
- return false;
1167
- }
1168
- /**
1169
- * Unsafe check whether given JSXAttribute is a render prop
1170
- * ```tsx
1171
- * _ = <Component renderRow={() => <div />} />
1172
- * ` ^^^^^^^^^^^^^^^^^^^^^^^^^ `
1173
- * ```
1174
- * @param context The rule context
1175
- * @param node The AST node to check
1176
- * @returns `true` if node is a render prop, `false` if not
1177
- */
1178
- function isRenderPropLoose(context, node) {
1179
- if (node.name.type !== AST_NODE_TYPES.JSXIdentifier) return false;
1180
- return node.name.name.startsWith("render") && node.value?.type === AST_NODE_TYPES.JSXExpressionContainer && isRenderFunctionLoose(context, node.value.expression);
1181
- }
1182
- /**
1183
- * Unsafe check whether given node is declared directly inside a render property
1184
- * ```tsx
1185
- * const rows = { render: () => <div /> }
1186
- * ` ^^^^^^^^^^^^^ `
1187
- * _ = <Component rows={ [{ render: () => <div /> }] } />
1188
- * ` ^^^^^^^^^^^^^ `
1189
- * ```
1190
- * @internal
1191
- * @param node The AST node to check
1192
- * @returns `true` if component is declared inside a render property, `false` if not
1193
- */
1194
- function isDirectValueOfRenderPropertyLoose(node) {
1195
- const matching = (node) => {
1196
- return node.type === AST_NODE_TYPES.Property && node.key.type === AST_NODE_TYPES.Identifier && node.key.name.startsWith("render");
1197
- };
1198
- return matching(node) || node.parent != null && matching(node.parent);
1199
- }
1200
- /**
1201
- * Unsafe check whether given node is declared inside a render prop
1202
- * ```tsx
1203
- * _ = <Component renderRow={"node"} />
1204
- * ` ^^^^^^ `
1205
- * _ = <Component rows={ [{ render: "node" }] } />
1206
- * ` ^^^^^^ `
1207
- * ```
1208
- * @param node The AST node to check
1209
- * @returns `true` if component is declared inside a render prop, `false` if not
1210
- */
1211
- function isDeclaredInRenderPropLoose(node) {
1212
- if (isDirectValueOfRenderPropertyLoose(node)) return true;
1213
- const parent = ast.findParentNode(node, ast.is(AST_NODE_TYPES.JSXExpressionContainer))?.parent;
1214
- if (parent?.type !== AST_NODE_TYPES.JSXAttribute) return false;
1215
- return parent.name.type === AST_NODE_TYPES.JSXIdentifier && parent.name.name.startsWith("render");
1216
- }
1217
-
1218
- //#endregion
1219
- //#region src/hierarchy/find-enclosing-component-or-hook.ts
1220
- /**
1221
- * Find the enclosing React component or hook for a given AST node
1222
- * @param node The AST node to start the search from
1223
- * @param test Optional test function to customize component or hook identification
1224
- * @returns The enclosing component or hook node, or `null` if none is ASAST.
1225
- */
1226
- function findEnclosingComponentOrHook(node, test = (n, name) => {
1227
- if (name == null) return false;
1228
- return isComponentNameLoose(name) || isHookName(name);
1229
- }) {
1230
- const enclosingNode = ast.findParentNode(node, (n) => {
1231
- if (!ast.isFunction(n)) return false;
1232
- return test(n, match(ast.getFunctionId(n)).with({ type: AST_NODE_TYPES.Identifier }, (id) => id.name).with({
1233
- type: AST_NODE_TYPES.MemberExpression,
1234
- property: { type: AST_NODE_TYPES.Identifier }
1235
- }, (me) => me.property.name).otherwise(() => null));
1236
- });
1237
- return ast.isFunction(enclosingNode) ? enclosingNode : unit;
1238
- }
1239
-
1240
- //#endregion
1241
- //#region src/hierarchy/is-inside-component-or-hook.ts
1242
- /**
1243
- * Check if a given AST node is inside a React component or hook
1244
- * @param node The AST node to check
1245
- * @returns True if the node is inside a component or hook, false otherwise
1246
- */
1247
- function isInsideComponentOrHook(node) {
1248
- return findEnclosingComponentOrHook(node) != null;
1249
- }
1250
1409
 
1251
1410
  //#endregion
1252
1411
  //#region src/ref/ref-name.ts
@@ -1255,12 +1414,18 @@ function isInsideComponentOrHook(node) {
1255
1414
  * @param name The name to check
1256
1415
  * @returns True if the name is "ref" or ends with "Ref"
1257
1416
  */
1258
- function isRefName(name) {
1417
+ function isRefLikeName(name) {
1259
1418
  return name === "ref" || name.endsWith("Ref");
1260
1419
  }
1261
1420
 
1262
1421
  //#endregion
1263
- //#region src/ref/is-from-ref.ts
1422
+ //#region src/ref/ref-id.ts
1423
+ function isRefId(node) {
1424
+ return node.type === AST_NODE_TYPES.Identifier && isRefLikeName(node.name);
1425
+ }
1426
+
1427
+ //#endregion
1428
+ //#region src/ref/ref-init.ts
1264
1429
  /**
1265
1430
  * Check if the variable with the given name is initialized or derived from a ref
1266
1431
  * @param name The variable name
@@ -1274,20 +1439,20 @@ function isInitializedFromRef(name, initialScope) {
1274
1439
  * Get the init expression of a ref variable
1275
1440
  * @param name The variable name
1276
1441
  * @param initialScope The initial scope
1277
- * @returns The init expression node if the variable is derived from a ref, or undefined otherwise
1442
+ * @returns The init expression node if the variable is derived from a ref, or null otherwise
1278
1443
  */
1279
1444
  function getRefInit(name, initialScope) {
1280
- for (const { node } of findVariable(initialScope)(name)?.defs ?? []) {
1445
+ for (const { node } of findVariable(initialScope, name)?.defs ?? []) {
1281
1446
  if (node.type !== AST_NODE_TYPES$1.VariableDeclarator) continue;
1282
1447
  const init = node.init;
1283
1448
  if (init == null) continue;
1284
1449
  switch (true) {
1285
- case init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier && isRefName(init.object.name): return init;
1450
+ case init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier && isRefLikeName(init.object.name): return init;
1286
1451
  case init.type === AST_NODE_TYPES$1.CallExpression && isUseRefCall(init): return init;
1287
1452
  }
1288
1453
  }
1289
- return unit;
1454
+ return null;
1290
1455
  }
1291
1456
 
1292
1457
  //#endregion
1293
- export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, findEnclosingComponentOrHook, findParentJsxAttribute, getComponentFlagFromInitPath, getFunctionComponentId, getJsxAttribute, getJsxAttributeName, getJsxConfigFromAnnotation, getJsxConfigFromContext, getJsxElementType, getRefInit, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidMountCallback, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUnmountCallback, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isComponentWrapperCallback, isComponentWrapperCallbackLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isDeclaredInRenderPropLoose, isDirectValueOfRenderPropertyLoose, isForwardRef, isForwardRefCall, isFunctionWithLooseComponentName, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHook, isHookCall, isHookCallWithName, isHookId, isHookName, isInitializedFromReact, isInitializedFromReactNative, isInitializedFromRef, isInsideComponentOrHook, isJsxFragmentElement, isJsxHostElement, isJsxLike, isJsxText, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isRefName, isRender, isRenderFunctionLoose, isRenderMethodLike, isRenderPropLoose, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectCleanupCallback, isUseEffectLikeCall, isUseEffectSetupCallback, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseStateLikeCall, isUseSyncExternalStoreCall, isUseTransitionCall, resolveJsxAttributeValue, stringifyJsx, useComponentCollector, useComponentCollectorLegacy, useHookCollector };
1458
+ export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, JsxInspector, REACT_BUILTIN_HOOK_NAMES, findImportSource, getComponentFlagFromInitPath, getFunctionComponentId, getJsxConfigFromAnnotation, getJsxConfigFromContext, getRefInit, isAssignmentToThisState, isCaptureOwnerStack, isCaptureOwnerStackCall, isChildrenCount, isChildrenCountCall, isChildrenForEach, isChildrenForEachCall, isChildrenMap, isChildrenMapCall, isChildrenOnly, isChildrenOnlyCall, isChildrenToArray, isChildrenToArrayCall, isClassComponent, isCloneElement, isCloneElementCall, isComponentDefinition, isComponentDidCatch, isComponentDidMount, isComponentDidMountCallback, isComponentDidUpdate, isComponentName, isComponentNameLoose, isComponentWillMount, isComponentWillReceiveProps, isComponentWillUnmount, isComponentWillUnmountCallback, isComponentWillUpdate, isComponentWrapperCall, isComponentWrapperCallLoose, isComponentWrapperCallback, isComponentWrapperCallbackLoose, isCreateContext, isCreateContextCall, isCreateElement, isCreateElementCall, isCreateRef, isCreateRefCall, isForwardRef, isForwardRefCall, isFunctionWithLooseComponentName, isGetChildContext, isGetDefaultProps, isGetDerivedStateFromError, isGetDerivedStateFromProps, isGetInitialState, isGetSnapshotBeforeUpdate, isHook, isHookCall, isHookCallWithName, isHookId, isHookName, isInitializedFromReact, isInitializedFromReactNative, isInitializedFromRef, isJsxLike, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, isRefId, isRefLikeName, isRender, isRenderMethodCallback, isRenderMethodLike, isShouldComponentUpdate, isThisSetState, isUnsafeComponentWillMount, isUnsafeComponentWillReceiveProps, isUnsafeComponentWillUpdate, isUseActionStateCall, isUseCall, isUseCallbackCall, isUseContextCall, isUseDebugValueCall, isUseDeferredValueCall, isUseEffectCall, isUseEffectCleanupCallback, isUseEffectLikeCall, isUseEffectSetupCallback, isUseFormStatusCall, isUseIdCall, isUseImperativeHandleCall, isUseInsertionEffectCall, isUseLayoutEffectCall, isUseMemoCall, isUseOptimisticCall, isUseReducerCall, isUseRefCall, isUseStateCall, isUseStateLikeCall, isUseSyncExternalStoreCall, isUseTransitionCall, useComponentCollector, useComponentCollectorLegacy, useHookCollector };