@eslint-react/core 3.0.0-next.69 → 3.0.0-next.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +184 -117
- package/dist/index.js +299 -219
- package/package.json +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -620,25 +620,6 @@ declare const REACT_BUILTIN_HOOK_NAMES: readonly ["use", "useActionState", "useC
|
|
|
620
620
|
*/
|
|
621
621
|
declare function isHookName(name: string): boolean;
|
|
622
622
|
//#endregion
|
|
623
|
-
//#region src/jsx/jsx-attribute.d.ts
|
|
624
|
-
/**
|
|
625
|
-
* Creates a helper function to find a specific JSX attribute by name
|
|
626
|
-
* Handles direct attributes and spread attributes (variables or object literals)
|
|
627
|
-
* @param context The ESLint rule context
|
|
628
|
-
* @param node The JSX element node
|
|
629
|
-
* @param initialScope (Optional) The initial scope to use for variable resolution
|
|
630
|
-
*/
|
|
631
|
-
declare function getJsxAttribute(context: RuleContext, node: TSESTree.JSXElement, initialScope?: Scope): (name: string) => TSESTree.JSXAttribute | TSESTree.JSXSpreadAttribute | undefined;
|
|
632
|
-
//#endregion
|
|
633
|
-
//#region src/jsx/jsx-attribute-name.d.ts
|
|
634
|
-
/**
|
|
635
|
-
* Get the stringified name of a JSX attribute
|
|
636
|
-
* @param context The ESLint rule context
|
|
637
|
-
* @param node The JSX attribute node
|
|
638
|
-
* @returns The name of the attribute
|
|
639
|
-
*/
|
|
640
|
-
declare function getJsxAttributeName(context: RuleContext, node: TSESTree$1.JSXAttribute): string;
|
|
641
|
-
//#endregion
|
|
642
623
|
//#region src/jsx/jsx-attribute-value.d.ts
|
|
643
624
|
/**
|
|
644
625
|
* Represents possible JSX attribute value types that can be resolved
|
|
@@ -664,57 +645,14 @@ type JsxAttributeValue = {
|
|
|
664
645
|
toStatic(): unknown;
|
|
665
646
|
} | {
|
|
666
647
|
kind: "spreadProps";
|
|
648
|
+
getProperty(name: string): unknown;
|
|
667
649
|
node: TSESTree.JSXSpreadAttribute["argument"];
|
|
668
650
|
toStatic(): unknown;
|
|
669
|
-
getProperty(name: string): unknown;
|
|
670
651
|
} | {
|
|
671
652
|
kind: "spreadChild";
|
|
653
|
+
getChildren(at: number): unknown;
|
|
672
654
|
node: TSESTree.JSXSpreadChild["expression"];
|
|
673
655
|
toStatic(): unknown;
|
|
674
|
-
getChildren(at: number): unknown;
|
|
675
|
-
};
|
|
676
|
-
/**
|
|
677
|
-
* Resolve the static value of a JSX attribute or spread attribute
|
|
678
|
-
*
|
|
679
|
-
* @param context - The ESLint rule context
|
|
680
|
-
* @param attribute - The JSX attribute node to resolve
|
|
681
|
-
* @returns An object containing the value kind, the node (if applicable), and a `toStatic` helper
|
|
682
|
-
*/
|
|
683
|
-
declare function resolveJsxAttributeValue(context: RuleContext, attribute: ast.TSESTreeJSXAttributeLike): {
|
|
684
|
-
readonly kind: "boolean";
|
|
685
|
-
readonly toStatic: () => true;
|
|
686
|
-
readonly node?: never;
|
|
687
|
-
readonly getChildren?: never;
|
|
688
|
-
} | {
|
|
689
|
-
readonly kind: "literal";
|
|
690
|
-
readonly node: TSESTree.BigIntLiteral | TSESTree.BooleanLiteral | TSESTree.NullLiteral | TSESTree.NumberLiteral | TSESTree.RegExpLiteral | TSESTree.StringLiteral;
|
|
691
|
-
readonly toStatic: () => string | number | bigint | boolean | RegExp | null;
|
|
692
|
-
readonly getChildren?: never;
|
|
693
|
-
} | {
|
|
694
|
-
readonly kind: "missing";
|
|
695
|
-
readonly node: TSESTree.JSXEmptyExpression;
|
|
696
|
-
readonly toStatic: () => "{}";
|
|
697
|
-
readonly getChildren?: never;
|
|
698
|
-
} | {
|
|
699
|
-
readonly kind: "expression";
|
|
700
|
-
readonly node: TSESTree.Expression;
|
|
701
|
-
readonly toStatic: () => unknown;
|
|
702
|
-
readonly getChildren?: never;
|
|
703
|
-
} | {
|
|
704
|
-
readonly kind: "element";
|
|
705
|
-
readonly node: TSESTree.JSXElement;
|
|
706
|
-
readonly toStatic: () => null;
|
|
707
|
-
readonly getChildren?: never;
|
|
708
|
-
} | {
|
|
709
|
-
readonly kind: "spreadChild";
|
|
710
|
-
readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression;
|
|
711
|
-
readonly toStatic: () => unknown;
|
|
712
|
-
readonly getChildren: (at: number) => null;
|
|
713
|
-
} | {
|
|
714
|
-
readonly kind: "spreadProps";
|
|
715
|
-
readonly node: TSESTree.Expression;
|
|
716
|
-
readonly toStatic: () => unknown;
|
|
717
|
-
readonly getProperty: (name: string) => unknown;
|
|
718
656
|
};
|
|
719
657
|
//#endregion
|
|
720
658
|
//#region src/jsx/jsx-config.d.ts
|
|
@@ -774,12 +712,6 @@ declare const JsxDetectionHint: {
|
|
|
774
712
|
* Skips undefined and boolean literals (common in React)
|
|
775
713
|
*/
|
|
776
714
|
declare const DEFAULT_JSX_DETECTION_HINT: bigint;
|
|
777
|
-
/**
|
|
778
|
-
* Check if a node is a `JSXText` or a `Literal` node
|
|
779
|
-
* @param node The AST node to check
|
|
780
|
-
* @returns `true` if the node is a `JSXText` or a `Literal` node
|
|
781
|
-
*/
|
|
782
|
-
declare function isJsxText(node: TSESTree$1.Node | null): node is TSESTree$1.JSXText | TSESTree$1.Literal;
|
|
783
715
|
/**
|
|
784
716
|
* Determine if a node represents JSX-like content based on heuristics
|
|
785
717
|
* Supports configuration through hint flags to customize detection behavior
|
|
@@ -791,59 +723,194 @@ declare function isJsxText(node: TSESTree$1.Node | null): node is TSESTree$1.JSX
|
|
|
791
723
|
*/
|
|
792
724
|
declare function isJsxLike(context: RuleContext, node: TSESTree$1.Node | null, hint?: JsxDetectionHint): boolean;
|
|
793
725
|
//#endregion
|
|
794
|
-
//#region src/jsx/jsx-
|
|
726
|
+
//#region src/jsx/jsx-inspector.d.ts
|
|
795
727
|
/**
|
|
796
|
-
*
|
|
797
|
-
*
|
|
728
|
+
* A stateful helper that binds an ESLint `RuleContext` once and exposes
|
|
729
|
+
* ergonomic methods for the most common JSX inspection tasks that rules need.
|
|
798
730
|
*
|
|
799
|
-
*
|
|
800
|
-
* @param node AST node to check
|
|
801
|
-
* @returns boolean indicating if the element is a host element
|
|
802
|
-
*/
|
|
803
|
-
declare function isJsxHostElement(context: RuleContext, node: TSESTree.Node): boolean;
|
|
804
|
-
/**
|
|
805
|
-
* Determine if a JSX element is a React Fragment
|
|
806
|
-
* Fragments can be imported from React and used like <Fragment> or <React.Fragment>
|
|
731
|
+
* ### Typical usage inside a rule's `create` function
|
|
807
732
|
*
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
* @param jsxConfig.jsxFragmentFactory Name of the fragment factory (e.g., React.Fragment)
|
|
812
|
-
* @returns boolean indicating if the element is a Fragment
|
|
813
|
-
*/
|
|
814
|
-
declare function isJsxFragmentElement(context: RuleContext, node: TSESTree.Node, jsxConfig?: Pick<JsxConfig, "jsxFragmentFactory">): boolean;
|
|
815
|
-
//#endregion
|
|
816
|
-
//#region src/jsx/jsx-element-type.d.ts
|
|
817
|
-
/**
|
|
818
|
-
* Extracts the element type name from a JSX element or fragment
|
|
819
|
-
* For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment")
|
|
820
|
-
* For JSX fragments, returns an empty string
|
|
733
|
+
* ```ts
|
|
734
|
+
* export function create(context: RuleContext) {
|
|
735
|
+
* const jsx = JsxInspector.from(context);
|
|
821
736
|
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
|
|
826
|
-
declare function getJsxElementType(context: RuleContext, node: TSESTree.JSXElement | TSESTree.JSXFragment): string;
|
|
827
|
-
//#endregion
|
|
828
|
-
//#region src/jsx/jsx-hierarchy.d.ts
|
|
829
|
-
/**
|
|
830
|
-
* Traverses up the AST to find a parent JSX attribute node that matches a given test
|
|
737
|
+
* return defineRuleListener({
|
|
738
|
+
* JSXElement(node) {
|
|
739
|
+
* // element type
|
|
740
|
+
* const type = jsx.getElementType(node); // "div" | "React.Fragment" | …
|
|
831
741
|
*
|
|
832
|
-
*
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
* @returns The first matching JSX attribute node found when traversing upwards, or null
|
|
836
|
-
*/
|
|
837
|
-
declare function findParentJsxAttribute(node: TSESTree.Node, test?: (node: TSESTree.JSXAttribute) => boolean): TSESTree.JSXAttribute | null;
|
|
838
|
-
//#endregion
|
|
839
|
-
//#region src/jsx/jsx-stringify.d.ts
|
|
840
|
-
/**
|
|
841
|
-
* Incomplete but sufficient stringification of JSX nodes for common use cases
|
|
742
|
+
* // attribute lookup + value resolution in one step
|
|
743
|
+
* const val = jsx.getAttributeValue(node, "sandbox");
|
|
744
|
+
* if (typeof val?.getStatic() === "string") { … }
|
|
842
745
|
*
|
|
843
|
-
*
|
|
844
|
-
*
|
|
746
|
+
* // simple boolean checks
|
|
747
|
+
* if (jsx.isHostElement(node)) { … }
|
|
748
|
+
* if (jsx.isFragmentElement(node)) { … }
|
|
749
|
+
* if (jsx.hasAttribute(node, "key")) { … }
|
|
750
|
+
* },
|
|
751
|
+
* });
|
|
752
|
+
* }
|
|
753
|
+
* ```
|
|
845
754
|
*/
|
|
846
|
-
declare
|
|
755
|
+
declare class JsxInspector {
|
|
756
|
+
#private;
|
|
757
|
+
readonly context: RuleContext;
|
|
758
|
+
/**
|
|
759
|
+
* Merged JSX configuration (tsconfig compiler options + pragma annotations).
|
|
760
|
+
* The result is lazily computed and cached for the lifetime of this inspector.
|
|
761
|
+
*/
|
|
762
|
+
get jsxConfig(): Required<JsxConfig>;
|
|
763
|
+
private constructor();
|
|
764
|
+
/**
|
|
765
|
+
* Walk **up** the AST from `node` to find the nearest ancestor that is a
|
|
766
|
+
* `JSXAttribute` and passes the optional `test` predicate.
|
|
767
|
+
* @param node The starting node for the search.
|
|
768
|
+
* @param test A predicate function to test each ancestor node.
|
|
769
|
+
*/
|
|
770
|
+
static findParentAttribute(node: TSESTree.Node, test?: (node: TSESTree.JSXAttribute) => boolean): TSESTree.JSXAttribute | null;
|
|
771
|
+
/**
|
|
772
|
+
* Create a new `JsxInspector` bound to the given rule context.
|
|
773
|
+
* @param context The ESLint rule context to bind to this inspector instance.
|
|
774
|
+
*/
|
|
775
|
+
static from(context: RuleContext): JsxInspector;
|
|
776
|
+
/**
|
|
777
|
+
* Whether the node is a `JSXText` or a `Literal` node.
|
|
778
|
+
* @param node The node to check.
|
|
779
|
+
*/
|
|
780
|
+
static isJsxText(node: TSESTree.Node | null): node is TSESTree.JSXText | TSESTree.Literal;
|
|
781
|
+
/**
|
|
782
|
+
* Find a JSX attribute (or spread attribute containing the property) by name
|
|
783
|
+
* on a given element.
|
|
784
|
+
*
|
|
785
|
+
* Returns the **last** matching attribute (to mirror React's behaviour where
|
|
786
|
+
* later props win), or `undefined` if not found.
|
|
787
|
+
* @param node The JSX element to search for the attribute.
|
|
788
|
+
* @param name The name of the attribute to find (e.g. `"className"`).
|
|
789
|
+
* @param initialScope An optional scope to use for resolving spread attributes. If not provided,
|
|
790
|
+
*/
|
|
791
|
+
findAttribute(node: TSESTree.JSXElement, name: string, initialScope?: Scope): ast.TSESTreeJSXAttributeLike | undefined;
|
|
792
|
+
/**
|
|
793
|
+
* Get the stringified name of a `JSXAttribute` node
|
|
794
|
+
* (e.g. `"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 (e.g. `"className"`).
|
|
813
|
+
* @param initialScope An optional scope to use for resolving spread attributes. If not provided, the scope will be determined from the context of the attribute node.
|
|
814
|
+
* @returns The static value of the attribute, or `undefined` if not found or not statically resolvable.
|
|
815
|
+
*/
|
|
816
|
+
getAttributeStaticValue(node: TSESTree.JSXElement, name: string, initialScope?: Scope): unknown;
|
|
817
|
+
/**
|
|
818
|
+
* **All-in-one helper** – find an attribute by name on an element *and*
|
|
819
|
+
* resolve its value in a single call.
|
|
820
|
+
*
|
|
821
|
+
* Returns `undefined` when the attribute is not present.
|
|
822
|
+
* @param node The JSX element to search for the attribute.
|
|
823
|
+
* @param name The name of the attribute to find and resolve (e.g. `"className"`).
|
|
824
|
+
* @param initialScope An optional scope to use for resolving spread attributes. If not provided, the scope will be determined from the context of the attribute node.
|
|
825
|
+
* @returns A descriptor of the attribute's value that can be further inspected, or `undefined` if the attribute is not found.
|
|
826
|
+
*/
|
|
827
|
+
getAttributeValue(node: TSESTree.JSXElement, name: string, initialScope?: Scope): JsxAttributeValue | undefined;
|
|
828
|
+
/**
|
|
829
|
+
* Get the **self name** (last segment) of a JSX element type.
|
|
830
|
+
*
|
|
831
|
+
* - `<Foo.Bar.Baz>` → `"Baz"`
|
|
832
|
+
* - `<div>` → `"div"`
|
|
833
|
+
* - `<></>` → `""`
|
|
834
|
+
* @param node The JSX element or fragment to extract the self name from.
|
|
835
|
+
*/
|
|
836
|
+
getElementSelfName(node: TSESTree.JSXElement | TSESTree.JSXFragment): string;
|
|
837
|
+
/**
|
|
838
|
+
* Get the string representation of a JSX element's type.
|
|
839
|
+
*
|
|
840
|
+
* - `<div>` → `"div"`
|
|
841
|
+
* - `<Foo.Bar>` → `"Foo.Bar"`
|
|
842
|
+
* - `<React.Fragment>` → `"React.Fragment"`
|
|
843
|
+
* - `<></>` (JSXFragment) → `""`
|
|
844
|
+
* @param node The JSX element or fragment to extract the type from.
|
|
845
|
+
*/
|
|
846
|
+
getElementType(node: TSESTree.JSXElement | TSESTree.JSXFragment): string;
|
|
847
|
+
/**
|
|
848
|
+
* Shorthand: check whether an attribute exists on the element.
|
|
849
|
+
* @param node The JSX element to check for the attribute.
|
|
850
|
+
* @param name The name of the attribute to check for (e.g. `"className"`).
|
|
851
|
+
* @param initialScope An optional scope to use for resolving spread attributes. If not provided, the scope will be determined from the context of the attribute node.
|
|
852
|
+
* @returns `true` if the attribute exists on the element, `false` otherwise.
|
|
853
|
+
*/
|
|
854
|
+
hasAttribute(node: TSESTree.JSXElement, name: string, initialScope?: Scope): boolean;
|
|
855
|
+
/**
|
|
856
|
+
* Whether the node is a React **Fragment** element (either `<Fragment>` /
|
|
857
|
+
* `<React.Fragment>` or the shorthand `<>` syntax).
|
|
858
|
+
*
|
|
859
|
+
* The check honours the configured `jsxFragmentFactory`.
|
|
860
|
+
* @param node The node to check.
|
|
861
|
+
*/
|
|
862
|
+
isFragmentElement(node: TSESTree.Node): node is TSESTree.JSXElement | TSESTree.JSXFragment;
|
|
863
|
+
/**
|
|
864
|
+
* Whether the node is a **host** (intrinsic / DOM) element – i.e. its tag
|
|
865
|
+
* name starts with a lowercase letter.
|
|
866
|
+
* @param node The node to check.
|
|
867
|
+
*/
|
|
868
|
+
isHostElement(node: TSESTree.Node): node is TSESTree.JSXElement;
|
|
869
|
+
/**
|
|
870
|
+
* Resolve the *value* of a JSX attribute (or spread attribute) into a
|
|
871
|
+
* descriptor that can be inspected further.
|
|
872
|
+
*
|
|
873
|
+
* See {@link JsxAttributeValue} for the full set of `kind` discriminants.
|
|
874
|
+
* @param attribute The attribute node to resolve the value of.
|
|
875
|
+
* @returns A descriptor of the attribute's value that can be further inspected.
|
|
876
|
+
*/
|
|
877
|
+
resolveAttributeValue(attribute: ast.TSESTreeJSXAttributeLike): {
|
|
878
|
+
readonly kind: "boolean";
|
|
879
|
+
readonly toStatic: () => true;
|
|
880
|
+
readonly node?: never;
|
|
881
|
+
readonly getChildren?: never;
|
|
882
|
+
} | {
|
|
883
|
+
readonly kind: "literal";
|
|
884
|
+
readonly node: TSESTree.BigIntLiteral | TSESTree.BooleanLiteral | TSESTree.NullLiteral | TSESTree.NumberLiteral | TSESTree.RegExpLiteral | TSESTree.StringLiteral;
|
|
885
|
+
readonly toStatic: () => string | number | bigint | boolean | RegExp | null;
|
|
886
|
+
readonly getChildren?: never;
|
|
887
|
+
} | {
|
|
888
|
+
readonly kind: "missing";
|
|
889
|
+
readonly node: TSESTree.JSXEmptyExpression;
|
|
890
|
+
readonly toStatic: () => "{}";
|
|
891
|
+
readonly getChildren?: never;
|
|
892
|
+
} | {
|
|
893
|
+
readonly kind: "expression";
|
|
894
|
+
readonly node: TSESTree.Expression;
|
|
895
|
+
readonly toStatic: () => unknown;
|
|
896
|
+
readonly getChildren?: never;
|
|
897
|
+
} | {
|
|
898
|
+
readonly kind: "element";
|
|
899
|
+
readonly node: TSESTree.JSXElement;
|
|
900
|
+
readonly toStatic: () => null;
|
|
901
|
+
readonly getChildren?: never;
|
|
902
|
+
} | {
|
|
903
|
+
readonly kind: "spreadChild";
|
|
904
|
+
readonly getChildren: (_at: number) => null;
|
|
905
|
+
readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression;
|
|
906
|
+
readonly toStatic: () => unknown;
|
|
907
|
+
} | {
|
|
908
|
+
readonly kind: "spreadProps";
|
|
909
|
+
readonly getProperty: (name: string) => unknown;
|
|
910
|
+
readonly node: TSESTree.Expression;
|
|
911
|
+
readonly toStatic: () => unknown;
|
|
912
|
+
};
|
|
913
|
+
}
|
|
847
914
|
//#endregion
|
|
848
915
|
//#region src/ref/ref-id.d.ts
|
|
849
916
|
declare function isRefId(node: TSESTree.Expression | TSESTree.PrivateIdentifier): boolean;
|
|
@@ -872,4 +939,4 @@ declare function getRefInit(name: string, initialScope: Scope): TSESTree$1.Expre
|
|
|
872
939
|
*/
|
|
873
940
|
declare function isRefLikeName(name: string): boolean;
|
|
874
941
|
//#endregion
|
|
875
|
-
export { ClassComponentSemanticNode, ClientFunctionSemanticNode, ComponentDetectionHint, ComponentFlag, ComponentSemanticNode, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, FunctionComponentSemanticNode, FunctionKind, FunctionSemanticNode, HookSemanticNode, JsxAttributeValue, JsxConfig, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, SemanticFunc, SemanticNode, ServerFunctionSemanticNode, findImportSource,
|
|
942
|
+
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 };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ast from "@eslint-react/ast";
|
|
2
|
-
import { constFalse,
|
|
2
|
+
import { constFalse, dual, flip, getOrElseUpdate, identity } from "@eslint-react/eff";
|
|
3
3
|
import { AST_NODE_TYPES } from "@typescript-eslint/types";
|
|
4
4
|
import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils";
|
|
5
5
|
import { P, match } from "ts-pattern";
|
|
@@ -358,165 +358,6 @@ function useHookCollector(context) {
|
|
|
358
358
|
};
|
|
359
359
|
}
|
|
360
360
|
|
|
361
|
-
//#endregion
|
|
362
|
-
//#region src/jsx/jsx-stringify.ts
|
|
363
|
-
/**
|
|
364
|
-
* Incomplete but sufficient stringification of JSX nodes for common use cases
|
|
365
|
-
*
|
|
366
|
-
* @param node JSX node from TypeScript ESTree
|
|
367
|
-
* @returns String representation of the JSX node
|
|
368
|
-
*/
|
|
369
|
-
function stringifyJsx(node) {
|
|
370
|
-
switch (node.type) {
|
|
371
|
-
case AST_NODE_TYPES.JSXIdentifier: return node.name;
|
|
372
|
-
case AST_NODE_TYPES.JSXNamespacedName: return `${node.namespace.name}:${node.name.name}`;
|
|
373
|
-
case AST_NODE_TYPES.JSXMemberExpression: return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`;
|
|
374
|
-
case AST_NODE_TYPES.JSXText: return node.value;
|
|
375
|
-
case AST_NODE_TYPES.JSXOpeningElement: return `<${stringifyJsx(node.name)}>`;
|
|
376
|
-
case AST_NODE_TYPES.JSXClosingElement: return `</${stringifyJsx(node.name)}>`;
|
|
377
|
-
case AST_NODE_TYPES.JSXOpeningFragment: return "<>";
|
|
378
|
-
case AST_NODE_TYPES.JSXClosingFragment: return "</>";
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
//#endregion
|
|
383
|
-
//#region src/jsx/jsx-attribute-name.ts
|
|
384
|
-
/**
|
|
385
|
-
* Get the stringified name of a JSX attribute
|
|
386
|
-
* @param context The ESLint rule context
|
|
387
|
-
* @param node The JSX attribute node
|
|
388
|
-
* @returns The name of the attribute
|
|
389
|
-
*/
|
|
390
|
-
function getJsxAttributeName(context, node) {
|
|
391
|
-
return stringifyJsx(node.name);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
//#endregion
|
|
395
|
-
//#region src/jsx/jsx-attribute.ts
|
|
396
|
-
/**
|
|
397
|
-
* Creates a helper function to find a specific JSX attribute by name
|
|
398
|
-
* Handles direct attributes and spread attributes (variables or object literals)
|
|
399
|
-
* @param context The ESLint rule context
|
|
400
|
-
* @param node The JSX element node
|
|
401
|
-
* @param initialScope (Optional) The initial scope to use for variable resolution
|
|
402
|
-
*/
|
|
403
|
-
function getJsxAttribute(context, node, initialScope) {
|
|
404
|
-
initialScope ?? context.sourceCode.getScope(node);
|
|
405
|
-
const attributes = node.openingElement.attributes;
|
|
406
|
-
/**
|
|
407
|
-
* Finds the last occurrence of a specific attribute
|
|
408
|
-
* @param name The attribute name to search for
|
|
409
|
-
*/
|
|
410
|
-
return (name) => {
|
|
411
|
-
return attributes.findLast((attr) => {
|
|
412
|
-
if (attr.type === AST_NODE_TYPES.JSXAttribute) return getJsxAttributeName(context, attr) === name;
|
|
413
|
-
switch (attr.argument.type) {
|
|
414
|
-
case AST_NODE_TYPES.Identifier: {
|
|
415
|
-
const initNode = resolve(context, attr.argument);
|
|
416
|
-
if (initNode?.type === AST_NODE_TYPES.ObjectExpression) return ast.findProperty(initNode.properties, name) != null;
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
case AST_NODE_TYPES.ObjectExpression: return ast.findProperty(attr.argument.properties, name) != null;
|
|
420
|
-
}
|
|
421
|
-
return false;
|
|
422
|
-
});
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
//#endregion
|
|
427
|
-
//#region src/jsx/jsx-attribute-value.ts
|
|
428
|
-
/**
|
|
429
|
-
* Resolve the static value of a JSX attribute or spread attribute
|
|
430
|
-
*
|
|
431
|
-
* @param context - The ESLint rule context
|
|
432
|
-
* @param attribute - The JSX attribute node to resolve
|
|
433
|
-
* @returns An object containing the value kind, the node (if applicable), and a `toStatic` helper
|
|
434
|
-
*/
|
|
435
|
-
function resolveJsxAttributeValue(context, attribute) {
|
|
436
|
-
const initialScope = context.sourceCode.getScope(attribute);
|
|
437
|
-
/**
|
|
438
|
-
* Handles standard JSX attributes (e.g., prop="value", prop={value}, prop)
|
|
439
|
-
* @param node The JSX attribute node
|
|
440
|
-
*/
|
|
441
|
-
function handleJsxAttribute(node) {
|
|
442
|
-
if (node.value == null) return {
|
|
443
|
-
kind: "boolean",
|
|
444
|
-
toStatic() {
|
|
445
|
-
return true;
|
|
446
|
-
}
|
|
447
|
-
};
|
|
448
|
-
switch (node.value.type) {
|
|
449
|
-
case AST_NODE_TYPES.Literal: {
|
|
450
|
-
const staticValue = node.value.value;
|
|
451
|
-
return {
|
|
452
|
-
kind: "literal",
|
|
453
|
-
node: node.value,
|
|
454
|
-
toStatic() {
|
|
455
|
-
return staticValue;
|
|
456
|
-
}
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
case AST_NODE_TYPES.JSXExpressionContainer: {
|
|
460
|
-
const expr = node.value.expression;
|
|
461
|
-
if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return {
|
|
462
|
-
kind: "missing",
|
|
463
|
-
node: expr,
|
|
464
|
-
toStatic() {
|
|
465
|
-
return "{}";
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
return {
|
|
469
|
-
kind: "expression",
|
|
470
|
-
node: expr,
|
|
471
|
-
toStatic() {
|
|
472
|
-
return getStaticValue(expr, initialScope)?.value;
|
|
473
|
-
}
|
|
474
|
-
};
|
|
475
|
-
}
|
|
476
|
-
case AST_NODE_TYPES.JSXElement: return {
|
|
477
|
-
kind: "element",
|
|
478
|
-
node: node.value,
|
|
479
|
-
toStatic() {
|
|
480
|
-
return null;
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
case AST_NODE_TYPES.JSXSpreadChild: {
|
|
484
|
-
const expr = node.value.expression;
|
|
485
|
-
return {
|
|
486
|
-
kind: "spreadChild",
|
|
487
|
-
node: node.value.expression,
|
|
488
|
-
toStatic() {
|
|
489
|
-
return getStaticValue(expr, initialScope)?.value;
|
|
490
|
-
},
|
|
491
|
-
getChildren(at) {
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
/**
|
|
499
|
-
* Handles JSX spread attributes (e.g., {...props})
|
|
500
|
-
* @param node The JSX spread attribute node
|
|
501
|
-
*/
|
|
502
|
-
function handleJsxSpreadAttribute(node) {
|
|
503
|
-
return {
|
|
504
|
-
kind: "spreadProps",
|
|
505
|
-
node: node.argument,
|
|
506
|
-
toStatic() {
|
|
507
|
-
return getStaticValue(node.argument, initialScope)?.value;
|
|
508
|
-
},
|
|
509
|
-
getProperty(name) {
|
|
510
|
-
return match(getStaticValue(node.argument, initialScope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => null);
|
|
511
|
-
}
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
switch (attribute.type) {
|
|
515
|
-
case AST_NODE_TYPES.JSXAttribute: return handleJsxAttribute(attribute);
|
|
516
|
-
case AST_NODE_TYPES.JSXSpreadAttribute: return handleJsxSpreadAttribute(attribute);
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
|
|
520
361
|
//#endregion
|
|
521
362
|
//#region src/jsx/jsx-config.ts
|
|
522
363
|
const JsxEmit = {
|
|
@@ -589,15 +430,6 @@ const JsxDetectionHint = {
|
|
|
589
430
|
*/
|
|
590
431
|
const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue;
|
|
591
432
|
/**
|
|
592
|
-
* Check if a node is a `JSXText` or a `Literal` node
|
|
593
|
-
* @param node The AST node to check
|
|
594
|
-
* @returns `true` if the node is a `JSXText` or a `Literal` node
|
|
595
|
-
*/
|
|
596
|
-
function isJsxText(node) {
|
|
597
|
-
if (node == null) return false;
|
|
598
|
-
return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
433
|
* Determine if a node represents JSX-like content based on heuristics
|
|
602
434
|
* Supports configuration through hint flags to customize detection behavior
|
|
603
435
|
*
|
|
@@ -659,66 +491,314 @@ function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) {
|
|
|
659
491
|
}
|
|
660
492
|
|
|
661
493
|
//#endregion
|
|
662
|
-
//#region src/jsx/jsx-
|
|
494
|
+
//#region src/jsx/jsx-stringify.ts
|
|
663
495
|
/**
|
|
664
|
-
*
|
|
665
|
-
* For JSX elements, returns the stringified name (e.g., "div", "Button", "React.Fragment")
|
|
666
|
-
* For JSX fragments, returns an empty string
|
|
496
|
+
* Incomplete but sufficient stringification of JSX nodes for common use cases
|
|
667
497
|
*
|
|
668
|
-
* @param
|
|
669
|
-
* @
|
|
670
|
-
* @returns String representation of the element type
|
|
498
|
+
* @param node JSX node from TypeScript ESTree
|
|
499
|
+
* @returns String representation of the JSX node
|
|
671
500
|
*/
|
|
672
|
-
function
|
|
673
|
-
|
|
674
|
-
|
|
501
|
+
function stringifyJsx(node) {
|
|
502
|
+
switch (node.type) {
|
|
503
|
+
case AST_NODE_TYPES.JSXIdentifier: return node.name;
|
|
504
|
+
case AST_NODE_TYPES.JSXNamespacedName: return `${node.namespace.name}:${node.name.name}`;
|
|
505
|
+
case AST_NODE_TYPES.JSXMemberExpression: return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`;
|
|
506
|
+
case AST_NODE_TYPES.JSXText: return node.value;
|
|
507
|
+
case AST_NODE_TYPES.JSXOpeningElement: return `<${stringifyJsx(node.name)}>`;
|
|
508
|
+
case AST_NODE_TYPES.JSXClosingElement: return `</${stringifyJsx(node.name)}>`;
|
|
509
|
+
case AST_NODE_TYPES.JSXOpeningFragment: return "<>";
|
|
510
|
+
case AST_NODE_TYPES.JSXClosingFragment: return "</>";
|
|
511
|
+
}
|
|
675
512
|
}
|
|
676
513
|
|
|
677
514
|
//#endregion
|
|
678
|
-
//#region src/jsx/jsx-
|
|
515
|
+
//#region src/jsx/jsx-inspector.ts
|
|
679
516
|
/**
|
|
680
|
-
*
|
|
681
|
-
*
|
|
517
|
+
* A stateful helper that binds an ESLint `RuleContext` once and exposes
|
|
518
|
+
* ergonomic methods for the most common JSX inspection tasks that rules need.
|
|
682
519
|
*
|
|
683
|
-
*
|
|
684
|
-
* @param node AST node to check
|
|
685
|
-
* @returns boolean indicating if the element is a host element
|
|
686
|
-
*/
|
|
687
|
-
function isJsxHostElement(context, node) {
|
|
688
|
-
return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* Determine if a JSX element is a React Fragment
|
|
692
|
-
* Fragments can be imported from React and used like <Fragment> or <React.Fragment>
|
|
520
|
+
* ### Typical usage inside a rule's `create` function
|
|
693
521
|
*
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
//#endregion
|
|
707
|
-
//#region src/jsx/jsx-hierarchy.ts
|
|
708
|
-
/**
|
|
709
|
-
* Traverses up the AST to find a parent JSX attribute node that matches a given test
|
|
522
|
+
* ```ts
|
|
523
|
+
* export function create(context: RuleContext) {
|
|
524
|
+
* const jsx = JsxInspector.from(context);
|
|
525
|
+
*
|
|
526
|
+
* return defineRuleListener({
|
|
527
|
+
* JSXElement(node) {
|
|
528
|
+
* // element type
|
|
529
|
+
* const type = jsx.getElementType(node); // "div" | "React.Fragment" | …
|
|
530
|
+
*
|
|
531
|
+
* // attribute lookup + value resolution in one step
|
|
532
|
+
* const val = jsx.getAttributeValue(node, "sandbox");
|
|
533
|
+
* if (typeof val?.getStatic() === "string") { … }
|
|
710
534
|
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
713
|
-
*
|
|
714
|
-
*
|
|
535
|
+
* // simple boolean checks
|
|
536
|
+
* if (jsx.isHostElement(node)) { … }
|
|
537
|
+
* if (jsx.isFragmentElement(node)) { … }
|
|
538
|
+
* if (jsx.hasAttribute(node, "key")) { … }
|
|
539
|
+
* },
|
|
540
|
+
* });
|
|
541
|
+
* }
|
|
542
|
+
* ```
|
|
715
543
|
*/
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
544
|
+
var JsxInspector = class JsxInspector {
|
|
545
|
+
context;
|
|
546
|
+
/**
|
|
547
|
+
* Merged JSX configuration (tsconfig compiler options + pragma annotations).
|
|
548
|
+
* The result is lazily computed and cached for the lifetime of this inspector.
|
|
549
|
+
*/
|
|
550
|
+
get jsxConfig() {
|
|
551
|
+
return this.#jsxConfig ??= {
|
|
552
|
+
...getJsxConfigFromContext(this.context),
|
|
553
|
+
...getJsxConfigFromAnnotation(this.context)
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Lazily resolved & cached JSX configuration (merged from tsconfig +
|
|
558
|
+
* pragma annotations). Use {@link jsxConfig} to access.
|
|
559
|
+
*/
|
|
560
|
+
#jsxConfig;
|
|
561
|
+
constructor(context) {
|
|
562
|
+
this.context = context;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Walk **up** the AST from `node` to find the nearest ancestor that is a
|
|
566
|
+
* `JSXAttribute` and passes the optional `test` predicate.
|
|
567
|
+
* @param node The starting node for the search.
|
|
568
|
+
* @param test A predicate function to test each ancestor node.
|
|
569
|
+
*/
|
|
570
|
+
static findParentAttribute(node, test = () => true) {
|
|
571
|
+
const guard = (n) => {
|
|
572
|
+
return n.type === AST_NODE_TYPES.JSXAttribute && test(n);
|
|
573
|
+
};
|
|
574
|
+
return ast.findParentNode(node, guard);
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Create a new `JsxInspector` bound to the given rule context.
|
|
578
|
+
* @param context The ESLint rule context to bind to this inspector instance.
|
|
579
|
+
*/
|
|
580
|
+
static from(context) {
|
|
581
|
+
return new JsxInspector(context);
|
|
582
|
+
}
|
|
583
|
+
/**
|
|
584
|
+
* Whether the node is a `JSXText` or a `Literal` node.
|
|
585
|
+
* @param node The node to check.
|
|
586
|
+
*/
|
|
587
|
+
static isJsxText(node) {
|
|
588
|
+
if (node == null) return false;
|
|
589
|
+
return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal;
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Find a JSX attribute (or spread attribute containing the property) by name
|
|
593
|
+
* on a given element.
|
|
594
|
+
*
|
|
595
|
+
* Returns the **last** matching attribute (to mirror React's behaviour where
|
|
596
|
+
* later props win), or `undefined` if not found.
|
|
597
|
+
* @param node The JSX element to search for the attribute.
|
|
598
|
+
* @param name The name of the attribute to find (e.g. `"className"`).
|
|
599
|
+
* @param initialScope An optional scope to use for resolving spread attributes. If not provided,
|
|
600
|
+
*/
|
|
601
|
+
findAttribute(node, name, initialScope) {
|
|
602
|
+
initialScope ?? this.context.sourceCode.getScope(node);
|
|
603
|
+
return node.openingElement.attributes.findLast((attr) => {
|
|
604
|
+
if (attr.type === AST_NODE_TYPES.JSXAttribute) return stringifyJsx(attr.name) === name;
|
|
605
|
+
switch (attr.argument.type) {
|
|
606
|
+
case AST_NODE_TYPES.Identifier: {
|
|
607
|
+
const initNode = resolve(this.context, attr.argument);
|
|
608
|
+
if (initNode?.type === AST_NODE_TYPES.ObjectExpression) return ast.findProperty(initNode.properties, name) != null;
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
case AST_NODE_TYPES.ObjectExpression: return ast.findProperty(attr.argument.properties, name) != null;
|
|
612
|
+
}
|
|
613
|
+
return false;
|
|
614
|
+
});
|
|
615
|
+
}
|
|
616
|
+
/**
|
|
617
|
+
* Get the stringified name of a `JSXAttribute` node
|
|
618
|
+
* (e.g. `"className"`, `"aria-label"`, `"xml:space"`).
|
|
619
|
+
* @param node The `JSXAttribute` node to extract the name from.
|
|
620
|
+
* @returns The stringified name of the attribute.
|
|
621
|
+
*/
|
|
622
|
+
getAttributeName(node) {
|
|
623
|
+
return stringifyJsx(node.name);
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Resolve the static value of an attribute, automatically handling the
|
|
627
|
+
* `spreadProps` case by extracting the named property.
|
|
628
|
+
*
|
|
629
|
+
* This eliminates the repetitive pattern:
|
|
630
|
+
* ```ts
|
|
631
|
+
* const v = core.resolveJsxAttributeValue(ctx, attr);
|
|
632
|
+
* const s = v.kind === "spreadProps" ? v.getProperty(name) : v.toStatic();
|
|
633
|
+
* ```
|
|
634
|
+
*
|
|
635
|
+
* Returns `undefined` when the attribute is not present or its value
|
|
636
|
+
* cannot be statically determined.
|
|
637
|
+
* @param node The JSX element to search for the attribute.
|
|
638
|
+
* @param name The name of the attribute to resolve (e.g. `"className"`).
|
|
639
|
+
* @param initialScope An optional scope to use for resolving spread attributes. If not provided, the scope will be determined from the context of the attribute node.
|
|
640
|
+
* @returns The static value of the attribute, or `undefined` if not found or not statically resolvable.
|
|
641
|
+
*/
|
|
642
|
+
getAttributeStaticValue(node, name, initialScope) {
|
|
643
|
+
const attr = this.findAttribute(node, name, initialScope);
|
|
644
|
+
if (attr == null) return void 0;
|
|
645
|
+
const resolved = this.resolveAttributeValue(attr);
|
|
646
|
+
if (resolved.kind === "spreadProps") return resolved.getProperty(name);
|
|
647
|
+
return resolved.toStatic();
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* **All-in-one helper** – find an attribute by name on an element *and*
|
|
651
|
+
* resolve its value in a single call.
|
|
652
|
+
*
|
|
653
|
+
* Returns `undefined` when the attribute is not present.
|
|
654
|
+
* @param node The JSX element to search for the attribute.
|
|
655
|
+
* @param name The name of the attribute to find and resolve (e.g. `"className"`).
|
|
656
|
+
* @param initialScope An optional scope to use for resolving spread attributes. If not provided, the scope will be determined from the context of the attribute node.
|
|
657
|
+
* @returns A descriptor of the attribute's value that can be further inspected, or `undefined` if the attribute is not found.
|
|
658
|
+
*/
|
|
659
|
+
getAttributeValue(node, name, initialScope) {
|
|
660
|
+
const attr = this.findAttribute(node, name, initialScope);
|
|
661
|
+
if (attr == null) return void 0;
|
|
662
|
+
return this.resolveAttributeValue(attr);
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Get the **self name** (last segment) of a JSX element type.
|
|
666
|
+
*
|
|
667
|
+
* - `<Foo.Bar.Baz>` → `"Baz"`
|
|
668
|
+
* - `<div>` → `"div"`
|
|
669
|
+
* - `<></>` → `""`
|
|
670
|
+
* @param node The JSX element or fragment to extract the self name from.
|
|
671
|
+
*/
|
|
672
|
+
getElementSelfName(node) {
|
|
673
|
+
return this.getElementType(node).split(".").at(-1) ?? "";
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Get the string representation of a JSX element's type.
|
|
677
|
+
*
|
|
678
|
+
* - `<div>` → `"div"`
|
|
679
|
+
* - `<Foo.Bar>` → `"Foo.Bar"`
|
|
680
|
+
* - `<React.Fragment>` → `"React.Fragment"`
|
|
681
|
+
* - `<></>` (JSXFragment) → `""`
|
|
682
|
+
* @param node The JSX element or fragment to extract the type from.
|
|
683
|
+
*/
|
|
684
|
+
getElementType(node) {
|
|
685
|
+
if (node.type === AST_NODE_TYPES.JSXFragment) return "";
|
|
686
|
+
return stringifyJsx(node.openingElement.name);
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Shorthand: check whether an attribute exists on the element.
|
|
690
|
+
* @param node The JSX element to check for the attribute.
|
|
691
|
+
* @param name The name of the attribute to check for (e.g. `"className"`).
|
|
692
|
+
* @param initialScope An optional scope to use for resolving spread attributes. If not provided, the scope will be determined from the context of the attribute node.
|
|
693
|
+
* @returns `true` if the attribute exists on the element, `false` otherwise.
|
|
694
|
+
*/
|
|
695
|
+
hasAttribute(node, name, initialScope) {
|
|
696
|
+
return this.findAttribute(node, name, initialScope) != null;
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Whether the node is a React **Fragment** element (either `<Fragment>` /
|
|
700
|
+
* `<React.Fragment>` or the shorthand `<>` syntax).
|
|
701
|
+
*
|
|
702
|
+
* The check honours the configured `jsxFragmentFactory`.
|
|
703
|
+
* @param node The node to check.
|
|
704
|
+
*/
|
|
705
|
+
isFragmentElement(node) {
|
|
706
|
+
if (node.type === AST_NODE_TYPES.JSXFragment) return true;
|
|
707
|
+
if (node.type !== AST_NODE_TYPES.JSXElement) return false;
|
|
708
|
+
const fragment = this.jsxConfig.jsxFragmentFactory.split(".").at(-1) ?? "Fragment";
|
|
709
|
+
return this.getElementType(node).split(".").at(-1) === fragment;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Whether the node is a **host** (intrinsic / DOM) element – i.e. its tag
|
|
713
|
+
* name starts with a lowercase letter.
|
|
714
|
+
* @param node The node to check.
|
|
715
|
+
*/
|
|
716
|
+
isHostElement(node) {
|
|
717
|
+
return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Resolve the *value* of a JSX attribute (or spread attribute) into a
|
|
721
|
+
* descriptor that can be inspected further.
|
|
722
|
+
*
|
|
723
|
+
* See {@link JsxAttributeValue} for the full set of `kind` discriminants.
|
|
724
|
+
* @param attribute The attribute node to resolve the value of.
|
|
725
|
+
* @returns A descriptor of the attribute's value that can be further inspected.
|
|
726
|
+
*/
|
|
727
|
+
resolveAttributeValue(attribute) {
|
|
728
|
+
const initialScope = this.context.sourceCode.getScope(attribute);
|
|
729
|
+
if (attribute.type === AST_NODE_TYPES.JSXAttribute) return this.#resolveJsxAttribute(attribute, initialScope);
|
|
730
|
+
return this.#resolveJsxSpreadAttribute(attribute, initialScope);
|
|
731
|
+
}
|
|
732
|
+
#resolveJsxAttribute(node, initialScope) {
|
|
733
|
+
if (node.value == null) return {
|
|
734
|
+
kind: "boolean",
|
|
735
|
+
toStatic() {
|
|
736
|
+
return true;
|
|
737
|
+
}
|
|
738
|
+
};
|
|
739
|
+
switch (node.value.type) {
|
|
740
|
+
case AST_NODE_TYPES.Literal: {
|
|
741
|
+
const staticValue = node.value.value;
|
|
742
|
+
return {
|
|
743
|
+
kind: "literal",
|
|
744
|
+
node: node.value,
|
|
745
|
+
toStatic() {
|
|
746
|
+
return staticValue;
|
|
747
|
+
}
|
|
748
|
+
};
|
|
749
|
+
}
|
|
750
|
+
case AST_NODE_TYPES.JSXExpressionContainer: {
|
|
751
|
+
const expr = node.value.expression;
|
|
752
|
+
if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return {
|
|
753
|
+
kind: "missing",
|
|
754
|
+
node: expr,
|
|
755
|
+
toStatic() {
|
|
756
|
+
return "{}";
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
return {
|
|
760
|
+
kind: "expression",
|
|
761
|
+
node: expr,
|
|
762
|
+
toStatic() {
|
|
763
|
+
return getStaticValue(expr, initialScope)?.value;
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
case AST_NODE_TYPES.JSXElement: return {
|
|
768
|
+
kind: "element",
|
|
769
|
+
node: node.value,
|
|
770
|
+
toStatic() {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
case AST_NODE_TYPES.JSXSpreadChild: {
|
|
775
|
+
const expr = node.value.expression;
|
|
776
|
+
return {
|
|
777
|
+
kind: "spreadChild",
|
|
778
|
+
getChildren(_at) {
|
|
779
|
+
return null;
|
|
780
|
+
},
|
|
781
|
+
node: node.value.expression,
|
|
782
|
+
toStatic() {
|
|
783
|
+
return getStaticValue(expr, initialScope)?.value;
|
|
784
|
+
}
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
#resolveJsxSpreadAttribute(node, initialScope) {
|
|
790
|
+
return {
|
|
791
|
+
kind: "spreadProps",
|
|
792
|
+
getProperty(name) {
|
|
793
|
+
return match(getStaticValue(node.argument, initialScope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => null);
|
|
794
|
+
},
|
|
795
|
+
node: node.argument,
|
|
796
|
+
toStatic() {
|
|
797
|
+
return getStaticValue(node.argument, initialScope)?.value;
|
|
798
|
+
}
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
};
|
|
722
802
|
|
|
723
803
|
//#endregion
|
|
724
804
|
//#region src/component/component-detection-legacy.ts
|
|
@@ -1222,4 +1302,4 @@ function getRefInit(name, initialScope) {
|
|
|
1222
1302
|
}
|
|
1223
1303
|
|
|
1224
1304
|
//#endregion
|
|
1225
|
-
export { ComponentDetectionHint, ComponentFlag, DEFAULT_COMPONENT_DETECTION_HINT, DEFAULT_JSX_DETECTION_HINT, JsxDetectionHint, JsxEmit, REACT_BUILTIN_HOOK_NAMES, findImportSource,
|
|
1305
|
+
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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eslint-react/core",
|
|
3
|
-
"version": "3.0.0-next.
|
|
3
|
+
"version": "3.0.0-next.70",
|
|
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": {
|
|
@@ -34,10 +34,10 @@
|
|
|
34
34
|
"@typescript-eslint/types": "canary",
|
|
35
35
|
"@typescript-eslint/utils": "canary",
|
|
36
36
|
"ts-pattern": "^5.9.0",
|
|
37
|
-
"@eslint-react/ast": "3.0.0-next.
|
|
38
|
-
"@eslint-react/
|
|
39
|
-
"@eslint-react/
|
|
40
|
-
"@eslint-react/
|
|
37
|
+
"@eslint-react/ast": "3.0.0-next.70",
|
|
38
|
+
"@eslint-react/eff": "3.0.0-next.70",
|
|
39
|
+
"@eslint-react/var": "3.0.0-next.70",
|
|
40
|
+
"@eslint-react/shared": "3.0.0-next.70"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"tsdown": "^0.21.0-beta.2",
|