forked from solidjs-community/eslint-plugin-solid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
components-return-once.ts
135 lines (111 loc) · 3.97 KB
/
components-return-once.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
import { FunctionNode } from "../utils";
const createRule = ESLintUtils.RuleCreator.withoutDocs;
export default createRule({
meta: {
type: "problem",
docs: {
description:
"Disallow early returns in components. Solid components only run once, and so conditionals should be inside JSX.",
url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/docs/components-return-once.md",
},
fixable: "code",
schema: [],
messages: {
noEarlyReturn:
"Solid components run once, so an early return breaks reactivity. Move the condition inside a JSX element, such as a fragment or <Show />.",
},
},
defaultOptions: [],
create(context) {
const JSXElement = T.AST_NODE_TYPES.JSXElement;
const isFunction = (node: T.Node | undefined) => {
return (
node?.type === T.AST_NODE_TYPES.FunctionDeclaration ||
node?.type === T.AST_NODE_TYPES.ArrowFunctionExpression ||
node?.type === T.AST_NODE_TYPES.FunctionExpression
);
};
const onFunctionEnter = (node: FunctionNode) => {
const checkEarlyReturn = (node: FunctionNode) => {
const fnName = (() => {
if (
node.type === T.AST_NODE_TYPES.ArrowFunctionExpression ||
node.type === T.AST_NODE_TYPES.FunctionExpression
) {
if (node.parent.type === T.AST_NODE_TYPES.VariableDeclarator) {
return (node.parent.id as T.Identifier).name;
}
}
if (node.parent.type === T.AST_NODE_TYPES.CallExpression) {
return (node.parent.callee as T.Identifier).name;
}
return node.id?.name;
})();
// skip non components
if (fnName?.at(0)?.toUpperCase() !== fnName?.at(0)) return;
const body = node.body;
if (body.type !== T.AST_NODE_TYPES.BlockStatement) return;
let hasJsx = false;
for (const statement of body.body) {
if (statement.type !== T.AST_NODE_TYPES.ReturnStatement) continue;
if (statement.argument?.type === T.AST_NODE_TYPES.LogicalExpression) {
if (
statement.argument.left.type === JSXElement ||
statement.argument.right.type === JSXElement
) {
hasJsx = true;
break;
}
}
if (statement.argument?.type === T.AST_NODE_TYPES.ConditionalExpression) {
if (
statement.argument.alternate.type === JSXElement ||
statement.argument.consequent.type === JSXElement
) {
hasJsx = true;
break;
}
}
if (statement.argument?.type === JSXElement) {
hasJsx = true;
break;
}
}
// if jsx was not found, return
if (!hasJsx) return;
const ifStatements = body.body.filter(
(x) =>
x.type === T.AST_NODE_TYPES.IfStatement ||
(x.type === T.AST_NODE_TYPES.ReturnStatement &&
(x.argument?.type === T.AST_NODE_TYPES.ConditionalExpression ||
x.argument?.type === T.AST_NODE_TYPES.LogicalExpression))
);
if (ifStatements.length === 0) return;
for (const statement of ifStatements) {
context.report({
node: statement,
messageId: "noEarlyReturn",
});
}
};
let parent = node.parent;
// go out until the next outer is not a function
while (isFunction(parent)) {
if (isFunction(parent.parent)) {
parent = parent.parent!;
console.log(parent);
continue;
}
parent && checkEarlyReturn(parent as FunctionNode);
return;
}
checkEarlyReturn(node);
};
return {
FunctionDeclaration: onFunctionEnter,
ArrowFunctionExpression: onFunctionEnter,
FunctionExpression: onFunctionEnter,
};
},
});