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 @@ -5,7 +5,6 @@
```ts
type Options = {
collectDisplayName?: boolean;
collectHookCalls?: boolean;
hint?: ComponentDetectionHint;
};
```
Expand All @@ -15,5 +14,4 @@ type Options = {
| Property | Type |
| ------ | ------ |
| <a id="collectdisplayname"></a> `collectDisplayName?` | `boolean` |
| <a id="collecthookcalls"></a> `collectHookCalls?` | `boolean` |
| <a id="hint"></a> `hint?` | [`ComponentDetectionHint`](../../../../type-aliases/ComponentDetectionHint.md) |
23 changes: 9 additions & 14 deletions packages/core/src/component/component-collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import { isComponentDefinition } from "./component-definition";
import { DEFAULT_COMPONENT_DETECTION_HINT } from "./component-detection-hint";
import { getFunctionComponentId } from "./component-id";
import { getComponentFlagFromInitPath } from "./component-init-path";
import { hasNoneOrLooseComponentName } from "./component-name";

const idGen = new IdGenerator("function_component_");

Expand All @@ -25,7 +24,6 @@ interface FunctionEntry extends FunctionComponentSemanticNode {
export declare namespace useComponentCollector {
type Options = {
collectDisplayName?: boolean;
collectHookCalls?: boolean;
hint?: ComponentDetectionHint;
};
type ReturnType = {
Expand All @@ -50,7 +48,6 @@ export function useComponentCollector(
): useComponentCollector.ReturnType {
const {
collectDisplayName = false,
collectHookCalls = false,
hint = DEFAULT_COMPONENT_DETECTION_HINT,
} = options;

Expand Down Expand Up @@ -78,7 +75,7 @@ export function useComponentCollector(
hint,
hookCalls: [],
initPath,
isComponentDefinition: hasNoneOrLooseComponentName(context, node) && isComponentDefinition(context, node, hint),
isComponentDefinition: isComponentDefinition(context, node, hint),
isExportDefault,
isExportDefaultDeclaration,
rets: [],
Expand Down Expand Up @@ -126,16 +123,14 @@ export function useComponentCollector(
},
}
: {},
...collectHookCalls
? {
"CallExpression:exit"(node: TSESTree.CallExpression) {
if (!isHookCall(node)) return;
const entry = getCurrentEntry();
if (entry == null) return;
entry.hookCalls.push(node);
},
}
: {},
CallExpression(node: TSESTree.CallExpression) {
if (!isHookCall(node)) return;
const entry = getCurrentEntry();
if (entry == null) return;
entry.hookCalls.push(node);
if (!entry.isComponentDefinition) return;
components.set(entry.key, entry);
},
ReturnStatement(node: TSESTree.ReturnStatement) {
const entry = getCurrentEntry();
if (entry == null) return;
Expand Down
12 changes: 9 additions & 3 deletions packages/core/src/component/component-definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AST_NODE_TYPES as T, type TSESTree } from "@typescript-eslint/types";
import { isCreateElementCall } from "../api";
import { ComponentDetectionHint } from "./component-detection-hint";
import { isClassComponent } from "./component-is";
import { hasNoneOrLooseComponentName } from "./component-name";
import { isRenderMethodLike } from "./component-render-method";

/**
Expand Down Expand Up @@ -107,17 +108,22 @@ export function isComponentDefinition(
node: AST.TSESTreeFunction,
hint: bigint,
) {
// 1. Check immediate contextual exclusions
// 1. Check for basic naming conventions
if (!hasNoneOrLooseComponentName(context, node)) {
return false;
}

// 2. Check immediate contextual exclusions
if (isChildrenOfCreateElement(context, node) || isRenderMethodCallback(node)) {
return false;
}

// 2. Check explicit hints provided by the caller
// 3. Check explicit hints provided by the caller
if (shouldExcludeBasedOnHint(node, hint)) {
return false;
}

// 3. Check if the function is embedded directly inside JSX (e.g., inline callbacks)
// 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(
node,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,25 @@ import rule, { RULE_NAME } from "./function-component";

ruleTester.run(RULE_NAME, rule, {
invalid: [
{
code: tsx`
function App() {
useEffect(() => {});
}
`,
errors: [{
messageId: "functionComponent",
data: {
json: stringify({
name: "App",
displayName: "none",
forwardRef: false,
hookCalls: 1,
memo: false,
}),
},
}],
},
{
code: tsx`
function App({ foo }) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {
context,
{
collectDisplayName: true,
collectHookCalls: true,
hint: DEFAULT_COMPONENT_DETECTION_HINT,
},
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export function create(context: RuleContext<MessageID, []>): RuleListener {

const { ctx, visitor } = useComponentCollector(context, {
collectDisplayName: true,
collectHookCalls: false,
hint: DEFAULT_COMPONENT_DETECTION_HINT,
});

Expand Down
Loading