diff --git a/docs/reactivity.md b/docs/reactivity.md
index 07c6296..81be87c 100644
--- a/docs/reactivity.md
+++ b/docs/reactivity.md
@@ -166,7 +166,19 @@ return
-
+## Rule Options
+
+Options shown here are the defaults. Manually configuring an array will *replace* the defaults.
+
+```js
+{
+ "solid/reactivity": ["warn", {
+ // 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.
+ customReactiveFunctions: [], // Array
+ }]
+}
+```
+
@@ -607,6 +619,11 @@ X.createFoo(() => bar());
const [bar, setBar] = createSignal();
X.Y.createFoo(() => bar());
+/* eslint solid/reactivity: ["error", { "customReactiveFunctions": ["customQuery"] }] */
+function customQuery(v) {}
+const [signal, setSignal] = createSignal();
+customQuery(() => signal());
+
const [signal, setSignal] = createSignal(1);
const element = document.getElementById("id");
element.addEventListener(
diff --git a/src/rules/reactivity.ts b/src/rules/reactivity.ts
index 4d7c551..911d147 100644
--- a/src/rules/reactivity.ts
+++ b/src/rules/reactivity.ts
@@ -216,7 +216,18 @@ const getReturnedVar = (id: T.Node, scope: Scope): Variable | null => {
return null;
};
-export default createRule({
+type MessageIds =
+ | "noWrite"
+ | "untrackedReactive"
+ | "expectedFunctionGotExpression"
+ | "badSignal"
+ | "badUnnamedDerivedSignal"
+ | "shouldDestructure"
+ | "shouldAssign"
+ | "noAsyncTrackedScope";
+type Options = [{ customReactiveFunctions: string[] }];
+
+export default createRule({
meta: {
type: "problem",
docs: {
@@ -224,7 +235,23 @@ export default createRule({
"Enforce that reactivity (props, signals, memos, etc.) is properly used, so changes in those values will be tracked and update the view as expected.",
url: "https://github.com/solidjs-community/eslint-plugin-solid/blob/main/docs/reactivity.md",
},
- schema: [],
+ schema: [
+ {
+ type: "object",
+ properties: {
+ customReactiveFunctions: {
+ description:
+ "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.",
+ type: "array",
+ items: {
+ type: "string",
+ },
+ default: [],
+ },
+ },
+ additionalProperties: false,
+ },
+ ],
messages: {
noWrite: "The reactive variable '{{name}}' should not be reassigned or altered directly.",
untrackedReactive:
@@ -243,8 +270,12 @@ export default createRule({
"This tracked scope should not be async. Solid's reactivity only tracks synchronously.",
},
},
- defaultOptions: [] as const,
- create(context) {
+ defaultOptions: [
+ {
+ customReactiveFunctions: [],
+ },
+ ],
+ create(context, [options]) {
const warnShouldDestructure = (node: T.Node, nth?: string) =>
context.report({
node,
@@ -994,7 +1025,10 @@ export default createRule({
pushTrackedScope(arg1, "function");
}
}
- } else if (/^(?:use|create)[A-Z]/.test(callee.name)) {
+ } else if (
+ /^(?:use|create)[A-Z]/.test(callee.name) ||
+ options.customReactiveFunctions.includes(callee.name)
+ ) {
// Custom hooks parameters may or may not be tracking scopes, no way to know.
// Assume all identifier/function arguments are tracked scopes, and use "called-function"
// to allow async handlers (permissive). Assume non-resolvable args are reactive expressions.
@@ -1019,7 +1053,11 @@ export default createRule({
) {
// Like `on*` event handlers, mark all `addEventListener` listeners as called functions.
pushTrackedScope(node.arguments[1], "called-function");
- } else if (property.type === "Identifier" && /^(?:use|create)[A-Z]/.test(property.name)) {
+ } else if (
+ property.type === "Identifier" &&
+ (/^(?:use|create)[A-Z]/.test(property.name) ||
+ options.customReactiveFunctions.includes(property.name))
+ ) {
// Handle custom hook parameters for property access custom hooks
for (const arg of node.arguments) {
if (isFunctionNode(arg)) {
diff --git a/test/rules/reactivity.test.ts b/test/rules/reactivity.test.ts
index f315828..b9c74d9 100644
--- a/test/rules/reactivity.test.ts
+++ b/test/rules/reactivity.test.ts
@@ -153,6 +153,12 @@ export const cases = run("reactivity", rule, {
X.createFoo(() => bar());`,
`const [bar, setBar] = createSignal();
X . Y\n. createFoo(() => bar());`,
+ {
+ code: `function customQuery(v) {}
+ const [signal, setSignal] = createSignal();
+ customQuery(() => signal());`,
+ options: [{ customReactiveFunctions: ["customQuery"] }], // only needed when not create*/use*
+ },
// Event listeners
`const [signal, setSignal] = createSignal(1);
const element = document.getElementById("id");