@eslint-react/core 3.0.0-rc.5 → 4.0.0-beta.0

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 +25 -352
  2. package/dist/index.js +43 -544
  3. package/package.json +11 -10
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  import * as ast from "@eslint-react/ast";
2
2
  import { TSESTree } from "@typescript-eslint/types";
3
3
  import { RegExpLike, RuleContext } from "@eslint-react/shared";
4
- import { ESLintUtils, TSESTree as TSESTree$1 } from "@typescript-eslint/utils";
5
4
  import { Scope } from "@typescript-eslint/scope-manager";
6
- import * as typescript from "typescript";
5
+ import { ESLintUtils, TSESTree as TSESTree$1 } from "@typescript-eslint/utils";
7
6
 
8
7
  //#region src/api/find-import-source.d.ts
9
8
  /**
@@ -37,8 +36,8 @@ declare function isInitializedFromReactNative(name: string, initialScope: Scope,
37
36
  //#region src/api/is-react-api.d.ts
38
37
  declare namespace isReactAPI {
39
38
  type ReturnType = {
40
- (context: RuleContext, node: null | TSESTree.Node): node is TSESTree.Identifier | TSESTree.MemberExpression;
41
- (context: RuleContext): (node: null | TSESTree.Node) => node is TSESTree.MemberExpression | TSESTree.Identifier;
39
+ (context: RuleContext, node: null | TSESTree.Node): boolean;
40
+ (context: RuleContext): (node: null | TSESTree.Node) => boolean;
42
41
  };
43
42
  }
44
43
  /**
@@ -92,14 +91,14 @@ type ComponentDetectionHint = bigint;
92
91
  * Hints for component collector
93
92
  */
94
93
  declare const ComponentDetectionHint: {
94
+ readonly DoNotIncludeFunctionDefinedAsClassMethod: bigint;
95
+ readonly DoNotIncludeFunctionDefinedAsClassProperty: bigint;
96
+ readonly DoNotIncludeFunctionDefinedAsObjectMethod: bigint;
97
+ readonly DoNotIncludeFunctionDefinedAsArrayExpressionElement: bigint;
98
+ readonly DoNotIncludeFunctionDefinedAsArrayPatternElement: bigint;
95
99
  readonly DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback: bigint;
96
100
  readonly DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: bigint;
97
101
  readonly DoNotIncludeFunctionDefinedAsArrayMapCallback: bigint;
98
- readonly DoNotIncludeFunctionDefinedInArrayExpression: bigint;
99
- readonly DoNotIncludeFunctionDefinedInArrayPattern: bigint;
100
- readonly DoNotIncludeFunctionDefinedOnClassMethod: bigint;
101
- readonly DoNotIncludeFunctionDefinedOnClassProperty: bigint;
102
- readonly DoNotIncludeFunctionDefinedOnObjectMethod: bigint;
103
102
  readonly None: 0n;
104
103
  readonly DoNotIncludeJsxWithNullValue: bigint;
105
104
  readonly DoNotIncludeJsxWithNumberValue: bigint;
@@ -185,7 +184,7 @@ interface FunctionComponentSemanticNode extends SemanticNode {
185
184
  /**
186
185
  * The kind of component
187
186
  */
188
- kind: "function-component";
187
+ kind: "component";
189
188
  /**
190
189
  * The AST node of the function
191
190
  */
@@ -266,46 +265,41 @@ interface ClassComponentSemanticNode extends SemanticNode {
266
265
  type ComponentSemanticNode = ClassComponentSemanticNode | FunctionComponentSemanticNode;
267
266
  //#endregion
268
267
  //#region src/component/component-collector.d.ts
269
- interface FunctionEntry$1 extends FunctionComponentSemanticNode {
270
- isComponentDefinition: boolean;
271
- }
272
- declare namespace useComponentCollector {
268
+ declare namespace getComponentCollector {
273
269
  type Options = {
274
270
  collectDisplayName?: boolean;
275
271
  hint?: ComponentDetectionHint;
276
272
  };
277
273
  type ReturnType = {
278
- ctx: {
274
+ api: {
279
275
  getAllComponents: (node: TSESTree.Program) => FunctionComponentSemanticNode[];
280
- getCurrentEntries: () => FunctionEntry$1[];
281
- getCurrentEntry: () => FunctionEntry$1 | null;
282
276
  };
283
277
  visitor: ESLintUtils.RuleListener;
284
278
  };
285
279
  }
286
280
  /**
287
- * Get a ctx and visitor object for the rule to collect function components
281
+ * Get a api and visitor object for the rule to collect function components
288
282
  * @param context The ESLint rule context
289
283
  * @param options The options to use
290
- * @returns The ctx and visitor of the collector
284
+ * @returns The api and visitor of the collector
291
285
  */
292
- declare function useComponentCollector(context: RuleContext, options?: useComponentCollector.Options): useComponentCollector.ReturnType;
286
+ declare function getComponentCollector(context: RuleContext, options?: getComponentCollector.Options): getComponentCollector.ReturnType;
293
287
  //#endregion
294
288
  //#region src/component/component-collector-legacy.d.ts
295
- declare namespace useComponentCollectorLegacy {
289
+ declare namespace getComponentCollectorLegacy {
296
290
  type ReturnType = {
297
- ctx: {
291
+ api: {
298
292
  getAllComponents: (node: TSESTree$1.Program) => ClassComponentSemanticNode[];
299
293
  };
300
294
  visitor: ESLintUtils.RuleListener;
301
295
  };
302
296
  }
303
297
  /**
304
- * Get a ctx and visitor object for the rule to collect class componentss
298
+ * Get a api and visitor object for the rule to collect class componentss
305
299
  * @param context The ESLint rule context
306
- * @returns The ctx and visitor of the collector
300
+ * @returns The api and visitor of the collector
307
301
  */
308
- declare function useComponentCollectorLegacy(context: RuleContext): useComponentCollectorLegacy.ReturnType;
302
+ declare function getComponentCollectorLegacy(context: RuleContext): getComponentCollectorLegacy.ReturnType;
309
303
  //#endregion
310
304
  //#region src/component/component-detection-legacy.d.ts
311
305
  /**
@@ -528,26 +522,20 @@ interface HookSemanticNode extends SemanticNode {
528
522
  }
529
523
  //#endregion
530
524
  //#region src/hook/hook-collector.d.ts
531
- type FunctionEntry = {
532
- key: string;
533
- node: ast.TSESTreeFunction;
534
- };
535
- declare namespace useHookCollector {
525
+ declare namespace getHookCollector {
536
526
  type ReturnType = {
537
- ctx: {
527
+ api: {
538
528
  getAllHooks(node: TSESTree$1.Program): HookSemanticNode[];
539
- getCurrentEntries(): FunctionEntry[];
540
- getCurrentEntry(): FunctionEntry | null;
541
529
  };
542
530
  visitor: ESLintUtils.RuleListener;
543
531
  };
544
532
  }
545
533
  /**
546
- * Get a ctx and visitor object for the rule to collect hooks
534
+ * Get a api and visitor object for the rule to collect hooks
547
535
  * @param context The ESLint rule context
548
- * @returns The ctx and visitor of the collector
536
+ * @returns The api and visitor of the collector
549
537
  */
550
- declare function useHookCollector(context: RuleContext): useHookCollector.ReturnType;
538
+ declare function getHookCollector(context: RuleContext): getHookCollector.ReturnType;
551
539
  //#endregion
552
540
  //#region src/hook/hook-id.d.ts
553
541
  /**
@@ -621,319 +609,4 @@ declare const REACT_BUILTIN_HOOK_NAMES: readonly ["use", "useActionState", "useC
621
609
  */
622
610
  declare function isHookName(name: string): boolean;
623
611
  //#endregion
624
- //#region src/jsx/jsx-attribute-value.d.ts
625
- /**
626
- * Represents possible JSX attribute value types that can be resolved
627
- */
628
- type JsxAttributeValue = {
629
- kind: "missing";
630
- node: TSESTree.JSXEmptyExpression;
631
- toStatic(): null;
632
- } | {
633
- kind: "boolean";
634
- toStatic(): true;
635
- } | {
636
- kind: "element";
637
- node: TSESTree.JSXElement;
638
- toStatic(): null;
639
- } | {
640
- kind: "literal";
641
- node: TSESTree.Literal;
642
- toStatic(): TSESTree.Literal["value"];
643
- } | {
644
- kind: "expression";
645
- node: TSESTree.JSXExpressionContainer["expression"];
646
- toStatic(): unknown;
647
- } | {
648
- kind: "spreadProps";
649
- getProperty(name: string): unknown;
650
- node: TSESTree.JSXSpreadAttribute["argument"];
651
- toStatic(): null;
652
- } | {
653
- kind: "spreadChild";
654
- getChildren(at: number): unknown;
655
- node: TSESTree.JSXSpreadChild["expression"];
656
- toStatic(): null;
657
- };
658
- //#endregion
659
- //#region src/jsx/jsx-config.d.ts
660
- declare const JsxEmit: {
661
- readonly None: 0;
662
- readonly Preserve: 1;
663
- readonly React: 2;
664
- readonly ReactNative: 3;
665
- readonly ReactJSX: 4;
666
- readonly ReactJSXDev: 5;
667
- };
668
- interface JsxConfig {
669
- jsx?: number;
670
- jsxFactory?: string;
671
- jsxFragmentFactory?: string;
672
- jsxImportSource?: string;
673
- }
674
- /**
675
- * Get JsxConfig from the rule context by reading compiler options
676
- * @param context The RuleContext
677
- * @returns JsxConfig derived from compiler options
678
- */
679
- declare function getJsxConfigFromContext(context: RuleContext): {
680
- jsx: 4 | typescript.JsxEmit;
681
- jsxFactory: string;
682
- jsxFragmentFactory: string;
683
- jsxImportSource: string;
684
- };
685
- /**
686
- * Get JsxConfig from pragma comments (annotations) in the source code
687
- * @param context The RuleContext
688
- * @returns JsxConfig derived from pragma comments
689
- */
690
- declare function getJsxConfigFromAnnotation(context: RuleContext): JsxConfig;
691
- //#endregion
692
- //#region src/jsx/jsx-detection.d.ts
693
- /**
694
- * BitFlags for configuring JSX detection behavior
695
- */
696
- type JsxDetectionHint = bigint;
697
- declare const JsxDetectionHint: {
698
- readonly None: 0n;
699
- readonly DoNotIncludeJsxWithNullValue: bigint;
700
- readonly DoNotIncludeJsxWithNumberValue: bigint;
701
- readonly DoNotIncludeJsxWithBigIntValue: bigint;
702
- readonly DoNotIncludeJsxWithStringValue: bigint;
703
- readonly DoNotIncludeJsxWithBooleanValue: bigint;
704
- readonly DoNotIncludeJsxWithUndefinedValue: bigint;
705
- readonly DoNotIncludeJsxWithEmptyArrayValue: bigint;
706
- readonly DoNotIncludeJsxWithCreateElementValue: bigint;
707
- readonly RequireAllArrayElementsToBeJsx: bigint;
708
- readonly RequireBothSidesOfLogicalExpressionToBeJsx: bigint;
709
- readonly RequireBothBranchesOfConditionalExpressionToBeJsx: bigint;
710
- };
711
- /**
712
- * Default JSX detection configuration
713
- * Skips undefined and boolean literals (common in React)
714
- */
715
- declare const DEFAULT_JSX_DETECTION_HINT: bigint;
716
- /**
717
- * Determine if a node represents JSX-like content based on heuristics
718
- * Supports configuration through hint flags to customize detection behavior
719
- *
720
- * @param context The rule context with scope lookup capability
721
- * @param node The AST node to analyze
722
- * @param hint The configuration flags to adjust detection behavior
723
- * @returns boolean Whether the node is considered JSX-like
724
- */
725
- declare function isJsxLike(context: RuleContext, node: TSESTree$1.Node | null, hint?: JsxDetectionHint): boolean;
726
- //#endregion
727
- //#region src/jsx/jsx-inspector.d.ts
728
- /**
729
- * A stateful helper that binds an ESLint `RuleContext` once and exposes
730
- * ergonomic methods for the most common JSX inspection tasks that rules need.
731
- *
732
- * ### Typical usage inside a rule's `create` function
733
- *
734
- * ```ts
735
- * export function create(context: RuleContext) {
736
- * const jsx = JsxInspector.from(context);
737
- *
738
- * return defineRuleListener({
739
- * JSXElement(node) {
740
- * // element type
741
- * const type = jsx.getElementType(node); // "div" | "React.Fragment" | …
742
- *
743
- * // attribute lookup + value resolution in one step
744
- * const val = jsx.getAttributeValue(node, "sandbox");
745
- * if (typeof val?.getStatic() === "string") { … }
746
- *
747
- * // simple boolean checks
748
- * if (jsx.isHostElement(node)) { … }
749
- * if (jsx.isFragmentElement(node)) { … }
750
- * if (jsx.hasAttribute(node, "key")) { … }
751
- * },
752
- * });
753
- * }
754
- * ```
755
- */
756
- declare class JsxInspector {
757
- #private;
758
- readonly context: RuleContext;
759
- /**
760
- * Merged JSX configuration (tsconfig compiler options + pragma annotations).
761
- * The result is lazily computed and cached for the lifetime of this inspector.
762
- */
763
- get jsxConfig(): Required<JsxConfig>;
764
- private constructor();
765
- /**
766
- * Walk **up** the AST from `node` to find the nearest ancestor that is a
767
- * `JSXAttribute` and passes the optional `test` predicate.
768
- * @param node The starting node for the search.
769
- * @param test A predicate function to test each ancestor node.
770
- */
771
- static findParentAttribute(node: TSESTree.Node, test?: (node: TSESTree.JSXAttribute) => boolean): TSESTree.JSXAttribute | null;
772
- /**
773
- * Create a new `JsxInspector` bound to the given rule context.
774
- * @param context The ESLint rule context to bind to this inspector instance.
775
- */
776
- static from(context: RuleContext): JsxInspector;
777
- /**
778
- * Whether the node is a `JSXText` or a `Literal` node.
779
- * @param node The node to check.
780
- */
781
- static isJsxText(node: TSESTree.Node | null): node is TSESTree.JSXText | TSESTree.Literal;
782
- /**
783
- * Find a JSX attribute (or spread attribute containing the property) by name
784
- * on a given element.
785
- *
786
- * Returns the **last** matching attribute (to mirror React's behaviour where
787
- * later props win), or `undefined` if not found.
788
- * @param node The JSX element to search for the attribute.
789
- * @param name The name of the attribute to find (ex: `"className"`).
790
- */
791
- findAttribute(node: TSESTree.JSXElement, name: string): ast.TSESTreeJSXAttributeLike | undefined;
792
- /**
793
- * Get the stringified name of a `JSXAttribute` node
794
- * (ex: `"className"`, `"aria-label"`, `"xml:space"`).
795
- * @param node The `JSXAttribute` node to extract the name from.
796
- * @returns The stringified name of the attribute.
797
- */
798
- getAttributeName(node: TSESTree.JSXAttribute): string;
799
- /**
800
- * Resolve the static value of an attribute, automatically handling the
801
- * `spreadProps` case by extracting the named property.
802
- *
803
- * This eliminates the repetitive pattern:
804
- * ```ts
805
- * const v = core.resolveJsxAttributeValue(ctx, attr);
806
- * const s = v.kind === "spreadProps" ? v.getProperty(name) : v.toStatic();
807
- * ```
808
- *
809
- * Returns `undefined` when the attribute is not present or its value
810
- * cannot be statically determined.
811
- * @param node The JSX element to search for the attribute.
812
- * @param name The name of the attribute to resolve (ex: `"className"`).
813
- * @returns The static value of the attribute, or `undefined` if not found or not statically resolvable.
814
- */
815
- getAttributeStaticValue(node: TSESTree.JSXElement, name: string): unknown;
816
- /**
817
- * **All-in-one helper** – find an attribute by name on an element *and*
818
- * resolve its value in a single call.
819
- *
820
- * Returns `undefined` when the attribute is not present.
821
- * @param node The JSX element to search for the attribute.
822
- * @param name The name of the attribute to find and resolve (ex: `"className"`).
823
- * @returns A descriptor of the attribute's value that can be further inspected, or `undefined` if the attribute is not found.
824
- */
825
- getAttributeValue(node: TSESTree.JSXElement, name: string): JsxAttributeValue | undefined;
826
- /**
827
- * Get the **self name** (last segment) of a JSX element type.
828
- *
829
- * - `<Foo.Bar.Baz>` → `"Baz"`
830
- * - `<div>` → `"div"`
831
- * - `<></>` → `""`
832
- * @param node The JSX element or fragment to extract the self name from.
833
- */
834
- getElementSelfName(node: TSESTree.JSXElement | TSESTree.JSXFragment): string;
835
- /**
836
- * Get the string representation of a JSX element's type.
837
- *
838
- * - `<div>` → `"div"`
839
- * - `<Foo.Bar>` → `"Foo.Bar"`
840
- * - `<React.Fragment>` → `"React.Fragment"`
841
- * - `<></>` (JSXFragment) → `""`
842
- * @param node The JSX element or fragment to extract the type from.
843
- */
844
- getElementType(node: TSESTree.JSXElement | TSESTree.JSXFragment): string;
845
- /**
846
- * Shorthand: check whether an attribute exists on the element.
847
- * @param node The JSX element to check for the attribute.
848
- * @param name The name of the attribute to check for (ex: `"className"`).
849
- * @returns `true` if the attribute exists on the element, `false` otherwise.
850
- */
851
- hasAttribute(node: TSESTree.JSXElement, name: string): boolean;
852
- /**
853
- * Whether the node is a React **Fragment** element (either `<Fragment>` /
854
- * `<React.Fragment>` or the shorthand `<>` syntax).
855
- *
856
- * The check honours the configured `jsxFragmentFactory`.
857
- * @param node The node to check.
858
- */
859
- isFragmentElement(node: TSESTree.Node): node is TSESTree.JSXElement | TSESTree.JSXFragment;
860
- /**
861
- * Whether the node is a **host** (intrinsic / DOM) element – i.e. its tag
862
- * name starts with a lowercase letter.
863
- * @param node The node to check.
864
- */
865
- isHostElement(node: TSESTree.Node): node is TSESTree.JSXElement;
866
- /**
867
- * Resolve the *value* of a JSX attribute (or spread attribute) into a
868
- * descriptor that can be inspected further.
869
- *
870
- * See {@link JsxAttributeValue} for the full set of `kind` discriminants.
871
- * @param attribute The attribute node to resolve the value of.
872
- * @returns A descriptor of the attribute's value that can be further inspected.
873
- */
874
- resolveAttributeValue(attribute: ast.TSESTreeJSXAttributeLike): {
875
- readonly kind: "boolean";
876
- readonly toStatic: () => true;
877
- readonly node?: never;
878
- readonly getChildren?: never;
879
- } | {
880
- readonly kind: "literal";
881
- readonly node: TSESTree.BigIntLiteral | TSESTree.BooleanLiteral | TSESTree.NullLiteral | TSESTree.NumberLiteral | TSESTree.RegExpLiteral | TSESTree.StringLiteral;
882
- readonly toStatic: () => string | number | bigint | boolean | RegExp | null;
883
- readonly getChildren?: never;
884
- } | {
885
- readonly kind: "missing";
886
- readonly node: TSESTree.JSXEmptyExpression;
887
- readonly toStatic: () => null;
888
- readonly getChildren?: never;
889
- } | {
890
- readonly kind: "expression";
891
- readonly node: TSESTree.Expression;
892
- readonly toStatic: () => unknown;
893
- readonly getChildren?: never;
894
- } | {
895
- readonly kind: "element";
896
- readonly node: TSESTree.JSXElement;
897
- readonly toStatic: () => null;
898
- readonly getChildren?: never;
899
- } | {
900
- readonly kind: "spreadChild";
901
- readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression;
902
- readonly toStatic: () => null;
903
- readonly getChildren: (_at: number) => null;
904
- } | {
905
- readonly kind: "spreadProps";
906
- readonly node: TSESTree.Expression;
907
- readonly toStatic: () => null;
908
- readonly getProperty: (name: string) => unknown;
909
- };
910
- }
911
- //#endregion
912
- //#region src/ref/ref-id.d.ts
913
- declare function isRefId(node: TSESTree.Expression | TSESTree.PrivateIdentifier): boolean;
914
- //#endregion
915
- //#region src/ref/ref-init.d.ts
916
- /**
917
- * Check if the variable with the given name is initialized or derived from a ref
918
- * @param name The variable name
919
- * @param initialScope The initial scope
920
- * @returns True if the variable is derived from a ref, false otherwise
921
- */
922
- declare function isInitializedFromRef(name: string, initialScope: Scope): boolean;
923
- /**
924
- * Get the init expression of a ref variable
925
- * @param name The variable name
926
- * @param initialScope The initial scope
927
- * @returns The init expression node if the variable is derived from a ref, or null otherwise
928
- */
929
- declare function getRefInit(name: string, initialScope: Scope): TSESTree$1.Expression | null;
930
- //#endregion
931
- //#region src/ref/ref-name.d.ts
932
- /**
933
- * Check if a given name corresponds to a ref name
934
- * @param name The name to check
935
- * @returns True if the name is "ref" or ends with "Ref"
936
- */
937
- declare function isRefLikeName(name: string): boolean;
938
- //#endregion
939
- export { ClassComponentSemanticNode, ClientFunctionSemanticNode, ComponentDetectionHint, ComponentFlag, ComponentSemanticNode, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, FunctionComponentSemanticNode, FunctionKind, FunctionSemanticNode, HookSemanticNode, JsxAttributeValue, JsxConfig, JsxDetectionHint, JsxEmit, JsxInspector, REACT_BUILTIN_HOOK_NAMES, SemanticFunc, SemanticNode, ServerFunctionSemanticNode, 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 };
612
+ export { ClassComponentSemanticNode, ClientFunctionSemanticNode, ComponentDetectionHint, ComponentFlag, ComponentSemanticNode, DEFAULT_COMPONENT_DETECTION_HINT, FunctionComponentSemanticNode, FunctionKind, FunctionSemanticNode, HookSemanticNode, REACT_BUILTIN_HOOK_NAMES, SemanticFunc, SemanticNode, ServerFunctionSemanticNode, findImportSource, getComponentCollector, getComponentCollectorLegacy, getComponentFlagFromInitPath, getFunctionComponentId, getHookCollector, 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, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, 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 };
package/dist/index.js CHANGED
@@ -1,10 +1,9 @@
1
1
  import * as ast from "@eslint-react/ast";
2
2
  import { AST_NODE_TYPES } from "@typescript-eslint/types";
3
- import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
3
+ import { findVariable } from "@typescript-eslint/utils/ast-utils";
4
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";
7
- import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils";
5
+ import { JsxDetectionHint, isJsxLike } from "@eslint-react/jsx";
6
+ import { IdGenerator, RE_COMPONENT_NAME, RE_COMPONENT_NAME_LOOSE } from "@eslint-react/shared";
8
7
 
9
8
  //#region ../../.pkgs/eff/dist/index.js
10
9
  /**
@@ -157,12 +156,6 @@ const flip = (f) => (...b) => (...a) => f(...a)(...b);
157
156
  * @since 1.0.0
158
157
  */
159
158
  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
159
 
167
160
  //#endregion
168
161
  //#region src/api/find-import-source.ts
@@ -264,7 +257,7 @@ function isReactAPICall(api) {
264
257
  const func = (context, node) => {
265
258
  if (node == null) return false;
266
259
  if (node.type !== AST_NODE_TYPES.CallExpression) return false;
267
- return isReactAPI(api)(context, node.callee);
260
+ return isReactAPI(api)(context, ast.getUnderlyingExpression(node.callee));
268
261
  };
269
262
  return dual(2, func);
270
263
  }
@@ -438,9 +431,9 @@ function isUseEffectSetupCallback(node) {
438
431
  */
439
432
  function isUseEffectCleanupCallback(node) {
440
433
  if (node == null) return false;
441
- const returnStatement = ast.findParentNode(node, ast.is(AST_NODE_TYPES.ReturnStatement));
442
- const enclosingFunction = ast.findParentNode(node, ast.isFunction);
443
- if (enclosingFunction !== ast.findParentNode(returnStatement, ast.isFunction)) return false;
434
+ const returnStatement = ast.findParent(node, ast.is(AST_NODE_TYPES.ReturnStatement));
435
+ const enclosingFunction = ast.findParent(node, ast.isFunction);
436
+ if (enclosingFunction !== ast.findParent(returnStatement, ast.isFunction)) return false;
444
437
  return isUseEffectSetupCallback(enclosingFunction);
445
438
  }
446
439
 
@@ -463,11 +456,11 @@ function isHookId(id) {
463
456
  //#region src/hook/hook-collector.ts
464
457
  const idGen$2 = new IdGenerator("hook:");
465
458
  /**
466
- * Get a ctx and visitor object for the rule to collect hooks
459
+ * Get a api and visitor object for the rule to collect hooks
467
460
  * @param context The ESLint rule context
468
- * @returns The ctx and visitor of the collector
461
+ * @returns The api and visitor of the collector
469
462
  */
470
- function useHookCollector(context) {
463
+ function getHookCollector(context) {
471
464
  const hooks = /* @__PURE__ */ new Map();
472
465
  const functionEntries = [];
473
466
  const getText = (n) => context.sourceCode.getText(n);
@@ -483,7 +476,7 @@ function useHookCollector(context) {
483
476
  hooks.set(key, {
484
477
  id,
485
478
  key,
486
- kind: "function",
479
+ kind: "hook",
487
480
  name: ast.getFullyQualifiedName(id, getText),
488
481
  directives: [],
489
482
  flag: 0n,
@@ -496,13 +489,9 @@ function useHookCollector(context) {
496
489
  functionEntries.pop();
497
490
  };
498
491
  return {
499
- ctx: {
500
- getAllHooks(node) {
501
- return [...hooks.values()];
502
- },
503
- getCurrentEntries: () => functionEntries,
504
- getCurrentEntry
505
- },
492
+ api: { getAllHooks(node) {
493
+ return [...hooks.values()];
494
+ } },
506
495
  visitor: {
507
496
  ":function": onFunctionEnter,
508
497
  ":function:exit": onFunctionExit,
@@ -516,443 +505,6 @@ function useHookCollector(context) {
516
505
  };
517
506
  }
518
507
 
519
- //#endregion
520
- //#region src/jsx/jsx-config.ts
521
- const JsxEmit = {
522
- None: 0,
523
- Preserve: 1,
524
- React: 2,
525
- ReactNative: 3,
526
- ReactJSX: 4,
527
- ReactJSXDev: 5
528
- };
529
- /**
530
- * Get JsxConfig from the rule context by reading compiler options
531
- * @param context The RuleContext
532
- * @returns JsxConfig derived from compiler options
533
- */
534
- function getJsxConfigFromContext(context) {
535
- const options = context.sourceCode.parserServices?.program?.getCompilerOptions() ?? {};
536
- return {
537
- jsx: options.jsx ?? JsxEmit.ReactJSX,
538
- jsxFactory: options.jsxFactory ?? "React.createElement",
539
- jsxFragmentFactory: options.jsxFragmentFactory ?? "React.Fragment",
540
- jsxImportSource: options.jsxImportSource ?? "react"
541
- };
542
- }
543
- const cache = /* @__PURE__ */ new WeakMap();
544
- /**
545
- * Get JsxConfig from pragma comments (annotations) in the source code
546
- * @param context The RuleContext
547
- * @returns JsxConfig derived from pragma comments
548
- */
549
- function getJsxConfigFromAnnotation(context) {
550
- return getOrElseUpdate(cache, context.sourceCode, () => {
551
- const options = {};
552
- if (!context.sourceCode.text.includes("@jsx")) return options;
553
- let jsx, jsxFrag, jsxRuntime, jsxImportSource;
554
- for (const comment of context.sourceCode.getAllComments().reverse()) {
555
- const value = comment.value;
556
- jsx ??= value.match(RE_ANNOTATION_JSX)?.[1];
557
- jsxFrag ??= value.match(RE_ANNOTATION_JSX_FRAG)?.[1];
558
- jsxRuntime ??= value.match(RE_ANNOTATION_JSX_RUNTIME)?.[1];
559
- jsxImportSource ??= value.match(RE_ANNOTATION_JSX_IMPORT_SOURCE)?.[1];
560
- }
561
- if (jsx != null) options.jsxFactory = jsx;
562
- if (jsxFrag != null) options.jsxFragmentFactory = jsxFrag;
563
- if (jsxRuntime != null) options.jsx = jsxRuntime === "classic" ? JsxEmit.React : JsxEmit.ReactJSX;
564
- if (jsxImportSource != null) options.jsxImportSource = jsxImportSource;
565
- return options;
566
- });
567
- }
568
-
569
- //#endregion
570
- //#region src/jsx/jsx-detection.ts
571
- const JsxDetectionHint = {
572
- None: 0n,
573
- DoNotIncludeJsxWithNullValue: 1n << 0n,
574
- DoNotIncludeJsxWithNumberValue: 1n << 1n,
575
- DoNotIncludeJsxWithBigIntValue: 1n << 2n,
576
- DoNotIncludeJsxWithStringValue: 1n << 3n,
577
- DoNotIncludeJsxWithBooleanValue: 1n << 4n,
578
- DoNotIncludeJsxWithUndefinedValue: 1n << 5n,
579
- DoNotIncludeJsxWithEmptyArrayValue: 1n << 6n,
580
- DoNotIncludeJsxWithCreateElementValue: 1n << 7n,
581
- RequireAllArrayElementsToBeJsx: 1n << 8n,
582
- RequireBothSidesOfLogicalExpressionToBeJsx: 1n << 9n,
583
- RequireBothBranchesOfConditionalExpressionToBeJsx: 1n << 10n
584
- };
585
- /**
586
- * Default JSX detection configuration
587
- * Skips undefined and boolean literals (common in React)
588
- */
589
- const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
590
- /**
591
- * Determine if a node represents JSX-like content based on heuristics
592
- * Supports configuration through hint flags to customize detection behavior
593
- *
594
- * @param context The rule context with scope lookup capability
595
- * @param node The AST node to analyze
596
- * @param hint The configuration flags to adjust detection behavior
597
- * @returns boolean Whether the node is considered JSX-like
598
- */
599
- function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
600
- if (node == null) return false;
601
- if (ast.isJSX(node)) return true;
602
- switch (node.type) {
603
- case AST_NODE_TYPES.Literal:
604
- switch (typeof node.value) {
605
- case "boolean": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithBooleanValue);
606
- case "string": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue);
607
- case "number": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithNumberValue);
608
- case "bigint": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithBigIntValue);
609
- }
610
- if (node.value == null) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithNullValue);
611
- return false;
612
- case AST_NODE_TYPES.TemplateLiteral: return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue);
613
- case AST_NODE_TYPES.ArrayExpression:
614
- if (node.elements.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
615
- if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(context, n, hint));
616
- return node.elements.some((n) => isJsxLike(context, n, hint));
617
- case AST_NODE_TYPES.LogicalExpression:
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);
620
- case AST_NODE_TYPES.ConditionalExpression: {
621
- function leftHasJSX(node) {
622
- if (Array.isArray(node.consequent)) {
623
- if (node.consequent.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue);
624
- if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.consequent.every((n) => isJsxLike(context, n, hint));
625
- return node.consequent.some((n) => isJsxLike(context, n, hint));
626
- }
627
- return isJsxLike(context, node.consequent, hint);
628
- }
629
- function rightHasJSX(node) {
630
- return isJsxLike(context, node.alternate, hint);
631
- }
632
- if (hint & JsxDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx) return leftHasJSX(node) && rightHasJSX(node);
633
- return leftHasJSX(node) || rightHasJSX(node);
634
- }
635
- case AST_NODE_TYPES.SequenceExpression: return isJsxLike(context, node.expressions.at(-1) ?? null, hint);
636
- case AST_NODE_TYPES.CallExpression:
637
- if (hint & JsxDetectionHint.DoNotIncludeJsxWithCreateElementValue) return false;
638
- switch (node.callee.type) {
639
- case AST_NODE_TYPES.Identifier: return node.callee.name === "createElement";
640
- case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement";
641
- }
642
- return false;
643
- case AST_NODE_TYPES.Identifier:
644
- if (node.name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue);
645
- if (ast.isJSXTagNameExpression(node)) return true;
646
- return isJsxLike(context, resolve(context, node), hint);
647
- }
648
- return false;
649
- }
650
-
651
- //#endregion
652
- //#region src/jsx/jsx-stringify.ts
653
- /**
654
- * Incomplete but sufficient stringification of JSX nodes for common use cases
655
- *
656
- * @param node JSX node from TypeScript ESTree
657
- * @returns String representation of the JSX node
658
- */
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
- }
670
- }
671
-
672
- //#endregion
673
- //#region src/jsx/jsx-inspector.ts
674
- /**
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.
677
- *
678
- * ### Typical usage inside a rule's `create` function
679
- *
680
- * ```ts
681
- * export function create(context: RuleContext) {
682
- * const jsx = JsxInspector.from(context);
683
- *
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
- * ```
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
- };
713
- }
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
508
  //#endregion
957
509
  //#region src/component/component-detection-legacy.ts
958
510
  /**
@@ -1184,19 +736,19 @@ function isFunctionWithLooseComponentName(context, fn, allowNone = false) {
1184
736
  */
1185
737
  const ComponentDetectionHint = {
1186
738
  ...JsxDetectionHint,
739
+ DoNotIncludeFunctionDefinedAsClassMethod: 1n << 11n,
740
+ DoNotIncludeFunctionDefinedAsClassProperty: 1n << 12n,
741
+ DoNotIncludeFunctionDefinedAsObjectMethod: 1n << 13n,
742
+ DoNotIncludeFunctionDefinedAsArrayExpressionElement: 1n << 14n,
743
+ DoNotIncludeFunctionDefinedAsArrayPatternElement: 1n << 15n,
1187
744
  DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback: 1n << 18n,
1188
- DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 17n,
1189
- DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 16n,
1190
- DoNotIncludeFunctionDefinedInArrayExpression: 1n << 15n,
1191
- DoNotIncludeFunctionDefinedInArrayPattern: 1n << 14n,
1192
- DoNotIncludeFunctionDefinedOnClassMethod: 1n << 12n,
1193
- DoNotIncludeFunctionDefinedOnClassProperty: 1n << 13n,
1194
- DoNotIncludeFunctionDefinedOnObjectMethod: 1n << 11n
745
+ DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 16n,
746
+ DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 17n
1195
747
  };
1196
748
  /**
1197
749
  * Default component detection hint
1198
750
  */
1199
- const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
751
+ const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx;
1200
752
  /**
1201
753
  * Determine if a function node represents a valid React component definition
1202
754
  *
@@ -1215,19 +767,19 @@ function isComponentDefinition(context, node, hint) {
1215
767
  while (ast.isTypeExpression(parent)) parent = parent.parent;
1216
768
  switch (true) {
1217
769
  case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.Property && parent.parent.type === AST_NODE_TYPES.ObjectExpression:
1218
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnObjectMethod) return false;
770
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsObjectMethod) return false;
1219
771
  break;
1220
772
  case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.MethodDefinition:
1221
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassMethod) return false;
773
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassMethod) return false;
1222
774
  break;
1223
775
  case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.Property:
1224
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassProperty) return false;
776
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassProperty) return false;
1225
777
  break;
1226
778
  case parent.type === AST_NODE_TYPES.ArrayPattern:
1227
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern) return false;
779
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement) return false;
1228
780
  break;
1229
781
  case parent.type === AST_NODE_TYPES.ArrayExpression:
1230
- if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression) return false;
782
+ if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement) return false;
1231
783
  break;
1232
784
  case parent.type === AST_NODE_TYPES.CallExpression && parent.callee.type === AST_NODE_TYPES.MemberExpression && parent.callee.property.type === AST_NODE_TYPES.Identifier && parent.callee.property.name === "map":
1233
785
  if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback) return false;
@@ -1239,7 +791,7 @@ function isComponentDefinition(context, node, hint) {
1239
791
  if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback) return false;
1240
792
  break;
1241
793
  }
1242
- const significantParent = ast.findParentNode(node, ast.isOneOf([
794
+ const significantParent = ast.findParent(node, ast.isOneOf([
1243
795
  AST_NODE_TYPES.JSXExpressionContainer,
1244
796
  AST_NODE_TYPES.ArrowFunctionExpression,
1245
797
  AST_NODE_TYPES.FunctionExpression,
@@ -1277,14 +829,14 @@ function getComponentFlagFromInitPath(initPath) {
1277
829
 
1278
830
  //#endregion
1279
831
  //#region src/component/component-collector.ts
1280
- const idGen$1 = new IdGenerator("function-component:");
832
+ const idGen$1 = new IdGenerator("component:");
1281
833
  /**
1282
- * Get a ctx and visitor object for the rule to collect function components
834
+ * Get a api and visitor object for the rule to collect function components
1283
835
  * @param context The ESLint rule context
1284
836
  * @param options The options to use
1285
- * @returns The ctx and visitor of the collector
837
+ * @returns The api and visitor of the collector
1286
838
  */
1287
- function useComponentCollector(context, options = {}) {
839
+ function getComponentCollector(context, options = {}) {
1288
840
  const { collectDisplayName = false, hint = DEFAULT_COMPONENT_DETECTION_HINT } = options;
1289
841
  const functionEntries = [];
1290
842
  const components = /* @__PURE__ */ new Map();
@@ -1292,7 +844,7 @@ function useComponentCollector(context, options = {}) {
1292
844
  const getCurrentEntry = () => functionEntries.at(-1) ?? null;
1293
845
  const onFunctionEnter = (node) => {
1294
846
  const key = idGen$1.next();
1295
- const exp = ast.findParentNode(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration);
847
+ const exp = ast.findParent(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration);
1296
848
  const isExportDefault = exp != null;
1297
849
  const isExportDefaultDeclaration = exp != null && ast.getUnderlyingExpression(exp.declaration) === node;
1298
850
  const id = getFunctionComponentId(context, node);
@@ -1302,7 +854,7 @@ function useComponentCollector(context, options = {}) {
1302
854
  const entry = {
1303
855
  id: getFunctionComponentId(context, node),
1304
856
  key,
1305
- kind: "function-component",
857
+ kind: "component",
1306
858
  name,
1307
859
  directives,
1308
860
  displayName: null,
@@ -1324,15 +876,9 @@ function useComponentCollector(context, options = {}) {
1324
876
  return functionEntries.pop();
1325
877
  };
1326
878
  return {
1327
- ctx: {
1328
- getAllComponents(node) {
1329
- return [...components.values()];
1330
- },
1331
- getCurrentEntries() {
1332
- return [...functionEntries];
1333
- },
1334
- getCurrentEntry
1335
- },
879
+ api: { getAllComponents(node) {
880
+ return [...components.values()];
881
+ } },
1336
882
  visitor: {
1337
883
  ":function": onFunctionEnter,
1338
884
  ":function:exit": onFunctionExit,
@@ -1379,13 +925,13 @@ function useComponentCollector(context, options = {}) {
1379
925
  //#region src/component/component-collector-legacy.ts
1380
926
  const idGen = new IdGenerator("class-component:");
1381
927
  /**
1382
- * Get a ctx and visitor object for the rule to collect class componentss
928
+ * Get a api and visitor object for the rule to collect class componentss
1383
929
  * @param context The ESLint rule context
1384
- * @returns The ctx and visitor of the collector
930
+ * @returns The api and visitor of the collector
1385
931
  */
1386
- function useComponentCollectorLegacy(context) {
932
+ function getComponentCollectorLegacy(context) {
1387
933
  const components = /* @__PURE__ */ new Map();
1388
- const ctx = { getAllComponents(node) {
934
+ const api = { getAllComponents(node) {
1389
935
  return [...components.values()];
1390
936
  } };
1391
937
  const getText = (n) => context.sourceCode.getText(n);
@@ -1408,7 +954,7 @@ function useComponentCollectorLegacy(context) {
1408
954
  });
1409
955
  };
1410
956
  return {
1411
- ctx,
957
+ api,
1412
958
  visitor: {
1413
959
  ClassDeclaration: collect,
1414
960
  ClassExpression: collect
@@ -1417,51 +963,4 @@ function useComponentCollectorLegacy(context) {
1417
963
  }
1418
964
 
1419
965
  //#endregion
1420
- //#region src/ref/ref-name.ts
1421
- /**
1422
- * Check if a given name corresponds to a ref name
1423
- * @param name The name to check
1424
- * @returns True if the name is "ref" or ends with "Ref"
1425
- */
1426
- function isRefLikeName(name) {
1427
- return name === "ref" || name.endsWith("Ref");
1428
- }
1429
-
1430
- //#endregion
1431
- //#region src/ref/ref-id.ts
1432
- function isRefId(node) {
1433
- return node.type === AST_NODE_TYPES.Identifier && isRefLikeName(node.name);
1434
- }
1435
-
1436
- //#endregion
1437
- //#region src/ref/ref-init.ts
1438
- /**
1439
- * Check if the variable with the given name is initialized or derived from a ref
1440
- * @param name The variable name
1441
- * @param initialScope The initial scope
1442
- * @returns True if the variable is derived from a ref, false otherwise
1443
- */
1444
- function isInitializedFromRef(name, initialScope) {
1445
- return getRefInit(name, initialScope) != null;
1446
- }
1447
- /**
1448
- * Get the init expression of a ref variable
1449
- * @param name The variable name
1450
- * @param initialScope The initial scope
1451
- * @returns The init expression node if the variable is derived from a ref, or null otherwise
1452
- */
1453
- function getRefInit(name, initialScope) {
1454
- for (const { node } of findVariable(initialScope, name)?.defs ?? []) {
1455
- if (node.type !== AST_NODE_TYPES$1.VariableDeclarator) continue;
1456
- const init = node.init;
1457
- if (init == null) continue;
1458
- switch (true) {
1459
- case init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier && isRefLikeName(init.object.name): return init;
1460
- case init.type === AST_NODE_TYPES$1.CallExpression && isUseRefCall(init): return init;
1461
- }
1462
- }
1463
- return null;
1464
- }
1465
-
1466
- //#endregion
1467
- 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 };
966
+ export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, REACT_BUILTIN_HOOK_NAMES, findImportSource, getComponentCollector, getComponentCollectorLegacy, getComponentFlagFromInitPath, getFunctionComponentId, getHookCollector, 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, isLazy, isLazyCall, isMemo, isMemoCall, isPureComponent, isReactAPI, isReactAPICall, 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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eslint-react/core",
3
- "version": "3.0.0-rc.5",
3
+ "version": "4.0.0-beta.0",
4
4
  "description": "ESLint React's ESLint utility module for static analysis of React core APIs and patterns.",
5
5
  "homepage": "https://github.com/Rel1cx/eslint-react",
6
6
  "bugs": {
@@ -30,18 +30,19 @@
30
30
  "./package.json"
31
31
  ],
32
32
  "dependencies": {
33
- "@typescript-eslint/scope-manager": "^8.57.0",
34
- "@typescript-eslint/types": "^8.57.0",
35
- "@typescript-eslint/utils": "^8.57.0",
33
+ "@typescript-eslint/scope-manager": "^8.57.2",
34
+ "@typescript-eslint/types": "^8.57.2",
35
+ "@typescript-eslint/utils": "^8.57.2",
36
36
  "ts-pattern": "^5.9.0",
37
- "@eslint-react/ast": "3.0.0-rc.5",
38
- "@eslint-react/var": "3.0.0-rc.5",
39
- "@eslint-react/shared": "3.0.0-rc.5"
37
+ "@eslint-react/jsx": "4.0.0-beta.0",
38
+ "@eslint-react/ast": "4.0.0-beta.0",
39
+ "@eslint-react/shared": "4.0.0-beta.0",
40
+ "@eslint-react/var": "4.0.0-beta.0"
40
41
  },
41
42
  "devDependencies": {
42
- "tsdown": "^0.21.2",
43
- "@local/configs": "0.0.0",
44
- "@local/eff": "3.0.0-beta.72"
43
+ "tsdown": "^0.21.4",
44
+ "@local/eff": "3.0.0-beta.72",
45
+ "@local/configs": "0.0.0"
45
46
  },
46
47
  "peerDependencies": {
47
48
  "eslint": "^10.0.0",