@eslint-react/core 3.0.0 → 4.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +25 -352
- package/dist/index.js +43 -544
- package/package.json +9 -8
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
|
|
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):
|
|
41
|
-
(context: RuleContext): (node: null | TSESTree.Node) =>
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
284
|
+
* @returns The api and visitor of the collector
|
|
291
285
|
*/
|
|
292
|
-
declare function
|
|
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
|
|
289
|
+
declare namespace getComponentCollectorLegacy {
|
|
296
290
|
type ReturnType = {
|
|
297
|
-
|
|
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
|
|
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
|
|
300
|
+
* @returns The api and visitor of the collector
|
|
307
301
|
*/
|
|
308
|
-
declare function
|
|
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
|
-
|
|
532
|
-
key: string;
|
|
533
|
-
node: ast.TSESTreeFunction;
|
|
534
|
-
};
|
|
535
|
-
declare namespace useHookCollector {
|
|
525
|
+
declare namespace getHookCollector {
|
|
536
526
|
type ReturnType = {
|
|
537
|
-
|
|
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
|
|
534
|
+
* Get a api and visitor object for the rule to collect hooks
|
|
547
535
|
* @param context The ESLint rule context
|
|
548
|
-
* @returns The
|
|
536
|
+
* @returns The api and visitor of the collector
|
|
549
537
|
*/
|
|
550
|
-
declare function
|
|
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
|
-
|
|
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
|
|
3
|
+
import { findVariable } from "@typescript-eslint/utils/ast-utils";
|
|
4
4
|
import { P, match } from "ts-pattern";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
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.
|
|
442
|
-
const enclosingFunction = ast.
|
|
443
|
-
if (enclosingFunction !== ast.
|
|
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
|
|
459
|
+
* Get a api and visitor object for the rule to collect hooks
|
|
467
460
|
* @param context The ESLint rule context
|
|
468
|
-
* @returns The
|
|
461
|
+
* @returns The api and visitor of the collector
|
|
469
462
|
*/
|
|
470
|
-
function
|
|
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: "
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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 <<
|
|
1189
|
-
DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n <<
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
776
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassProperty) return false;
|
|
1225
777
|
break;
|
|
1226
778
|
case parent.type === AST_NODE_TYPES.ArrayPattern:
|
|
1227
|
-
if (hint & ComponentDetectionHint.
|
|
779
|
+
if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement) return false;
|
|
1228
780
|
break;
|
|
1229
781
|
case parent.type === AST_NODE_TYPES.ArrayExpression:
|
|
1230
|
-
if (hint & ComponentDetectionHint.
|
|
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.
|
|
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("
|
|
832
|
+
const idGen$1 = new IdGenerator("component:");
|
|
1281
833
|
/**
|
|
1282
|
-
* Get a
|
|
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
|
|
837
|
+
* @returns The api and visitor of the collector
|
|
1286
838
|
*/
|
|
1287
|
-
function
|
|
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.
|
|
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: "
|
|
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
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
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
|
|
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
|
|
930
|
+
* @returns The api and visitor of the collector
|
|
1385
931
|
*/
|
|
1386
|
-
function
|
|
932
|
+
function getComponentCollectorLegacy(context) {
|
|
1387
933
|
const components = /* @__PURE__ */ new Map();
|
|
1388
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
+
"version": "4.0.0-beta.1",
|
|
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,16 +30,17 @@
|
|
|
30
30
|
"./package.json"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@typescript-eslint/scope-manager": "^8.57.
|
|
34
|
-
"@typescript-eslint/types": "^8.57.
|
|
35
|
-
"@typescript-eslint/utils": "^8.57.
|
|
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": "
|
|
38
|
-
"@eslint-react/
|
|
39
|
-
"@eslint-react/var": "
|
|
37
|
+
"@eslint-react/ast": "4.0.0-beta.1",
|
|
38
|
+
"@eslint-react/jsx": "4.0.0-beta.1",
|
|
39
|
+
"@eslint-react/var": "4.0.0-beta.1",
|
|
40
|
+
"@eslint-react/shared": "4.0.0-beta.1"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
|
-
"tsdown": "^0.21.
|
|
43
|
+
"tsdown": "^0.21.4",
|
|
43
44
|
"@local/configs": "0.0.0",
|
|
44
45
|
"@local/eff": "3.0.0-beta.72"
|
|
45
46
|
},
|