It is recommended that you also include
NoUnused.Patterns
in your config, as the fixes for this rule can sometimes generate nested as
patterns in very complex cases (e.g. when nested single-pattern cases are
simplified). These cases cannot be resolved automatically, as it is unclear
which name should be preferred.
rule : Config fixBy -> Review.Rule.Rule
Reports single-pattern case expressions, which may be written more concisely or removed entirely.
config =
[ NoSinglePatternCase.rule NoSinglePatternCase.fixInArgument ]
See Config
for configuration details.
Single-pattern case expressions for destructuring are not allowed, as:
type Opaque
= Opaque Int
unpack : Opaque -> Int
unpack o =
case o of
Opaque i ->
i
may instead be written more concisely, for example as
unpack : Opaque -> Int
unpack (Opaque i) =
i
Similarly, single-pattern case expressions that ase not used for destructuring are not allowed, as:
type AOrB
= A
| B
pointless : AOrB -> Bool
pointless aOrB =
case aOrB of
_ ->
True
may instead be written more concisely, for example as
pointless : AOrB -> Bool
pointless _ =
True
or
pointless : AOrB -> Bool
pointless =
always True
Single patterns with constructors that do not match their type name, e.g. type
Msg = ButtonClicked
, are allowed by default, unless they are imported from
dependencies (as those types are not expected to be iterated on). This behavior
can be changed with reportAllCustomTypes
.
Any case expression with more than one pattern match will not be reported.
Consider using jfmengels/elm-review-simplify
to detect unnecessary multi-pattern cases.
This rule is useful if you prefer destructuring in function/lambda arguments or
let
bindings, rather than in a single-pattern case.
This rule is not useful if you prefer the more verbose style.
You can try this rule out by running the following command:
elm-review --template SiriusStarr/elm-review-no-single-pattern-case/example/fix-in-argument --rules NoSinglePatternCase
Configure the rule, determining how automatic fixes are generated.
The default Config
s fixInArgument
and
fixInLet
should be used as reasonable defaults, with more
customization detailed in those sections.
The behavior of the rule with constructors that don't match their type name can
be configured via reportAllCustomTypes
. By default,
only constructors with an identical name to their type are reported.
The behavior of the rule in the context of useless single pattern cases can also
be configured via replaceUnusedBindings
. A single
pattern case is considered to be useless if its pattern does not bind any name
that is actually used in the expression.
fixInArgument : Config FixInArgument
Always fix by destructuring in the argument. This will use as
patterns if
necessary. If the argument cannot be destructured in, no fix will be generated.
For example:
f1 o =
case o of
Opaque i ->
i
f2 o =
let
x =
someFunc o
in
case o of
Opaque i ->
i + x
f3 { recordField } =
case recordField of
Opaque i ->
i
will be fixed to:
f1 (Opaque i) =
i
f2 ((Opaque i) as o) =
let
x =
someFunc o
in
i + x
f3 { recordField } =
case recordField of
Opaque i ->
i
Use ifAsPatternRequired
and
ifCannotDestructureAtArgument
to customize
the behavior in either of these cases.
fixInLet : Config FixInLet
Always fix by destructuring in a let
block, creating a new one if none
exists.
For example:
f1 o =
case o of
Opaque i ->
i
f2 o =
let
x =
someFunc o
in
case o of
Opaque i ->
i + x
will be fixed to:
f1 o =
let
(Opaque i) =
o
in
i
f2 ((Opaque i) as o) =
let
x =
someFunc o
(Opaque i) =
o
in
i + x
Use ifNoLetExists
to customize the behavior in the case
where no let
block exists within the scope the pattern is valid in. To
clarify, the following counts as no let
existing:
unpack : Opaque -> Int
unpack o =
let
foo =
bar
in
(\a ->
case a of
Opaque i ->
i
)
o
as the name a
is not in scope in the extant let
block.
ifNoLetExists
also handles the case where the closest let
block would result in a name clash. To clarify, the following counts as no
let
existing:
unpack : Opaque -> Int
unpack o =
let
foo =
(\i -> i + 1) 0
in
case o of
Opaque i ->
i
as i
cannot be unpacked in the let
block, as doing so would cause a name
clash with the i
in foo
.
reportAllCustomTypes : Config fixBy -> Config fixBy
By default, only constructors whose names are identical to their type are
reported, along with types imported from dependencies (since those aren't
expected to be iterated on). This setting changes behavior back to that of
version 2.0.2
and earlier, where absolutely all single pattern cases are
flagged by the rule, regardless of the types.
-- import the constructor `OutsideConstructor` from some other package
import SomeOutsidePackage exposing (OutsideType(..))
type Date
= -- Constructor has same name as type
Date Int
type Msg
= -- Constructor has different name than type
ThingieClicked
update1 : Date -> Int -> Int
update1 date i =
case date of
-- THIS CASE IS ALWAYS FLAGGED
Date j ->
i + j
update2 : Msg -> Int -> Int
update2 msg i =
case msg of
-- THIS CASE IS NOT FLAGGED BY DEFAULT, IS FLAGGED WITH `reportAllCustomTypes`
ThingieClicked ->
i + 1
update3 : OutsideType -> Int -> Int
update3 oType i =
case oType of
-- THIS CASE IS ALWAYS FLAGGED
OutsideConstructor j ->
i + j
replaceUnusedBindings : Config fixBy -> Config fixBy
A single pattern case is considered to be useless if its pattern does not bind any name that is actually used in the expression, e.g.
case x of
_ ->
True
case x of
() ->
True
case x of
A ({ field1, field2 } as record) ->
List.map foo bar
|> List.sum
|> baz
The rule will always provide fixes for such cases but by default will not
replace the binding used in the case...of
expression. This option configures
the rule to replace such bindings where possible with the most specific option.
For example:
f unusedArg =
case unusedArg of
_ ->
True
will be fixed to
f _ =
True
and
f x =
case x of
A ({ field1, field2 } as record) ->
List.map foo bar
|> List.sum
|> baz
will be fixed to
f (A _) =
List.map foo bar
|> List.sum
|> baz
This provides a more clear indication that the binding is unused. The binding
will of course not be replaced if it used anywhere but in case...of
.
replaceUnusedBindingsWithWildcard : Config fixBy -> Config fixBy
This setting changes behavior back to that of version 2.0.2
and earlier,
where useless bindings are replaced by _
(or ()
), not by the constructor
name.
For example,
f x =
case x of
A ({ field1, field2 } as record) ->
List.map foo bar
|> List.sum
|> baz
will be fixed to
f _ =
List.map foo bar
|> List.sum
|> baz
This is useful if you are reliant on an IDE that doesn't recognize
pointless CreateNewLet =
foo
as valid Elm syntax. The downside is that you will not receive compiler warnings if the argument changes (e.g. if you add a new constructor to the type).
ifAsPatternRequired : UseAsPatternOrLetsOrFail -> Config FixInArgument -> Config FixInArgument
Specify what to do if an as
pattern would be required to destructure in
the argument, e.g.
f o =
let
x =
someFunc o
in
case o of
Opaque i ->
i + x
Available options are useAsPattern
(this is the default),
fixInLetInstead
or fail
,
e.g.
c1 =
fixInArgument
|> ifAsPatternRequired fail
c2 =
fixInArgument
|> ifAsPatternRequired useAsPattern
c3 =
fixInArgument
|> ifAsPatternRequired
(fixInLetInstead
|> andIfNoLetExists createNewLet
)
c4 =
fixInArgument
|> ifAsPatternRequired
(fixInLetInstead
|> andIfNoLetExists useAsPattern
)
ifCannotDestructureAtArgument : UseLetOrFail -> Config FixInArgument -> Config FixInArgument
Specify what to do if the argument cannot be destructured at, either due to it being a record field, e.g.
f { recordField } =
case recordField of
Opaque i ->
i
or a more complex case
expression, e.g.
f a =
case foo <| bar a of
Opaque i ->
i
or due to a name clash that would be caused by the increase in scope, e.g.
unpack : Opaque -> Int
unpack o =
let
foo =
(\i -> i + 1) 0
in
case o of
Opaque i ->
i
Available options are fixInLetInstead
or fail
(this is the default).
c1 =
fixInArgument
|> ifCannotDestructureAtArgument fail
c2 =
fixInArgument
|> ifCannotDestructureAtArgument
(fixInLetInstead
|> andIfNoLetExists fail
)
c3 =
fixInArgument
|> ifCannotDestructureAtArgument
(fixInLetInstead
|> andIfNoLetExists createNewLet
)
ifNoLetExists : UseArgOrCreateNewLetOrFail -> Config FixInLet -> Config FixInLet
Specify what to do it no let
block exists in scope, instead of creating a
new one.
Available options are fixInArgumentInstead
,
createNewLet
(this is the default), or fail
. Note
that andIfAsPatternRequired
and
andIfCannotDestructureAtArgument
must appear
in that order after fixInArgumentInstead
.
c1 =
fixInLet
|> ifNoLetExists fail
c2 =
fixInLet
-- This is the default
|> ifNoLetExists createNewLet
c3 =
fixInLet
|> ifNoLetExists
(fixInArgumentInstead
|> andIfAsPatternRequired useAsPattern
|> andIfCannotDestructureAtArgument fail
)
These functions are simply used by
ifAsPatternRequired
,
ifCannotDestructureAtArgument
, and
ifNoLetExists
to customize behavior of the default configs.
Look at the examples in those to understand how to use them.
fail : Either or Fail
Choose to fail at generating a fix.
createNewLet : CreateNewLetOr or
Choose to create a let
block when none exists.
useAsPattern : UseAsPatternOrFailOr or
Choose to use an as
pattern to destructure in the argument if necessary.
fixInArgumentInstead : UseAsPatternOrFailOr CreateNewLet -> CreateNewLetOr Fail -> UseArgOrCreateNewLetOrFail
Fallback to destructuring in the argument instead of a let
block.
Note that andIfAsPatternRequired
and
andIfCannotDestructureAtArgument
must appear
in that order, e.g.
c =
fixInLet
|> ifNoLetExists
(fixInArgumentInstead
|> andIfAsPatternRequired useAsPattern
|> andIfCannotDestructureAtArgument fail
)
andIfAsPatternRequired : UseAsPatternOrFailOr CreateNewLet -> (UseAsPatternOrFailOr CreateNewLet -> CreateNewLetOr Fail -> UseArgOrCreateNewLetOrFail) -> CreateNewLetOr Fail -> UseArgOrCreateNewLetOrFail
Specify what to do if an as
pattern would be necessary.
Available options are useAsPattern
,
createNewLet
or fail
c1 =
fixInLet
|> ifNoLetExists
(fixInArgumentInstead
|> andIfAsPatternRequired useAsPattern
|> andIfCannotDestructureAtArgument fail
)
c2 =
fixInLet
|> ifNoLetExists
(fixInArgumentInstead
|> andIfAsPatternRequired createNewLet
|> andIfCannotDestructureAtArgument fail
)
c3 =
fixInLet
|> ifNoLetExists
(fixInArgumentInstead
|> andIfAsPatternRequired fail
|> andIfCannotDestructureAtArgument fail
)
andIfCannotDestructureAtArgument : CreateNewLetOr Fail -> (CreateNewLetOr Fail -> UseArgOrCreateNewLetOrFail) -> UseArgOrCreateNewLetOrFail
Specify what to do if the argument cannot be destructured at, e.g.
f { recordField } =
case recordField of
Opaque i ->
i
Available options are createNewLet
or fail
c1 =
fixInLet
|> ifNoLetExists
(fixInArgumentInstead
|> andIfAsPatternRequired fail
|> andIfCannotDestructureAtArgument createNewLet
)
c2 =
fixInLet
|> ifNoLetExists
(fixInArgumentInstead
|> andIfAsPatternRequired fail
|> andIfCannotDestructureAtArgument fail
)
fixInLetInstead : a -> UseLetOr a b
Fallback to destructuring in a let
block instead of the argument.
andIfNoLetExists : a -> (a -> UseLetOr a b) -> UseLetOr a b
If no let
block exists to destructure in, choose some other behavior
instead.
You shouldn't need to worry about these types; they are exported solely for the sake of annotation, should it be necessary.
Phantom type for Config fixBy
.
Phantom type for Config fixBy
.
Fallback to destructuring in the argument, if choosing not to create a let
block.
Fallback to using a let
block if destructuring in the argument has failed
(or we've chosen not to).
Choose to create a new let
block to destructure in.
Choose to fail at generating a fix.
Choose to use an as
pattern.
Either (Either or UseAsPattern) Fail
At this point, an as
pattern could be used or some other option.
Either (Either CreateNewLet or) Fail
At this point, a new let
could be created or some other option.
Either (Either CreateNewLet UseArgInstead) Fail
At this point, the argument could be used or a new let
block created.
block.
Either (Either (UseLetInstead or1) or2) Fail
At this point, a let
could be used or either of two other options.
UseLetOr (CreateNewLetOr Fail) Fail
At this point, the only option is to use a let
block.
UseAsPatternOrFailOr (UseLetInstead (UseAsPatternOrFailOr CreateNewLet))
At this point, an as
pattern could be used, or we could use a let
block.
Util.Either a b
Offer a choice between two options.