diff --git a/.pkgs/configs/eslint.js b/.pkgs/configs/eslint.js index e703efd91c..17213e539c 100644 --- a/.pkgs/configs/eslint.js +++ b/.pkgs/configs/eslint.js @@ -112,11 +112,20 @@ export const typescript = tseslint.config({ type: "natural", groups: [ "type", - ["parent-type", "sibling-type", "index-type", "internal-type"], + [ + "parent-type", + "sibling-type", + "index-type", + "internal-type", + ], "builtin", "external", "internal", - ["parent", "sibling", "index"], + [ + "parent", + "sibling", + "index", + ], "side-effect", "object", "unknown", diff --git a/.pkgs/configs/eslint.ts b/.pkgs/configs/eslint.ts index 4685c2b625..273d5d4352 100644 --- a/.pkgs/configs/eslint.ts +++ b/.pkgs/configs/eslint.ts @@ -125,11 +125,20 @@ export const typescript: ConfigArray = tseslint.config( type: "natural", groups: [ "type", - ["parent-type", "sibling-type", "index-type", "internal-type"], + [ + "parent-type", + "sibling-type", + "index-type", + "internal-type", + ], "builtin", "external", "internal", - ["parent", "sibling", "index"], + [ + "parent", + "sibling", + "index", + ], "side-effect", "object", "unknown", diff --git a/packages/core/src/component/component-collector.ts b/packages/core/src/component/component-collector.ts index b883ae8316..32aff9dc0d 100644 --- a/packages/core/src/component/component-collector.ts +++ b/packages/core/src/component/component-collector.ts @@ -7,9 +7,9 @@ import type { ERFunctionComponent } from "./component-semantic-node"; import * as AST from "@eslint-react/ast"; import { _ } from "@eslint-react/eff"; import * as JSX from "@eslint-react/jsx"; - import { getId } from "@eslint-react/shared"; import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; + import { DISPLAY_NAME_ASSIGNMENT_SELECTOR } from "../constants"; import { isReactHookCall } from "../hook"; import { DEFAULT_COMPONENT_HINT } from "./component-collector-hint"; diff --git a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.spec.ts b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.spec.ts index 89891b8104..3d743e01d1 100644 --- a/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.spec.ts +++ b/packages/plugins/eslint-plugin-react-x/src/rules/no-nested-component-definitions.spec.ts @@ -19,7 +19,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -35,7 +43,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -51,7 +67,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedVariableComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedVariableComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -67,7 +91,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedVariableComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedVariableComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -83,7 +115,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -99,7 +139,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -115,7 +163,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -131,7 +187,15 @@ ruleTester.run(RULE_NAME, rule, { ); }; `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -147,7 +211,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedVariableComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedVariableComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -163,7 +235,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedVariableComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedVariableComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -181,7 +261,13 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedClassComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedClassComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -199,7 +285,13 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedClassComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedClassComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -219,7 +311,13 @@ ruleTester.run(RULE_NAME, rule, { } } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedClassComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedClassComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -239,7 +337,13 @@ ruleTester.run(RULE_NAME, rule, { } } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedClassComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedClassComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -257,7 +361,15 @@ ruleTester.run(RULE_NAME, rule, { } } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -275,7 +387,13 @@ ruleTester.run(RULE_NAME, rule, { } } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedClassComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedClassComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -293,7 +411,15 @@ ruleTester.run(RULE_NAME, rule, { } } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedVariableComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedVariableComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -311,7 +437,15 @@ ruleTester.run(RULE_NAME, rule, { } } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedClassComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedClassComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -331,7 +465,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "NestedUnstableFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "NestedUnstableFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -347,7 +489,15 @@ ruleTester.run(RULE_NAME, rule, { return React.createElement("div", null, getComponent()); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "NestedUnstableFunctionComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "NestedUnstableFunctionComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -363,7 +513,15 @@ ruleTester.run(RULE_NAME, rule, { }); } `, - errors: [{ messageId: "noNestedComponentDefinitionInProps", data: { name: "SomeFooter" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "SomeFooter", + suggestion: "Move it to the top level or pass it as a prop.", + }, + }, + ], }, { code: tsx` @@ -389,7 +547,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedComponent" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedComponent", + suggestion: "Move it to the top level.", + }, + }, + ], }, { code: tsx` @@ -399,7 +565,15 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinitionInProps", data: { name: "Header" } }], + errors: [ + { + messageId: "noNestedComponentDefinitions", + data: { + name: "Header", + suggestion: "Move it to the top level or pass it as a prop.", + }, + }, + ], }, { code: tsx` @@ -413,7 +587,13 @@ ruleTester.run(RULE_NAME, rule, { } } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "List" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "List", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -434,7 +614,13 @@ ruleTester.run(RULE_NAME, rule, { } } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "List" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "List", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -450,7 +636,13 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -466,7 +658,13 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -484,7 +682,13 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -502,7 +706,13 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "UnstableNestedComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "UnstableNestedComponent", + suggestion: "Move it to the top level.", + }, + }], }, { code: tsx` @@ -516,7 +726,10 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "MemoizedNestedComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { name: "MemoizedNestedComponent", suggestion: "Move it to the top level." }, + }], }, { code: tsx` @@ -533,7 +746,10 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "MemoizedNestedComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { name: "MemoizedNestedComponent", suggestion: "Move it to the top level." }, + }], }, { code: tsx` @@ -552,7 +768,10 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "MemoizedNestedFunctionComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { name: "MemoizedNestedFunctionComponent", suggestion: "Move it to the top level." }, + }], }, { code: tsx` @@ -571,7 +790,10 @@ ruleTester.run(RULE_NAME, rule, { ); } `, - errors: [{ messageId: "noNestedComponentDefinition", data: { name: "MemoizedNestedFunctionComponent" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { name: "MemoizedNestedFunctionComponent", suggestion: "Move it to the top level." }, + }], }, { code: tsx` @@ -581,7 +803,13 @@ ruleTester.run(RULE_NAME, rule, { ) } `, - errors: [{ messageId: "noNestedComponentDefinitionInProps", data: { name: "Header" } }], + errors: [{ + messageId: "noNestedComponentDefinitions", + data: { + name: "Header", + suggestion: "Move it to the top level or pass it as a prop.", + }, + }], }, ], valid: [ 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 13db80a7f3..ee3c1db578 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 @@ -1,6 +1,7 @@ import type { RuleContext, RuleFeature } from "@eslint-react/kit"; import type { TSESTree } from "@typescript-eslint/types"; import type { RuleListener } from "@typescript-eslint/utils/ts-eslint"; +import type { CamelCase } from "string-ts"; import * as AST from "@eslint-react/ast"; import { ERComponentHint, @@ -12,17 +13,15 @@ import { useComponentCollectorLegacy, } from "@eslint-react/core"; import * as JSX from "@eslint-react/jsx"; -import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; +import { AST_NODE_TYPES as T } from "@typescript-eslint/types"; import { createRule } from "../utils"; export const RULE_NAME = "no-nested-component-definitions"; export const RULE_FEATURES = [] as const satisfies RuleFeature[]; -export type MessageID = - | "noNestedComponentDefinition" - | "noNestedComponentDefinitionInProps"; +export type MessageID = CamelCase; export default createRule<[], MessageID>({ meta: { @@ -32,10 +31,8 @@ export default createRule<[], MessageID>({ [Symbol.for("rule_features")]: RULE_FEATURES, }, messages: { - noNestedComponentDefinition: - "Do not nest component definitions inside other components. Move it to the top level.", - noNestedComponentDefinitionInProps: - "Do not nest component definitions inside props. Move it to the top level or pass it as a prop.", + noNestedComponentDefinitions: + "Do not nest component definitions inside other components or props. {{suggestion}}", }, schema: [], }, @@ -89,10 +86,11 @@ export function create(context: RuleContext): RuleListener { if (isInsideJSXAttributeValue(component)) { if (!isDeclaredInRenderPropLoose(component)) { context.report({ - messageId: "noNestedComponentDefinitionInProps", + messageId: "noNestedComponentDefinitions", node: component, data: { name, + suggestion: "Move it to the top level or pass it as a prop.", }, }); } @@ -101,10 +99,11 @@ export function create(context: RuleContext): RuleListener { } if (isInsideCreateElementProps(context, component)) { context.report({ - messageId: "noNestedComponentDefinitionInProps", + messageId: "noNestedComponentDefinitions", node: component, data: { name, + suggestion: "Move it to the top level or pass it as a prop.", }, }); @@ -113,12 +112,13 @@ export function create(context: RuleContext): RuleListener { const parentComponent = AST.findParentNode(component, isFunctionComponent); if (parentComponent != null && !isDirectValueOfRenderPropertyLoose(parentComponent)) { context.report({ - messageId: component.parent.type === T.Property - ? "noNestedComponentDefinitionInProps" - : "noNestedComponentDefinition", + messageId: "noNestedComponentDefinitions", node: component, data: { name, + suggestion: component.parent.type === T.Property + ? "Move it to the top level or pass it as a prop." + : "Move it to the top level.", }, }); @@ -126,10 +126,11 @@ export function create(context: RuleContext): RuleListener { } if (isInsideRenderMethod(component)) { context.report({ - messageId: "noNestedComponentDefinition", + messageId: "noNestedComponentDefinitions", node: component, data: { name, + suggestion: "Move it to the top level.", }, }); } @@ -139,10 +140,13 @@ export function create(context: RuleContext): RuleListener { continue; } context.report({ - messageId: "noNestedComponentDefinition", + messageId: "noNestedComponentDefinitions", node: component, data: { name, + suggestion: component.parent.type === T.Property + ? "Move it to the top level or pass it as a prop." + : "Move it to the top level.", }, }); }