@eslint-react/core
Advanced tools
+25
-352
| import * as ast from "@eslint-react/ast"; | ||
| import { TSESTree } from "@typescript-eslint/types"; | ||
| import { RegExpLike, RuleContext } from "@eslint-react/shared"; | ||
| import { Scope } from "@typescript-eslint/scope-manager"; | ||
| import { ESLintUtils, TSESTree as TSESTree$1 } from "@typescript-eslint/utils"; | ||
| import { Scope } from "@typescript-eslint/scope-manager"; | ||
| import * as typescript from "typescript"; | ||
@@ -40,4 +39,4 @@ //#region src/api/find-import-source.d.ts | ||
| type ReturnType = { | ||
| (context: RuleContext, node: null | TSESTree.Node): node is TSESTree.Identifier | TSESTree.MemberExpression; | ||
| (context: RuleContext): (node: null | TSESTree.Node) => node is TSESTree.MemberExpression | TSESTree.Identifier; | ||
| (context: RuleContext, node: null | TSESTree.Node): boolean; | ||
| (context: RuleContext): (node: null | TSESTree.Node) => boolean; | ||
| }; | ||
@@ -96,10 +95,10 @@ } | ||
| declare const ComponentDetectionHint: { | ||
| readonly DoNotIncludeFunctionDefinedAsClassMethod: bigint; | ||
| readonly DoNotIncludeFunctionDefinedAsClassProperty: bigint; | ||
| readonly DoNotIncludeFunctionDefinedAsObjectMethod: bigint; | ||
| readonly DoNotIncludeFunctionDefinedAsArrayExpressionElement: bigint; | ||
| readonly DoNotIncludeFunctionDefinedAsArrayPatternElement: bigint; | ||
| readonly DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback: bigint; | ||
| readonly DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: bigint; | ||
| readonly DoNotIncludeFunctionDefinedAsArrayMapCallback: bigint; | ||
| readonly DoNotIncludeFunctionDefinedInArrayExpression: bigint; | ||
| readonly DoNotIncludeFunctionDefinedInArrayPattern: bigint; | ||
| readonly DoNotIncludeFunctionDefinedOnClassMethod: bigint; | ||
| readonly DoNotIncludeFunctionDefinedOnClassProperty: bigint; | ||
| readonly DoNotIncludeFunctionDefinedOnObjectMethod: bigint; | ||
| readonly None: 0n; | ||
@@ -190,3 +189,3 @@ readonly DoNotIncludeJsxWithNullValue: bigint; | ||
| */ | ||
| kind: "function-component"; | ||
| kind: "component"; | ||
| /** | ||
@@ -272,6 +271,3 @@ * The AST node of the function | ||
| //#region src/component/component-collector.d.ts | ||
| interface FunctionEntry$1 extends FunctionComponentSemanticNode { | ||
| isComponentDefinition: boolean; | ||
| } | ||
| declare namespace useComponentCollector { | ||
| declare namespace getComponentCollector { | ||
| type Options = { | ||
@@ -282,6 +278,4 @@ collectDisplayName?: boolean; | ||
| type ReturnType = { | ||
| ctx: { | ||
| api: { | ||
| getAllComponents: (node: TSESTree.Program) => FunctionComponentSemanticNode[]; | ||
| getCurrentEntries: () => FunctionEntry$1[]; | ||
| getCurrentEntry: () => FunctionEntry$1 | null; | ||
| }; | ||
@@ -292,13 +286,13 @@ visitor: ESLintUtils.RuleListener; | ||
| /** | ||
| * Get a ctx and visitor object for the rule to collect function components | ||
| * Get a api and visitor object for the rule to collect function components | ||
| * @param context The ESLint rule context | ||
| * @param options The options to use | ||
| * @returns The ctx and visitor of the collector | ||
| * @returns The api and visitor of the collector | ||
| */ | ||
| declare function useComponentCollector(context: RuleContext, options?: useComponentCollector.Options): useComponentCollector.ReturnType; | ||
| declare function getComponentCollector(context: RuleContext, options?: getComponentCollector.Options): getComponentCollector.ReturnType; | ||
| //#endregion | ||
| //#region src/component/component-collector-legacy.d.ts | ||
| declare namespace useComponentCollectorLegacy { | ||
| declare namespace getComponentCollectorLegacy { | ||
| type ReturnType = { | ||
| ctx: { | ||
| api: { | ||
| getAllComponents: (node: TSESTree$1.Program) => ClassComponentSemanticNode[]; | ||
@@ -310,7 +304,7 @@ }; | ||
| /** | ||
| * Get a ctx and visitor object for the rule to collect class componentss | ||
| * Get a api and visitor object for the rule to collect class componentss | ||
| * @param context The ESLint rule context | ||
| * @returns The ctx and visitor of the collector | ||
| * @returns The api and visitor of the collector | ||
| */ | ||
| declare function useComponentCollectorLegacy(context: RuleContext): useComponentCollectorLegacy.ReturnType; | ||
| declare function getComponentCollectorLegacy(context: RuleContext): getComponentCollectorLegacy.ReturnType; | ||
| //#endregion | ||
@@ -538,12 +532,6 @@ //#region src/component/component-detection-legacy.d.ts | ||
| //#region src/hook/hook-collector.d.ts | ||
| type FunctionEntry = { | ||
| key: string; | ||
| node: ast.TSESTreeFunction; | ||
| }; | ||
| declare namespace useHookCollector { | ||
| declare namespace getHookCollector { | ||
| type ReturnType = { | ||
| ctx: { | ||
| api: { | ||
| getAllHooks(node: TSESTree$1.Program): HookSemanticNode[]; | ||
| getCurrentEntries(): FunctionEntry[]; | ||
| getCurrentEntry(): FunctionEntry | null; | ||
| }; | ||
@@ -554,7 +542,7 @@ visitor: ESLintUtils.RuleListener; | ||
| /** | ||
| * Get a ctx and visitor object for the rule to collect hooks | ||
| * Get a api and visitor object for the rule to collect hooks | ||
| * @param context The ESLint rule context | ||
| * @returns The ctx and visitor of the collector | ||
| * @returns The api and visitor of the collector | ||
| */ | ||
| declare function useHookCollector(context: RuleContext): useHookCollector.ReturnType; | ||
| declare function getHookCollector(context: RuleContext): getHookCollector.ReturnType; | ||
| //#endregion | ||
@@ -633,317 +621,2 @@ //#region src/hook/hook-id.d.ts | ||
| //#endregion | ||
| //#region src/jsx/jsx-attribute-value.d.ts | ||
| /** | ||
| * Represents possible JSX attribute value types that can be resolved | ||
| */ | ||
| type JsxAttributeValue = { | ||
| kind: "missing"; | ||
| node: TSESTree.JSXEmptyExpression; | ||
| toStatic(): null; | ||
| } | { | ||
| kind: "boolean"; | ||
| toStatic(): true; | ||
| } | { | ||
| kind: "element"; | ||
| node: TSESTree.JSXElement; | ||
| toStatic(): null; | ||
| } | { | ||
| kind: "literal"; | ||
| node: TSESTree.Literal; | ||
| toStatic(): TSESTree.Literal["value"]; | ||
| } | { | ||
| kind: "expression"; | ||
| node: TSESTree.JSXExpressionContainer["expression"]; | ||
| toStatic(): unknown; | ||
| } | { | ||
| kind: "spreadProps"; | ||
| getProperty(name: string): unknown; | ||
| node: TSESTree.JSXSpreadAttribute["argument"]; | ||
| toStatic(): null; | ||
| } | { | ||
| kind: "spreadChild"; | ||
| getChildren(at: number): unknown; | ||
| node: TSESTree.JSXSpreadChild["expression"]; | ||
| toStatic(): null; | ||
| }; | ||
| //#endregion | ||
| //#region src/jsx/jsx-config.d.ts | ||
| declare const JsxEmit: { | ||
| readonly None: 0; | ||
| readonly Preserve: 1; | ||
| readonly React: 2; | ||
| readonly ReactNative: 3; | ||
| readonly ReactJSX: 4; | ||
| readonly ReactJSXDev: 5; | ||
| }; | ||
| interface JsxConfig { | ||
| jsx?: number; | ||
| jsxFactory?: string; | ||
| jsxFragmentFactory?: string; | ||
| jsxImportSource?: string; | ||
| } | ||
| /** | ||
| * Get JsxConfig from the rule context by reading compiler options | ||
| * @param context The RuleContext | ||
| * @returns JsxConfig derived from compiler options | ||
| */ | ||
| declare function getJsxConfigFromContext(context: RuleContext): { | ||
| jsx: 4 | typescript.JsxEmit; | ||
| jsxFactory: string; | ||
| jsxFragmentFactory: string; | ||
| jsxImportSource: string; | ||
| }; | ||
| /** | ||
| * Get JsxConfig from pragma comments (annotations) in the source code | ||
| * @param context The RuleContext | ||
| * @returns JsxConfig derived from pragma comments | ||
| */ | ||
| declare function getJsxConfigFromAnnotation(context: RuleContext): JsxConfig; | ||
| //#endregion | ||
| //#region src/jsx/jsx-detection.d.ts | ||
| /** | ||
| * BitFlags for configuring JSX detection behavior | ||
| */ | ||
| type JsxDetectionHint = bigint; | ||
| declare const JsxDetectionHint: { | ||
| readonly None: 0n; | ||
| readonly DoNotIncludeJsxWithNullValue: bigint; | ||
| readonly DoNotIncludeJsxWithNumberValue: bigint; | ||
| readonly DoNotIncludeJsxWithBigIntValue: bigint; | ||
| readonly DoNotIncludeJsxWithStringValue: bigint; | ||
| readonly DoNotIncludeJsxWithBooleanValue: bigint; | ||
| readonly DoNotIncludeJsxWithUndefinedValue: bigint; | ||
| readonly DoNotIncludeJsxWithEmptyArrayValue: bigint; | ||
| readonly DoNotIncludeJsxWithCreateElementValue: bigint; | ||
| readonly RequireAllArrayElementsToBeJsx: bigint; | ||
| readonly RequireBothSidesOfLogicalExpressionToBeJsx: bigint; | ||
| readonly RequireBothBranchesOfConditionalExpressionToBeJsx: bigint; | ||
| }; | ||
| /** | ||
| * Default JSX detection configuration | ||
| * Skips undefined and boolean literals (common in React) | ||
| */ | ||
| declare const DEFAULT_JSX_DETECTION_HINT: bigint; | ||
| /** | ||
| * Determine if a node represents JSX-like content based on heuristics | ||
| * Supports configuration through hint flags to customize detection behavior | ||
| * | ||
| * @param context The rule context with scope lookup capability | ||
| * @param node The AST node to analyze | ||
| * @param hint The configuration flags to adjust detection behavior | ||
| * @returns boolean Whether the node is considered JSX-like | ||
| */ | ||
| declare function isJsxLike(context: RuleContext, node: TSESTree$1.Node | null, hint?: JsxDetectionHint): boolean; | ||
| //#endregion | ||
| //#region src/jsx/jsx-inspector.d.ts | ||
| /** | ||
| * A stateful helper that binds an ESLint `RuleContext` once and exposes | ||
| * ergonomic methods for the most common JSX inspection tasks that rules need. | ||
| * | ||
| * ### Typical usage inside a rule's `create` function | ||
| * | ||
| * ```ts | ||
| * export function create(context: RuleContext) { | ||
| * const jsx = JsxInspector.from(context); | ||
| * | ||
| * return defineRuleListener({ | ||
| * JSXElement(node) { | ||
| * // element type | ||
| * const type = jsx.getElementType(node); // "div" | "React.Fragment" | … | ||
| * | ||
| * // attribute lookup + value resolution in one step | ||
| * const val = jsx.getAttributeValue(node, "sandbox"); | ||
| * if (typeof val?.getStatic() === "string") { … } | ||
| * | ||
| * // simple boolean checks | ||
| * if (jsx.isHostElement(node)) { … } | ||
| * if (jsx.isFragmentElement(node)) { … } | ||
| * if (jsx.hasAttribute(node, "key")) { … } | ||
| * }, | ||
| * }); | ||
| * } | ||
| * ``` | ||
| */ | ||
| declare class JsxInspector { | ||
| #private; | ||
| readonly context: RuleContext; | ||
| /** | ||
| * Merged JSX configuration (tsconfig compiler options + pragma annotations). | ||
| * The result is lazily computed and cached for the lifetime of this inspector. | ||
| */ | ||
| get jsxConfig(): Required<JsxConfig>; | ||
| private constructor(); | ||
| /** | ||
| * Walk **up** the AST from `node` to find the nearest ancestor that is a | ||
| * `JSXAttribute` and passes the optional `test` predicate. | ||
| * @param node The starting node for the search. | ||
| * @param test A predicate function to test each ancestor node. | ||
| */ | ||
| static findParentAttribute(node: TSESTree.Node, test?: (node: TSESTree.JSXAttribute) => boolean): TSESTree.JSXAttribute | null; | ||
| /** | ||
| * Create a new `JsxInspector` bound to the given rule context. | ||
| * @param context The ESLint rule context to bind to this inspector instance. | ||
| */ | ||
| static from(context: RuleContext): JsxInspector; | ||
| /** | ||
| * Whether the node is a `JSXText` or a `Literal` node. | ||
| * @param node The node to check. | ||
| */ | ||
| static isJsxText(node: TSESTree.Node | null): node is TSESTree.JSXText | TSESTree.Literal; | ||
| /** | ||
| * Find a JSX attribute (or spread attribute containing the property) by name | ||
| * on a given element. | ||
| * | ||
| * Returns the **last** matching attribute (to mirror React's behaviour where | ||
| * later props win), or `undefined` if not found. | ||
| * @param node The JSX element to search for the attribute. | ||
| * @param name The name of the attribute to find (ex: `"className"`). | ||
| */ | ||
| findAttribute(node: TSESTree.JSXElement, name: string): ast.TSESTreeJSXAttributeLike | undefined; | ||
| /** | ||
| * Get the stringified name of a `JSXAttribute` node | ||
| * (ex: `"className"`, `"aria-label"`, `"xml:space"`). | ||
| * @param node The `JSXAttribute` node to extract the name from. | ||
| * @returns The stringified name of the attribute. | ||
| */ | ||
| getAttributeName(node: TSESTree.JSXAttribute): string; | ||
| /** | ||
| * Resolve the static value of an attribute, automatically handling the | ||
| * `spreadProps` case by extracting the named property. | ||
| * | ||
| * This eliminates the repetitive pattern: | ||
| * ```ts | ||
| * const v = core.resolveJsxAttributeValue(ctx, attr); | ||
| * const s = v.kind === "spreadProps" ? v.getProperty(name) : v.toStatic(); | ||
| * ``` | ||
| * | ||
| * Returns `undefined` when the attribute is not present or its value | ||
| * cannot be statically determined. | ||
| * @param node The JSX element to search for the attribute. | ||
| * @param name The name of the attribute to resolve (ex: `"className"`). | ||
| * @returns The static value of the attribute, or `undefined` if not found or not statically resolvable. | ||
| */ | ||
| getAttributeStaticValue(node: TSESTree.JSXElement, name: string): unknown; | ||
| /** | ||
| * **All-in-one helper** – find an attribute by name on an element *and* | ||
| * resolve its value in a single call. | ||
| * | ||
| * Returns `undefined` when the attribute is not present. | ||
| * @param node The JSX element to search for the attribute. | ||
| * @param name The name of the attribute to find and resolve (ex: `"className"`). | ||
| * @returns A descriptor of the attribute's value that can be further inspected, or `undefined` if the attribute is not found. | ||
| */ | ||
| getAttributeValue(node: TSESTree.JSXElement, name: string): JsxAttributeValue | undefined; | ||
| /** | ||
| * Get the **self name** (last segment) of a JSX element type. | ||
| * | ||
| * - `<Foo.Bar.Baz>` → `"Baz"` | ||
| * - `<div>` → `"div"` | ||
| * - `<></>` → `""` | ||
| * @param node The JSX element or fragment to extract the self name from. | ||
| */ | ||
| getElementSelfName(node: TSESTree.JSXElement | TSESTree.JSXFragment): string; | ||
| /** | ||
| * Get the string representation of a JSX element's type. | ||
| * | ||
| * - `<div>` → `"div"` | ||
| * - `<Foo.Bar>` → `"Foo.Bar"` | ||
| * - `<React.Fragment>` → `"React.Fragment"` | ||
| * - `<></>` (JSXFragment) → `""` | ||
| * @param node The JSX element or fragment to extract the type from. | ||
| */ | ||
| getElementType(node: TSESTree.JSXElement | TSESTree.JSXFragment): string; | ||
| /** | ||
| * Shorthand: check whether an attribute exists on the element. | ||
| * @param node The JSX element to check for the attribute. | ||
| * @param name The name of the attribute to check for (ex: `"className"`). | ||
| * @returns `true` if the attribute exists on the element, `false` otherwise. | ||
| */ | ||
| hasAttribute(node: TSESTree.JSXElement, name: string): boolean; | ||
| /** | ||
| * Whether the node is a React **Fragment** element (either `<Fragment>` / | ||
| * `<React.Fragment>` or the shorthand `<>` syntax). | ||
| * | ||
| * The check honours the configured `jsxFragmentFactory`. | ||
| * @param node The node to check. | ||
| */ | ||
| isFragmentElement(node: TSESTree.Node): node is TSESTree.JSXElement | TSESTree.JSXFragment; | ||
| /** | ||
| * Whether the node is a **host** (intrinsic / DOM) element – i.e. its tag | ||
| * name starts with a lowercase letter. | ||
| * @param node The node to check. | ||
| */ | ||
| isHostElement(node: TSESTree.Node): node is TSESTree.JSXElement; | ||
| /** | ||
| * Resolve the *value* of a JSX attribute (or spread attribute) into a | ||
| * descriptor that can be inspected further. | ||
| * | ||
| * See {@link JsxAttributeValue} for the full set of `kind` discriminants. | ||
| * @param attribute The attribute node to resolve the value of. | ||
| * @returns A descriptor of the attribute's value that can be further inspected. | ||
| */ | ||
| resolveAttributeValue(attribute: ast.TSESTreeJSXAttributeLike): { | ||
| readonly kind: "boolean"; | ||
| readonly toStatic: () => true; | ||
| readonly node?: never; | ||
| readonly getChildren?: never; | ||
| } | { | ||
| readonly kind: "literal"; | ||
| readonly node: TSESTree.BigIntLiteral | TSESTree.BooleanLiteral | TSESTree.NullLiteral | TSESTree.NumberLiteral | TSESTree.RegExpLiteral | TSESTree.StringLiteral; | ||
| readonly toStatic: () => string | number | bigint | boolean | RegExp | null; | ||
| readonly getChildren?: never; | ||
| } | { | ||
| readonly kind: "missing"; | ||
| readonly node: TSESTree.JSXEmptyExpression; | ||
| readonly toStatic: () => null; | ||
| readonly getChildren?: never; | ||
| } | { | ||
| readonly kind: "expression"; | ||
| readonly node: TSESTree.Expression; | ||
| readonly toStatic: () => unknown; | ||
| readonly getChildren?: never; | ||
| } | { | ||
| readonly kind: "element"; | ||
| readonly node: TSESTree.JSXElement; | ||
| readonly toStatic: () => null; | ||
| readonly getChildren?: never; | ||
| } | { | ||
| readonly kind: "spreadChild"; | ||
| readonly node: TSESTree.JSXEmptyExpression | TSESTree.Expression; | ||
| readonly toStatic: () => null; | ||
| readonly getChildren: (_at: number) => null; | ||
| } | { | ||
| readonly kind: "spreadProps"; | ||
| readonly node: TSESTree.Expression; | ||
| readonly toStatic: () => null; | ||
| readonly getProperty: (name: string) => unknown; | ||
| }; | ||
| } | ||
| //#endregion | ||
| //#region src/ref/ref-id.d.ts | ||
| declare function isRefId(node: TSESTree.Expression | TSESTree.PrivateIdentifier): boolean; | ||
| //#endregion | ||
| //#region src/ref/ref-init.d.ts | ||
| /** | ||
| * Check if the variable with the given name is initialized or derived from a ref | ||
| * @param name The variable name | ||
| * @param initialScope The initial scope | ||
| * @returns True if the variable is derived from a ref, false otherwise | ||
| */ | ||
| declare function isInitializedFromRef(name: string, initialScope: Scope): boolean; | ||
| /** | ||
| * Get the init expression of a ref variable | ||
| * @param name The variable name | ||
| * @param initialScope The initial scope | ||
| * @returns The init expression node if the variable is derived from a ref, or null otherwise | ||
| */ | ||
| declare function getRefInit(name: string, initialScope: Scope): TSESTree$1.Expression | null; | ||
| //#endregion | ||
| //#region src/ref/ref-name.d.ts | ||
| /** | ||
| * Check if a given name corresponds to a ref name | ||
| * @param name The name to check | ||
| * @returns True if the name is "ref" or ends with "Ref" | ||
| */ | ||
| declare function isRefLikeName(name: string): boolean; | ||
| //#endregion | ||
| 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 }; | ||
| 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 }; |
+43
-544
| import * as ast from "@eslint-react/ast"; | ||
| import { AST_NODE_TYPES } from "@typescript-eslint/types"; | ||
| import { findVariable, getStaticValue } from "@typescript-eslint/utils/ast-utils"; | ||
| import { findVariable } from "@typescript-eslint/utils/ast-utils"; | ||
| import { P, match } from "ts-pattern"; | ||
| import { IdGenerator, RE_ANNOTATION_JSX, RE_ANNOTATION_JSX_FRAG, RE_ANNOTATION_JSX_IMPORT_SOURCE, RE_ANNOTATION_JSX_RUNTIME, RE_COMPONENT_NAME, RE_COMPONENT_NAME_LOOSE } from "@eslint-react/shared"; | ||
| import { resolve } from "@eslint-react/var"; | ||
| import { AST_NODE_TYPES as AST_NODE_TYPES$1 } from "@typescript-eslint/utils"; | ||
| import { JsxDetectionHint, isJsxLike } from "@eslint-react/jsx"; | ||
| import { IdGenerator, RE_COMPONENT_NAME, RE_COMPONENT_NAME_LOOSE } from "@eslint-react/shared"; | ||
@@ -160,8 +159,2 @@ //#region ../../.pkgs/eff/dist/index.js | ||
| const compose = dual(2, (ab, bc) => (a) => bc(ab(a))); | ||
| function getOrElseUpdate(map, key, callback) { | ||
| if (map.has(key)) return map.get(key); | ||
| const value = callback(); | ||
| map.set(key, value); | ||
| return value; | ||
| } | ||
@@ -268,3 +261,3 @@ //#endregion | ||
| if (node.type !== AST_NODE_TYPES.CallExpression) return false; | ||
| return isReactAPI(api)(context, node.callee); | ||
| return isReactAPI(api)(context, ast.getUnderlyingExpression(node.callee)); | ||
| }; | ||
@@ -443,5 +436,5 @@ return dual(2, func); | ||
| if (node == null) return false; | ||
| const returnStatement = ast.findParentNode(node, ast.is(AST_NODE_TYPES.ReturnStatement)); | ||
| const enclosingFunction = ast.findParentNode(node, ast.isFunction); | ||
| if (enclosingFunction !== ast.findParentNode(returnStatement, ast.isFunction)) return false; | ||
| const returnStatement = ast.findParent(node, ast.is(AST_NODE_TYPES.ReturnStatement)); | ||
| const enclosingFunction = ast.findParent(node, ast.isFunction); | ||
| if (enclosingFunction !== ast.findParent(returnStatement, ast.isFunction)) return false; | ||
| return isUseEffectSetupCallback(enclosingFunction); | ||
@@ -469,7 +462,7 @@ } | ||
| /** | ||
| * Get a ctx and visitor object for the rule to collect hooks | ||
| * Get a api and visitor object for the rule to collect hooks | ||
| * @param context The ESLint rule context | ||
| * @returns The ctx and visitor of the collector | ||
| * @returns The api and visitor of the collector | ||
| */ | ||
| function useHookCollector(context) { | ||
| function getHookCollector(context) { | ||
| const hooks = /* @__PURE__ */ new Map(); | ||
@@ -490,3 +483,3 @@ const functionEntries = []; | ||
| key, | ||
| kind: "function", | ||
| kind: "hook", | ||
| name: ast.getFullyQualifiedName(id, getText), | ||
@@ -504,9 +497,5 @@ directives: [], | ||
| return { | ||
| ctx: { | ||
| getAllHooks(node) { | ||
| return [...hooks.values()]; | ||
| }, | ||
| getCurrentEntries: () => functionEntries, | ||
| getCurrentEntry | ||
| }, | ||
| api: { getAllHooks(node) { | ||
| return [...hooks.values()]; | ||
| } }, | ||
| visitor: { | ||
@@ -526,439 +515,2 @@ ":function": onFunctionEnter, | ||
| //#endregion | ||
| //#region src/jsx/jsx-config.ts | ||
| const JsxEmit = { | ||
| None: 0, | ||
| Preserve: 1, | ||
| React: 2, | ||
| ReactNative: 3, | ||
| ReactJSX: 4, | ||
| ReactJSXDev: 5 | ||
| }; | ||
| /** | ||
| * Get JsxConfig from the rule context by reading compiler options | ||
| * @param context The RuleContext | ||
| * @returns JsxConfig derived from compiler options | ||
| */ | ||
| function getJsxConfigFromContext(context) { | ||
| const options = context.sourceCode.parserServices?.program?.getCompilerOptions() ?? {}; | ||
| return { | ||
| jsx: options.jsx ?? JsxEmit.ReactJSX, | ||
| jsxFactory: options.jsxFactory ?? "React.createElement", | ||
| jsxFragmentFactory: options.jsxFragmentFactory ?? "React.Fragment", | ||
| jsxImportSource: options.jsxImportSource ?? "react" | ||
| }; | ||
| } | ||
| const cache = /* @__PURE__ */ new WeakMap(); | ||
| /** | ||
| * Get JsxConfig from pragma comments (annotations) in the source code | ||
| * @param context The RuleContext | ||
| * @returns JsxConfig derived from pragma comments | ||
| */ | ||
| function getJsxConfigFromAnnotation(context) { | ||
| return getOrElseUpdate(cache, context.sourceCode, () => { | ||
| const options = {}; | ||
| if (!context.sourceCode.text.includes("@jsx")) return options; | ||
| let jsx, jsxFrag, jsxRuntime, jsxImportSource; | ||
| for (const comment of context.sourceCode.getAllComments().reverse()) { | ||
| const value = comment.value; | ||
| jsx ??= value.match(RE_ANNOTATION_JSX)?.[1]; | ||
| jsxFrag ??= value.match(RE_ANNOTATION_JSX_FRAG)?.[1]; | ||
| jsxRuntime ??= value.match(RE_ANNOTATION_JSX_RUNTIME)?.[1]; | ||
| jsxImportSource ??= value.match(RE_ANNOTATION_JSX_IMPORT_SOURCE)?.[1]; | ||
| } | ||
| if (jsx != null) options.jsxFactory = jsx; | ||
| if (jsxFrag != null) options.jsxFragmentFactory = jsxFrag; | ||
| if (jsxRuntime != null) options.jsx = jsxRuntime === "classic" ? JsxEmit.React : JsxEmit.ReactJSX; | ||
| if (jsxImportSource != null) options.jsxImportSource = jsxImportSource; | ||
| return options; | ||
| }); | ||
| } | ||
| //#endregion | ||
| //#region src/jsx/jsx-detection.ts | ||
| const JsxDetectionHint = { | ||
| None: 0n, | ||
| DoNotIncludeJsxWithNullValue: 1n << 0n, | ||
| DoNotIncludeJsxWithNumberValue: 1n << 1n, | ||
| DoNotIncludeJsxWithBigIntValue: 1n << 2n, | ||
| DoNotIncludeJsxWithStringValue: 1n << 3n, | ||
| DoNotIncludeJsxWithBooleanValue: 1n << 4n, | ||
| DoNotIncludeJsxWithUndefinedValue: 1n << 5n, | ||
| DoNotIncludeJsxWithEmptyArrayValue: 1n << 6n, | ||
| DoNotIncludeJsxWithCreateElementValue: 1n << 7n, | ||
| RequireAllArrayElementsToBeJsx: 1n << 8n, | ||
| RequireBothSidesOfLogicalExpressionToBeJsx: 1n << 9n, | ||
| RequireBothBranchesOfConditionalExpressionToBeJsx: 1n << 10n | ||
| }; | ||
| /** | ||
| * Default JSX detection configuration | ||
| * Skips undefined and boolean literals (common in React) | ||
| */ | ||
| const DEFAULT_JSX_DETECTION_HINT = 0n | JsxDetectionHint.DoNotIncludeJsxWithNumberValue | JsxDetectionHint.DoNotIncludeJsxWithBigIntValue | JsxDetectionHint.DoNotIncludeJsxWithBooleanValue | JsxDetectionHint.DoNotIncludeJsxWithStringValue | JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue; | ||
| /** | ||
| * Determine if a node represents JSX-like content based on heuristics | ||
| * Supports configuration through hint flags to customize detection behavior | ||
| * | ||
| * @param context The rule context with scope lookup capability | ||
| * @param node The AST node to analyze | ||
| * @param hint The configuration flags to adjust detection behavior | ||
| * @returns boolean Whether the node is considered JSX-like | ||
| */ | ||
| function isJsxLike(context, node, hint = DEFAULT_JSX_DETECTION_HINT) { | ||
| if (node == null) return false; | ||
| if (ast.isJSX(node)) return true; | ||
| switch (node.type) { | ||
| case AST_NODE_TYPES.Literal: | ||
| switch (typeof node.value) { | ||
| case "boolean": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithBooleanValue); | ||
| case "string": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue); | ||
| case "number": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithNumberValue); | ||
| case "bigint": return !(hint & JsxDetectionHint.DoNotIncludeJsxWithBigIntValue); | ||
| } | ||
| if (node.value == null) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithNullValue); | ||
| return false; | ||
| case AST_NODE_TYPES.TemplateLiteral: return !(hint & JsxDetectionHint.DoNotIncludeJsxWithStringValue); | ||
| case AST_NODE_TYPES.ArrayExpression: | ||
| if (node.elements.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue); | ||
| if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.elements.every((n) => isJsxLike(context, n, hint)); | ||
| return node.elements.some((n) => isJsxLike(context, n, hint)); | ||
| case AST_NODE_TYPES.LogicalExpression: | ||
| if (hint & JsxDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx) return isJsxLike(context, node.left, hint) && isJsxLike(context, node.right, hint); | ||
| return isJsxLike(context, node.left, hint) || isJsxLike(context, node.right, hint); | ||
| case AST_NODE_TYPES.ConditionalExpression: { | ||
| function leftHasJSX(node) { | ||
| if (Array.isArray(node.consequent)) { | ||
| if (node.consequent.length === 0) return !(hint & JsxDetectionHint.DoNotIncludeJsxWithEmptyArrayValue); | ||
| if (hint & JsxDetectionHint.RequireAllArrayElementsToBeJsx) return node.consequent.every((n) => isJsxLike(context, n, hint)); | ||
| return node.consequent.some((n) => isJsxLike(context, n, hint)); | ||
| } | ||
| return isJsxLike(context, node.consequent, hint); | ||
| } | ||
| function rightHasJSX(node) { | ||
| return isJsxLike(context, node.alternate, hint); | ||
| } | ||
| if (hint & JsxDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx) return leftHasJSX(node) && rightHasJSX(node); | ||
| return leftHasJSX(node) || rightHasJSX(node); | ||
| } | ||
| case AST_NODE_TYPES.SequenceExpression: return isJsxLike(context, node.expressions.at(-1) ?? null, hint); | ||
| case AST_NODE_TYPES.CallExpression: | ||
| if (hint & JsxDetectionHint.DoNotIncludeJsxWithCreateElementValue) return false; | ||
| switch (node.callee.type) { | ||
| case AST_NODE_TYPES.Identifier: return node.callee.name === "createElement"; | ||
| case AST_NODE_TYPES.MemberExpression: return node.callee.property.type === AST_NODE_TYPES.Identifier && node.callee.property.name === "createElement"; | ||
| } | ||
| return false; | ||
| case AST_NODE_TYPES.Identifier: | ||
| if (node.name === "undefined") return !(hint & JsxDetectionHint.DoNotIncludeJsxWithUndefinedValue); | ||
| if (ast.isJSXTagNameExpression(node)) return true; | ||
| return isJsxLike(context, resolve(context, node), hint); | ||
| } | ||
| return false; | ||
| } | ||
| //#endregion | ||
| //#region src/jsx/jsx-stringify.ts | ||
| /** | ||
| * Incomplete but sufficient stringification of JSX nodes for common use cases | ||
| * | ||
| * @param node JSX node from TypeScript ESTree | ||
| * @returns String representation of the JSX node | ||
| */ | ||
| function stringifyJsx(node) { | ||
| switch (node.type) { | ||
| case AST_NODE_TYPES.JSXIdentifier: return node.name; | ||
| case AST_NODE_TYPES.JSXNamespacedName: return `${node.namespace.name}:${node.name.name}`; | ||
| case AST_NODE_TYPES.JSXMemberExpression: return `${stringifyJsx(node.object)}.${stringifyJsx(node.property)}`; | ||
| case AST_NODE_TYPES.JSXText: return node.value; | ||
| case AST_NODE_TYPES.JSXOpeningElement: return `<${stringifyJsx(node.name)}>`; | ||
| case AST_NODE_TYPES.JSXClosingElement: return `</${stringifyJsx(node.name)}>`; | ||
| case AST_NODE_TYPES.JSXOpeningFragment: return "<>"; | ||
| case AST_NODE_TYPES.JSXClosingFragment: return "</>"; | ||
| } | ||
| } | ||
| //#endregion | ||
| //#region src/jsx/jsx-inspector.ts | ||
| /** | ||
| * A stateful helper that binds an ESLint `RuleContext` once and exposes | ||
| * ergonomic methods for the most common JSX inspection tasks that rules need. | ||
| * | ||
| * ### Typical usage inside a rule's `create` function | ||
| * | ||
| * ```ts | ||
| * export function create(context: RuleContext) { | ||
| * const jsx = JsxInspector.from(context); | ||
| * | ||
| * return defineRuleListener({ | ||
| * JSXElement(node) { | ||
| * // element type | ||
| * const type = jsx.getElementType(node); // "div" | "React.Fragment" | … | ||
| * | ||
| * // attribute lookup + value resolution in one step | ||
| * const val = jsx.getAttributeValue(node, "sandbox"); | ||
| * if (typeof val?.getStatic() === "string") { … } | ||
| * | ||
| * // simple boolean checks | ||
| * if (jsx.isHostElement(node)) { … } | ||
| * if (jsx.isFragmentElement(node)) { … } | ||
| * if (jsx.hasAttribute(node, "key")) { … } | ||
| * }, | ||
| * }); | ||
| * } | ||
| * ``` | ||
| */ | ||
| var JsxInspector = class JsxInspector { | ||
| context; | ||
| /** | ||
| * Merged JSX configuration (tsconfig compiler options + pragma annotations). | ||
| * The result is lazily computed and cached for the lifetime of this inspector. | ||
| */ | ||
| get jsxConfig() { | ||
| return this.#jsxConfig ??= { | ||
| ...getJsxConfigFromContext(this.context), | ||
| ...getJsxConfigFromAnnotation(this.context) | ||
| }; | ||
| } | ||
| /** | ||
| * Lazily resolved & cached JSX configuration (merged from tsconfig + | ||
| * pragma annotations). Use {@link jsxConfig} to access. | ||
| */ | ||
| #jsxConfig; | ||
| constructor(context) { | ||
| this.context = context; | ||
| } | ||
| /** | ||
| * Walk **up** the AST from `node` to find the nearest ancestor that is a | ||
| * `JSXAttribute` and passes the optional `test` predicate. | ||
| * @param node The starting node for the search. | ||
| * @param test A predicate function to test each ancestor node. | ||
| */ | ||
| static findParentAttribute(node, test = () => true) { | ||
| const guard = (n) => { | ||
| return n.type === AST_NODE_TYPES.JSXAttribute && test(n); | ||
| }; | ||
| return ast.findParentNode(node, guard); | ||
| } | ||
| /** | ||
| * Create a new `JsxInspector` bound to the given rule context. | ||
| * @param context The ESLint rule context to bind to this inspector instance. | ||
| */ | ||
| static from(context) { | ||
| return new JsxInspector(context); | ||
| } | ||
| /** | ||
| * Whether the node is a `JSXText` or a `Literal` node. | ||
| * @param node The node to check. | ||
| */ | ||
| static isJsxText(node) { | ||
| if (node == null) return false; | ||
| return node.type === AST_NODE_TYPES.JSXText || node.type === AST_NODE_TYPES.Literal; | ||
| } | ||
| /** | ||
| * Find a JSX attribute (or spread attribute containing the property) by name | ||
| * on a given element. | ||
| * | ||
| * Returns the **last** matching attribute (to mirror React's behaviour where | ||
| * later props win), or `undefined` if not found. | ||
| * @param node The JSX element to search for the attribute. | ||
| * @param name The name of the attribute to find (ex: `"className"`). | ||
| */ | ||
| findAttribute(node, name) { | ||
| return node.openingElement.attributes.findLast((attr) => { | ||
| if (attr.type === AST_NODE_TYPES.JSXAttribute) return stringifyJsx(attr.name) === name; | ||
| switch (attr.argument.type) { | ||
| case AST_NODE_TYPES.Identifier: { | ||
| const initNode = resolve(this.context, attr.argument); | ||
| if (initNode?.type === AST_NODE_TYPES.ObjectExpression) return ast.findProperty(initNode.properties, name) != null; | ||
| return false; | ||
| } | ||
| case AST_NODE_TYPES.ObjectExpression: return ast.findProperty(attr.argument.properties, name) != null; | ||
| } | ||
| return false; | ||
| }); | ||
| } | ||
| /** | ||
| * Get the stringified name of a `JSXAttribute` node | ||
| * (ex: `"className"`, `"aria-label"`, `"xml:space"`). | ||
| * @param node The `JSXAttribute` node to extract the name from. | ||
| * @returns The stringified name of the attribute. | ||
| */ | ||
| getAttributeName(node) { | ||
| return stringifyJsx(node.name); | ||
| } | ||
| /** | ||
| * Resolve the static value of an attribute, automatically handling the | ||
| * `spreadProps` case by extracting the named property. | ||
| * | ||
| * This eliminates the repetitive pattern: | ||
| * ```ts | ||
| * const v = core.resolveJsxAttributeValue(ctx, attr); | ||
| * const s = v.kind === "spreadProps" ? v.getProperty(name) : v.toStatic(); | ||
| * ``` | ||
| * | ||
| * Returns `undefined` when the attribute is not present or its value | ||
| * cannot be statically determined. | ||
| * @param node The JSX element to search for the attribute. | ||
| * @param name The name of the attribute to resolve (ex: `"className"`). | ||
| * @returns The static value of the attribute, or `undefined` if not found or not statically resolvable. | ||
| */ | ||
| getAttributeStaticValue(node, name) { | ||
| const attr = this.findAttribute(node, name); | ||
| if (attr == null) return void 0; | ||
| const resolved = this.resolveAttributeValue(attr); | ||
| if (resolved.kind === "spreadProps") return resolved.getProperty(name); | ||
| return resolved.toStatic(); | ||
| } | ||
| /** | ||
| * **All-in-one helper** – find an attribute by name on an element *and* | ||
| * resolve its value in a single call. | ||
| * | ||
| * Returns `undefined` when the attribute is not present. | ||
| * @param node The JSX element to search for the attribute. | ||
| * @param name The name of the attribute to find and resolve (ex: `"className"`). | ||
| * @returns A descriptor of the attribute's value that can be further inspected, or `undefined` if the attribute is not found. | ||
| */ | ||
| getAttributeValue(node, name) { | ||
| const attr = this.findAttribute(node, name); | ||
| if (attr == null) return void 0; | ||
| return this.resolveAttributeValue(attr); | ||
| } | ||
| /** | ||
| * Get the **self name** (last segment) of a JSX element type. | ||
| * | ||
| * - `<Foo.Bar.Baz>` → `"Baz"` | ||
| * - `<div>` → `"div"` | ||
| * - `<></>` → `""` | ||
| * @param node The JSX element or fragment to extract the self name from. | ||
| */ | ||
| getElementSelfName(node) { | ||
| return this.getElementType(node).split(".").at(-1) ?? ""; | ||
| } | ||
| /** | ||
| * Get the string representation of a JSX element's type. | ||
| * | ||
| * - `<div>` → `"div"` | ||
| * - `<Foo.Bar>` → `"Foo.Bar"` | ||
| * - `<React.Fragment>` → `"React.Fragment"` | ||
| * - `<></>` (JSXFragment) → `""` | ||
| * @param node The JSX element or fragment to extract the type from. | ||
| */ | ||
| getElementType(node) { | ||
| if (node.type === AST_NODE_TYPES.JSXFragment) return ""; | ||
| return stringifyJsx(node.openingElement.name); | ||
| } | ||
| /** | ||
| * Shorthand: check whether an attribute exists on the element. | ||
| * @param node The JSX element to check for the attribute. | ||
| * @param name The name of the attribute to check for (ex: `"className"`). | ||
| * @returns `true` if the attribute exists on the element, `false` otherwise. | ||
| */ | ||
| hasAttribute(node, name) { | ||
| return this.findAttribute(node, name) != null; | ||
| } | ||
| /** | ||
| * Whether the node is a React **Fragment** element (either `<Fragment>` / | ||
| * `<React.Fragment>` or the shorthand `<>` syntax). | ||
| * | ||
| * The check honours the configured `jsxFragmentFactory`. | ||
| * @param node The node to check. | ||
| */ | ||
| isFragmentElement(node) { | ||
| if (node.type === AST_NODE_TYPES.JSXFragment) return true; | ||
| if (node.type !== AST_NODE_TYPES.JSXElement) return false; | ||
| const fragment = this.jsxConfig.jsxFragmentFactory.split(".").at(-1) ?? "Fragment"; | ||
| return this.getElementType(node).split(".").at(-1) === fragment; | ||
| } | ||
| /** | ||
| * Whether the node is a **host** (intrinsic / DOM) element – i.e. its tag | ||
| * name starts with a lowercase letter. | ||
| * @param node The node to check. | ||
| */ | ||
| isHostElement(node) { | ||
| return node.type === AST_NODE_TYPES.JSXElement && node.openingElement.name.type === AST_NODE_TYPES.JSXIdentifier && /^[a-z]/u.test(node.openingElement.name.name); | ||
| } | ||
| /** | ||
| * Resolve the *value* of a JSX attribute (or spread attribute) into a | ||
| * descriptor that can be inspected further. | ||
| * | ||
| * See {@link JsxAttributeValue} for the full set of `kind` discriminants. | ||
| * @param attribute The attribute node to resolve the value of. | ||
| * @returns A descriptor of the attribute's value that can be further inspected. | ||
| */ | ||
| resolveAttributeValue(attribute) { | ||
| if (attribute.type === AST_NODE_TYPES.JSXAttribute) return this.#resolveJsxAttribute(attribute); | ||
| return this.#resolveJsxSpreadAttribute(attribute); | ||
| } | ||
| #resolveJsxAttribute(node) { | ||
| const scope = this.context.sourceCode.getScope(node); | ||
| if (node.value == null) return { | ||
| kind: "boolean", | ||
| toStatic() { | ||
| return true; | ||
| } | ||
| }; | ||
| switch (node.value.type) { | ||
| case AST_NODE_TYPES.Literal: { | ||
| const staticValue = node.value.value; | ||
| return { | ||
| kind: "literal", | ||
| node: node.value, | ||
| toStatic() { | ||
| return staticValue; | ||
| } | ||
| }; | ||
| } | ||
| case AST_NODE_TYPES.JSXExpressionContainer: { | ||
| const expr = node.value.expression; | ||
| if (expr.type === AST_NODE_TYPES.JSXEmptyExpression) return { | ||
| kind: "missing", | ||
| node: expr, | ||
| toStatic() { | ||
| return null; | ||
| } | ||
| }; | ||
| return { | ||
| kind: "expression", | ||
| node: expr, | ||
| toStatic() { | ||
| return getStaticValue(expr, scope)?.value; | ||
| } | ||
| }; | ||
| } | ||
| case AST_NODE_TYPES.JSXElement: return { | ||
| kind: "element", | ||
| node: node.value, | ||
| toStatic() { | ||
| return null; | ||
| } | ||
| }; | ||
| case AST_NODE_TYPES.JSXSpreadChild: | ||
| node.value.expression; | ||
| return { | ||
| kind: "spreadChild", | ||
| node: node.value.expression, | ||
| toStatic() { | ||
| return null; | ||
| }, | ||
| getChildren(_at) { | ||
| return null; | ||
| } | ||
| }; | ||
| } | ||
| } | ||
| #resolveJsxSpreadAttribute(node) { | ||
| const scope = this.context.sourceCode.getScope(node); | ||
| return { | ||
| kind: "spreadProps", | ||
| node: node.argument, | ||
| toStatic() { | ||
| return null; | ||
| }, | ||
| getProperty(name) { | ||
| return match(getStaticValue(node.argument, scope)?.value).with({ [name]: P.select(P.any) }, identity).otherwise(() => null); | ||
| } | ||
| }; | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/component/component-detection-legacy.ts | ||
@@ -1194,10 +746,10 @@ /** | ||
| ...JsxDetectionHint, | ||
| DoNotIncludeFunctionDefinedAsClassMethod: 1n << 11n, | ||
| DoNotIncludeFunctionDefinedAsClassProperty: 1n << 12n, | ||
| DoNotIncludeFunctionDefinedAsObjectMethod: 1n << 13n, | ||
| DoNotIncludeFunctionDefinedAsArrayExpressionElement: 1n << 14n, | ||
| DoNotIncludeFunctionDefinedAsArrayPatternElement: 1n << 15n, | ||
| DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback: 1n << 18n, | ||
| DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 17n, | ||
| DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 16n, | ||
| DoNotIncludeFunctionDefinedInArrayExpression: 1n << 15n, | ||
| DoNotIncludeFunctionDefinedInArrayPattern: 1n << 14n, | ||
| DoNotIncludeFunctionDefinedOnClassMethod: 1n << 12n, | ||
| DoNotIncludeFunctionDefinedOnClassProperty: 1n << 13n, | ||
| DoNotIncludeFunctionDefinedOnObjectMethod: 1n << 11n | ||
| DoNotIncludeFunctionDefinedAsArrayFlatMapCallback: 1n << 16n, | ||
| DoNotIncludeFunctionDefinedAsArrayMapCallback: 1n << 17n | ||
| }; | ||
@@ -1207,3 +759,3 @@ /** | ||
| */ | ||
| const DEFAULT_COMPONENT_DETECTION_HINT = 0n | ComponentDetectionHint.DoNotIncludeJsxWithBigIntValue | ComponentDetectionHint.DoNotIncludeJsxWithBooleanValue | ComponentDetectionHint.DoNotIncludeJsxWithNumberValue | ComponentDetectionHint.DoNotIncludeJsxWithStringValue | ComponentDetectionHint.DoNotIncludeJsxWithUndefinedValue | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArbitraryCallExpressionCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayFlatMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayMapCallback | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression | ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern | ComponentDetectionHint.RequireAllArrayElementsToBeJsx | ComponentDetectionHint.RequireBothBranchesOfConditionalExpressionToBeJsx | ComponentDetectionHint.RequireBothSidesOfLogicalExpressionToBeJsx; | ||
| 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; | ||
| /** | ||
@@ -1227,15 +779,15 @@ * Determine if a function node represents a valid React component definition | ||
| 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: | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnObjectMethod) return false; | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsObjectMethod) return false; | ||
| break; | ||
| case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.MethodDefinition: | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassMethod) return false; | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassMethod) return false; | ||
| break; | ||
| case ast.isOneOf([AST_NODE_TYPES.ArrowFunctionExpression, AST_NODE_TYPES.FunctionExpression])(node) && parent.type === AST_NODE_TYPES.Property: | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedOnClassProperty) return false; | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsClassProperty) return false; | ||
| break; | ||
| case parent.type === AST_NODE_TYPES.ArrayPattern: | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayPattern) return false; | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayPatternElement) return false; | ||
| break; | ||
| case parent.type === AST_NODE_TYPES.ArrayExpression: | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedInArrayExpression) return false; | ||
| if (hint & ComponentDetectionHint.DoNotIncludeFunctionDefinedAsArrayExpressionElement) return false; | ||
| break; | ||
@@ -1252,3 +804,3 @@ 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": | ||
| } | ||
| const significantParent = ast.findParentNode(node, ast.isOneOf([ | ||
| const significantParent = ast.findParent(node, ast.isOneOf([ | ||
| AST_NODE_TYPES.JSXExpressionContainer, | ||
@@ -1291,10 +843,10 @@ AST_NODE_TYPES.ArrowFunctionExpression, | ||
| //#region src/component/component-collector.ts | ||
| const idGen$1 = new IdGenerator("function-component:"); | ||
| const idGen$1 = new IdGenerator("component:"); | ||
| /** | ||
| * Get a ctx and visitor object for the rule to collect function components | ||
| * Get a api and visitor object for the rule to collect function components | ||
| * @param context The ESLint rule context | ||
| * @param options The options to use | ||
| * @returns The ctx and visitor of the collector | ||
| * @returns The api and visitor of the collector | ||
| */ | ||
| function useComponentCollector(context, options = {}) { | ||
| function getComponentCollector(context, options = {}) { | ||
| const { collectDisplayName = false, hint = DEFAULT_COMPONENT_DETECTION_HINT } = options; | ||
@@ -1307,3 +859,3 @@ const functionEntries = []; | ||
| const key = idGen$1.next(); | ||
| const exp = ast.findParentNode(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration); | ||
| const exp = ast.findParent(node, (n) => n.type === AST_NODE_TYPES.ExportDefaultDeclaration); | ||
| const isExportDefault = exp != null; | ||
@@ -1318,3 +870,3 @@ const isExportDefaultDeclaration = exp != null && ast.getUnderlyingExpression(exp.declaration) === node; | ||
| key, | ||
| kind: "function-component", | ||
| kind: "component", | ||
| name, | ||
@@ -1341,11 +893,5 @@ directives, | ||
| return { | ||
| ctx: { | ||
| getAllComponents(node) { | ||
| return [...components.values()]; | ||
| }, | ||
| getCurrentEntries() { | ||
| return [...functionEntries]; | ||
| }, | ||
| getCurrentEntry | ||
| }, | ||
| api: { getAllComponents(node) { | ||
| return [...components.values()]; | ||
| } }, | ||
| visitor: { | ||
@@ -1397,9 +943,9 @@ ":function": onFunctionEnter, | ||
| /** | ||
| * Get a ctx and visitor object for the rule to collect class componentss | ||
| * Get a api and visitor object for the rule to collect class componentss | ||
| * @param context The ESLint rule context | ||
| * @returns The ctx and visitor of the collector | ||
| * @returns The api and visitor of the collector | ||
| */ | ||
| function useComponentCollectorLegacy(context) { | ||
| function getComponentCollectorLegacy(context) { | ||
| const components = /* @__PURE__ */ new Map(); | ||
| const ctx = { getAllComponents(node) { | ||
| const api = { getAllComponents(node) { | ||
| return [...components.values()]; | ||
@@ -1427,3 +973,3 @@ } }; | ||
| return { | ||
| ctx, | ||
| api, | ||
| visitor: { | ||
@@ -1437,49 +983,2 @@ ClassDeclaration: collect, | ||
| //#endregion | ||
| //#region src/ref/ref-name.ts | ||
| /** | ||
| * Check if a given name corresponds to a ref name | ||
| * @param name The name to check | ||
| * @returns True if the name is "ref" or ends with "Ref" | ||
| */ | ||
| function isRefLikeName(name) { | ||
| return name === "ref" || name.endsWith("Ref"); | ||
| } | ||
| //#endregion | ||
| //#region src/ref/ref-id.ts | ||
| function isRefId(node) { | ||
| return node.type === AST_NODE_TYPES.Identifier && isRefLikeName(node.name); | ||
| } | ||
| //#endregion | ||
| //#region src/ref/ref-init.ts | ||
| /** | ||
| * Check if the variable with the given name is initialized or derived from a ref | ||
| * @param name The variable name | ||
| * @param initialScope The initial scope | ||
| * @returns True if the variable is derived from a ref, false otherwise | ||
| */ | ||
| function isInitializedFromRef(name, initialScope) { | ||
| return getRefInit(name, initialScope) != null; | ||
| } | ||
| /** | ||
| * Get the init expression of a ref variable | ||
| * @param name The variable name | ||
| * @param initialScope The initial scope | ||
| * @returns The init expression node if the variable is derived from a ref, or null otherwise | ||
| */ | ||
| function getRefInit(name, initialScope) { | ||
| for (const { node } of findVariable(initialScope, name)?.defs ?? []) { | ||
| if (node.type !== AST_NODE_TYPES$1.VariableDeclarator) continue; | ||
| const init = node.init; | ||
| if (init == null) continue; | ||
| switch (true) { | ||
| case init.type === AST_NODE_TYPES$1.MemberExpression && init.object.type === AST_NODE_TYPES$1.Identifier && isRefLikeName(init.object.name): return init; | ||
| case init.type === AST_NODE_TYPES$1.CallExpression && isUseRefCall(init): return init; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
| //#endregion | ||
| 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 }; | ||
| 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 }; |
+11
-10
| { | ||
| "name": "@eslint-react/core", | ||
| "version": "3.0.0", | ||
| "version": "4.0.0-beta.0", | ||
| "description": "ESLint React's ESLint utility module for static analysis of React core APIs and patterns.", | ||
@@ -33,14 +33,15 @@ "homepage": "https://github.com/Rel1cx/eslint-react", | ||
| "dependencies": { | ||
| "@typescript-eslint/scope-manager": "^8.57.0", | ||
| "@typescript-eslint/types": "^8.57.0", | ||
| "@typescript-eslint/utils": "^8.57.0", | ||
| "@typescript-eslint/scope-manager": "^8.57.2", | ||
| "@typescript-eslint/types": "^8.57.2", | ||
| "@typescript-eslint/utils": "^8.57.2", | ||
| "ts-pattern": "^5.9.0", | ||
| "@eslint-react/ast": "3.0.0", | ||
| "@eslint-react/shared": "3.0.0", | ||
| "@eslint-react/var": "3.0.0" | ||
| "@eslint-react/jsx": "4.0.0-beta.0", | ||
| "@eslint-react/ast": "4.0.0-beta.0", | ||
| "@eslint-react/shared": "4.0.0-beta.0", | ||
| "@eslint-react/var": "4.0.0-beta.0" | ||
| }, | ||
| "devDependencies": { | ||
| "tsdown": "^0.21.2", | ||
| "@local/configs": "0.0.0", | ||
| "@local/eff": "3.0.0-beta.72" | ||
| "tsdown": "^0.21.4", | ||
| "@local/eff": "3.0.0-beta.72", | ||
| "@local/configs": "0.0.0" | ||
| }, | ||
@@ -47,0 +48,0 @@ "peerDependencies": { |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
3
-62.5%69951
-30.68%10
11.11%1561
-34.49%1
Infinity%+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed