SiriusStarr/elm-review-no-single-pattern-case/2.0.2

NOTE: the latest version is 2.1.2

NoSinglePatternCase

Notes

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.

Review Rule

rule : Config fixBy -> 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.

Fails

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

Success

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.

When (not) to enable this rule

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.

Try it out

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

Config

type Config fixBy

Configure the rule, determining how automatic fixes are generated.

The default Configs fixInArgument and fixInLet should be used as reasonable defaults, with more customization detailed in those sections.

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.

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.

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.

Customizing Config Behavior

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. Fox example:

f unusedArg =
    case unusedArg of
        _ ->
            True

will be fixed to

f _ =
    True

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.

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
            )

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
            )

Config Behavior Options

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.

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
            )
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
            )
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.

Types

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.

type UseLetInstead noValidLetOptions

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.

type Fail

Choose to fail at generating a fix.

Choose to use an as pattern.

type alias UseAsPatternOrFailOr or =
Either (Either or UseAsPattern) Fail

At this point, an as pattern could be used or some other option.

type alias CreateNewLetOr or =
Either (Either CreateNewLet or) Fail

At this point, a new let could be created or some other option.

At this point, the argument could be used or a new let block created. block.

type alias UseLetOr or1 or2 =
Either (Either (UseLetInstead or1) or2) Fail

At this point, a let could be used or either of two other options.

type alias UseLetOrFail =
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.

type alias Either a b =
Either a b

Offer a choice between two options.