Skip to content

Commit 20a0e83

Browse files
Support custom reactive functions for reactivity rule
1 parent 5bdce80 commit 20a0e83

File tree

2 files changed

+57
-7
lines changed

2 files changed

+57
-7
lines changed

docs/reactivity.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,19 @@ return <Component staticName={props.name} />
166166
</details>
167167

168168
<!-- AUTO-GENERATED-CONTENT:START (OPTIONS) -->
169-
169+
## Rule Options
170+
171+
Options shown here are the defaults. Manually configuring an array will *replace* the defaults.
172+
173+
```js
174+
{
175+
"solid/reactivity": ["warn", {
176+
// List of function names to consider as reactive functions (allow signals to be safely passed as arguments). In addition, any create* or use* functions are automatically included.
177+
customReactiveFunctions: [], // Array<string>
178+
}]
179+
}
180+
```
181+
170182
<!-- AUTO-GENERATED-CONTENT:END -->
171183

172184
<!-- AUTO-GENERATED-CONTENT:START (CASES) -->

src/rules/reactivity.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -216,15 +216,42 @@ const getReturnedVar = (id: T.Node, scope: Scope): Variable | null => {
216216
return null;
217217
};
218218

219-
export default createRule({
219+
type MessageIds =
220+
| "noWrite"
221+
| "untrackedReactive"
222+
| "expectedFunctionGotExpression"
223+
| "badSignal"
224+
| "badUnnamedDerivedSignal"
225+
| "shouldDestructure"
226+
| "shouldAssign"
227+
| "noAsyncTrackedScope";
228+
type Options = [{ customReactiveFunctions: string[] }];
229+
230+
export default createRule<Options, MessageIds>({
220231
meta: {
221232
type: "problem",
222233
docs: {
223234
description:
224235
"Enforce that reactivity (props, signals, memos, etc.) is properly used, so changes in those values will be tracked and update the view as expected.",
225236
url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/docs/reactivity.md",
226237
},
227-
schema: [],
238+
schema: [
239+
{
240+
type: "object",
241+
properties: {
242+
customReactiveFunctions: {
243+
description:
244+
"List of function names to consider as reactive functions (allow signals to be safely passed as arguments). In addition, any create* or use* functions are automatically included.",
245+
type: "array",
246+
items: {
247+
type: "string",
248+
},
249+
default: [],
250+
},
251+
},
252+
additionalProperties: false,
253+
},
254+
],
228255
messages: {
229256
noWrite: "The reactive variable '{{name}}' should not be reassigned or altered directly.",
230257
untrackedReactive:
@@ -243,8 +270,12 @@ export default createRule({
243270
"This tracked scope should not be async. Solid's reactivity only tracks synchronously.",
244271
},
245272
},
246-
defaultOptions: [] as const,
247-
create(context) {
273+
defaultOptions: [
274+
{
275+
customReactiveFunctions: [],
276+
},
277+
],
278+
create(context, [options]) {
248279
const warnShouldDestructure = (node: T.Node, nth?: string) =>
249280
context.report({
250281
node,
@@ -994,7 +1025,10 @@ export default createRule({
9941025
pushTrackedScope(arg1, "function");
9951026
}
9961027
}
997-
} else if (/^(?:use|create)[A-Z]/.test(callee.name)) {
1028+
} else if (
1029+
/^(?:use|create)[A-Z]/.test(callee.name) ||
1030+
options.customReactiveFunctions.includes(callee.name)
1031+
) {
9981032
// Custom hooks parameters may or may not be tracking scopes, no way to know.
9991033
// Assume all identifier/function arguments are tracked scopes, and use "called-function"
10001034
// to allow async handlers (permissive). Assume non-resolvable args are reactive expressions.
@@ -1019,7 +1053,11 @@ export default createRule({
10191053
) {
10201054
// Like `on*` event handlers, mark all `addEventListener` listeners as called functions.
10211055
pushTrackedScope(node.arguments[1], "called-function");
1022-
} else if (property.type === "Identifier" && /^(?:use|create)[A-Z]/.test(property.name)) {
1056+
} else if (
1057+
property.type === "Identifier" &&
1058+
(/^(?:use|create)[A-Z]/.test(property.name) ||
1059+
options.customReactiveFunctions.includes(property.name))
1060+
) {
10231061
// Handle custom hook parameters for property access custom hooks
10241062
for (const arg of node.arguments) {
10251063
if (isFunctionNode(arg)) {

0 commit comments

Comments
 (0)