rule : RuleConfig -> Review.Rule.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
]
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"
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"
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.
You can try this rule out by running the following command:
elm-review --template SiriusStarr/elm-review-no-unsorted/example --rules NoUnsortedCases
Configuration for this rule. Create a new one with defaults
and use
doNotSortLiterals
, sortListPatternsByLength
, etc. to alter it.
defaults : RuleConfig
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 x =
case x of
T () Bar ->
1
T () Baz ->
2
T () Foo ->
3
will be sorted to
func x =
case x of
T () Foo ->
3
T () Bar ->
1
T () Baz ->
2
Use doNotSortLiterals
, sortListPatternsByLength
, etc. to alter any of this
behavior, e.g.
config =
[ NoUnsortedCases.defaults
|> NoUnsortedCases.doNotSortLiterals
|> NoUnsortedCases.sortListPatternsByLength
|> NoUnsortedCases.rule
]
sortOnlyMatchingTypes : (String -> String -> Basics.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"
doNotSortLiterals : RuleConfig -> RuleConfig
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.
doNotSortTypesFromDependencies : RuleConfig -> RuleConfig
Do not sort types from dependencies at all. Note that this will render unsortable any patterns requiring types from dependencies to be sorted.
sortTypesFromDependenciesAlphabetically : RuleConfig -> RuleConfig
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.
sortListPatternsByLength : RuleConfig -> RuleConfig
List patterns may be sorted in one of two ways:
List.sort
(which is why it is the default).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..."
doNotLookPastUnsortable : RuleConfig -> RuleConfig
Do not look beyond unsortable patterns, i.e. do not tiebreak cases with an unsortable sub-pattern by the next sub-pattern.
For example, given this:
type X
= A
| B () Int
f x =
case x of
B () 2 ->
1
B () 1 ->
2
A ->
3
By default, this will be sorted to:
case x of
A ->
3
-- v The rule sorted these patterns because even though it can't compare the (), 1 comes before 2
B () 1 ->
2
B () 2 ->
1
With doNotLookPastUnsortable
, however, the two B
patterns will be considered
unsortable, so it will instead be sorted to this:
case x of
A ->
3
-- v Comparison stopped for these patterns because the rule didn't look beyond the ()
B () 2 ->
1
B () 1 ->
2
Note that A
is sorted above B
in both cases because it did not require
comparing the unsortable ()
pattern.
It's not clear why you'd ever want to use this, so it will likely be removed in a future major version. Please let me know if you actually find it useful!