mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-08-16 19:40:22 +03:00
115 lines
3.1 KiB
Elm
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 )
|