From 415293b21306cfe84e71685333eded897db0d56a Mon Sep 17 00:00:00 2001 From: Rel1cx Date: Tue, 2 Dec 2025 17:37:58 +0800 Subject: [PATCH 1/2] refactor: update component and hook collectors to return arrays instead of maps - Changed `getAllComponents` in `useComponentCollector` and `useComponentCollectorLegacy` to return arrays of components instead of maps. - Updated documentation to reflect the new return types. - Adjusted all usages of `getAllComponents` and `getAllHooks` across the codebase to accommodate the new array structure. - Cleaned up related rules in the ESLint plugins to ensure compatibility with the updated collectors. --- .../type-aliases/ReturnType.md | 6 ++-- .../type-aliases/ReturnType.md | 6 ++-- .../type-aliases/ReturnType.md | 6 ++-- .../component/component-collector-legacy.ts | 6 ++-- .../core/src/component/component-collector.ts | 31 +++++-------------- packages/core/src/hook/hook-collector.ts | 6 ++-- .../src/rules/class-component.ts | 3 +- .../src/rules/function-component.ts | 3 +- .../src/rules/hook.ts | 4 +-- .../src/rules/component-name.ts | 14 ++++----- .../src/rules/no-class-component.ts | 3 +- .../src/rules/no-component-will-mount.ts | 3 +- .../rules/no-component-will-receive-props.ts | 3 +- .../src/rules/no-component-will-update.ts | 3 +- .../no-missing-component-display-name.spec.ts | 24 ++++++++++---- .../no-missing-component-display-name.ts | 26 ++++++---------- .../rules/no-nested-component-definitions.ts | 30 ++++++------------ .../no-nested-lazy-component-declarations.ts | 18 ++++------- .../no-redundant-should-component-update.ts | 4 +-- .../src/rules/no-unnecessary-use-prefix.ts | 4 +-- .../rules/no-unsafe-component-will-mount.ts | 4 +-- .../no-unsafe-component-will-receive-props.ts | 4 +-- .../rules/no-unsafe-component-will-update.ts | 4 +-- .../src/rules/no-unstable-context-value.ts | 3 +- .../src/rules/no-unstable-default-props.ts | 3 +- .../src/rules/no-unused-props.ts | 4 +-- .../rules/prefer-destructuring-assignment.ts | 4 +-- .../src/rules/prefer-read-only-props.ts | 3 +- packages/plugins/eslint-plugin/README.md | 4 +-- 29 files changed, 94 insertions(+), 142 deletions(-) diff --git a/packages/core/docs/@eslint-react/namespaces/useComponentCollector/type-aliases/ReturnType.md b/packages/core/docs/@eslint-react/namespaces/useComponentCollector/type-aliases/ReturnType.md index 5e6bdf32a7..f13de90062 100644 --- a/packages/core/docs/@eslint-react/namespaces/useComponentCollector/type-aliases/ReturnType.md +++ b/packages/core/docs/@eslint-react/namespaces/useComponentCollector/type-aliases/ReturnType.md @@ -5,7 +5,7 @@ ```ts type ReturnType = { ctx: { - getAllComponents: (node: TSESTree.Program) => Map; + getAllComponents: (node: TSESTree.Program) => FunctionComponent[]; getCurrentEntries: () => FunctionEntry[]; getCurrentEntry: () => FunctionEntry | unit; }; @@ -17,8 +17,8 @@ type ReturnType = { | Property | Type | | ------ | ------ | -| `ctx` | \{ `getAllComponents`: (`node`: `TSESTree.Program`) => [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`FunctionComponent`](../../../../interfaces/FunctionComponent.md)\>; `getCurrentEntries`: () => `FunctionEntry`[]; `getCurrentEntry`: () => `FunctionEntry` \| `unit`; \} | -| `ctx.getAllComponents` | (`node`: `TSESTree.Program`) => [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`FunctionComponent`](../../../../interfaces/FunctionComponent.md)\> | +| `ctx` | \{ `getAllComponents`: (`node`: `TSESTree.Program`) => [`FunctionComponent`](../../../../interfaces/FunctionComponent.md)[]; `getCurrentEntries`: () => `FunctionEntry`[]; `getCurrentEntry`: () => `FunctionEntry` \| `unit`; \} | +| `ctx.getAllComponents` | (`node`: `TSESTree.Program`) => [`FunctionComponent`](../../../../interfaces/FunctionComponent.md)[] | | `ctx.getCurrentEntries` | () => `FunctionEntry`[] | | `ctx.getCurrentEntry` | () => `FunctionEntry` \| `unit` | | `listeners` | `ESLintUtils.RuleListener` | diff --git a/packages/core/docs/@eslint-react/namespaces/useComponentCollectorLegacy/type-aliases/ReturnType.md b/packages/core/docs/@eslint-react/namespaces/useComponentCollectorLegacy/type-aliases/ReturnType.md index b7c15b19f3..915c3d1e3e 100644 --- a/packages/core/docs/@eslint-react/namespaces/useComponentCollectorLegacy/type-aliases/ReturnType.md +++ b/packages/core/docs/@eslint-react/namespaces/useComponentCollectorLegacy/type-aliases/ReturnType.md @@ -5,7 +5,7 @@ ```ts type ReturnType = { ctx: { - getAllComponents: (node: TSESTree.Program) => Map; + getAllComponents: (node: TSESTree.Program) => ClassComponent[]; }; listeners: ESLintUtils.RuleListener; }; @@ -15,6 +15,6 @@ type ReturnType = { | Property | Type | | ------ | ------ | -| `ctx` | \{ `getAllComponents`: (`node`: `TSESTree.Program`) => [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`ClassComponent`](../../../../interfaces/ClassComponent.md)\>; \} | -| `ctx.getAllComponents` | (`node`: `TSESTree.Program`) => [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`ClassComponent`](../../../../interfaces/ClassComponent.md)\> | +| `ctx` | \{ `getAllComponents`: (`node`: `TSESTree.Program`) => [`ClassComponent`](../../../../interfaces/ClassComponent.md)[]; \} | +| `ctx.getAllComponents` | (`node`: `TSESTree.Program`) => [`ClassComponent`](../../../../interfaces/ClassComponent.md)[] | | `listeners` | `ESLintUtils.RuleListener` | diff --git a/packages/core/docs/@eslint-react/namespaces/useHookCollector/type-aliases/ReturnType.md b/packages/core/docs/@eslint-react/namespaces/useHookCollector/type-aliases/ReturnType.md index a098eacf9c..2fbcf4322a 100644 --- a/packages/core/docs/@eslint-react/namespaces/useHookCollector/type-aliases/ReturnType.md +++ b/packages/core/docs/@eslint-react/namespaces/useHookCollector/type-aliases/ReturnType.md @@ -5,7 +5,7 @@ ```ts type ReturnType = { ctx: { - getAllHooks: Map; + getAllHooks: Hook[]; }; listeners: ESLintUtils.RuleListener; }; @@ -15,6 +15,6 @@ type ReturnType = { | Property | Type | | ------ | ------ | -| `ctx` | \{ `getAllHooks`: [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`Hook`](../../../../interfaces/Hook.md)\>; \} | -| `ctx.getAllHooks` | [`Map`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map)\<`string`, [`Hook`](../../../../interfaces/Hook.md)\> | +| `ctx` | \{ `getAllHooks`: [`Hook`](../../../../interfaces/Hook.md)[]; \} | +| `ctx.getAllHooks` | [`Hook`](../../../../interfaces/Hook.md)[] | | `listeners` | `ESLintUtils.RuleListener` | diff --git a/packages/core/src/component/component-collector-legacy.ts b/packages/core/src/component/component-collector-legacy.ts index f7aa70a55c..1e137b4910 100644 --- a/packages/core/src/component/component-collector-legacy.ts +++ b/packages/core/src/component/component-collector-legacy.ts @@ -12,7 +12,7 @@ const idGen = new IdGenerator("class_component_"); export declare namespace useComponentCollectorLegacy { type ReturnType = { ctx: { - getAllComponents: (node: TSESTree.Program) => Map; + getAllComponents: (node: TSESTree.Program) => ClassComponent[]; }; listeners: ESLintUtils.RuleListener; }; @@ -27,8 +27,8 @@ export function useComponentCollectorLegacy(): useComponentCollectorLegacy.Retur const ctx = { // eslint-disable-next-line @typescript-eslint/no-unused-vars - getAllComponents(node: TSESTree.Program): typeof components { - return components; + getAllComponents(node: TSESTree.Program) { + return [...components.values()]; }, } as const; diff --git a/packages/core/src/component/component-collector.ts b/packages/core/src/component/component-collector.ts index f72e9abc8f..bca6f9d20e 100644 --- a/packages/core/src/component/component-collector.ts +++ b/packages/core/src/component/component-collector.ts @@ -33,7 +33,7 @@ export declare namespace useComponentCollector { }; type ReturnType = { ctx: { - getAllComponents: (node: TSESTree.Program) => Map; + getAllComponents: (node: TSESTree.Program) => FunctionComponent[]; getCurrentEntries: () => FunctionEntry[]; getCurrentEntry: () => FunctionEntry | unit; }; @@ -57,8 +57,8 @@ export function useComponentCollector( hint = DEFAULT_COMPONENT_DETECTION_HINT, } = options; - const components = new Map(); const functionEntries: FunctionEntry[] = []; + const components = new Map(); const getCurrentEntry = () => functionEntries.at(-1); const onFunctionEnter = (node: AST.TSESTreeFunction) => { @@ -66,28 +66,13 @@ export function useComponentCollector( functionEntries.push({ key, node, hookCalls: [], isComponent: false }); }; const onFunctionExit = () => { - const entry = functionEntries.at(-1); - if (entry == null) return; - if (!entry.isComponent) return functionEntries.pop(); - const rets = AST.getNestedReturnStatements(entry.node.body); - for (let i = rets.length - 1; i >= 0; i--) { - const ret = rets[i]; - if (ret == null) continue; - const shouldDrop = context.sourceCode.getScope(ret).block === entry.node - && ret.argument != null - && !isJsxLike(context.sourceCode, ret.argument, hint); - if (shouldDrop) { - components.delete(entry.key); - break; - } - } return functionEntries.pop(); }; const ctx = { // eslint-disable-next-line @typescript-eslint/no-unused-vars - getAllComponents(node: TSESTree.Program): typeof components { - return components; + getAllComponents(node: TSESTree.Program) { + return [...components.values()]; }, getCurrentEntries() { return [...functionEntries]; @@ -131,8 +116,7 @@ export function useComponentCollector( const componentName = left.object.type === T.Identifier ? left.object.name : unit; - const component = [...components.values()] - .findLast(({ name }) => name != null && name === componentName); + const component = [...components.values()].findLast(({ name }) => name != null && name === componentName); if (component == null) return; component.displayName = right; }, @@ -158,10 +142,11 @@ export function useComponentCollector( entry.isComponent = true; const initPath = AST.getFunctionInitPath(entry.node); const id = getFunctionComponentId(context, entry.node); + const key = entry.key; const name = getComponentNameFromId(id); - components.set(entry.key, { + components.set(key, { id, - key: entry.key, + key, kind: "function", name, node: entry.node, diff --git a/packages/core/src/hook/hook-collector.ts b/packages/core/src/hook/hook-collector.ts index 2e1a0c7890..3dccfa2920 100644 --- a/packages/core/src/hook/hook-collector.ts +++ b/packages/core/src/hook/hook-collector.ts @@ -17,7 +17,7 @@ type FunctionEntry = { export declare namespace useHookCollector { type ReturnType = { ctx: { - getAllHooks(node: TSESTree.Program): Map; + getAllHooks(node: TSESTree.Program): Hook[]; }; listeners: ESLintUtils.RuleListener; }; @@ -51,8 +51,8 @@ export function useHookCollector(): useHookCollector.ReturnType { }; const ctx = { // eslint-disable-next-line @typescript-eslint/no-unused-vars - getAllHooks(node: TSESTree.Program): typeof hooks { - return hooks; + getAllHooks(node: TSESTree.Program) { + return [...hooks.values()]; }, } as const; const listeners = { diff --git a/packages/plugins/eslint-plugin-react-debug/src/rules/class-component.ts b/packages/plugins/eslint-plugin-react-debug/src/rules/class-component.ts index 32939988dd..24f81115b6 100644 --- a/packages/plugins/eslint-plugin-react-debug/src/rules/class-component.ts +++ b/packages/plugins/eslint-plugin-react-debug/src/rules/class-component.ts @@ -35,8 +35,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const { name = "anonymous", node: component } of components.values()) { + for (const { name = "anonymous", node: component } of ctx.getAllComponents(program)) { context.report({ messageId: "classComponent", node: component, diff --git a/packages/plugins/eslint-plugin-react-debug/src/rules/function-component.ts b/packages/plugins/eslint-plugin-react-debug/src/rules/function-component.ts index 31b64ec529..e5c37ec917 100644 --- a/packages/plugins/eslint-plugin-react-debug/src/rules/function-component.ts +++ b/packages/plugins/eslint-plugin-react-debug/src/rules/function-component.ts @@ -43,8 +43,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const { name = "anonymous", node, displayName, flag, hookCalls } of components.values()) { + for (const { name = "anonymous", node, displayName, flag, hookCalls } of ctx.getAllComponents(program)) { context.report({ messageId: "functionComponent", node, diff --git a/packages/plugins/eslint-plugin-react-debug/src/rules/hook.ts b/packages/plugins/eslint-plugin-react-debug/src/rules/hook.ts index 3df00738cc..fd7c1271c4 100644 --- a/packages/plugins/eslint-plugin-react-debug/src/rules/hook.ts +++ b/packages/plugins/eslint-plugin-react-debug/src/rules/hook.ts @@ -36,9 +36,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const allHooks = ctx.getAllHooks(program); - - for (const { name, node, hookCalls } of allHooks.values()) { + for (const { name, node, hookCalls } of ctx.getAllHooks(program)) { context.report({ messageId: "hook", node, diff --git a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts index 74a7ba346f..5921072e74 100644 --- a/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts +++ b/packages/plugins/eslint-plugin-react-naming-convention/src/rules/component-name.ts @@ -82,16 +82,14 @@ export default createRule({ export function create(context: RuleContext): RuleListener { const options = normalizeOptions(context.options); const { rule } = options; - const collector = useComponentCollector(context); - const collectorLegacy = useComponentCollectorLegacy(); + const fCollector = useComponentCollector(context); + const cCollector = useComponentCollectorLegacy(); return { - ...collector.listeners, - ...collectorLegacy.listeners, + ...fCollector.listeners, + ...cCollector.listeners, "Program:exit"(program) { - const functionComponents = collector.ctx.getAllComponents(program); - const classComponents = collectorLegacy.ctx.getAllComponents(program); - for (const { node: component } of functionComponents.values()) { + for (const { node: component } of fCollector.ctx.getAllComponents(program)) { const id = AST.getFunctionId(component); if (id?.name == null) continue; const name = id.name; @@ -102,7 +100,7 @@ export function create(context: RuleContext): RuleListener { data: { name, rule }, }); } - for (const { node: component } of classComponents.values()) { + for (const { node: component } of cCollector.ctx.getAllComponents(program)) { const id = AST.getClassId(component); if (id?.name == null) continue; const name = id.name; diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-class-component.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-class-component.ts index 6f2ac06633..1d331c6cba 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-class-component.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-class-component.ts @@ -35,8 +35,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const { name = "anonymous", node: component } of components.values()) { + for (const { name = "anonymous", node: component } of ctx.getAllComponents(program)) { if (component.body.body.some((m) => isComponentDidCatch(m) || isGetDerivedStateFromError(m))) { continue; } diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-mount.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-mount.ts index 46390a6d42..d33df5d5dc 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-mount.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-mount.ts @@ -39,8 +39,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const { node: component } of components.values()) { + for (const { node: component } of ctx.getAllComponents(program)) { const { body } = component.body; for (const member of body) { if (isComponentWillMount(member)) { diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-receive-props.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-receive-props.ts index d660b5396b..6eff6ce33a 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-receive-props.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-receive-props.ts @@ -38,8 +38,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const { node: component } of components.values()) { + for (const { node: component } of ctx.getAllComponents(program)) { const { body } = component.body; for (const member of body) { if (isComponentWillReceiveProps(member)) { diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-update.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-update.ts index 960873a597..d6d76ad2c7 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-update.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-component-will-update.ts @@ -39,8 +39,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const { node: component } of components.values()) { + for (const { node: component } of ctx.getAllComponents(program)) { const { body } = component.body; for (const member of body) { if (isComponentWillUpdate(member)) { diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.spec.ts index 774f4e849e..2695073ffa 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.spec.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.spec.ts @@ -6,7 +6,9 @@ import rule, { RULE_NAME } from "./no-missing-component-display-name"; ruleTester.run(RULE_NAME, rule, { invalid: [ { - code: tsx`const App = React.memo(() =>
foo
)`, + code: tsx` + const App = React.memo(() =>
foo
) + `, errors: [{ messageId: "noMissingComponentDisplayName", }], @@ -22,31 +24,41 @@ ruleTester.run(RULE_NAME, rule, { }], }, { - code: tsx`const App = React.forwardRef(() =>
foo
)`, + code: tsx` + const App = React.forwardRef(() =>
foo
) + `, errors: [{ messageId: "noMissingComponentDisplayName", }], }, { - code: tsx`const MemoComponent = React.memo(() =>
)`, + code: tsx` + const MemoComponent = React.memo(() =>
) + `, errors: [{ messageId: "noMissingComponentDisplayName", }], }, { - code: tsx`const ForwardRefComponent = React.forwardRef(() =>
)`, + code: tsx` + const ForwardRefComponent = React.forwardRef(() =>
) + `, errors: [{ messageId: "noMissingComponentDisplayName", }], }, { - code: tsx`const MemoForwardRefComponent = React.memo(forwardRef(() =>
))`, + code: tsx` + const MemoForwardRefComponent = React.memo(forwardRef(() =>
)) + `, errors: [{ messageId: "noMissingComponentDisplayName", }], }, { - code: tsx`const MemoForwardRefComponent = React.memo(React.forwardRef(() =>
))`, + code: tsx` + const MemoForwardRefComponent = React.memo(React.forwardRef(() =>
)) + `, errors: [{ messageId: "noMissingComponentDisplayName", }], diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts index 448d8712cc..1b87ce7473 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts @@ -33,27 +33,22 @@ export function create(context: RuleContext): RuleListener { // Fast path: skip if `memo` or `forwardRef` is not present in the file if (!context.sourceCode.text.includes("memo") && !context.sourceCode.text.includes("forwardRef")) return {}; - const { - ctx, - listeners, - } = useComponentCollector( - context, - { - collectDisplayName: true, // We need to collect the displayName of components - collectHookCalls: false, - hint: DEFAULT_COMPONENT_DETECTION_HINT, - }, - ); + const { ctx, listeners } = useComponentCollector(context, { + collectDisplayName: true, + collectHookCalls: false, + hint: DEFAULT_COMPONENT_DETECTION_HINT, + }); return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const { node, displayName, flag } of components.values()) { + for (const { node, displayName, flag } of ctx.getAllComponents(program)) { + console.log(context.sourceCode.getText(node)); + const id = AST.getFunctionId(node); // Check if the component is wrapped with `forwardRef` or `memo` const isMemoOrForwardRef = (flag & (ComponentFlag.ForwardRef | ComponentFlag.Memo)) > 0n; // If the component is a named function, it has an implicit displayName - if (AST.getFunctionId(node) != null) { + if (id != null) { continue; } // This rule only targets components wrapped with `forwardRef` or `memo` @@ -62,10 +57,9 @@ export function create(context: RuleContext): RuleListener { } // If the component has no displayName, report an error if (displayName == null) { - const id = AST.getFunctionId(node); context.report({ messageId: "noMissingComponentDisplayName", - node: id ?? node, + node, }); } } diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.ts index 4b2831593b..33705eaec5 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.ts @@ -54,38 +54,28 @@ export function create(context: RuleContext): RuleListener { | ComponentDetectionHint.StrictConditional; // Collectors to find all component definitions in the code - const collector = useComponentCollector(context, { hint }); - const collectorLegacy = useComponentCollectorLegacy(); + const fCollector = useComponentCollector(context, { hint }); + const cCollector = useComponentCollectorLegacy(); return { - ...collector.listeners, - ...collectorLegacy.listeners, + ...fCollector.listeners, + ...cCollector.listeners, "Program:exit"(program) { // Gather all function and class components found by the collectors - const functionComponents = [ - ...collector - .ctx - .getAllComponents(program) - .values(), - ]; - const classComponents = [ - ...collectorLegacy - .ctx - .getAllComponents(program) - .values(), - ]; + const fComponents = [...fCollector.ctx.getAllComponents(program)]; + const cComponents = [...cCollector.ctx.getAllComponents(program)]; // Helper to check if a node is a collected function component const isFunctionComponent = (node: TSESTree.Node): node is AST.TSESTreeFunction => { return AST.isFunction(node) - && functionComponents.some((component) => component.node === node); + && fComponents.some((component) => component.node === node); }; // Helper to check if a node is a collected class component const isClassComponent = (node: TSESTree.Node): node is AST.TSESTreeClass => { return AST.isClass(node) - && classComponents.some((component) => component.node === node); + && cComponents.some((component) => component.node === node); }; // Iterate over function components to find nested definitions - for (const { name, node: component } of functionComponents) { + for (const { name, node: component } of fComponents) { // Skip anonymous function components to reduce false positives if (name == null) continue; // Skip components that are directly returned from a render prop @@ -148,7 +138,7 @@ export function create(context: RuleContext): RuleListener { } } // Iterate over class components to find nested definitions - for (const { name = "unknown", node: component } of classComponents) { + for (const { name = "unknown", node: component } of cComponents) { // Find if the parent is another component if (AST.findParentNode(component, (n) => isClassComponent(n) || isFunctionComponent(n)) == null) { continue; diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-lazy-component-declarations.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-lazy-component-declarations.ts index fee7c34333..7278d1debc 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-lazy-component-declarations.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-lazy-component-declarations.ts @@ -63,19 +63,13 @@ export function create(context: RuleContext): RuleListener { // After traversing the whole program, check if any lazy component is nested "Program:exit"(program) { // Get all collected function and class components - const functionComponents = [ - ...collector - .ctx - .getAllComponents(program) - .values(), - ]; + const functionComponents = collector + .ctx + .getAllComponents(program); - const classComponents = [ - ...collectorLegacy - .ctx - .getAllComponents(program) - .values(), - ]; + const classComponents = collectorLegacy + .ctx + .getAllComponents(program); // Iterate over each found `React.lazy()` call for (const lazy of lazyComponentDeclarations) { diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-redundant-should-component-update.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-redundant-should-component-update.ts index 03eee9a723..e74a5ed1fd 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-redundant-should-component-update.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-redundant-should-component-update.ts @@ -46,9 +46,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - - for (const { name = "PureComponent", node: component, flag } of components.values()) { + for (const { name = "PureComponent", node: component, flag } of ctx.getAllComponents(program)) { if ((flag & ComponentFlag.PureComponent) === 0n) { continue; } diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-prefix.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-prefix.ts index f79056c55e..70d97390fa 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-prefix.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unnecessary-use-prefix.ts @@ -46,11 +46,11 @@ export default createRule<[], MessageID>({ export function create(context: RuleContext): RuleListener { const { ctx, listeners } = useHookCollector(); + return { ...listeners, "Program:exit"(program) { - const allHooks = ctx.getAllHooks(program); - for (const { id, name, node, hookCalls } of allHooks.values()) { + for (const { id, name, node, hookCalls } of ctx.getAllHooks(program)) { // If the function calls at least one real hook, it's a valid custom hook, so we skip it if (hookCalls.length > 0) { continue; diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-mount.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-mount.ts index a98c2fd79f..f1900a9cab 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-mount.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-mount.ts @@ -36,9 +36,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - - for (const { node: component } of components.values()) { + for (const { node: component } of ctx.getAllComponents(program)) { const { body } = component.body; for (const member of body) { diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-receive-props.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-receive-props.ts index 5e6152d4c4..fb3ba5b692 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-receive-props.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-receive-props.ts @@ -38,9 +38,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - - for (const { node: component } of components.values()) { + for (const { node: component } of ctx.getAllComponents(program)) { const { body } = component.body; for (const member of body) { diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-update.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-update.ts index 0b9f35a380..e500b9f46e 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-update.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unsafe-component-will-update.ts @@ -36,9 +36,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - - for (const { node: component } of components.values()) { + for (const { node: component } of ctx.getAllComponents(program)) { const { body } = component.body; for (const member of body) { diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts index e37f97d68c..0f594b1c14 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-context-value.ts @@ -69,8 +69,7 @@ export function create(context: RuleContext): RuleListener { getOrElseUpdate(constructions, functionEntry.node, () => []).push(construction); }, "Program:exit"(program) { - const components = ctx.getAllComponents(program).values(); - for (const { node: component } of components) { + for (const { node: component } of ctx.getAllComponents(program)) { for (const construction of constructions.get(component) ?? []) { const { kind, node: constructionNode } = construction; const suggestion = kind === "function" diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-default-props.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-default-props.ts index 210ae2b482..3a33430c83 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-default-props.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unstable-default-props.ts @@ -92,8 +92,7 @@ export function create(context: RuleContext, [options]: Opti ).push(node); }, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const { node: component } of components.values()) { + for (const { node: component } of ctx.getAllComponents(program)) { const { params } = component; const [props] = params; if (props == null) { diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-unused-props.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-unused-props.ts index 9e398393dc..f23306b560 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-unused-props.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-unused-props.ts @@ -41,12 +41,10 @@ export function create(context: RuleContext): RuleListener { ...listeners, "Program:exit"(program) { const checker = services.program.getTypeChecker(); - const components = ctx.getAllComponents(program); - const totalDeclaredProps = new Set(); const totalUsedProps = new Set(); - for (const [, component] of components) { + for (const component of ctx.getAllComponents(program)) { const [props] = component.node.params; if (props == null) continue; diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/prefer-destructuring-assignment.ts b/packages/plugins/eslint-plugin-react-x/src/rules/prefer-destructuring-assignment.ts index d191c6cac8..c6d95eec5e 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/prefer-destructuring-assignment.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/prefer-destructuring-assignment.ts @@ -56,7 +56,7 @@ export function create(context: RuleContext): RuleListener { // After traversing the whole AST, check the collected member expressions "Program:exit"(program) { - const components = [...ctx.getAllComponents(program).values()]; + const componentBlocks = new Set(ctx.getAllComponents(program).map((component) => component.node)); // Check if a node is a function component collected by `useComponentCollector` function isFunctionComponent(block: TSESTree.Node): block is AST.TSESTreeFunction { if (!AST.isFunction(block)) { @@ -65,7 +65,7 @@ export function create(context: RuleContext): RuleListener { const id = AST.getFunctionId(block); return id != null && isComponentNameLoose(id.name) - && components.some((component) => component.node === block); + && componentBlocks.has(block); } // For each member expression, find its parent component diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/prefer-read-only-props.ts b/packages/plugins/eslint-plugin-react-x/src/rules/prefer-read-only-props.ts index 0e2f2fcba7..e4542a6fbe 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/prefer-read-only-props.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/prefer-read-only-props.ts @@ -44,8 +44,7 @@ export function create(context: RuleContext): RuleListener { return { ...listeners, "Program:exit"(program) { - const components = ctx.getAllComponents(program); - for (const [, component] of components) { + for (const component of ctx.getAllComponents(program)) { const [props] = component.node.params; // Skip if the component is anonymous to reduce false positives if (component.id == null || component.name == null) continue; diff --git a/packages/plugins/eslint-plugin/README.md b/packages/plugins/eslint-plugin/README.md index c7e628075f..1ad8af9335 100644 --- a/packages/plugins/eslint-plugin/README.md +++ b/packages/plugins/eslint-plugin/README.md @@ -191,8 +191,8 @@ This project is and will continue to maintain that 90% of the code is written by Contributions are welcome! -Please follow our [contributing guidelines](https://github.com/Rel1cx/eslint-react/tree/prefer-destructuring-assignment/.github/CONTRIBUTING.md). +Please follow our [contributing guidelines](https://github.com/Rel1cx/eslint-react/tree/code-optimization-2/.github/CONTRIBUTING.md). ## License -This project is licensed under the MIT License - see the [LICENSE](https://github.com/Rel1cx/eslint-react/tree/prefer-destructuring-assignment/LICENSE) file for details. +This project is licensed under the MIT License - see the [LICENSE](https://github.com/Rel1cx/eslint-react/tree/code-optimization-2/LICENSE) file for details. From fc60e7b607984f1a9db1065c2aa0f08dcd715767 Mon Sep 17 00:00:00 2001 From: REL1CX Date: Tue, 2 Dec 2025 17:42:33 +0800 Subject: [PATCH 2/2] Update packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: REL1CX --- .../src/rules/no-missing-component-display-name.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts index 1b87ce7473..102670140b 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-missing-component-display-name.ts @@ -43,7 +43,6 @@ export function create(context: RuleContext): RuleListener { ...listeners, "Program:exit"(program) { for (const { node, displayName, flag } of ctx.getAllComponents(program)) { - console.log(context.sourceCode.getText(node)); const id = AST.getFunctionId(node); // Check if the component is wrapped with `forwardRef` or `memo` const isMemoOrForwardRef = (flag & (ComponentFlag.ForwardRef | ComponentFlag.Memo)) > 0n;