SiriusStarr/elm-review-no-unsorted/1.1.2

NOTE: the latest version is 1.1.8

NoUnsortedCases

Review Rule

rule : RuleConfig -> Rule

Reports case patterns that are not in the "proper" order.

🔧 Running with --fix will automatically sort the patterns.

The proper order of custom types is the order in which they are defined in your source files, and the order of other patterns may be specified in the rule configuration. See the Configuration section below for more information.

config =
    [ NoUnsortedCases.rule NoUnsortedCases.defaults
    ]

Fail

type Custom
    = Foo
    | Bar
    | Baz


func1 c =
    case c of
        Bar ->
            "bar"


        Foo ->
            "foo"


        Baz ->
            "baz"


func2 cs =
    case cs of
        [ Bar ] ->
            "bar"


        [ Foo ] ->
            "foo"


        [ Foo, Foo ] ->
            "foofoo"


        [ Baz ] ->
            "baz"


        _ ->
            "other"


func3 c =
    case c of
        Nothing ->
            ""


        Just Bar ->
            "bar"


        Just Foo ->
            "foo"


        Just Baz ->
            "baz"


func4 c1 c2 =
    case ( c1, c2 ) of
        ( Foo, Baz ) ->
            "foo baz"


        ( Foo, Bar ) ->
            "foo bar"


        ( Bar, Foo ) ->
            "bar foo"


        ( Baz, Foo ) ->
            "baz foo"


        _ ->
            "other"

Success

type Custom
    = Foo
    | Bar
    | Baz


func1 c =
    case c of
        Foo ->
            "foo"


        Bar ->
            "bar"


        Baz ->
            "baz"


func2 cs =
    case cs of
        [ Foo ] ->
            "foo"


        [ Foo, Foo ] ->
            "foofoo"


        [ Bar ] ->
            "bar"


        [ Baz ] ->
            "baz"


        _ ->
            "other"


func3 c =
    case c of
        Just Foo ->
            "foo"


        Just Bar ->
            "bar"


        Just Baz ->
            "baz"


        Nothing ->
            ""


func4 c1 c2 =
    case ( c1, c2 ) of
        ( Foo, Bar ) ->
            "foo bar"


        ( Foo, Baz ) ->
            "foo baz"


        ( Bar, Foo ) ->
            "bar foo"


        ( Baz, Foo ) ->
            "baz foo"


        _ ->
            "other"

When (not) to enable this rule

This rule is useful when you want to ensure that you pattern match in a consistent, predictable order, that is consistent with the order in which a type was defined, as well as ensuring (optionally) that literal patterns and the like are sorted.

This rule is not useful when you want to be able to write case patterns in different orders throughout your codebase, e.g. if you want to emphasize what pattern is most important at any given point or glean a tiny bit of performance out of matching the more commonly-expected patterns first.

Try it out

You can try this rule out by running the following command:

elm-review --template SiriusStarr/elm-review-no-unsorted/example --rules NoUnsortedCases

Configuration

Configuration for this rule. Create a new one with defaults and use doNotSortLiterals, sortListPatternsByLength, etc. to alter it.

The default configuration, with the following behavior:

  • All custom types are sorted. (This can be restricted by using sortOnlyMatchingTypes.)

  • Literal patterns (String, Int, etc.) are sorted in the natural order for their type.

  • Types imported from dependencies are sorted in declaration order, i.e. in the order they appear in the dependency's source file (or more technically in its documentation); this is identical to the behavior of types defined within your own modules.

  • Lists are sorted elementwise, by comparing the elements sequentially at each position (from left to right).

  • Unsortable patterns can be looked beyond to resolve ties, for example:

func custom =
    case custom of
        Container { field } Bar ->
            not field

        Container { field } Baz ->
            field

        Container { field } Foo ->
            field

will be sorted to

func custom =
    case custom of
        Container { field } Foo ->
            field


        Container { field } Bar ->
            not field


        Container { field } Baz ->
            field

Use doNotSortLiterals, sortListPatternsByLength, etc. to alter any of this behavior, e.g.

config =
    [ NoUnsortedCases.defaults
        |> NoUnsortedCases.doNotSortLiterals
        |> NoUnsortedCases.sortListPatternsByLength
        |> NoUnsortedCases.rule
    ]
sortOnlyMatchingTypes : (String -> String -> Bool) -> RuleConfig -> RuleConfig

Restrict custom type sorting to only those matching a provided predicate. This function takes two strings, the first being the full module name of a type, e.g. "Review.Rule" and the second being the name of a type, e.g. "Rule", and returns a Bool indicating whether the type should be sorted (with True meaning sortable). For example:

Module Foo:

module Foo exposing (Foo(..))


type Foo
    = Foo
    | Bar
    | Baz

Module Main:

module Main exposing (..)


type Msg
    = ButtonPressed
    | ButtonClicked

Module ReviewConfig:

onlyMsg moduleName typeName =
    case ( moduleName, typeName ) of
        ( "Main", "Msg" ) ->
            True


        _ ->
            False


config =
    [ NoUnsortedCases.defaults
        |> NoUnsortedCases.sortOnlyMatchingTypes onlyMsg
        |> NoUnsortedCases.rule
    ]

will sort the following pattern:

case msg of
    ButtonClicked ->
        ( { model | clicked = True }, Cmd.none )


    ButtonPressed ->
        ( { model | pressed = True }, Cmd.none )

but will not sort:

case foo of
    Bar ->
        "bar"


    Baz ->
        "baz"


    Foo ->
        "foo"

Change the behavior of the rule to not sort literal patterns. If literals are not sorted, case expressions that would require sorting literals cannot be sorted and will thus be ignored by the rule.

Do not sort types from dependencies at all. Note that this will render unsortable any patterns requiring types from dependencies to be sorted.

Sort custom types imported from dependencies (including Basics types like Maybe and Bool) alphabetically, rather than by their source order in the dependency's source code.

List patterns may be sorted in one of two ways:

  • Elementwise (default) -- Patterns are sorted by comparing elements sequentially at each position (from left to right). This is the same behavior as List.sort (which is why it is the default).
  • Length First -- Shorter patterns always come before longer pattern, with patterns of the same length sorted elementwise at each position.

Note that uncons patterns are considered the length of their matching list, with wildcard patterns considered to have infinite length for the purposes of sorting. This is necessary to ensure that earlier patterns are not erroneously matched by wildcards.

Elementwise

case list of
    [] ->
        ""


    [ 1 ] ->
        "1"


    [ 1, 1 ] ->
        "11"


    [ 1, 1, 1 ] ->
        "111"


    [ 1, 2 ] ->
        "12"


    [ 1, 3 ] ->
        "13"


    [ 2 ] ->
        "2"


    [ 2, 1 ] ->
        "21"


    [ 2, 2 ] ->
        "22"


    [ 2, 3 ] ->
        "23"


    [ 3 ] ->
        "3"


    _ ->
        "Too many..."

Length First

case list of
    [] ->
        ""


    [ 1 ] ->
        "1"


    [ 2 ] ->
        "2"


    [ 3 ] ->
        "3"


    [ 1, 1 ] ->
        "11"


    [ 1, 2 ] ->
        "12"


    [ 1, 3 ] ->
        "13"


    [ 2, 1 ] ->
        "21"


    [ 2, 2 ] ->
        "22"


    [ 2, 3 ] ->
        "23"


    [ 1, 1, 1 ] ->
        "111"


    _ ->
        "Too many..."

Do not look beyond unsortable patterns, rendering the following unsortable:

func custom =
    case custom of
        Container { field } Bar ->
            not field


        Container { field } Baz ->
            field


        Container { field } Foo ->
            field