Skip to content

Commit 85c4f5a

Browse files
authored
Merge pull request #121 from solidjs-community/fix/func-name-check
Make function name checking more resilient for #116.
2 parents 87e2f9b + a69c97f commit 85c4f5a

File tree

5 files changed

+78
-9
lines changed

5 files changed

+78
-9
lines changed

docs/components-return-once.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,33 @@ callback(() => {
145145
return <div />;
146146
});
147147

148+
function Component() {
149+
const renderContent = () => {
150+
if (false) return <></>;
151+
return <></>;
152+
};
153+
return <>{renderContent()}</>;
154+
}
155+
156+
function Component() {
157+
function renderContent() {
158+
if (false) return <></>;
159+
return <></>;
160+
}
161+
return <>{renderContent()}</>;
162+
}
163+
164+
function Component() {
165+
const renderContent = () => {
166+
const renderContentInner = () => {
167+
// ifs in render functions are fine no matter what nesting level this is
168+
if (false) return;
169+
return <></>;
170+
};
171+
return <>{renderContentInner()}</>;
172+
};
173+
return <></>;
174+
}
175+
148176
```
149177
<!-- AUTO-GENERATED-CONTENT:END -->

src/rules/components-return-once.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
2-
import type { FunctionNode } from "../utils";
2+
import { getFunctionName, type FunctionNode } from "../utils";
33

44
const createRule = ESLintUtils.RuleCreator.withoutDocs;
55

@@ -63,8 +63,8 @@ export default createRule({
6363

6464
const onFunctionExit = (node: FunctionNode) => {
6565
if (
66-
(node.type === "FunctionDeclaration" && node.id?.name?.match(/^[a-z]/)) ||
6766
// "render props" aren't components
67+
getFunctionName(node)?.match(/^[a-z]/) ||
6868
node.parent?.type === "JSXExpressionContainer" ||
6969
// ignore createMemo(() => conditional JSX), report HOC(() => conditional JSX)
7070
(node.parent?.type === "CallExpression" &&

src/rules/reactivity.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
trackImports,
1616
isDOMElementName,
1717
ignoreTransparentWrappers,
18+
getFunctionName,
1819
} from "../utils";
1920

2021
const { findVariable, getFunctionHeadLocation } = ASTUtils;
@@ -425,15 +426,17 @@ export default createRule<Options, MessageIds>({
425426
const onFunctionExit = (currentScopeNode: ProgramOrFunctionNode) => {
426427
// If this function is a component, add its props as a reactive variable
427428
if (isFunctionNode(currentScopeNode)) {
428-
markPropsOnCondition(
429-
currentScopeNode,
430-
(props) =>
429+
markPropsOnCondition(currentScopeNode, (props) => {
430+
if (
431431
!isPropsByName(props.name) && // already added in markPropsOnEnter
432-
currentScope().hasJSX &&
432+
currentScope().hasJSX
433+
) {
434+
const functionName = getFunctionName(currentScopeNode);
433435
// begins with lowercase === not component
434-
(currentScopeNode.type !== "FunctionDeclaration" ||
435-
!currentScopeNode.id?.name?.match(/^[a-z]/))
436-
);
436+
if (functionName && !/^[a-z]/.test(functionName)) return true;
437+
}
438+
return false;
439+
});
437440
}
438441

439442
// Ignore sync callbacks like Array#forEach and certain Solid primitives.

src/utils.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ export const isProgramOrFunctionNode = (
9797
node: T.Node | null | undefined
9898
): node is ProgramOrFunctionNode => !!node && PROGRAM_OR_FUNCTION_TYPES.includes(node.type);
9999

100+
export const getFunctionName = (node: FunctionNode): string | null => {
101+
if (
102+
(node.type === "FunctionDeclaration" || node.type === "FunctionExpression") &&
103+
node.id != null
104+
) {
105+
return node.id.name;
106+
}
107+
if (node.parent?.type === "VariableDeclarator" && node.parent.id.type === "Identifier") {
108+
return node.parent.id.name;
109+
}
110+
return null;
111+
};
112+
100113
export function findInScope(
101114
node: T.Node,
102115
scope: ProgramOrFunctionNode,

test/rules/components-return-once.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,31 @@ export const cases = run("components-return-once", rule, {
2424
}
2525
return <div />;
2626
});`,
27+
`function Component() {
28+
const renderContent = () => {
29+
if (false) return <></>;
30+
return <></>;
31+
}
32+
return <>{renderContent()}</>;
33+
}`,
34+
`function Component() {
35+
function renderContent() {
36+
if (false) return <></>;
37+
return <></>;
38+
}
39+
return <>{renderContent()}</>;
40+
}`,
41+
`function Component() {
42+
const renderContent = () => {
43+
const renderContentInner = () => {
44+
// ifs in render functions are fine no matter what nesting level this is
45+
if (false) return;
46+
return <></>;
47+
};
48+
return <>{renderContentInner()}</>;
49+
};
50+
return <></>;
51+
}`,
2752
],
2853
invalid: [
2954
// Early returns

0 commit comments

Comments
 (0)