Created
March 22, 2026 15:22
-
-
Save danieljvdm/c5dac845c91f808d021b8d99cabf36e7 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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