Skip to content

Instantly share code, notes, and snippets.

@danieljvdm
Created March 22, 2026 15:22
Show Gist options
  • Select an option

  • Save danieljvdm/c5dac845c91f808d021b8d99cabf36e7 to your computer and use it in GitHub Desktop.

Select an option

Save danieljvdm/c5dac845c91f808d021b8d99cabf36e7 to your computer and use it in GitHub Desktop.
const EFFECT_FN_METHODS = new Set(["fn", "fnUntraced", "fnUntracedEager"]);
function isIdentifier(node, name) {
return node?.type === "Identifier" && node.name === name;
}
function isEffectFnFactoryCall(node) {
if (node?.type !== "CallExpression") {
return false;
}
const callee = node.callee;
if (callee?.type !== "MemberExpression" || callee.computed) {
return false;
}
return (
isIdentifier(callee.object, "Effect") &&
callee.property.type === "Identifier" &&
EFFECT_FN_METHODS.has(callee.property.name)
);
}
function getEffectFnBody(node) {
if (node?.type !== "CallExpression") {
return null;
}
if (isEffectFnFactoryCall(node)) {
return node.arguments[0] ?? null;
}
if (isEffectFnFactoryCall(node.callee)) {
return node.arguments[0] ?? null;
}
return null;
}
function isTypedPattern(node) {
return Boolean(
node &&
typeof node === "object" &&
"typeAnnotation" in node &&
node.typeAnnotation
);
}
function isExplicitlyTypedVariableDeclarator(node) {
return node?.type === "VariableDeclarator" && isTypedPattern(node.id);
}
function getUntypedParameterNode(param) {
switch (param.type) {
case "Identifier":
return param.typeAnnotation ? null : param;
case "AssignmentPattern":
return getUntypedParameterNode(param.left);
case "RestElement":
return getUntypedParameterNode(param.argument);
case "ArrayPattern":
case "ObjectPattern":
return param.typeAnnotation ? null : param;
default:
return null;
}
}
const explicitEffectFnParamsRule = {
meta: {
type: "suggestion",
docs: {
description:
"Require explicit parameter type annotations for Effect.fn callback parameters",
},
schema: [],
},
create(context) {
const typedEffectFnBodies = new WeakSet();
return {
VariableDeclarator(node) {
const body = getEffectFnBody(node.init);
if (!body || !isExplicitlyTypedVariableDeclarator(node)) {
return;
}
typedEffectFnBodies.add(body);
},
CallExpression(node) {
const body = getEffectFnBody(node);
if (
body?.type !== "FunctionExpression" &&
body?.type !== "ArrowFunctionExpression"
) {
return;
}
if (typedEffectFnBodies.has(body)) {
return;
}
for (const param of body.params) {
const untypedParam = getUntypedParameterNode(param);
if (!untypedParam) {
continue;
}
context.report({
node: untypedParam,
message:
"Effect.fn callback parameters must declare an explicit type annotation",
});
}
},
};
},
};
export default {
meta: {
name: "hyper",
},
rules: {
"explicit-effect-fn-params": explicitEffectFnParamsRule,
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment