elm-review/tests/NoRecursiveUpdate.elm
2022-04-22 22:04:13 +02:00

115 lines
3.1 KiB
Elm

module NoRecursiveUpdate exposing (rule)
{-|
@docs rule
-}
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Expression as Expression exposing (Expression)
import Elm.Syntax.Node as Node exposing (Node)
import Review.Rule as Rule exposing (Error, Rule)
{-| Reports when the `update` function calls itself.
This is often done in order to have one message (A) trigger (all or some of) the same
model updates and commands as another message (B).
update msg model =
case msg of
Foo ->
{ model | foo = True }
Bar ->
update Foo { model | bar = True }
This is advised against, because if the way that message B is handled changes,
that will implicitly change how message A is handled in ways that may not have
been foreseen.
A better solution is to move the common handling into a different function and
have it called in the handling of both messages.
update msg model =
case msg of
Foo ->
commonOperationOnModel model
Bar ->
commonOperationOnModel { model | bar = True }
commonOperationOnModel model =
{ model | foo = True }
Calls to other modules' `update` function are allowed.
To add the rule to your configuration:
config =
[ NoRecursiveUpdate.rule
]
## Try it out
You can try this rule out by running the following command:
```bash
elm-review --template jfmengels/elm-review-the-elm-architecture/example --rules NoRecursiveUpdate
```
-}
rule : Rule
rule =
Rule.newModuleRuleSchema "NoRecursiveUpdate" { isInUpdateFunction = False }
|> Rule.withDeclarationEnterVisitor declarationVisitor
|> Rule.withExpressionEnterVisitor expressionVisitor
|> Rule.fromModuleRuleSchema
type alias Context =
{ isInUpdateFunction : Bool
}
declarationVisitor : Node Declaration -> Context -> ( List nothing, Context )
declarationVisitor node _ =
case Node.value node of
Declaration.FunctionDeclaration function ->
( []
, { isInUpdateFunction =
(function.declaration
|> Node.value
|> .name
|> Node.value
)
== "update"
}
)
_ ->
( [], { isInUpdateFunction = False } )
expressionVisitor : Node Expression -> Context -> ( List (Error {}), Context )
expressionVisitor node context =
if context.isInUpdateFunction then
case Node.value node of
Expression.FunctionOrValue [] "update" ->
( [ Rule.error
{ message = "`update` shouldn't call itself"
, details = [ "If you wish to have the same behavior for different messages, move that behavior into a new function and have it called in the handling of both messages." ]
}
(Node.range node)
]
, context
)
_ ->
( [], context )
else
( [], context )