Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,4 @@ Find the enclosing React component or hook for a given AST node
\| `FunctionExpression`
\| `undefined`

The enclosing component or hook node, or `null` if none is found
The enclosing component or hook node, or `null` if none is ASAST.
8 changes: 4 additions & 4 deletions packages/core/src/api/is-react-api.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/* eslint-disable function/function-return-boolean */
import * as AST from "@eslint-react/ast";
import * as ast from "@eslint-react/ast";
import { dual, type unit } from "@eslint-react/eff";
import type { RuleContext } from "@eslint-react/shared";
import { AST_NODE_TYPES as T, type TSESTree } from "@typescript-eslint/types";
import { AST_NODE_TYPES as AST, type TSESTree } from "@typescript-eslint/types";

export declare namespace isReactAPI {
type ReturnType = {
Expand All @@ -25,7 +25,7 @@ export function isReactAPI(api: string): isReactAPI.ReturnType {
{
if (node == null) return false;
const getText = (n: TSESTree.Node) => context.sourceCode.getText(n);
const name = AST.toStringFormat(node, getText);
const name = ast.toStringFormat(node, getText);
if (name === api) return true;
if (name.substring(name.indexOf(".") + 1) === api) return true;
return false;
Expand All @@ -48,7 +48,7 @@ export declare namespace isReactAPICall {
export function isReactAPICall(api: string): isReactAPICall.ReturnType {
const func = (context: RuleContext, node: unit | null | TSESTree.Node): node is TSESTree.CallExpression => {
if (node == null) return false;
if (node.type !== T.CallExpression) return false;
if (node.type !== AST.CallExpression) return false;
return isReactAPI(api)(context, node.callee);
};
return dual(2, func);
Expand Down
22 changes: 11 additions & 11 deletions packages/core/src/component/component-collector-legacy.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as AST from "@eslint-react/ast";
import * as ast from "@eslint-react/ast";
import { unit } from "@eslint-react/eff";
import { IdGenerator, type RuleContext } from "@eslint-react/shared";
import type { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
import { AST_NODE_TYPES as T } from "@typescript-eslint/utils";
import { AST_NODE_TYPES as AST } from "@typescript-eslint/utils";

import { ComponentFlag } from "./component-flag";
import { isClassComponent, isPureComponent } from "./component-is";
Expand Down Expand Up @@ -35,13 +35,13 @@ export function useComponentCollectorLegacy(context: RuleContext): useComponentC
} as const;

const getText = (n: TSESTree.Node) => context.sourceCode.getText(n);
const collect = (node: AST.TSESTreeClass) => {
const collect = (node: ast.TSESTreeClass) => {
if (!isClassComponent(node)) {
return;
}
const id = AST.getClassId(node);
const id = ast.getClassId(node);
const key = idGen.next();
const name = id == null ? unit : AST.toStringFormat(id, getText);
const name = id == null ? unit : ast.toStringFormat(id, getText);
const flag = isPureComponent(node)
? ComponentFlag.PureComponent
: ComponentFlag.None;
Expand Down Expand Up @@ -79,9 +79,9 @@ export function useComponentCollectorLegacy(context: RuleContext): useComponentC
export function isThisSetState(node: TSESTree.CallExpression) {
const { callee } = node;
return (
callee.type === T.MemberExpression
&& AST.isThisExpressionLoose(callee.object)
&& callee.property.type === T.Identifier
callee.type === AST.MemberExpression
&& ast.isThisExpressionLoose(callee.object)
&& callee.property.type === AST.Identifier
&& callee.property.name === "setState"
);
}
Expand All @@ -93,7 +93,7 @@ export function isThisSetState(node: TSESTree.CallExpression) {
*/
export function isAssignmentToThisState(node: TSESTree.AssignmentExpression) {
const { left } = node;
return left.type === T.MemberExpression
&& AST.isThisExpressionLoose(left.object)
&& AST.getPropertyName(left.property) === "state";
return left.type === AST.MemberExpression
&& ast.isThisExpressionLoose(left.object)
&& ast.getPropertyName(left.property) === "state";
}
24 changes: 12 additions & 12 deletions packages/core/src/component/component-collector.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as AST from "@eslint-react/ast";
import * as ast from "@eslint-react/ast";
import { unit } from "@eslint-react/eff";
import type { RuleContext } from "@eslint-react/shared";
import { IdGenerator } from "@eslint-react/shared";
import type { TSESTree } from "@typescript-eslint/types";
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
import { AST_NODE_TYPES as AST } from "@typescript-eslint/types";
import type { ESLintUtils } from "@typescript-eslint/utils";
import type { ComponentDetectionHint } from "./component-detection-hint";
import type { FunctionComponentSemanticNode } from "./component-semantic-node";
Expand Down Expand Up @@ -57,15 +57,15 @@ export function useComponentCollector(

const getText = (n: TSESTree.Node) => context.sourceCode.getText(n);
const getCurrentEntry = () => functionEntries.at(-1);
const onFunctionEnter = (node: AST.TSESTreeFunction) => {
const onFunctionEnter = (node: ast.TSESTreeFunction) => {
const key = idGen.next();
const exp = AST.findParentNode(node, (n) => n.type === T.ExportDefaultDeclaration);
const exp = ast.findParentNode(node, (n) => n.type === AST.ExportDefaultDeclaration);
const isExportDefault = exp != null;
const isExportDefaultDeclaration = exp != null && AST.getUnderlyingExpression(exp.declaration) === node;
const isExportDefaultDeclaration = exp != null && ast.getUnderlyingExpression(exp.declaration) === node;
const id = getFunctionComponentId(context, node);
const name = id == null ? unit : AST.toStringFormat(id, getText);
const initPath = AST.getFunctionInitPath(node);
const directives = AST.getFunctionDirectives(node);
const name = id == null ? unit : ast.toStringFormat(id, getText);
const initPath = ast.getFunctionInitPath(node);
const directives = ast.getFunctionDirectives(node);
const entry = {
id: getFunctionComponentId(context, node),
key,
Expand Down Expand Up @@ -111,18 +111,18 @@ export function useComponentCollector(
const entry = getCurrentEntry();
if (entry == null) return;
const { body } = entry.node;
if (body.type === T.BlockStatement) return;
if (body.type === AST.BlockStatement) return;
entry.rets.push(body);
if (!entry.isComponentDefinition) return;
if (!components.has(entry.key) && !isJsxLike(context.sourceCode, body, hint)) return;
components.set(entry.key, entry);
},
...collectDisplayName
? {
[AST.SEL_DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node: TSESTree.AssignmentExpression) {
[ast.SEL_DISPLAY_NAME_ASSIGNMENT_EXPRESSION](node: TSESTree.AssignmentExpression) {
const { left, right } = node;
if (left.type !== T.MemberExpression) return;
const componentName = left.object.type === T.Identifier
if (left.type !== AST.MemberExpression) return;
const componentName = left.object.type === AST.Identifier
? left.object.name
: unit;
const component = [...components.values()].findLast(({ name }) => name != null && name === componentName);
Expand Down
52 changes: 26 additions & 26 deletions packages/core/src/component/component-definition.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as AST from "@eslint-react/ast";
import * as ast from "@eslint-react/ast";
import type { RuleContext } from "@eslint-react/shared";
import { AST_NODE_TYPES as T, type TSESTree } from "@typescript-eslint/types";
import { AST_NODE_TYPES as AST, type TSESTree } from "@typescript-eslint/types";

import { isCreateElementCall } from "../api";
import { ComponentDetectionHint } from "./component-detection-hint";
Expand All @@ -21,7 +21,7 @@ import { isRenderMethodLike } from "./component-render-method";
* }
* ```
*/
function isRenderMethodCallback(node: AST.TSESTreeFunction) {
function isRenderMethodCallback(node: ast.TSESTreeFunction) {
const parent = node.parent;
const grandparent = parent.parent;
const greatGrandparent = grandparent?.parent;
Expand All @@ -40,31 +40,31 @@ function isRenderMethodCallback(node: AST.TSESTreeFunction) {
* @param hint Component detection hints as bit flags
* @returns `true` if the function matches an exclusion hint
*/
function shouldExcludeBasedOnHint(node: AST.TSESTreeFunction, hint: bigint): boolean {
function shouldExcludeBasedOnHint(node: ast.TSESTreeFunction, hint: bigint): boolean {
switch (true) {
case (hint & ComponentDetectionHint.SkipObjectMethod)
&& AST.isOneOf([T.ArrowFunctionExpression, T.FunctionExpression])(node)
&& node.parent.type === T.Property
&& node.parent.parent.type === T.ObjectExpression:
&& ast.isOneOf([AST.ArrowFunctionExpression, AST.FunctionExpression])(node)
&& node.parent.type === AST.Property
&& node.parent.parent.type === AST.ObjectExpression:
return true;
case (hint & ComponentDetectionHint.SkipClassMethod)
&& AST.isOneOf([T.ArrowFunctionExpression, T.FunctionExpression])(node)
&& node.parent.type === T.MethodDefinition:
&& ast.isOneOf([AST.ArrowFunctionExpression, AST.FunctionExpression])(node)
&& node.parent.type === AST.MethodDefinition:
return true;
case (hint & ComponentDetectionHint.SkipClassProperty)
&& AST.isOneOf([T.ArrowFunctionExpression, T.FunctionExpression])(node)
&& node.parent.type === T.Property:
&& ast.isOneOf([AST.ArrowFunctionExpression, AST.FunctionExpression])(node)
&& node.parent.type === AST.Property:
return true;
case (hint & ComponentDetectionHint.SkipArrayPattern)
&& node.parent.type === T.ArrayPattern:
&& node.parent.type === AST.ArrayPattern:
return true;
case (hint & ComponentDetectionHint.SkipArrayExpression)
&& node.parent.type === T.ArrayExpression:
&& node.parent.type === AST.ArrayExpression:
return true;
case (hint & ComponentDetectionHint.SkipArrayMapCallback)
&& node.parent.type === T.CallExpression
&& node.parent.callee.type === T.MemberExpression
&& node.parent.callee.property.type === T.Identifier
&& node.parent.type === AST.CallExpression
&& node.parent.callee.type === AST.MemberExpression
&& node.parent.callee.property.type === AST.Identifier
&& node.parent.callee.property.name === "map":
return true;
}
Expand All @@ -81,7 +81,7 @@ function shouldExcludeBasedOnHint(node: AST.TSESTreeFunction, hint: bigint): boo
function isChildrenOfCreateElement(context: RuleContext, node: TSESTree.Node): boolean {
const parent = node.parent;

if (parent?.type !== T.CallExpression) {
if (parent?.type !== AST.CallExpression) {
return false;
}

Expand All @@ -105,7 +105,7 @@ function isChildrenOfCreateElement(context: RuleContext, node: TSESTree.Node): b
*/
export function isComponentDefinition(
context: RuleContext,
node: AST.TSESTreeFunction,
node: ast.TSESTreeFunction,
hint: bigint,
) {
// 1. Check for basic naming conventions
Expand All @@ -125,19 +125,19 @@ export function isComponentDefinition(

// 4. Check if the function is embedded directly inside JSX (e.g., inline callbacks)
// We look for the closest parent that is significant (Function, Class, or JSXContainer)
const significantParent = AST.findParentNode(
const significantParent = ast.findParentNode(
node,
AST.isOneOf([
T.JSXExpressionContainer,
T.ArrowFunctionExpression,
T.FunctionExpression,
T.Property,
T.ClassBody,
ast.isOneOf([
AST.JSXExpressionContainer,
AST.ArrowFunctionExpression,
AST.FunctionExpression,
AST.Property,
AST.ClassBody,
]),
);

if (significantParent == null) return true;
// If the immediate significant parent is a JSX expression, this is likely an event handler or a render prop, not a component definition itself
if (significantParent.type === T.JSXExpressionContainer) return false;
if (significantParent.type === AST.JSXExpressionContainer) return false;
return true;
}
20 changes: 10 additions & 10 deletions packages/core/src/component/component-id.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as AST from "@eslint-react/ast";
import * as ast from "@eslint-react/ast";
import { unit } from "@eslint-react/eff";
import type { RuleContext } from "@eslint-react/shared";
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
import { AST_NODE_TYPES as AST } from "@typescript-eslint/types";

import { isComponentWrapperCallLoose } from "./component-wrapper";

Expand All @@ -13,28 +13,28 @@ import { isComponentWrapperCallLoose } from "./component-wrapper";
*/
export function getFunctionComponentId(
context: RuleContext,
node: AST.TSESTreeFunction,
): AST.FunctionID | unit {
const functionId = AST.getFunctionId(node);
node: ast.TSESTreeFunction,
): ast.FunctionID | unit {
const functionId = ast.getFunctionId(node);
if (functionId != null) {
return functionId;
}
const { parent } = node;
// Get function component identifier from `const Component = memo(() => {});`
if (
parent.type === T.CallExpression
parent.type === AST.CallExpression
&& isComponentWrapperCallLoose(context, parent)
&& parent.parent.type === T.VariableDeclarator
&& parent.parent.type === AST.VariableDeclarator
) {
return parent.parent.id;
}
// Get function component identifier from `const Component = memo(forwardRef(() => {}));`
if (
parent.type === T.CallExpression
parent.type === AST.CallExpression
&& isComponentWrapperCallLoose(context, parent)
&& parent.parent.type === T.CallExpression
&& parent.parent.type === AST.CallExpression
&& isComponentWrapperCallLoose(context, parent.parent)
&& parent.parent.parent.type === T.VariableDeclarator
&& parent.parent.parent.type === AST.VariableDeclarator
) {
return parent.parent.parent.id;
}
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/component/component-init-path.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as AST from "@eslint-react/ast";
import * as ast from "@eslint-react/ast";

import { ComponentFlag } from "./component-flag";
import type { FunctionComponentSemanticNode } from "./component-semantic-node";
Expand All @@ -10,10 +10,10 @@ import type { FunctionComponentSemanticNode } from "./component-semantic-node";
*/
export function getComponentFlagFromInitPath(initPath: FunctionComponentSemanticNode["initPath"]) {
let flag = ComponentFlag.None;
if (initPath != null && AST.hasCallInFunctionInitPath("memo", initPath)) {
if (initPath != null && ast.hasCallInFunctionInitPath("memo", initPath)) {
flag |= ComponentFlag.Memo;
}
if (initPath != null && AST.hasCallInFunctionInitPath("forwardRef", initPath)) {
if (initPath != null && ast.hasCallInFunctionInitPath("forwardRef", initPath)) {
flag |= ComponentFlag.ForwardRef;
}
return flag;
Expand Down
18 changes: 9 additions & 9 deletions packages/core/src/component/component-is.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import type * as AST from "@eslint-react/ast";
import type * as ast from "@eslint-react/ast";
import type { TSESTree } from "@typescript-eslint/types";
import { AST_NODE_TYPES as T } from "@typescript-eslint/types";
import { AST_NODE_TYPES as AST } from "@typescript-eslint/types";

/**
* Check if a node is a React class component
* @param node The AST node to check
* @returns `true` if the node is a class component, `false` otherwise
*/
export function isClassComponent(node: TSESTree.Node): node is AST.TSESTreeClass {
export function isClassComponent(node: TSESTree.Node): node is ast.TSESTreeClass {
if ("superClass" in node && node.superClass != null) {
const re = /^(?:Pure)?Component$/u;
switch (true) {
case node.superClass.type === T.Identifier:
case node.superClass.type === AST.Identifier:
return re.test(node.superClass.name);
case node.superClass.type === T.MemberExpression
&& node.superClass.property.type === T.Identifier:
case node.superClass.type === AST.MemberExpression
&& node.superClass.property.type === AST.Identifier:
return re.test(node.superClass.property.name);
}
}
Expand All @@ -30,10 +30,10 @@ export function isPureComponent(node: TSESTree.Node) {
if ("superClass" in node && node.superClass != null) {
const re = /^PureComponent$/u;
switch (true) {
case node.superClass.type === T.Identifier:
case node.superClass.type === AST.Identifier:
return re.test(node.superClass.name);
case node.superClass.type === T.MemberExpression
&& node.superClass.property.type === T.Identifier:
case node.superClass.type === AST.MemberExpression
&& node.superClass.property.type === AST.Identifier:
return re.test(node.superClass.property.name);
}
}
Expand Down
6 changes: 3 additions & 3 deletions packages/core/src/component/component-method-callback.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import * as AST from "@eslint-react/ast";
import * as ast from "@eslint-react/ast";
import type { TSESTree } from "@typescript-eslint/types";

import { isComponentDidMount, isComponentWillUnmount } from "./component-method-is";
Expand All @@ -9,7 +9,7 @@ import { isComponentDidMount, isComponentWillUnmount } from "./component-method-
* @returns True if the node is a componentDidMount callback, false otherwise
*/
export function isComponentDidMountCallback(node: TSESTree.Node) {
return AST.isFunction(node)
return ast.isFunction(node)
&& isComponentDidMount(node.parent)
&& node.parent.value === node;
}
Expand All @@ -20,7 +20,7 @@ export function isComponentDidMountCallback(node: TSESTree.Node) {
* @returns True if the node is a componentWillUnmount callback, false otherwise
*/
export function isComponentWillUnmountCallback(node: TSESTree.Node) {
return AST.isFunction(node)
return ast.isFunction(node)
&& isComponentWillUnmount(node.parent)
&& node.parent.value === node;
}
Loading
Loading