elm-review/tests/Simplify.elm

9192 lines
345 KiB
Elm
Raw Permalink Normal View History

2021-08-19 21:57:29 +03:00
module Simplify exposing
( rule
2023-09-03 12:30:45 +03:00
, Configuration, defaults, expectNaN, ignoreCaseOfForTypes
2021-08-19 21:57:29 +03:00
)
{-| Reports when an expression can be simplified.
🔧 Running with `--fix` will automatically remove all the reported errors.
config =
[ Simplify.rule Simplify.defaults
]
@docs rule
2023-09-03 12:30:45 +03:00
@docs Configuration, defaults, expectNaN, ignoreCaseOfForTypes
2021-08-19 21:57:29 +03:00
## Try it out
You can try this rule out by running the following command:
```bash
elm-review --template jfmengels/elm-review-simplify/example --rules Simplify
```
## Simplifications
Below is the list of all kinds of simplifications this rule applies.
### Booleans
x || True
--> True
x || False
--> x
x && True
--> x
x && False
--> False
not True
--> False
2022-09-01 17:15:28 +03:00
not (not x)
--> x
2023-09-03 12:30:45 +03:00
-- for `<`, `>`, `<=`, `>=`, `==` and `/=`
not (a < b)
--> a >= b
2022-09-01 17:15:28 +03:00
### Comparisons
2021-08-19 21:57:29 +03:00
x == True
--> x
x /= False
--> x
not x == not y
--> x == y
anything == anything
--> True
anything /= anything
--> False
{ r | a = 1 } == { r | a = 2 }
--> False
### If expressions
if True then x else y
--> x
if False then x else y
--> y
if condition then x else x
--> x
if condition then True else False
--> condition
if condition then False else True
--> not condition
2022-09-01 17:15:28 +03:00
a =
if condition then
if not condition then
1
else
2
else
3
--> if condition then 2 else 3
2021-08-19 21:57:29 +03:00
### Case expressions
case condition of
True -> x
False -> y
--> if condition then x else y
case condition of
False -> y
True -> x
--> if not condition then x else y
2022-09-01 17:15:28 +03:00
-- only when no variables are introduced in the pattern
-- and no custom types defined in the project are referenced
2021-08-19 21:57:29 +03:00
case value of
2022-09-01 17:15:28 +03:00
Just _ -> x
Nothing -> x
2021-08-19 21:57:29 +03:00
--> x
Destructuring using case expressions
case value of
( x, y ) ->
x + y
-->
let
( x, y ) =
value
in
x + y
### Let expressions
let
a =
1
in
let
b =
1
in
a + b
-->
let
a =
1
b =
1
in
a + b
2021-08-19 21:57:29 +03:00
### Record updates
{ a | b = a.b }
--> a
{ a | b = a.b, c = 1 }
--> { a | c = 1 }
2022-09-01 17:15:28 +03:00
### Field access
{ a = b }.a
--> b
{ a | b = c }.b
--> c
{ a | b = c }.d
--> a.d
(let a = b in c).d
--> let a = b in c.d
2021-08-19 21:57:29 +03:00
### Basics functions
identity x
--> x
f >> identity
--> f
always x y
--> x
f >> always x
--> always x
### Lambdas
2023-06-25 21:01:32 +03:00
(\_ -> x) data
2021-08-19 21:57:29 +03:00
--> x
2023-06-25 21:01:32 +03:00
(\() y -> x) ()
2021-08-19 21:57:29 +03:00
--> (\y -> x)
2023-06-25 21:01:32 +03:00
(\_ y -> x) data
2021-08-19 21:57:29 +03:00
--> (\y -> x)
2023-09-03 12:30:45 +03:00
(\x y -> x + y) n m
-- Reported because simplifiable but not autofixed
2021-08-19 21:57:29 +03:00
### Operators
(++) a b
--> a ++ b
2023-09-03 12:30:45 +03:00
a |> f >> g
--> a |> f |> g
2021-08-19 21:57:29 +03:00
### Numbers
n + 0
--> n
n - 0
--> n
0 - n
--> -n
n * 1
--> n
n / 1
--> n
2023-09-03 12:30:45 +03:00
0 / n
--> 0
2021-08-19 21:57:29 +03:00
-(-n)
--> n
2023-06-25 21:01:32 +03:00
negate (negate n)
--> n
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
n - n
--> 0
2021-08-19 21:57:29 +03:00
### Strings
"a" ++ ""
--> "a"
2023-06-25 21:01:32 +03:00
String.fromList []
--> ""
String.fromList [ a ]
--> String.fromChar a
2021-08-19 21:57:29 +03:00
String.isEmpty ""
--> True
String.isEmpty "a"
--> False
String.concat []
--> ""
String.join str []
--> ""
String.join "" list
--> String.concat list
String.length "abc"
--> 3
String.repeat n ""
--> ""
String.repeat 0 str
--> ""
String.repeat 1 str
--> str
2022-04-17 09:59:11 +03:00
String.replace x y ""
--> ""
String.replace x x z
--> z
String.replace "x" "y" "z"
--> "z" -- only when resulting string is unchanged
2021-08-19 21:57:29 +03:00
String.words ""
--> []
String.lines ""
--> []
String.reverse ""
--> ""
2023-06-25 21:01:32 +03:00
String.reverse (String.reverse str)
--> str
String.slice n n str
--> ""
String.slice n 0 str
--> ""
String.slice a z ""
--> ""
String.left 0 str
--> ""
String.left -1 str
--> ""
String.left n ""
--> ""
String.right 0 str
--> ""
String.right -1 str
--> ""
String.right n ""
--> ""
String.slice 2 1 str
--> ""
String.slice -1 -2 str
--> ""
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
### Maybes
2021-08-19 21:57:29 +03:00
Maybe.map identity x
--> x
Maybe.map f Nothing
--> Nothing
Maybe.map f (Just x)
--> Just (f x)
2023-06-25 21:01:32 +03:00
Maybe.andThen f Nothing
2021-08-19 21:57:29 +03:00
--> Nothing
Maybe.andThen (always Nothing) x
--> Nothing
Maybe.andThen (\a -> Just b) x
--> Maybe.map (\a -> b) x
2022-04-17 09:59:11 +03:00
Maybe.andThen (\a -> if condition a then Just b else Just c) x
--> Maybe.map (\a -> if condition a then b else c) x
2021-08-19 21:57:29 +03:00
Maybe.andThen f (Just x)
--> f x
Maybe.withDefault x Nothing
--> x
Maybe.withDefault x (Just y)
--> y
2023-06-25 21:01:32 +03:00
### Results
2021-08-19 21:57:29 +03:00
Result.map identity x
2023-06-25 21:01:32 +03:00
--> x
2021-08-19 21:57:29 +03:00
Result.map f (Err x)
--> Err x
Result.map f (Ok x)
--> Ok (f x)
2023-06-25 21:01:32 +03:00
Result.mapError identity x
--> x
Result.mapError f (Ok x)
--> Ok x
Result.mapError f (Err x)
--> Err (f x)
2021-08-19 21:57:29 +03:00
Result.andThen f (Err x)
--> Err x
Result.andThen f (Ok x)
--> f x
Result.andThen (\a -> Ok b) x
--> Result.map (\a -> b) x
Result.withDefault x (Err y)
--> x
Result.withDefault x (Ok y)
--> y
2023-06-25 21:01:32 +03:00
Result.toMaybe (Ok x)
--> Just x
Result.toMaybe (Err e)
--> Nothing
2021-08-19 21:57:29 +03:00
### Lists
a :: []
--> [ a ]
a :: [ b ]
--> [ a, b ]
[ a ] ++ list
--> a :: list
[] ++ list
--> list
[ a, b ] ++ [ c ]
--> [ a, b, c ]
2023-06-25 21:01:32 +03:00
List.append [] ys
--> ys
List.append [ a, b ] [ c ]
2021-08-19 21:57:29 +03:00
--> [ a, b, c ]
2023-06-25 21:01:32 +03:00
List.head []
--> Nothing
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
List.head (a :: bToZ)
--> Just a
List.tail []
--> Nothing
List.tail (a :: bToZ)
--> Just bToZ
List.member a []
--> False
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
List.member a [ a, b, c ]
--> True
List.member a [ b ]
--> a == b
List.map f [] -- same for most List functions like List.filter, List.filterMap, ...
--> []
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
List.map identity list
2021-08-19 21:57:29 +03:00
--> list
2023-09-03 12:30:45 +03:00
List.filter (always True) list
2021-08-19 21:57:29 +03:00
--> list
List.filter (always False) list
--> []
List.filterMap Just list
--> list
2022-04-17 09:59:11 +03:00
List.filterMap (\a -> if condition a then Just b else Just c) list
--> List.map (\a -> if condition a then b else c) list
2021-08-19 21:57:29 +03:00
List.filterMap (always Nothing) list
--> []
2023-06-25 21:01:32 +03:00
List.filterMap identity (List.map f list)
--> List.filterMap f list
2022-04-17 09:59:11 +03:00
List.filterMap identity [ Just x, Just y ]
--> [ x, y ]
List.concat [ [ a, b ], [ c ] ]
--> [ a, b, c ]
List.concat [ a, [ 1 ], [ 2 ] ]
--> List.concat [ a, [ 1, 2 ] ]
2023-09-03 12:30:45 +03:00
List.concat [ a, [], b ]
--> List.concat [ a, b ]
2023-06-25 21:01:32 +03:00
List.concatMap identity list
2022-04-17 09:59:11 +03:00
--> List.concat list
List.concatMap (\a -> [ b ]) list
--> List.map (\a -> b) list
2023-06-25 21:01:32 +03:00
List.concatMap f [ x ]
--> f x
2022-04-17 09:59:11 +03:00
List.concatMap (always []) list
--> []
2023-06-25 21:01:32 +03:00
List.concat (List.map f list)
--> List.concatMap f list
2022-04-17 09:59:11 +03:00
List.indexedMap (\_ value -> f value) list
--> List.map (\value -> f value) list
2021-08-19 21:57:29 +03:00
List.isEmpty []
--> True
List.isEmpty [ a ]
--> False
List.isEmpty (x :: xs)
--> False
2023-06-25 21:01:32 +03:00
List.sum []
--> 0
List.sum [ a ]
--> a
List.product []
--> 1
List.product [ a ]
--> a
List.minimum []
--> Nothing
List.minimum [ a ]
--> Just a
List.maximum []
--> Nothing
List.maximum [ a ]
--> Just a
-- The following simplifications for List.foldl also work for List.foldr
List.foldl f x []
--> x
List.foldl (\_ soFar -> soFar) x list
--> x
List.foldl (+) 0 list
--> List.sum list
List.foldl (+) initial list
--> initial + List.sum list
List.foldl (*) 1 list
--> List.product list
List.foldl (*) 0 list
--> 0
List.foldl (*) initial list
--> initial * List.product list
List.foldl (&&) True list
--> List.all identity list
List.foldl (&&) False list
--> False
List.foldl (||) False list
--> List.any identity list
List.foldl (||) True list
--> True
List.all f []
2021-08-19 21:57:29 +03:00
--> True
List.all (always True) list
--> True
2023-06-25 21:01:32 +03:00
List.any f []
2021-08-19 21:57:29 +03:00
--> True
List.any (always False) list
2023-06-25 21:01:32 +03:00
--> False
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
List.any ((==) x) list
--> List.member x list
2021-08-19 21:57:29 +03:00
List.range 6 3
--> []
2023-06-25 21:01:32 +03:00
List.length [ a, b, c ]
--> 3
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
List.repeat 0 x
2021-08-19 21:57:29 +03:00
--> []
2023-06-25 21:01:32 +03:00
List.partition f []
2021-08-19 21:57:29 +03:00
--> ( [], [] )
List.partition (always True) list
--> ( list, [] )
2023-09-03 12:30:45 +03:00
List.partition (always False) list
--> ( [], list )
2023-06-25 21:01:32 +03:00
List.take 0 list
--> []
List.drop 0 list
--> list
List.reverse (List.reverse list)
--> list
2023-09-03 12:30:45 +03:00
List.sortBy (always a) list
2023-06-25 21:01:32 +03:00
--> list
List.sortBy identity list
--> List.sort list
List.sortWith (\_ _ -> LT) list
--> List.reverse list
List.sortWith (\_ _ -> EQ) list
--> list
List.sortWith (\_ _ -> GT) list
--> list
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
-- The following simplifications for List.sort also work for List.sortBy f and List.sortWith f
List.sort []
2021-08-19 21:57:29 +03:00
--> []
2023-06-25 21:01:32 +03:00
List.sort [ a ]
--> [ a ]
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
-- same for up to List.map5 when any list is empty
List.map2 f xs []
--> []
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
List.map2 f [] ys
--> []
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
List.unzip []
--> ( [], [] )
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
### Sets
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
Set.map f Set.empty -- same for Set.filter, Set.remove...
2021-08-19 21:57:29 +03:00
--> Set.empty
2022-09-01 17:15:28 +03:00
Set.map identity set
--> set
2021-08-19 21:57:29 +03:00
Set.isEmpty Set.empty
--> True
Set.member x Set.empty
--> False
Set.fromList []
--> Set.empty
2023-06-25 21:01:32 +03:00
Set.fromList [ a ]
--> Set.singleton a
2021-08-19 21:57:29 +03:00
Set.toList Set.empty
--> []
Set.length Set.empty
--> 0
Set.intersect Set.empty set
--> Set.empty
Set.diff Set.empty set
--> Set.empty
Set.diff set Set.empty
--> set
Set.union set Set.empty
--> set
Set.insert x Set.empty
--> Set.singleton x
2023-06-25 21:01:32 +03:00
-- same for foldr
List.foldl f x (Set.toList set)
--> Set.foldl f x set
Set.partition f Set.empty
2021-08-19 21:57:29 +03:00
--> ( Set.empty, Set.empty )
Set.partition (always True) set
2022-09-01 17:15:28 +03:00
--> ( set, Set.empty )
2021-08-19 21:57:29 +03:00
### Dict
Dict.isEmpty Dict.empty
--> True
Dict.fromList []
--> Dict.empty
Dict.toList Dict.empty
--> []
Dict.size Dict.empty
--> 0
2022-09-01 17:15:28 +03:00
Dict.member x Dict.empty
--> False
2023-06-25 21:01:32 +03:00
Dict.partition f Dict.empty
--> ( Dict.empty, Dict.empty )
Dict.partition (always True) dict
--> ( dict, Dict.empty )
Dict.partition (always False) dict
--> ( Dict.empty, dict )
List.map Tuple.first (Dict.toList dict)
--> Dict.keys dict
List.map Tuple.second (Dict.toList dict)
--> Dict.values dict
2021-08-19 21:57:29 +03:00
### Cmd / Sub
All of these also apply for `Sub`.
Cmd.batch []
--> Cmd.none
Cmd.batch [ a ]
--> a
Cmd.batch [ a, Cmd.none, b ]
--> Cmd.batch [ a, b ]
Cmd.map identity cmd
--> cmd
2023-06-25 21:01:32 +03:00
Cmd.map f Cmd.none
2021-08-19 21:57:29 +03:00
--> Cmd.none
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
### Html.Attributes
Html.Attributes.classList [ x, y, ( z, False ) ]
--> Html.Attributes.classList [ x, y ]
Html.Attributes.classList [ ( onlyOneThing, True ) ]
--> Html.Attributes.class onlyOneThing
2022-04-17 09:59:11 +03:00
### Json.Decode
2023-06-25 21:01:32 +03:00
Json.Decode.oneOf [ a ]
2022-04-17 09:59:11 +03:00
--> a
### Parser
2023-06-25 21:01:32 +03:00
Parser.oneOf [ a ]
2022-04-17 09:59:11 +03:00
--> a
2023-09-03 12:30:45 +03:00
### Random
Random.uniform a []
--> Random.constant a
Random.weighted ( weight, a ) []
--> Random.constant a
Random.weighted tuple []
--> Random.constant (Tuple.first tuple)
Random.list 0 generator
--> Random.constant []
Random.list 1 generator
--> Random.map List.singleton generator
Random.list n (Random.constant el)
--> Random.constant (List.repeat n el)
Random.map identity generator
--> generator
Random.map (always a) generator
--> Random.constant a
Random.map f (Random.constant x)
--> Random.constant (f x)
2021-08-19 21:57:29 +03:00
-}
import Dict exposing (Dict)
2022-09-01 17:15:28 +03:00
import Elm.Docs
2023-06-25 21:01:32 +03:00
import Elm.Project exposing (Exposed)
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Exposing as Exposing
2023-09-03 12:30:45 +03:00
import Elm.Syntax.Expression as Expression exposing (Expression)
2023-06-25 21:01:32 +03:00
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.Module
2021-08-19 21:57:29 +03:00
import Elm.Syntax.ModuleName exposing (ModuleName)
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
import Elm.Syntax.Range as Range exposing (Location, Range)
import Review.Fix as Fix exposing (Fix)
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
import Review.Project.Dependency as Dependency exposing (Dependency)
import Review.Rule as Rule exposing (Error, Rule)
import Set exposing (Set)
2023-09-03 12:30:45 +03:00
import Simplify.AstHelpers as AstHelpers exposing (emptyStringAsString, qualifiedToString)
2022-09-01 17:15:28 +03:00
import Simplify.Evaluate as Evaluate
import Simplify.Infer as Infer
2022-04-17 09:59:11 +03:00
import Simplify.Match as Match exposing (Match(..))
2021-08-19 21:57:29 +03:00
import Simplify.Normalize as Normalize
2022-09-01 17:15:28 +03:00
import Simplify.RangeDict as RangeDict exposing (RangeDict)
2021-08-19 21:57:29 +03:00
{-| Rule to simplify Elm code.
-}
rule : Configuration -> Rule
rule (Configuration config) =
2022-09-01 17:15:28 +03:00
Rule.newProjectRuleSchema "Simplify" initialContext
|> Rule.withDirectDependenciesProjectVisitor (dependenciesVisitor (Set.fromList config.ignoreConstructors))
2023-09-03 12:30:45 +03:00
|> Rule.withModuleVisitor (moduleVisitor config)
2023-06-25 21:01:32 +03:00
|> Rule.withContextFromImportedModules
2022-09-01 17:15:28 +03:00
|> Rule.withModuleContextUsingContextCreator
{ fromProjectToModule = fromProjectToModule
, fromModuleToProject = fromModuleToProject
2023-06-25 21:01:32 +03:00
, foldProjectContexts = foldProjectContexts
2022-09-01 17:15:28 +03:00
}
|> Rule.providesFixesForProjectRule
2022-09-01 17:15:28 +03:00
|> Rule.fromProjectRuleSchema
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
moduleVisitor : { config | expectNaN : Bool } -> Rule.ModuleRuleSchema schemaState ModuleContext -> Rule.ModuleRuleSchema { schemaState | hasAtLeastOneVisitor : () } ModuleContext
moduleVisitor config schema =
2021-08-19 21:57:29 +03:00
schema
2023-09-03 12:30:45 +03:00
|> Rule.withCommentsVisitor (\comments context -> ( [], commentsVisitor comments context ))
2023-06-25 21:01:32 +03:00
|> Rule.withDeclarationListVisitor (\decls context -> ( [], declarationListVisitor decls context ))
2022-09-01 17:15:28 +03:00
|> Rule.withDeclarationEnterVisitor (\node context -> ( [], declarationVisitor node context ))
2023-09-03 12:30:45 +03:00
|> Rule.withExpressionEnterVisitor (\expressionNode context -> expressionVisitor expressionNode config context)
2022-09-01 17:15:28 +03:00
|> Rule.withExpressionExitVisitor (\node context -> ( [], expressionExitVisitor node context ))
2021-08-19 21:57:29 +03:00
-- CONFIGURATION
{-| Configuration for this rule. Create a new one with [`defaults`](#defaults) and use [`ignoreCaseOfForTypes`](#ignoreCaseOfForTypes) to alter it.
-}
type Configuration
= Configuration
{ ignoreConstructors : List String
2023-09-03 12:30:45 +03:00
, expectNaN : Bool
2021-08-19 21:57:29 +03:00
}
2023-09-03 12:30:45 +03:00
{-| Default configuration for this rule.
The rule aims tries to improve the code through simplifications that don't impact the behavior. An exception to this are
when the presence of `NaN` values
Use [`expectNaN`](#expectNaN) if you want to opt out of changes that can impact the behaviour of your code if you expect to work with `NaN` values.
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Use [`ignoreCaseOfForTypes`](#ignoreCaseOfForTypes) if you want to prevent simplifying case expressions that work on custom types defined in dependencies.
config =
[ Simplify.rule Simplify.defaults
]
-- or
2021-08-19 21:57:29 +03:00
config =
[ Simplify.defaults
2023-09-03 12:30:45 +03:00
|> Simplify.expectNaN
2021-08-19 21:57:29 +03:00
|> Simplify.ignoreCaseOfForTypes [ "Module.Name.Type" ]
|> Simplify.rule
]
-}
defaults : Configuration
defaults =
2023-09-03 12:30:45 +03:00
Configuration
{ ignoreConstructors = []
, expectNaN = False
}
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
{-| Ignore some reports about types from dependencies used in case expressions.
2021-08-19 21:57:29 +03:00
This rule simplifies the following construct:
module Module.Name exposing (..)
case value of
2022-09-01 17:15:28 +03:00
Just _ -> x
Nothing -> x
2021-08-19 21:57:29 +03:00
--> x
2022-09-01 17:15:28 +03:00
(Since `v2.0.19`) it will not try to simplify the case expression when some of the patterns references custom types constructors
defined in the project. It will only do so for custom types that are defined in dependencies (including `elm/core`).
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
If you do happen to want to disable this simplification for a type `Module.Name.Type`, you can configure the rule like this:
2021-08-19 21:57:29 +03:00
config =
[ Simplify.defaults
|> Simplify.ignoreCaseOfForTypes [ "Module.Name.Type" ]
|> Simplify.rule
]
I personally don't recommend to use this function too much, because this could be a sign of premature abstraction, and because
I think that often [You Aren't Gonna Need this code](https://jfmengels.net/safe-dead-code-removal/#yagni-you-arent-gonna-need-it).
2022-09-01 17:15:28 +03:00
Please let me know by opening an issue if you do use this function, I am very curious to know;
2021-08-19 21:57:29 +03:00
-}
ignoreCaseOfForTypes : List String -> Configuration -> Configuration
ignoreCaseOfForTypes ignoreConstructors (Configuration config) =
2023-09-03 12:30:45 +03:00
Configuration { ignoreConstructors = ignoreConstructors ++ config.ignoreConstructors, expectNaN = config.expectNaN }
{-| Usually, `elm-review-simplify` will only suggest simplifications that are safe to apply without risk of changing the original behavior.
However, when encountering [`NaN`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NaN)
values, some simplifications can actually impact behavior.
For instance, the following expression will evaluate to `True`:
x == x
--> True
However, if `x` is `NaN` or a value containing `NaN` then the expression will evaluate to `False`:
-- given x = NaN
x == x
--> False
-- given x = { a = ( NaN, 0 ) }
x == x
--> False
Given the potential presence of `NaN`, some simplifications become unsafe to apply:
- `x == x` to `True`
- `List.member x [ x ]` to `True`
- `n * 0` to `0`
This special value is hard to recreate in Elm code both intentionally and unintentionally,
and it's therefore unlikely to be found in your application,
which is why the rule applies these simplifications by defaults.
If you somehow expect to create and encounter `NaN` values in your codebase, then you can use this function to disable these simplifications altogether.
config =
[ Simplify.defaults
|> Simplify.expectNaN
|> Simplify.rule
]
-}
expectNaN : Configuration -> Configuration
expectNaN (Configuration config) =
Configuration { ignoreConstructors = config.ignoreConstructors, expectNaN = True }
2021-08-19 21:57:29 +03:00
-- CONTEXT
type alias ProjectContext =
2022-09-01 17:15:28 +03:00
{ customTypesToReportInCases : Set ( ModuleName, ConstructorName )
2023-06-25 21:01:32 +03:00
, exposedVariants : Dict ModuleName (Set String)
2021-08-19 21:57:29 +03:00
}
type alias ModuleContext =
{ lookupTable : ModuleNameLookupTable
, moduleName : ModuleName
2023-06-25 21:01:32 +03:00
, exposedVariantTypes : Exposed
2023-09-03 12:30:45 +03:00
, commentRanges : List Range
2023-06-25 21:01:32 +03:00
, moduleBindings : Set String
, localBindings : RangeDict (Set String)
, branchLocalBindings : RangeDict (Set String)
, rangesToIgnore : RangeDict ()
, rightSidesOfPlusPlus : RangeDict ()
2022-09-01 17:15:28 +03:00
, customTypesToReportInCases : Set ( ModuleName, ConstructorName )
2021-08-19 21:57:29 +03:00
, localIgnoredCustomTypes : List Constructor
, constructorsToIgnore : Set ( ModuleName, String )
2022-09-01 17:15:28 +03:00
, inferredConstantsDict : RangeDict Infer.Inferred
, inferredConstants : ( Infer.Inferred, List Infer.Inferred )
, extractSourceCode : Range -> String
2023-06-25 21:01:32 +03:00
, exposedVariants : Set String
, importLookup : ImportLookup
}
type alias ImportLookup =
Dict
ModuleName
{ alias : Maybe ModuleName
, exposed : Exposed -- includes names of found variants
}
type alias QualifyResources a =
{ a
| importLookup : ImportLookup
, moduleBindings : Set String
, localBindings : RangeDict (Set String)
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
type Exposed
= ExposedAll
| ExposedSome (Set String)
isExposedFrom : Exposed -> String -> Bool
isExposedFrom exposed name =
case exposed of
ExposedAll ->
True
ExposedSome some ->
Set.member name some
2022-09-01 17:15:28 +03:00
type alias ConstructorName =
String
2021-08-19 21:57:29 +03:00
type alias Constructor =
{ moduleName : ModuleName
, name : String
, constructors : List String
}
initialContext : ProjectContext
initialContext =
2022-09-01 17:15:28 +03:00
{ customTypesToReportInCases = Set.empty
2023-06-25 21:01:32 +03:00
, exposedVariants = Dict.empty
2021-08-19 21:57:29 +03:00
}
fromModuleToProject : Rule.ContextCreator ModuleContext ProjectContext
fromModuleToProject =
2023-06-25 21:01:32 +03:00
Rule.initContextCreator
(\moduleContext ->
{ customTypesToReportInCases = Set.empty
, exposedVariants =
Dict.singleton moduleContext.moduleName
moduleContext.exposedVariants
}
)
2021-08-19 21:57:29 +03:00
fromProjectToModule : Rule.ContextCreator ProjectContext ModuleContext
fromProjectToModule =
Rule.initContextCreator
2023-06-25 21:01:32 +03:00
(\lookupTable metadata extractSourceCode fullAst projectContext ->
let
moduleExposedVariantTypes : Exposed
moduleExposedVariantTypes =
moduleExposingContext fullAst.moduleDefinition
imports : ImportLookup
imports =
List.foldl
(\import_ importLookup ->
let
importInfo : { moduleName : ModuleName, exposed : Exposed, alias : Maybe ModuleName }
importInfo =
importContext import_
in
insertImport importInfo.moduleName { alias = importInfo.alias, exposed = importInfo.exposed } importLookup
)
implicitImports
fullAst.imports
in
2021-08-19 21:57:29 +03:00
{ lookupTable = lookupTable
2022-09-01 17:15:28 +03:00
, moduleName = Rule.moduleNameFromMetadata metadata
2023-06-25 21:01:32 +03:00
, exposedVariantTypes = moduleExposedVariantTypes
, importLookup =
createImportLookup
{ imports = imports
, importExposedVariants = projectContext.exposedVariants
}
2023-09-03 12:30:45 +03:00
, commentRanges = []
2023-06-25 21:01:32 +03:00
, moduleBindings = Set.empty
, localBindings = RangeDict.empty
, branchLocalBindings = RangeDict.empty
, rangesToIgnore = RangeDict.empty
, rightSidesOfPlusPlus = RangeDict.empty
2021-08-19 21:57:29 +03:00
, localIgnoredCustomTypes = []
2022-09-01 17:15:28 +03:00
, customTypesToReportInCases = projectContext.customTypesToReportInCases
, constructorsToIgnore = Set.empty
, inferredConstantsDict = RangeDict.empty
, inferredConstants = ( Infer.empty, [] )
, extractSourceCode = extractSourceCode
2023-06-25 21:01:32 +03:00
, exposedVariants = Set.empty
2021-08-19 21:57:29 +03:00
}
)
|> Rule.withModuleNameLookupTable
2022-09-01 17:15:28 +03:00
|> Rule.withMetadata
|> Rule.withSourceCodeExtractor
2023-06-25 21:01:32 +03:00
|> Rule.withFullAst
importContext : Node Import -> { moduleName : ModuleName, exposed : Exposed, alias : Maybe ModuleName }
importContext importNode =
let
import_ : Import
import_ =
Node.value importNode
in
{ moduleName = import_.moduleName |> Node.value
, alias =
import_.moduleAlias |> Maybe.map Node.value
, exposed =
case import_.exposingList of
Nothing ->
ExposedSome Set.empty
Just (Node _ existingExposing) ->
case existingExposing of
Exposing.All _ ->
ExposedAll
Exposing.Explicit exposes ->
ExposedSome
(Set.fromList
(List.map
(\(Node _ expose) -> AstHelpers.nameOfExpose expose)
exposes
)
)
}
createImportLookup :
{ imports : Dict ModuleName { alias : Maybe ModuleName, exposed : Exposed }
, importExposedVariants : Dict ModuleName (Set String)
}
-> ImportLookup
createImportLookup context =
context.imports
|> Dict.map
(\moduleName import_ ->
case import_.exposed of
ExposedAll ->
import_
ExposedSome some ->
case Dict.get moduleName context.importExposedVariants of
Nothing ->
import_
Just importExposedVariants ->
{ import_
| exposed =
ExposedSome
(Set.union some importExposedVariants)
}
)
moduleExposingContext : Node Elm.Syntax.Module.Module -> Exposed
moduleExposingContext moduleHeader =
case Elm.Syntax.Module.exposingList (Node.value moduleHeader) of
Exposing.All _ ->
ExposedAll
Exposing.Explicit some ->
ExposedSome
(List.foldl
(\(Node _ expose) acc ->
case AstHelpers.getTypeExposeIncludingVariants expose of
Just name ->
Set.insert name acc
Nothing ->
acc
)
Set.empty
some
)
foldProjectContexts : ProjectContext -> ProjectContext -> ProjectContext
foldProjectContexts newContext previousContext =
{ customTypesToReportInCases = Set.empty
, exposedVariants = Dict.union newContext.exposedVariants previousContext.exposedVariants
}
2021-08-19 21:57:29 +03:00
-- DEPENDENCIES VISITOR
2022-09-01 17:15:28 +03:00
dependenciesVisitor : Set String -> Dict String Dependency -> ProjectContext -> ( List (Error scope), ProjectContext )
2023-06-25 21:01:32 +03:00
dependenciesVisitor typeNamesAsStrings dict context =
2022-09-01 17:15:28 +03:00
let
modules : List Elm.Docs.Module
modules =
2021-08-19 21:57:29 +03:00
dict
|> Dict.values
|> List.concatMap Dependency.modules
2022-09-01 17:15:28 +03:00
unions : Set String
unions =
List.concatMap (\module_ -> List.map (\union -> module_.name ++ "." ++ union.name) module_.unions) modules
|> Set.fromList
unknownTypesToIgnore : List String
unknownTypesToIgnore =
Set.diff typeNamesAsStrings unions
|> Set.toList
customTypesToReportInCases : Set ( ModuleName, String )
customTypesToReportInCases =
modules
2021-08-19 21:57:29 +03:00
|> List.concatMap
(\mod ->
let
moduleName : ModuleName
moduleName =
2023-06-25 21:01:32 +03:00
AstHelpers.moduleNameFromString mod.name
2021-08-19 21:57:29 +03:00
in
mod.unions
2022-09-01 17:15:28 +03:00
|> List.filter (\union -> not (Set.member (mod.name ++ "." ++ union.name) typeNamesAsStrings))
|> List.concatMap (\union -> union.tags)
|> List.map (\( tagName, _ ) -> ( moduleName, tagName ))
2021-08-19 21:57:29 +03:00
)
|> Set.fromList
2023-06-25 21:01:32 +03:00
dependencyExposedVariants : Dict ModuleName (Set String)
dependencyExposedVariants =
List.foldl
(\moduleDoc acc ->
Dict.insert
(AstHelpers.moduleNameFromString moduleDoc.name)
(moduleDoc.unions
|> List.concatMap
(\union ->
union.tags
|> List.map (\( variantName, _ ) -> variantName)
)
|> Set.fromList
)
acc
)
context.exposedVariants
modules
2021-08-19 21:57:29 +03:00
in
2022-09-01 17:15:28 +03:00
( if List.isEmpty unknownTypesToIgnore then
2021-08-19 21:57:29 +03:00
[]
2022-09-01 17:15:28 +03:00
else
[ errorForUnknownIgnoredConstructor unknownTypesToIgnore ]
2023-06-25 21:01:32 +03:00
, { customTypesToReportInCases = customTypesToReportInCases
, exposedVariants = dependencyExposedVariants
}
2021-08-19 21:57:29 +03:00
)
2022-09-01 17:15:28 +03:00
errorForUnknownIgnoredConstructor : List String -> Error scope
errorForUnknownIgnoredConstructor list =
Rule.globalError
{ message = "Could not find type names: " ++ (String.join ", " <| List.map wrapInBackticks list)
, details =
[ "I expected to find these custom types in the dependencies, but I could not find them."
, "Please check whether these types and have not been removed, and if so, remove them from the configuration of this rule."
, "If you find that these types have been moved or renamed, please update your configuration."
, "Note that I may have provided fixes for things you didn't wish to be fixed, so you might want to undo the changes I have applied."
, "Also note that the configuration for this rule changed in v2.0.19: types that are custom to your project are ignored by default, so this configuration setting can only be used to avoid simplifying case expressions that use custom types defined in dependencies."
]
}
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
-- COMMENTS VISITOR
commentsVisitor : List (Node String) -> ModuleContext -> ModuleContext
commentsVisitor comments context =
{ context | commentRanges = List.map Node.range comments }
2023-06-25 21:01:32 +03:00
-- DECLARATION LIST VISITOR
declarationListVisitor : List (Node Declaration) -> ModuleContext -> ModuleContext
declarationListVisitor declarationList context =
{ context
| moduleBindings = AstHelpers.declarationListBindings declarationList
}
2021-08-19 21:57:29 +03:00
-- DECLARATION VISITOR
2023-06-25 21:01:32 +03:00
declarationVisitor : Node Declaration -> ModuleContext -> ModuleContext
declarationVisitor declarationNode context =
case Node.value declarationNode of
Declaration.CustomTypeDeclaration variantType ->
let
variantTypeName : String
variantTypeName =
Node.value variantType.name
in
if isExposedFrom context.exposedVariantTypes variantTypeName then
let
exposedVariants : Set String
exposedVariants =
List.foldl
(\(Node _ variant) acc -> Set.insert (Node.value variant.name) acc)
context.exposedVariants
variantType.constructors
in
{ context | exposedVariants = exposedVariants }
else
context
Declaration.FunctionDeclaration functionDeclaration ->
{ context
| rangesToIgnore = RangeDict.empty
, rightSidesOfPlusPlus = RangeDict.empty
, inferredConstantsDict = RangeDict.empty
, localBindings =
RangeDict.singleton
(Node.range functionDeclaration.declaration)
(AstHelpers.patternListBindings (Node.value functionDeclaration.declaration).arguments)
}
_ ->
context
2021-08-19 21:57:29 +03:00
-- EXPRESSION VISITOR
2023-09-03 12:30:45 +03:00
expressionVisitor : Node Expression -> { config | expectNaN : Bool } -> ModuleContext -> ( List (Error {}), ModuleContext )
expressionVisitor node config context =
2022-09-01 17:15:28 +03:00
let
2023-06-25 21:01:32 +03:00
expressionRange : Range
expressionRange =
Node.range node
contextWithInferredConstants : ModuleContext
contextWithInferredConstants =
case RangeDict.get expressionRange context.inferredConstantsDict of
Nothing ->
context
2022-09-01 17:15:28 +03:00
Just inferredConstants ->
let
( previous, previousStack ) =
context.inferredConstants
in
2023-06-25 21:01:32 +03:00
{ context
| inferredConstants = ( inferredConstants, previous :: previousStack )
}
2022-09-01 17:15:28 +03:00
in
2023-06-25 21:01:32 +03:00
if RangeDict.member expressionRange context.rangesToIgnore then
( [], contextWithInferredConstants )
2021-08-19 21:57:29 +03:00
else
let
2023-09-03 12:30:45 +03:00
expression : Expression
expression =
Node.value node
2023-06-25 21:01:32 +03:00
withExpressionSurfaceBindings : RangeDict (Set String)
withExpressionSurfaceBindings =
2023-09-03 12:30:45 +03:00
RangeDict.insert expressionRange (expressionSurfaceBindings expression) context.localBindings
2023-06-25 21:01:32 +03:00
withNewBranchLocalBindings : RangeDict (Set String)
withNewBranchLocalBindings =
2023-09-03 12:30:45 +03:00
RangeDict.union (expressionBranchLocalBindings expression)
2023-06-25 21:01:32 +03:00
context.branchLocalBindings
contextWithInferredConstantsAndLocalBindings : ModuleContext
contextWithInferredConstantsAndLocalBindings =
case RangeDict.get expressionRange context.branchLocalBindings of
Nothing ->
{ contextWithInferredConstants
| localBindings = withExpressionSurfaceBindings
, branchLocalBindings =
withNewBranchLocalBindings
}
Just currentBranchLocalBindings ->
{ contextWithInferredConstants
| localBindings =
RangeDict.insert expressionRange currentBranchLocalBindings withExpressionSurfaceBindings
, branchLocalBindings =
RangeDict.remove expressionRange withNewBranchLocalBindings
}
2023-09-03 12:30:45 +03:00
expressionChecked : { errors : List (Error {}), rangesToIgnore : RangeDict (), rightSidesOfPlusPlus : RangeDict (), inferredConstants : List ( Range, Infer.Inferred ) }
expressionChecked =
expressionVisitorHelp node config contextWithInferredConstantsAndLocalBindings
2021-08-19 21:57:29 +03:00
in
2023-09-03 12:30:45 +03:00
( expressionChecked.errors
2023-06-25 21:01:32 +03:00
, { contextWithInferredConstantsAndLocalBindings
2023-09-03 12:30:45 +03:00
| rangesToIgnore = RangeDict.union expressionChecked.rangesToIgnore context.rangesToIgnore
, rightSidesOfPlusPlus = RangeDict.union expressionChecked.rightSidesOfPlusPlus context.rightSidesOfPlusPlus
2023-06-25 21:01:32 +03:00
, inferredConstantsDict =
List.foldl (\( range, constants ) acc -> RangeDict.insert range constants acc)
contextWithInferredConstants.inferredConstantsDict
2023-09-03 12:30:45 +03:00
expressionChecked.inferredConstants
2021-08-19 21:57:29 +03:00
}
)
2023-09-03 12:30:45 +03:00
{-| From the `elm/core` readme:
>
> ### Default Imports
> The modules in this package are so common, that some of them are imported by default in all Elm files. So it is as if every Elm file starts with these imports:
>
> import Basics exposing (..)
> import List exposing (List, (::))
> import Maybe exposing (Maybe(..))
> import Result exposing (Result(..))
> import String exposing (String)
> import Char exposing (Char)
> import Tuple
> import Debug
> import Platform exposing (Program)
> import Platform.Cmd as Cmd exposing (Cmd)
> import Platform.Sub as Sub exposing (Sub)
-}
implicitImports : ImportLookup
implicitImports =
[ ( [ "Basics" ], { alias = Nothing, exposed = ExposedAll } )
, ( [ "List" ], { alias = Nothing, exposed = ExposedSome (Set.fromList [ "List", "(::)" ]) } )
, ( [ "Maybe" ], { alias = Nothing, exposed = ExposedSome (Set.fromList [ "Maybe", "Just", "Nothing" ]) } )
, ( [ "Result" ], { alias = Nothing, exposed = ExposedSome (Set.fromList [ "Result", "Ok", "Err" ]) } )
, ( [ "String" ], { alias = Nothing, exposed = ExposedSome (Set.singleton "String") } )
, ( [ "Char" ], { alias = Nothing, exposed = ExposedSome (Set.singleton "Char") } )
, ( [ "Tuple" ], { alias = Nothing, exposed = ExposedSome Set.empty } )
, ( [ "Debug" ], { alias = Nothing, exposed = ExposedSome Set.empty } )
, ( [ "Platform" ], { alias = Nothing, exposed = ExposedSome (Set.singleton "Program") } )
, ( [ "Platform", "Cmd" ], { alias = Just [ "Cmd" ], exposed = ExposedSome (Set.singleton "Cmd") } )
, ( [ "Platform", "Sub" ], { alias = Just [ "Sub" ], exposed = ExposedSome (Set.singleton "Sub") } )
]
|> Dict.fromList
{-| Merge a given new import with an existing import lookup.
This is strongly preferred over Dict.insert since the implicit default imports can be overridden
-}
insertImport : ModuleName -> { alias : Maybe ModuleName, exposed : Exposed } -> ImportLookup -> ImportLookup
insertImport moduleName importInfoToAdd importLookup =
Dict.update moduleName
(\existingImport ->
let
newImportInfo : { alias : Maybe ModuleName, exposed : Exposed }
newImportInfo =
case existingImport of
Nothing ->
importInfoToAdd
Just import_ ->
{ alias = findMap .alias [ import_, importInfoToAdd ]
, exposed = exposedMerge ( import_.exposed, importInfoToAdd.exposed )
}
in
Just newImportInfo
)
importLookup
exposedMerge : ( Exposed, Exposed ) -> Exposed
exposedMerge exposedTuple =
case exposedTuple of
( ExposedAll, _ ) ->
ExposedAll
( ExposedSome _, ExposedAll ) ->
ExposedAll
( ExposedSome aSet, ExposedSome bSet ) ->
ExposedSome (Set.union aSet bSet)
qualify : ( ModuleName, String ) -> QualifyResources a -> ( ModuleName, String )
qualify ( moduleName, name ) qualifyResources =
let
qualification : ModuleName
qualification =
case qualifyResources.importLookup |> Dict.get moduleName of
Nothing ->
moduleName
Just import_ ->
let
moduleImportedName : ModuleName
moduleImportedName =
import_.alias |> Maybe.withDefault moduleName
in
if not (isExposedFrom import_.exposed name) then
moduleImportedName
else
let
isShadowed : Bool
isShadowed =
isBindingInScope qualifyResources name
in
if isShadowed then
moduleImportedName
else
[]
in
( qualification, name )
isBindingInScope :
{ a
| moduleBindings : Set String
, localBindings : RangeDict (Set String)
}
-> String
-> Bool
isBindingInScope resources name =
Set.member name resources.moduleBindings
|| RangeDict.any (\bindings -> Set.member name bindings) resources.localBindings
2023-06-25 21:01:32 +03:00
{-| Whenever you add ranges on expression enter, the same ranges should be removed on expression exit.
Having one function finding unique ranges and a function for extracting bindings there ensures said consistency.
An alternative approach would be to use some kind of tree structure
with parent and sub ranges and bindings as leaves (maybe a "trie", tho I've not seen one as an elm package).
Removing all bindings for an expression's range on leave would then be trivial
-}
expressionSurfaceBindings : Expression -> Set String
expressionSurfaceBindings expression =
case expression of
Expression.LambdaExpression lambda ->
AstHelpers.patternListBindings lambda.args
Expression.LetExpression letBlock ->
AstHelpers.letDeclarationListBindings letBlock.declarations
_ ->
Set.empty
expressionBranchLocalBindings : Expression -> RangeDict (Set String)
expressionBranchLocalBindings expression =
case expression of
Expression.CaseExpression caseBlock ->
RangeDict.mapFromList
(\( Node _ pattern, Node resultRange _ ) ->
( resultRange
, AstHelpers.patternBindings pattern
)
)
caseBlock.cases
Expression.LetExpression letBlock ->
List.foldl
(\(Node _ letDeclaration) acc ->
case letDeclaration of
Expression.LetFunction letFunctionOrValueDeclaration ->
RangeDict.insert
(Node.range (Node.value letFunctionOrValueDeclaration.declaration).expression)
(AstHelpers.patternListBindings
(Node.value letFunctionOrValueDeclaration.declaration).arguments
)
acc
_ ->
acc
)
RangeDict.empty
letBlock.declarations
_ ->
RangeDict.empty
2022-09-01 17:15:28 +03:00
expressionExitVisitor : Node Expression -> ModuleContext -> ModuleContext
2023-09-03 12:30:45 +03:00
expressionExitVisitor (Node expressionRange _) context =
2023-06-25 21:01:32 +03:00
let
contextWithUpdatedLocalBindings : ModuleContext
contextWithUpdatedLocalBindings =
2023-09-03 12:30:45 +03:00
if RangeDict.member expressionRange context.rangesToIgnore then
2023-06-25 21:01:32 +03:00
context
else
{ context
| localBindings =
2023-09-03 12:30:45 +03:00
RangeDict.remove expressionRange context.localBindings
2023-06-25 21:01:32 +03:00
}
in
2023-09-03 12:30:45 +03:00
if RangeDict.member expressionRange context.inferredConstantsDict then
2022-09-01 17:15:28 +03:00
case Tuple.second context.inferredConstants of
topOfStack :: restOfStack ->
2023-06-25 21:01:32 +03:00
{ contextWithUpdatedLocalBindings | inferredConstants = ( topOfStack, restOfStack ) }
2022-09-01 17:15:28 +03:00
[] ->
-- should never be empty
2023-06-25 21:01:32 +03:00
contextWithUpdatedLocalBindings
2022-09-01 17:15:28 +03:00
else
2023-06-25 21:01:32 +03:00
contextWithUpdatedLocalBindings
2022-09-01 17:15:28 +03:00
2023-06-25 21:01:32 +03:00
errorsAndRangesToIgnore : List (Error {}) -> RangeDict () -> { errors : List (Error {}), rangesToIgnore : RangeDict (), rightSidesOfPlusPlus : RangeDict (), inferredConstants : List ( Range, Infer.Inferred ) }
2021-08-19 21:57:29 +03:00
errorsAndRangesToIgnore errors rangesToIgnore =
{ errors = errors
, rangesToIgnore = rangesToIgnore
2023-06-25 21:01:32 +03:00
, rightSidesOfPlusPlus = RangeDict.empty
2022-09-01 17:15:28 +03:00
, inferredConstants = []
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
onlyErrors : List (Error {}) -> { errors : List (Error {}), rangesToIgnore : RangeDict (), rightSidesOfPlusPlus : RangeDict (), inferredConstants : List ( Range, Infer.Inferred ) }
2021-08-19 21:57:29 +03:00
onlyErrors errors =
{ errors = errors
2023-06-25 21:01:32 +03:00
, rangesToIgnore = RangeDict.empty
, rightSidesOfPlusPlus = RangeDict.empty
2022-09-01 17:15:28 +03:00
, inferredConstants = []
2021-08-19 21:57:29 +03:00
}
2023-09-03 12:30:45 +03:00
firstThatReportsError : List (a -> List (Error {})) -> a -> List (Error {})
firstThatReportsError remainingChecks data =
findMap
(\checkFn ->
case checkFn data of
[] ->
Nothing
firstError :: afterFirstError ->
Just (firstError :: afterFirstError)
)
remainingChecks
|> Maybe.withDefault []
expressionVisitorHelp : Node Expression -> { config | expectNaN : Bool } -> ModuleContext -> { errors : List (Error {}), rangesToIgnore : RangeDict (), rightSidesOfPlusPlus : RangeDict (), inferredConstants : List ( Range, Infer.Inferred ) }
expressionVisitorHelp (Node expressionRange expression) config context =
2023-06-25 21:01:32 +03:00
let
toCheckInfo :
{ fnRange : Range
, firstArg : Node Expression
, argsAfterFirst : List (Node Expression)
, usingRightPizza : Bool
}
-> CheckInfo
toCheckInfo checkInfo =
{ lookupTable = context.lookupTable
2023-09-03 12:30:45 +03:00
, expectNaN = config.expectNaN
2023-06-25 21:01:32 +03:00
, extractSourceCode = context.extractSourceCode
, importLookup = context.importLookup
2023-09-03 12:30:45 +03:00
, commentRanges = context.commentRanges
2023-06-25 21:01:32 +03:00
, moduleBindings = context.moduleBindings
, localBindings = context.localBindings
, inferredConstants = context.inferredConstants
2023-09-03 12:30:45 +03:00
, parentRange = expressionRange
2023-06-25 21:01:32 +03:00
, fnRange = checkInfo.fnRange
, firstArg = checkInfo.firstArg
, argsAfterFirst = checkInfo.argsAfterFirst
, secondArg = List.head checkInfo.argsAfterFirst
, thirdArg = List.head (List.drop 1 checkInfo.argsAfterFirst)
, usingRightPizza = checkInfo.usingRightPizza
}
2023-09-03 12:30:45 +03:00
toCompositionCheckInfo :
{ direction : LeftOrRightDirection
, earlier : Node Expression
, later : Node Expression
, parentRange : Range
}
-> CompositionCheckInfo
toCompositionCheckInfo compositionSpecific =
{ lookupTable = context.lookupTable
, importLookup = context.importLookup
, moduleBindings = context.moduleBindings
, localBindings = context.localBindings
, direction = compositionSpecific.direction
, parentRange = compositionSpecific.parentRange
, earlier = compositionSpecific.earlier
, later = compositionSpecific.later
}
2023-06-25 21:01:32 +03:00
in
2023-09-03 12:30:45 +03:00
case expression of
-----------------
-- APPLICATION --
-----------------
Expression.Application (applied :: firstArg :: argsAfterFirst) ->
onlyErrors
(case applied of
Node fnRange (Expression.FunctionOrValue _ fnName) ->
case ModuleNameLookupTable.moduleNameAt context.lookupTable fnRange of
Just moduleName ->
case Dict.get ( moduleName, fnName ) functionCallChecks of
Just checkFn ->
checkFn
(toCheckInfo
{ fnRange = fnRange
, firstArg = firstArg
, argsAfterFirst = argsAfterFirst
, usingRightPizza = False
}
)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Node _ (Expression.ParenthesizedExpression (Node lambdaRange (Expression.LambdaExpression lambda))) ->
appliedLambdaChecks
{ nodeRange = expressionRange
, lambdaRange = lambdaRange
, lambda = lambda
2021-08-19 21:57:29 +03:00
}
2023-09-03 12:30:45 +03:00
Node operatorRange (Expression.PrefixOperator operator) ->
case argsAfterFirst of
right :: [] ->
fullyAppliedPrefixOperatorChecks
{ operator = operator
, operatorRange = operatorRange
, left = firstArg
, right = right
}
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
----------
-- (<|) --
----------
Expression.OperatorApplication "<|" _ pipedInto lastArg ->
case pipedInto of
Node fnRange (Expression.FunctionOrValue _ fnName) ->
onlyErrors
(case ModuleNameLookupTable.moduleNameAt context.lookupTable fnRange of
Just moduleName ->
case Dict.get ( moduleName, fnName ) functionCallChecks of
Just checkFn ->
checkFn
(toCheckInfo
{ fnRange = fnRange
, firstArg = lastArg
, argsAfterFirst = []
, usingRightPizza = False
}
)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Node applicationRange (Expression.Application ((Node fnRange (Expression.FunctionOrValue _ fnName)) :: firstArg :: argsBetweenFirstAndLast)) ->
case ModuleNameLookupTable.moduleNameAt context.lookupTable fnRange of
Just moduleName ->
case Dict.get ( moduleName, fnName ) functionCallChecks of
Just checkFn ->
errorsAndRangesToIgnore
(checkFn
(toCheckInfo
{ fnRange = fnRange
, firstArg = firstArg
, argsAfterFirst = argsBetweenFirstAndLast ++ [ lastArg ]
, usingRightPizza = False
}
)
)
(RangeDict.singleton applicationRange ())
2023-09-03 12:30:45 +03:00
Nothing ->
onlyErrors []
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
onlyErrors []
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
pipedIntoOther ->
onlyErrors
(pipelineChecks
{ commentRanges = context.commentRanges
, extractSourceCode = context.extractSourceCode
, direction = RightToLeft
, nodeRange = expressionRange
, pipedInto = pipedIntoOther
, arg = lastArg
}
2021-08-19 21:57:29 +03:00
)
----------
-- (|>) --
----------
2023-09-03 12:30:45 +03:00
Expression.OperatorApplication "|>" _ lastArg pipedInto ->
case pipedInto of
Node fnRange (Expression.FunctionOrValue _ fnName) ->
2021-08-19 21:57:29 +03:00
onlyErrors
2023-09-03 12:30:45 +03:00
(case ModuleNameLookupTable.moduleNameAt context.lookupTable fnRange of
Just moduleName ->
case Dict.get ( moduleName, fnName ) functionCallChecks of
Just checks ->
checks
(toCheckInfo
{ fnRange = fnRange
, firstArg = lastArg
, argsAfterFirst = []
, usingRightPizza = True
}
)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2021-08-19 21:57:29 +03:00
)
2023-09-03 12:30:45 +03:00
Node applicationRange (Expression.Application ((Node fnRange (Expression.FunctionOrValue _ fnName)) :: firstArg :: argsBetweenFirstAndLast)) ->
case ModuleNameLookupTable.moduleNameAt context.lookupTable fnRange of
Just moduleName ->
case Dict.get ( moduleName, fnName ) functionCallChecks of
Just checks ->
errorsAndRangesToIgnore
(checks
(toCheckInfo
{ fnRange = fnRange
, firstArg = firstArg
, argsAfterFirst = argsBetweenFirstAndLast ++ [ lastArg ]
, usingRightPizza = True
}
)
)
(RangeDict.singleton applicationRange ())
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
onlyErrors []
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
onlyErrors []
pipedIntoOther ->
onlyErrors
(pipelineChecks
{ commentRanges = context.commentRanges
, extractSourceCode = context.extractSourceCode
, direction = LeftToRight
, nodeRange = expressionRange
, pipedInto = pipedIntoOther
, arg = lastArg
}
)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
----------
-- (>>) --
----------
Expression.OperatorApplication ">>" _ earlier composedLater ->
let
( later, parentRange ) =
case composedLater of
Node _ (Expression.OperatorApplication ">>" _ later_ _) ->
( later_, { start = (Node.range earlier).start, end = (Node.range later_).end } )
endLater ->
( endLater, expressionRange )
in
2021-08-19 21:57:29 +03:00
onlyErrors
2023-09-03 12:30:45 +03:00
(compositionChecks
(toCompositionCheckInfo
{ direction = LeftToRight
, parentRange = parentRange
, earlier = earlier
, later = later
}
)
2021-08-19 21:57:29 +03:00
)
2023-09-03 12:30:45 +03:00
----------
-- (<<) --
----------
Expression.OperatorApplication "<<" _ composedLater earlier ->
let
( later, parentRange ) =
case composedLater of
Node _ (Expression.OperatorApplication "<<" _ _ later_) ->
( later_, { start = (Node.range later_).start, end = (Node.range earlier).end } )
endLater ->
( endLater, expressionRange )
in
2021-08-19 21:57:29 +03:00
onlyErrors
2023-09-03 12:30:45 +03:00
(compositionChecks
(toCompositionCheckInfo
{ direction = RightToLeft
, parentRange = parentRange
, earlier = earlier
, later = later
}
)
2021-08-19 21:57:29 +03:00
)
2023-09-03 12:30:45 +03:00
---------------------
-- OTHER OPERATION --
---------------------
2021-08-19 21:57:29 +03:00
Expression.OperatorApplication operator _ left right ->
case Dict.get operator operatorChecks of
Just checkFn ->
{ errors =
2023-09-03 12:30:45 +03:00
let
leftRange : Range
leftRange =
Node.range left
rightRange : Range
rightRange =
Node.range right
in
2021-08-19 21:57:29 +03:00
checkFn
{ lookupTable = context.lookupTable
2023-09-03 12:30:45 +03:00
, expectNaN = config.expectNaN
2023-06-25 21:01:32 +03:00
, importLookup = context.importLookup
, moduleBindings = context.moduleBindings
, localBindings = context.localBindings
2022-09-01 17:15:28 +03:00
, inferredConstants = context.inferredConstants
2023-09-03 12:30:45 +03:00
, parentRange = expressionRange
2021-08-19 21:57:29 +03:00
, operator = operator
2023-09-03 12:30:45 +03:00
, operatorRange =
findOperatorRange
{ operator = operator
, commentRanges = context.commentRanges
, extractSourceCode = context.extractSourceCode
, leftRange = leftRange
, rightRange = rightRange
}
2021-08-19 21:57:29 +03:00
, left = left
2023-09-03 12:30:45 +03:00
, leftRange = leftRange
2021-08-19 21:57:29 +03:00
, right = right
2023-09-03 12:30:45 +03:00
, rightRange = rightRange
, isOnTheRightSideOfPlusPlus = RangeDict.member expressionRange context.rightSidesOfPlusPlus
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
, rangesToIgnore = RangeDict.empty
2021-08-19 21:57:29 +03:00
, rightSidesOfPlusPlus =
2023-09-03 12:30:45 +03:00
case operator of
"++" ->
RangeDict.singleton (Node.range (AstHelpers.removeParens right)) ()
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
RangeDict.empty
2022-09-01 17:15:28 +03:00
, inferredConstants = []
2021-08-19 21:57:29 +03:00
}
Nothing ->
onlyErrors []
2023-09-03 12:30:45 +03:00
--------------
-- NEGATION --
--------------
Expression.Negation negatedExpression ->
onlyErrors
(negationChecks { parentRange = expressionRange, negatedExpression = negatedExpression })
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
-------------------
-- RECORD ACCESS --
-------------------
2022-09-01 17:15:28 +03:00
Expression.RecordAccess record field ->
case Node.value (AstHelpers.removeParens record) of
Expression.RecordExpr setters ->
2023-09-03 12:30:45 +03:00
onlyErrors (recordAccessChecks expressionRange Nothing (Node.value field) setters)
2022-09-01 17:15:28 +03:00
Expression.RecordUpdateExpression (Node recordNameRange _) setters ->
2023-09-03 12:30:45 +03:00
onlyErrors (recordAccessChecks expressionRange (Just recordNameRange) (Node.value field) setters)
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.LetExpression letIn ->
onlyErrors [ injectRecordAccessIntoLetExpression (Node.range record) letIn.expression field ]
Expression.IfBlock _ thenBranch elseBranch ->
onlyErrors (distributeFieldAccess "an if/then/else" (Node.range record) [ thenBranch, elseBranch ] field)
2023-09-03 12:30:45 +03:00
Expression.CaseExpression caseOf ->
onlyErrors (distributeFieldAccess "a case/of" (Node.range record) (List.map Tuple.second caseOf.cases) field)
2022-09-01 17:15:28 +03:00
_ ->
onlyErrors []
2023-09-03 12:30:45 +03:00
--------
-- IF --
--------
Expression.IfBlock condition trueBranch falseBranch ->
2023-06-25 21:01:32 +03:00
let
2023-09-03 12:30:45 +03:00
ifCheckInfo : IfCheckInfo
ifCheckInfo =
{ nodeRange = expressionRange
, condition = condition
, trueBranch = trueBranch
, falseBranch = falseBranch
, lookupTable = context.lookupTable
, inferredConstants = context.inferredConstants
, importLookup = context.importLookup
, moduleBindings = context.moduleBindings
, localBindings = context.localBindings
}
2023-06-25 21:01:32 +03:00
in
2023-09-03 12:30:45 +03:00
case ifChecks ifCheckInfo of
Just ifErrors ->
errorsAndRangesToIgnore ifErrors.errors ifErrors.rangesToIgnore
2023-06-25 21:01:32 +03:00
Nothing ->
2023-09-03 12:30:45 +03:00
{ errors = []
, rangesToIgnore = RangeDict.empty
, rightSidesOfPlusPlus = RangeDict.empty
, inferredConstants =
Infer.inferForIfCondition
(Node.value (Normalize.normalize context condition))
{ trueBranchRange = Node.range trueBranch
, falseBranchRange = Node.range falseBranch
}
(Tuple.first context.inferredConstants)
}
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
-------------
-- CASE OF --
-------------
Expression.CaseExpression caseBlock ->
onlyErrors
(firstThatReportsError caseOfChecks
{ lookupTable = context.lookupTable
, extractSourceCode = context.extractSourceCode
, customTypesToReportInCases = context.customTypesToReportInCases
, inferredConstants = context.inferredConstants
, parentRange = expressionRange
, caseOf = caseBlock
}
)
2023-09-03 12:30:45 +03:00
------------
-- LET IN --
------------
Expression.LetExpression caseBlock ->
onlyErrors (letInChecks caseBlock)
2023-09-03 12:30:45 +03:00
-------------------
-- RECORD UPDATE --
-------------------
Expression.RecordUpdateExpression variable fields ->
onlyErrors (removeRecordFields expressionRange variable fields)
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
--------------------
-- NOT SIMPLIFIED --
--------------------
Expression.UnitExpr ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.CharLiteral _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.Integer _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.Hex _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.Floatable _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.Literal _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.GLSLExpression _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.PrefixOperator _ ->
onlyErrors []
2023-09-03 12:30:45 +03:00
Expression.RecordAccessFunction _ ->
onlyErrors []
2023-09-03 12:30:45 +03:00
Expression.FunctionOrValue _ _ ->
onlyErrors []
2023-09-03 12:30:45 +03:00
Expression.ParenthesizedExpression _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.TupledExpression _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.ListExpr _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.RecordExpr _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.LambdaExpression _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
----------------------
-- IMPOSSIBLE CASES --
----------------------
Expression.Operator _ ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.Application [] ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
Expression.Application (_ :: []) ->
onlyErrors []
2022-09-01 17:15:28 +03:00
2021-08-19 21:57:29 +03:00
type alias CheckInfo =
{ lookupTable : ModuleNameLookupTable
2023-09-03 12:30:45 +03:00
, expectNaN : Bool
2023-06-25 21:01:32 +03:00
, importLookup : ImportLookup
, extractSourceCode : Range -> String
2023-09-03 12:30:45 +03:00
, commentRanges : List Range
2023-06-25 21:01:32 +03:00
, moduleBindings : Set String
, localBindings : RangeDict (Set String)
2022-09-01 17:15:28 +03:00
, inferredConstants : ( Infer.Inferred, List Infer.Inferred )
2021-08-19 21:57:29 +03:00
, parentRange : Range
, fnRange : Range
2023-06-25 21:01:32 +03:00
, usingRightPizza : Bool
2021-08-19 21:57:29 +03:00
, firstArg : Node Expression
2023-06-25 21:01:32 +03:00
, argsAfterFirst : List (Node Expression)
-- stored for quick access since usage is very common
-- prefer using secondArg and thirdArg functions
-- because the optimization could change in the future
2021-08-19 21:57:29 +03:00
, secondArg : Maybe (Node Expression)
2022-04-17 09:59:11 +03:00
, thirdArg : Maybe (Node Expression)
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
secondArg : CheckInfo -> Maybe (Node Expression)
secondArg checkInfo =
checkInfo.secondArg
thirdArg : CheckInfo -> Maybe (Node Expression)
thirdArg checkInfo =
checkInfo.thirdArg
2021-08-19 21:57:29 +03:00
functionCallChecks : Dict ( ModuleName, String ) (CheckInfo -> List (Error {}))
functionCallChecks =
Dict.fromList
[ ( ( [ "Basics" ], "identity" ), basicsIdentityChecks )
, ( ( [ "Basics" ], "always" ), basicsAlwaysChecks )
, ( ( [ "Basics" ], "not" ), basicsNotChecks )
, ( ( [ "Basics" ], "negate" ), basicsNegateChecks )
, ( ( [ "Maybe" ], "map" ), maybeMapChecks )
, ( ( [ "Maybe" ], "andThen" ), maybeAndThenChecks )
, ( ( [ "Maybe" ], "withDefault" ), maybeWithDefaultChecks )
, ( ( [ "Result" ], "map" ), resultMapChecks )
2023-06-25 21:01:32 +03:00
, ( ( [ "Result" ], "mapError" ), resultMapErrorChecks )
2021-08-19 21:57:29 +03:00
, ( ( [ "Result" ], "andThen" ), resultAndThenChecks )
, ( ( [ "Result" ], "withDefault" ), resultWithDefaultChecks )
2023-06-25 21:01:32 +03:00
, ( ( [ "Result" ], "toMaybe" ), resultToMaybeChecks )
, ( ( [ "List" ], "append" ), listAppendChecks )
, ( ( [ "List" ], "head" ), listHeadChecks )
, ( ( [ "List" ], "tail" ), listTailChecks )
, ( ( [ "List" ], "member" ), listMemberChecks )
, ( ( [ "List" ], "map" ), listMapChecks )
2021-08-19 21:57:29 +03:00
, ( ( [ "List" ], "filter" ), collectionFilterChecks listCollection )
, reportEmptyListSecondArgument ( ( [ "List" ], "filterMap" ), listFilterMapChecks )
, reportEmptyListFirstArgument ( ( [ "List" ], "concat" ), listConcatChecks )
, reportEmptyListSecondArgument ( ( [ "List" ], "concatMap" ), listConcatMapChecks )
2022-04-17 09:59:11 +03:00
, reportEmptyListSecondArgument ( ( [ "List" ], "indexedMap" ), listIndexedMapChecks )
2023-06-25 21:01:32 +03:00
, reportEmptyListSecondArgument ( ( [ "List" ], "intersperse" ), \_ -> [] )
, ( ( [ "List" ], "sum" ), listSumChecks )
, ( ( [ "List" ], "product" ), listProductChecks )
, ( ( [ "List" ], "minimum" ), listMinimumChecks )
, ( ( [ "List" ], "maximum" ), listMaximumChecks )
, ( ( [ "List" ], "foldl" ), listFoldlChecks )
, ( ( [ "List" ], "foldr" ), listFoldrChecks )
2021-08-19 21:57:29 +03:00
, ( ( [ "List" ], "all" ), listAllChecks )
, ( ( [ "List" ], "any" ), listAnyChecks )
, ( ( [ "List" ], "range" ), listRangeChecks )
, ( ( [ "List" ], "length" ), collectionSizeChecks listCollection )
, ( ( [ "List" ], "repeat" ), listRepeatChecks )
, ( ( [ "List" ], "isEmpty" ), collectionIsEmptyChecks listCollection )
, ( ( [ "List" ], "partition" ), collectionPartitionChecks listCollection )
, ( ( [ "List" ], "reverse" ), listReverseChecks )
2023-06-25 21:01:32 +03:00
, ( ( [ "List" ], "sort" ), listSortChecks )
, ( ( [ "List" ], "sortBy" ), listSortByChecks )
, ( ( [ "List" ], "sortWith" ), listSortWithChecks )
2022-04-17 09:59:11 +03:00
, ( ( [ "List" ], "take" ), listTakeChecks )
, ( ( [ "List" ], "drop" ), listDropChecks )
2023-06-25 21:01:32 +03:00
, ( ( [ "List" ], "map2" ), listMapNChecks { n = 2 } )
, ( ( [ "List" ], "map3" ), listMapNChecks { n = 3 } )
, ( ( [ "List" ], "map4" ), listMapNChecks { n = 4 } )
, ( ( [ "List" ], "map5" ), listMapNChecks { n = 5 } )
, ( ( [ "List" ], "unzip" ), listUnzipChecks )
2021-08-19 21:57:29 +03:00
, ( ( [ "Set" ], "map" ), collectionMapChecks setCollection )
, ( ( [ "Set" ], "filter" ), collectionFilterChecks setCollection )
, ( ( [ "Set" ], "remove" ), collectionRemoveChecks setCollection )
, ( ( [ "Set" ], "isEmpty" ), collectionIsEmptyChecks setCollection )
, ( ( [ "Set" ], "size" ), collectionSizeChecks setCollection )
, ( ( [ "Set" ], "member" ), collectionMemberChecks setCollection )
2023-06-25 21:01:32 +03:00
, ( ( [ "Set" ], "fromList" ), setFromListChecks )
2021-08-19 21:57:29 +03:00
, ( ( [ "Set" ], "toList" ), collectionToListChecks setCollection )
, ( ( [ "Set" ], "partition" ), collectionPartitionChecks setCollection )
, ( ( [ "Set" ], "intersect" ), collectionIntersectChecks setCollection )
, ( ( [ "Set" ], "diff" ), collectionDiffChecks setCollection )
, ( ( [ "Set" ], "union" ), collectionUnionChecks setCollection )
, ( ( [ "Set" ], "insert" ), collectionInsertChecks setCollection )
, ( ( [ "Dict" ], "isEmpty" ), collectionIsEmptyChecks dictCollection )
, ( ( [ "Dict" ], "fromList" ), collectionFromListChecks dictCollection )
, ( ( [ "Dict" ], "toList" ), collectionToListChecks dictCollection )
, ( ( [ "Dict" ], "size" ), collectionSizeChecks dictCollection )
2022-09-01 17:15:28 +03:00
, ( ( [ "Dict" ], "member" ), collectionMemberChecks dictCollection )
2023-06-25 21:01:32 +03:00
, ( ( [ "Dict" ], "partition" ), collectionPartitionChecks dictCollection )
, ( ( [ "String" ], "fromList" ), stringFromListChecks )
2021-08-19 21:57:29 +03:00
, ( ( [ "String" ], "isEmpty" ), stringIsEmptyChecks )
, ( ( [ "String" ], "concat" ), stringConcatChecks )
, ( ( [ "String" ], "join" ), stringJoinChecks )
, ( ( [ "String" ], "length" ), stringLengthChecks )
, ( ( [ "String" ], "repeat" ), stringRepeatChecks )
2022-04-17 09:59:11 +03:00
, ( ( [ "String" ], "replace" ), stringReplaceChecks )
2021-08-19 21:57:29 +03:00
, ( ( [ "String" ], "words" ), stringWordsChecks )
, ( ( [ "String" ], "lines" ), stringLinesChecks )
, ( ( [ "String" ], "reverse" ), stringReverseChecks )
2023-06-25 21:01:32 +03:00
, ( ( [ "String" ], "slice" ), stringSliceChecks )
, ( ( [ "String" ], "left" ), stringLeftChecks )
, ( ( [ "String" ], "right" ), stringRightChecks )
2021-08-19 21:57:29 +03:00
, ( ( [ "Platform", "Cmd" ], "batch" ), subAndCmdBatchChecks "Cmd" )
, ( ( [ "Platform", "Cmd" ], "map" ), collectionMapChecks cmdCollection )
, ( ( [ "Platform", "Sub" ], "batch" ), subAndCmdBatchChecks "Sub" )
, ( ( [ "Platform", "Sub" ], "map" ), collectionMapChecks subCollection )
2022-04-17 09:59:11 +03:00
, ( ( [ "Json", "Decode" ], "oneOf" ), oneOfChecks )
2023-06-25 21:01:32 +03:00
, ( ( [ "Html", "Attributes" ], "classList" ), htmlAttributesClassListChecks )
2022-04-17 09:59:11 +03:00
, ( ( [ "Parser" ], "oneOf" ), oneOfChecks )
, ( ( [ "Parser", "Advanced" ], "oneOf" ), oneOfChecks )
2023-09-03 12:30:45 +03:00
, ( ( [ "Random" ], "uniform" ), randomUniformChecks )
, ( ( [ "Random" ], "weighted" ), randomWeightedChecks )
, ( ( [ "Random" ], "list" ), randomListChecks )
, ( ( [ "Random" ], "map" ), randomMapChecks )
2021-08-19 21:57:29 +03:00
]
type alias OperatorCheckInfo =
{ lookupTable : ModuleNameLookupTable
2023-09-03 12:30:45 +03:00
, expectNaN : Bool
2023-06-25 21:01:32 +03:00
, importLookup : ImportLookup
, moduleBindings : Set String
, localBindings : RangeDict (Set String)
2022-09-01 17:15:28 +03:00
, inferredConstants : ( Infer.Inferred, List Infer.Inferred )
2021-08-19 21:57:29 +03:00
, parentRange : Range
, operator : String
2023-09-03 12:30:45 +03:00
, operatorRange : Range
2021-08-19 21:57:29 +03:00
, left : Node Expression
, leftRange : Range
, right : Node Expression
, rightRange : Range
, isOnTheRightSideOfPlusPlus : Bool
}
operatorChecks : Dict String (OperatorCheckInfo -> List (Error {}))
operatorChecks =
Dict.fromList
[ ( "+", plusChecks )
, ( "-", minusChecks )
, ( "*", multiplyChecks )
, ( "/", divisionChecks )
, ( "++", plusplusChecks )
, ( "::", consChecks )
, ( "||", orChecks )
, ( "&&", andChecks )
, ( "==", equalityChecks True )
, ( "/=", equalityChecks False )
, ( "<", comparisonChecks (<) )
, ( ">", comparisonChecks (>) )
, ( "<=", comparisonChecks (<=) )
, ( ">=", comparisonChecks (>=) )
]
type alias CompositionCheckInfo =
{ lookupTable : ModuleNameLookupTable
2023-06-25 21:01:32 +03:00
, importLookup : ImportLookup
, moduleBindings : Set String
, localBindings : RangeDict (Set String)
2023-09-03 12:30:45 +03:00
, direction : LeftOrRightDirection
2021-08-19 21:57:29 +03:00
, parentRange : Range
2023-09-03 12:30:45 +03:00
, earlier : Node Expression
, later : Node Expression
2021-08-19 21:57:29 +03:00
}
2023-09-03 12:30:45 +03:00
compositionChecks : CompositionCheckInfo -> List (Error {})
compositionChecks checkInfo =
firstThatReportsError
[ \() -> basicsIdentityCompositionChecks checkInfo
, \() -> basicsNotCompositionChecks checkInfo
, \() -> basicsNegateCompositionChecks checkInfo
, \() ->
case
( AstHelpers.getValueOrFunctionOrFunctionCall checkInfo.earlier
, AstHelpers.getValueOrFunctionOrFunctionCall checkInfo.later
)
of
( Just earlierFnOrCall, Just laterFnOrCall ) ->
case
( ModuleNameLookupTable.moduleNameAt checkInfo.lookupTable earlierFnOrCall.fnRange
, ModuleNameLookupTable.moduleNameAt checkInfo.lookupTable laterFnOrCall.fnRange
)
of
( Just earlierFnModuleName, Just laterFnModuleName ) ->
case Dict.get ( laterFnModuleName, laterFnOrCall.fnName ) compositionIntoChecks of
Just compositionIntoChecksForSpecificLater ->
compositionIntoChecksForSpecificLater
{ lookupTable = checkInfo.lookupTable
, importLookup = checkInfo.importLookup
, moduleBindings = checkInfo.moduleBindings
, localBindings = checkInfo.localBindings
, direction = checkInfo.direction
, parentRange = checkInfo.parentRange
, later =
{ range = laterFnOrCall.nodeRange
, fnRange = laterFnOrCall.fnRange
, args = laterFnOrCall.args
}
, earlier =
{ range = earlierFnOrCall.nodeRange
, fn = ( earlierFnModuleName, earlierFnOrCall.fnName )
, fnRange = earlierFnOrCall.fnRange
, args = earlierFnOrCall.args
}
}
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
( Nothing, _ ) ->
[]
( _, Nothing ) ->
[]
( Nothing, _ ) ->
[]
( _, Nothing ) ->
[]
]
()
type alias CompositionIntoCheckInfo =
{ lookupTable : ModuleNameLookupTable
, importLookup : ImportLookup
, moduleBindings : Set String
, localBindings : RangeDict (Set String)
, direction : LeftOrRightDirection
, parentRange : Range
, later :
{ range : Range
, fnRange : Range
, args : List (Node Expression)
}
, earlier :
{ range : Range
, fn : ( ModuleName, String )
, fnRange : Range
, args : List (Node Expression)
}
}
compositionIntoChecks : Dict ( ModuleName, String ) (CompositionIntoCheckInfo -> List (Error {}))
compositionIntoChecks =
Dict.fromList
[ ( ( [ "Basics" ], "always" ), basicsAlwaysCompositionChecks )
, ( ( [ "Maybe" ], "map" ), maybeMapCompositionChecks )
, ( ( [ "Result" ], "map" ), resultMapCompositionChecks )
, ( ( [ "Result" ], "mapError" ), resultMapErrorCompositionChecks )
, ( ( [ "Result" ], "toMaybe" ), resultToMaybeCompositionChecks )
, ( ( [ "List" ], "map" ), listMapCompositionChecks )
, ( ( [ "List" ], "filterMap" ), listFilterMapCompositionChecks )
, ( ( [ "List" ], "concat" ), listConcatCompositionChecks )
, ( ( [ "List" ], "foldl" ), listFoldlCompositionChecks )
, ( ( [ "List" ], "foldr" ), listFoldrCompositionChecks )
, ( ( [ "Set" ], "fromList" ), setFromListCompositionChecks )
, ( ( [ "Random" ], "map" ), randomMapCompositionChecks )
]
2021-08-19 21:57:29 +03:00
removeAlongWithOtherFunctionCheck :
{ message : String, details : List String }
-> (ModuleNameLookupTable -> Node Expression -> Maybe Range)
-> CheckInfo
-> List (Error {})
removeAlongWithOtherFunctionCheck errorMessage secondFunctionCheck checkInfo =
2022-09-01 17:15:28 +03:00
case Node.value (AstHelpers.removeParens checkInfo.firstArg) of
2021-08-19 21:57:29 +03:00
Expression.Application (secondFn :: firstArgOfSecondCall :: _) ->
case secondFunctionCheck checkInfo.lookupTable secondFn of
Just secondRange ->
[ Rule.errorWithFix
errorMessage
(Range.combine [ checkInfo.fnRange, secondRange ])
2023-09-03 12:30:45 +03:00
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg }
++ replaceBySubExpressionFix (Node.range checkInfo.firstArg)
firstArgOfSecondCall
)
2021-08-19 21:57:29 +03:00
]
Nothing ->
[]
Expression.OperatorApplication "|>" _ firstArgOfSecondCall secondFn ->
case secondFunctionCheck checkInfo.lookupTable secondFn of
Just secondRange ->
[ Rule.errorWithFix
errorMessage
(Range.combine [ checkInfo.fnRange, secondRange ])
2023-09-03 12:30:45 +03:00
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg }
++ replaceBySubExpressionFix (Node.range checkInfo.firstArg)
firstArgOfSecondCall
)
2021-08-19 21:57:29 +03:00
]
Nothing ->
[]
Expression.OperatorApplication "<|" _ secondFn firstArgOfSecondCall ->
case secondFunctionCheck checkInfo.lookupTable secondFn of
Just secondRange ->
[ Rule.errorWithFix
errorMessage
(Range.combine [ checkInfo.fnRange, secondRange ])
2023-09-03 12:30:45 +03:00
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg }
++ replaceBySubExpressionFix (Node.range checkInfo.firstArg)
firstArgOfSecondCall
)
2021-08-19 21:57:29 +03:00
]
Nothing ->
[]
_ ->
[]
2023-09-03 12:30:45 +03:00
findOperatorRange :
{ extractSourceCode : Range -> String
, commentRanges : List Range
, operator : String
, leftRange : Range
, rightRange : Range
}
-> Range
findOperatorRange context =
let
betweenOperands : String
betweenOperands =
context.extractSourceCode
{ start = context.leftRange.end, end = context.rightRange.start }
operatorStartLocationFound : Maybe Location
operatorStartLocationFound =
String.indexes context.operator betweenOperands
|> findMap
(\operatorOffset ->
let
operatorStartLocation : Location
operatorStartLocation =
offsetInStringToLocation
{ offset = operatorOffset
, startLocation = context.leftRange.end
, source = betweenOperands
}
isPartOfComment : Bool
isPartOfComment =
List.any
(\commentRange ->
rangeContainsLocation operatorStartLocation commentRange
)
context.commentRanges
in
if isPartOfComment then
Nothing
else
Just operatorStartLocation
)
in
case operatorStartLocationFound of
Just operatorStartLocation ->
{ start = operatorStartLocation
, end =
{ row = operatorStartLocation.row
, column = operatorStartLocation.column + String.length context.operator
}
}
-- there's a bug somewhere
Nothing ->
Range.emptyRange
offsetInStringToLocation : { offset : Int, source : String, startLocation : Location } -> Location
offsetInStringToLocation config =
case config.source |> String.left config.offset |> String.lines |> List.reverse of
[] ->
config.startLocation
onlyLine :: [] ->
{ row = config.startLocation.row
, column = config.startLocation.column + String.length onlyLine
}
lineWithOffsetLocation :: _ :: linesBeforeBeforeWithOffsetLocation ->
{ row = config.startLocation.row + 1 + List.length linesBeforeBeforeWithOffsetLocation
, column = 1 + String.length lineWithOffsetLocation
}
2021-08-19 21:57:29 +03:00
plusChecks : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
plusChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ addingZeroCheck
, addingOppositesCheck
]
checkInfo
addingZeroCheck : OperatorCheckInfo -> List (Error {})
addingZeroCheck checkInfo =
2021-08-19 21:57:29 +03:00
findMap
2023-09-03 12:30:45 +03:00
(\side ->
if AstHelpers.getUncomputedNumberValue side.node == Just 0 then
2021-08-19 21:57:29 +03:00
Just
[ Rule.errorWithFix
{ message = "Unnecessary addition with 0"
, details = [ "Adding 0 does not change the value of the number." ]
}
2023-09-03 12:30:45 +03:00
side.errorRange
[ Fix.removeRange side.removeRange ]
2021-08-19 21:57:29 +03:00
]
else
Nothing
)
2023-09-03 12:30:45 +03:00
(operationToSides checkInfo)
2021-08-19 21:57:29 +03:00
|> Maybe.withDefault []
2023-09-03 12:30:45 +03:00
addingOppositesCheck : OperatorCheckInfo -> List (Error {})
addingOppositesCheck checkInfo =
if checkInfo.expectNaN then
[]
else
case Normalize.compare checkInfo checkInfo.left (Node Range.emptyRange (Expression.Negation checkInfo.right)) of
Normalize.ConfirmedEquality ->
[ Rule.errorWithFix
{ message = "Addition always results in 0"
, details = [ "These two expressions have an equal absolute value but an opposite sign. This means adding them they will cancel out to 0." ]
}
checkInfo.parentRange
[ Fix.replaceRangeBy checkInfo.parentRange "0" ]
]
Normalize.ConfirmedInequality ->
[]
Normalize.Unconfirmed ->
[]
2021-08-19 21:57:29 +03:00
minusChecks : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
minusChecks checkInfo =
if AstHelpers.getUncomputedNumberValue checkInfo.right == Just 0 then
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "Unnecessary subtraction with 0"
, details = [ "Subtracting 0 does not change the value of the number." ]
}
2023-09-03 12:30:45 +03:00
(errorToRightRange checkInfo)
[ Fix.removeRange (fixToRightRange checkInfo) ]
2021-08-19 21:57:29 +03:00
]
2023-06-25 21:01:32 +03:00
else if AstHelpers.getUncomputedNumberValue checkInfo.left == Just 0 then
2021-08-19 21:57:29 +03:00
let
2023-09-03 12:30:45 +03:00
replacedRange : Range
replacedRange =
fixToLeftRange checkInfo
2021-08-19 21:57:29 +03:00
in
[ Rule.errorWithFix
{ message = "Unnecessary subtracting from 0"
, details = [ "You can negate the expression on the right like `-n`." ]
}
2023-09-03 12:30:45 +03:00
(errorToLeftRange checkInfo)
2023-06-25 21:01:32 +03:00
(if needsParens (Node.value checkInfo.right) then
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy replacedRange "-(", Fix.insertAt checkInfo.rightRange.end ")" ]
else
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy replacedRange "-" ]
)
2021-08-19 21:57:29 +03:00
]
2023-09-03 12:30:45 +03:00
else if checkInfo.expectNaN then
2021-08-19 21:57:29 +03:00
[]
2023-09-03 12:30:45 +03:00
else
checkIfMinusResultsInZero checkInfo
checkIfMinusResultsInZero : OperatorCheckInfo -> List (Error {})
checkIfMinusResultsInZero checkInfo =
case Normalize.compare checkInfo checkInfo.left checkInfo.right of
Normalize.ConfirmedEquality ->
[ Rule.errorWithFix
{ message = "Subtraction always results in 0"
, details = [ "These two expressions have the same value, which means they will cancel add when subtracting one by the other." ]
}
checkInfo.parentRange
[ Fix.replaceRangeBy checkInfo.parentRange "0" ]
]
Normalize.ConfirmedInequality ->
[]
Normalize.Unconfirmed ->
[]
2021-08-19 21:57:29 +03:00
multiplyChecks : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
multiplyChecks checkInfo =
2021-08-19 21:57:29 +03:00
findMap
2023-09-03 12:30:45 +03:00
(\side ->
case AstHelpers.getUncomputedNumberValue side.node of
2023-06-25 21:01:32 +03:00
Just number ->
if number == 1 then
2021-08-19 21:57:29 +03:00
Just
[ Rule.errorWithFix
{ message = "Unnecessary multiplication by 1"
, details = [ "Multiplying by 1 does not change the value of the number." ]
}
2023-09-03 12:30:45 +03:00
side.errorRange
[ Fix.removeRange side.removeRange ]
2021-08-19 21:57:29 +03:00
]
2023-06-25 21:01:32 +03:00
else if number == 0 then
2021-08-19 21:57:29 +03:00
Just
2023-09-03 12:30:45 +03:00
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Multiplication by 0 should be replaced"
, details =
[ "Multiplying by 0 will turn finite numbers into 0 and keep NaN and (-)Infinity"
, "Most likely, multiplying by 0 was unintentional and you had a different factor in mind."
, """If you do want the described behavior, though, make your intention clear for the reader
by explicitly checking for `Basics.isNaN` and `Basics.isInfinite`."""
, """Basics.isNaN: https://package.elm-lang.org/packages/elm/core/latest/Basics#isNaN
Basics.isInfinite: https://package.elm-lang.org/packages/elm/core/latest/Basics#isInfinite"""
]
2021-08-19 21:57:29 +03:00
}
2023-09-03 12:30:45 +03:00
side.errorRange
(if checkInfo.expectNaN then
[]
else
[ Fix.replaceRangeBy checkInfo.parentRange "0" ]
)
2021-08-19 21:57:29 +03:00
]
else
Nothing
2023-06-25 21:01:32 +03:00
Nothing ->
2021-08-19 21:57:29 +03:00
Nothing
)
2023-09-03 12:30:45 +03:00
(operationToSides checkInfo)
2021-08-19 21:57:29 +03:00
|> Maybe.withDefault []
2023-09-03 12:30:45 +03:00
operationToSides : OperatorCheckInfo -> List { node : Node Expression, removeRange : Range, errorRange : Range }
operationToSides checkInfo =
[ { node = checkInfo.right
, removeRange = fixToRightRange checkInfo
, errorRange = errorToRightRange checkInfo
}
, { node = checkInfo.left
, removeRange = fixToLeftRange checkInfo
, errorRange = errorToLeftRange checkInfo
}
]
fixToLeftRange : { checkInfo | leftRange : Range, rightRange : Range } -> Range
fixToLeftRange checkInfo =
{ start = checkInfo.leftRange.start, end = checkInfo.rightRange.start }
errorToLeftRange : { checkInfo | leftRange : Range, operatorRange : Range } -> Range
errorToLeftRange checkInfo =
{ start = checkInfo.leftRange.start, end = checkInfo.operatorRange.end }
fixToRightRange : { checkInfo | leftRange : Range, rightRange : Range } -> Range
fixToRightRange checkInfo =
{ start = checkInfo.leftRange.end, end = checkInfo.rightRange.end }
errorToRightRange : { checkInfo | rightRange : Range, operatorRange : Range } -> Range
errorToRightRange checkInfo =
{ start = checkInfo.operatorRange.start, end = checkInfo.rightRange.end }
2021-08-19 21:57:29 +03:00
divisionChecks : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
divisionChecks checkInfo =
if AstHelpers.getUncomputedNumberValue checkInfo.right == Just 1 then
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "Unnecessary division by 1"
, details = [ "Dividing by 1 does not change the value of the number." ]
}
2023-09-03 12:30:45 +03:00
(errorToRightRange checkInfo)
[ Fix.removeRange (fixToRightRange checkInfo) ]
]
else if not checkInfo.expectNaN && (AstHelpers.getUncomputedNumberValue checkInfo.left == Just 0) then
[ Rule.errorWithFix
{ message = "Dividing 0 always returns 0"
, details =
[ "Dividing 0 by anything, even infinite numbers, gives 0 which means you can replace the whole division operation by 0."
, "Most likely, dividing 0 was unintentional and you had a different number in mind."
]
}
(errorToLeftRange checkInfo)
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = checkInfo.leftRange })
2021-08-19 21:57:29 +03:00
]
else
[]
plusplusChecks : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
plusplusChecks checkInfo =
case ( Node.value checkInfo.left, Node.value checkInfo.right ) of
2022-04-17 09:59:11 +03:00
( Expression.Literal "", Expression.Literal _ ) ->
2023-09-03 12:30:45 +03:00
[ errorForAddingEmptyStrings
{ removed =
{ start = checkInfo.leftRange.start
, end = checkInfo.rightRange.start
}
, error =
{ start = checkInfo.leftRange.start
, end = checkInfo.operatorRange.end
}
2021-08-19 21:57:29 +03:00
}
]
2022-04-17 09:59:11 +03:00
( Expression.Literal _, Expression.Literal "" ) ->
2023-09-03 12:30:45 +03:00
[ errorForAddingEmptyStrings
{ removed =
{ start = checkInfo.leftRange.end
, end = checkInfo.rightRange.end
}
, error =
{ start = checkInfo.operatorRange.start
, end = checkInfo.rightRange.end
}
2021-08-19 21:57:29 +03:00
}
]
( Expression.ListExpr [], _ ) ->
2023-09-03 12:30:45 +03:00
[ errorForAddingEmptyLists
{ removed =
{ start = checkInfo.leftRange.start
, end = checkInfo.rightRange.start
}
, error =
{ start = checkInfo.leftRange.start
, end = checkInfo.operatorRange.end
}
2021-08-19 21:57:29 +03:00
}
]
( _, Expression.ListExpr [] ) ->
2023-09-03 12:30:45 +03:00
[ errorForAddingEmptyLists
{ removed =
{ start = checkInfo.leftRange.end
, end = checkInfo.rightRange.end
}
, error =
{ start = checkInfo.operatorRange.start
, end = checkInfo.rightRange.end
}
2021-08-19 21:57:29 +03:00
}
]
( Expression.ListExpr _, Expression.ListExpr _ ) ->
[ Rule.errorWithFix
{ message = "Expression could be simplified to be a single List"
, details = [ "Try moving all the elements into a single list." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.replaceRangeBy
2023-06-25 21:01:32 +03:00
{ start = { row = checkInfo.leftRange.end.row, column = checkInfo.leftRange.end.column - 1 }
, end = { row = checkInfo.rightRange.start.row, column = checkInfo.rightRange.start.column + 1 }
2021-08-19 21:57:29 +03:00
}
","
]
]
2023-09-03 12:30:45 +03:00
( Expression.ListExpr (listElement :: []), _ ) ->
2023-06-25 21:01:32 +03:00
if checkInfo.isOnTheRightSideOfPlusPlus then
2021-08-19 21:57:29 +03:00
[]
else
[ Rule.errorWithFix
{ message = "Should use (::) instead of (++)"
, details = [ "Concatenating a list with a single value is the same as using (::) on the list with the value." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2023-09-03 12:30:45 +03:00
(Fix.replaceRangeBy checkInfo.operatorRange
"::"
2023-06-25 21:01:32 +03:00
:: replaceBySubExpressionFix checkInfo.leftRange listElement
2022-09-01 17:15:28 +03:00
)
2021-08-19 21:57:29 +03:00
]
_ ->
[]
2023-09-03 12:30:45 +03:00
errorForAddingEmptyStrings : { error : Range, removed : Range } -> Error {}
errorForAddingEmptyStrings ranges =
2021-08-19 21:57:29 +03:00
Rule.errorWithFix
{ message = "Unnecessary concatenation with an empty string"
, details = [ "You should remove the concatenation with the empty string." ]
}
2023-09-03 12:30:45 +03:00
ranges.error
[ Fix.removeRange ranges.removed ]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
errorForAddingEmptyLists : { error : Range, removed : Range } -> Error {}
errorForAddingEmptyLists ranges =
2021-08-19 21:57:29 +03:00
Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Unnecessary concatenation with an empty list"
2021-08-19 21:57:29 +03:00
, details = [ "You should remove the concatenation with the empty list." ]
}
2023-09-03 12:30:45 +03:00
ranges.error
[ Fix.removeRange ranges.removed ]
2021-08-19 21:57:29 +03:00
consChecks : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
consChecks checkInfo =
case Node.value checkInfo.right of
2023-09-03 12:30:45 +03:00
Expression.ListExpr tailElements ->
let
fix : List Fix
fix =
case tailElements of
[] ->
[ Fix.insertAt checkInfo.leftRange.start "[ "
, Fix.replaceRangeBy
{ start = checkInfo.leftRange.end
, end = checkInfo.rightRange.end
}
" ]"
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ :: _ ->
[ Fix.insertAt checkInfo.leftRange.start "[ "
, Fix.replaceRangeBy checkInfo.operatorRange ","
, Fix.removeRange (leftBoundaryRange checkInfo.rightRange)
]
in
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "Element added to the beginning of the list could be included in the list"
, details = [ "Try moving the element inside the list it is being added to." ]
}
2023-09-03 12:30:45 +03:00
checkInfo.operatorRange
fix
2021-08-19 21:57:29 +03:00
]
_ ->
[]
2023-09-03 12:30:45 +03:00
toggleCompositionChecks : ( ModuleName, String ) -> CompositionCheckInfo -> List (Error {})
toggleCompositionChecks toggle checkInfo =
let
errorInfo : { message : String, details : List String }
errorInfo =
let
toggleFullyQualifiedAsString : String
toggleFullyQualifiedAsString =
qualifiedToString toggle
in
-- TODO rework error info
{ message = "Unnecessary double " ++ toggleFullyQualifiedAsString
, details = [ "Composing " ++ toggleFullyQualifiedAsString ++ " with " ++ toggleFullyQualifiedAsString ++ " cancels each other out." ]
}
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
getToggleFn : Node Expression -> Maybe Range
getToggleFn =
AstHelpers.getSpecificValueOrFunction toggle checkInfo.lookupTable
maybeEarlierToggleFn : Maybe Range
maybeEarlierToggleFn =
getToggleFn checkInfo.earlier
maybeLaterToggleFn : Maybe Range
maybeLaterToggleFn =
getToggleFn checkInfo.later
getToggleComposition : { earlierToLater : Bool } -> Node Expression -> Maybe { removeFix : List Fix, range : Range }
getToggleComposition takeFirstFunction expressionNode =
case AstHelpers.getComposition expressionNode of
Just composition ->
if takeFirstFunction.earlierToLater then
getToggleFn composition.earlier
|> Maybe.map
(\toggleFn ->
{ range = toggleFn
, removeFix = keepOnlyFix { parentRange = composition.parentRange, keep = Node.range composition.later }
}
)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
else
getToggleFn composition.later
|> Maybe.map
(\toggleFn ->
{ range = toggleFn
, removeFix = keepOnlyFix { parentRange = composition.parentRange, keep = Node.range composition.earlier }
}
)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
Nothing
in
firstThatReportsError
[ \() ->
case ( maybeEarlierToggleFn, maybeLaterToggleFn ) of
( Just _, Just _ ) ->
[ Rule.errorWithFix
errorInfo
checkInfo.parentRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "identity" ) checkInfo))
]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
( Nothing, _ ) ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
( _, Nothing ) ->
[]
, \() ->
case maybeEarlierToggleFn of
Just earlierToggleFn ->
case getToggleComposition { earlierToLater = True } checkInfo.later of
Just laterToggle ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
errorInfo
(Range.combine [ earlierToggleFn, laterToggle.range ])
(laterToggle.removeFix
++ keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.later }
)
2021-08-19 21:57:29 +03:00
]
Nothing ->
[]
Nothing ->
2023-09-03 12:30:45 +03:00
[]
, \() ->
case maybeLaterToggleFn of
Just laterToggleFn ->
case getToggleComposition { earlierToLater = False } checkInfo.earlier of
Just earlierToggle ->
[ Rule.errorWithFix
errorInfo
(Range.combine [ earlierToggle.range, laterToggleFn ])
(earlierToggle.removeFix
++ keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.earlier }
)
]
2021-08-19 21:57:29 +03:00
Nothing ->
[]
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
toggleChainErrorInfo : ( ModuleName, String ) -> { message : String, details : List String }
toggleChainErrorInfo toggle =
let
toggleFullyQualifiedAsString : String
toggleFullyQualifiedAsString =
qualifiedToString toggle
in
-- TODO rework error info
{ message = "Unnecessary double " ++ toggleFullyQualifiedAsString
, details = [ "Composing " ++ toggleFullyQualifiedAsString ++ " with " ++ toggleFullyQualifiedAsString ++ " cancels each other out." ]
}
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
-- NEGATE
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
basicsNegateCompositionChecks : CompositionCheckInfo -> List (Error {})
basicsNegateCompositionChecks checkInfo =
toggleCompositionChecks ( [ "Basics" ], "negate" ) checkInfo
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
basicsNegateChecks : CheckInfo -> List (Error {})
basicsNegateChecks checkInfo =
removeAlongWithOtherFunctionCheck
(toggleChainErrorInfo ( [ "Basics" ], "negate" ))
(AstHelpers.getSpecificValueOrFunction ( [ "Basics" ], "negate" ))
checkInfo
2021-08-19 21:57:29 +03:00
-- BOOLEAN
basicsNotChecks : CheckInfo -> List (Error {})
basicsNotChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ notOnKnownBoolCheck
, removeAlongWithOtherFunctionCheck
(toggleChainErrorInfo ( [ "Basics" ], "not" ))
(AstHelpers.getSpecificValueOrFunction ( [ "Basics" ], "not" ))
, isNotOnBooleanOperatorCheck
]
checkInfo
notOnKnownBoolCheck : CheckInfo -> List (Error {})
notOnKnownBoolCheck checkInfo =
2022-09-01 17:15:28 +03:00
case Evaluate.getBoolean checkInfo checkInfo.firstArg of
2022-04-17 09:59:11 +03:00
Determined bool ->
2023-09-03 12:30:45 +03:00
let
notBoolAsString : String
notBoolAsString =
AstHelpers.boolToString (not bool)
in
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Expression is equal to " ++ notBoolAsString
2021-08-19 21:57:29 +03:00
, details = [ "You can replace the call to `not` by the boolean value directly." ]
}
checkInfo.parentRange
2023-06-25 21:01:32 +03:00
[ Fix.replaceRangeBy checkInfo.parentRange
2023-09-03 12:30:45 +03:00
(qualifiedToString (qualify ( [ "Basics" ], notBoolAsString ) checkInfo))
2023-06-25 21:01:32 +03:00
]
2021-08-19 21:57:29 +03:00
]
2022-04-17 09:59:11 +03:00
Undetermined ->
2023-09-03 12:30:45 +03:00
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
isNotOnBooleanOperatorCheck : CheckInfo -> List (Error {})
isNotOnBooleanOperatorCheck checkInfo =
case Node.value checkInfo.firstArg of
Expression.ParenthesizedExpression (Node _ (Expression.OperatorApplication operator _ (Node leftRange _) (Node rightRange _))) ->
case isNegatableOperator operator of
Just replacement ->
let
operatorRange : Range
operatorRange =
findOperatorRange
{ operator = operator
, commentRanges = checkInfo.commentRanges
, extractSourceCode = checkInfo.extractSourceCode
, leftRange = leftRange
, rightRange = rightRange
}
in
2022-04-17 09:59:11 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "`not` is used on a negatable boolean operation"
, details = [ "You can remove the `not` call and use `" ++ replacement ++ "` instead." ]
}
checkInfo.fnRange
[ Fix.removeRange { start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).start }
, Fix.replaceRangeBy operatorRange replacement
2022-04-17 09:59:11 +03:00
]
]
2021-08-19 21:57:29 +03:00
2022-04-17 09:59:11 +03:00
Nothing ->
[]
_ ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
isNegatableOperator : String -> Maybe String
isNegatableOperator op =
case op of
"<" ->
Just ">="
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
">" ->
Just "<="
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
"<=" ->
Just ">"
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
">=" ->
Just "<"
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
"==" ->
Just "/="
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
"/=" ->
Just "=="
2021-08-19 21:57:29 +03:00
_ ->
Nothing
2023-09-03 12:30:45 +03:00
basicsNotCompositionChecks : CompositionCheckInfo -> List (Error {})
basicsNotCompositionChecks checkInfo =
toggleCompositionChecks ( [ "Basics" ], "not" ) checkInfo
2021-08-19 21:57:29 +03:00
orChecks : OperatorCheckInfo -> List (Error {})
orChecks operatorCheckInfo =
firstThatReportsError
[ \() ->
List.concat
[ or_isLeftSimplifiableError operatorCheckInfo
, or_isRightSimplifiableError operatorCheckInfo
]
, \() -> findSimilarConditionsError operatorCheckInfo
]
()
type RedundantConditionResolution
= RemoveFrom Location
| ReplaceByNoop Bool
findSimilarConditionsError : OperatorCheckInfo -> List (Error {})
findSimilarConditionsError operatorCheckInfo =
let
conditionsOnTheRight : List ( RedundantConditionResolution, Node Expression )
conditionsOnTheRight =
listConditions
operatorCheckInfo.operator
(RemoveFrom operatorCheckInfo.leftRange.end)
operatorCheckInfo.right
errorsForNode : Node Expression -> List (Error {})
errorsForNode nodeToCompareTo =
List.concatMap
(areSimilarConditionsError
2022-09-01 17:15:28 +03:00
operatorCheckInfo
2021-08-19 21:57:29 +03:00
operatorCheckInfo.operator
nodeToCompareTo
)
conditionsOnTheRight
in
operatorCheckInfo.left
|> listConditions operatorCheckInfo.operator (RemoveFrom operatorCheckInfo.leftRange.end)
|> List.concatMap (Tuple.second >> errorsForNode)
2023-06-25 21:01:32 +03:00
areSimilarConditionsError :
QualifyResources (Infer.Resources a)
-> String
-> Node Expression
-> ( RedundantConditionResolution, Node Expression )
-> List (Error {})
2022-09-01 17:15:28 +03:00
areSimilarConditionsError resources operator nodeToCompareTo ( redundantConditionResolution, nodeToLookAt ) =
case Normalize.compare resources nodeToCompareTo nodeToLookAt of
2021-08-19 21:57:29 +03:00
Normalize.ConfirmedEquality ->
2023-06-25 21:01:32 +03:00
errorForRedundantCondition operator redundantConditionResolution nodeToLookAt resources
2021-08-19 21:57:29 +03:00
Normalize.ConfirmedInequality ->
[]
Normalize.Unconfirmed ->
[]
2023-06-25 21:01:32 +03:00
errorForRedundantCondition : String -> RedundantConditionResolution -> Node a -> QualifyResources b -> List (Error {})
errorForRedundantCondition operator redundantConditionResolution node qualifyResources =
2021-08-19 21:57:29 +03:00
let
( range, fix ) =
2023-06-25 21:01:32 +03:00
rangeAndFixForRedundantCondition redundantConditionResolution node qualifyResources
2021-08-19 21:57:29 +03:00
in
[ Rule.errorWithFix
{ message = "Condition is redundant"
, details =
[ "This condition is the same as another one found on the left side of the (" ++ operator ++ ") operator, therefore one of them can be removed."
]
}
range
fix
]
2023-06-25 21:01:32 +03:00
rangeAndFixForRedundantCondition : RedundantConditionResolution -> Node a -> QualifyResources b -> ( Range, List Fix )
2023-09-03 12:30:45 +03:00
rangeAndFixForRedundantCondition redundantConditionResolution (Node nodeRange _) qualifyResources =
2021-08-19 21:57:29 +03:00
case redundantConditionResolution of
RemoveFrom locationOfPrevElement ->
let
range : Range
range =
{ start = locationOfPrevElement
2023-09-03 12:30:45 +03:00
, end = nodeRange.end
2021-08-19 21:57:29 +03:00
}
in
( range
, [ Fix.removeRange range ]
)
ReplaceByNoop noopValue ->
2023-09-03 12:30:45 +03:00
( nodeRange
, [ Fix.replaceRangeBy nodeRange
2023-06-25 21:01:32 +03:00
(qualifiedToString (qualify ( [ "Basics" ], AstHelpers.boolToString noopValue ) qualifyResources))
]
2021-08-19 21:57:29 +03:00
)
listConditions : String -> RedundantConditionResolution -> Node Expression -> List ( RedundantConditionResolution, Node Expression )
2023-09-03 12:30:45 +03:00
listConditions operatorToLookFor redundantConditionResolution expressionNode =
case Node.value expressionNode of
2021-08-19 21:57:29 +03:00
Expression.ParenthesizedExpression expr ->
let
noopValue : Bool
noopValue =
operatorToLookFor == "&&"
in
listConditions operatorToLookFor (ReplaceByNoop noopValue) expr
Expression.OperatorApplication operator _ left right ->
if operator == operatorToLookFor then
listConditions operatorToLookFor redundantConditionResolution left
++ listConditions operatorToLookFor (RemoveFrom (Node.range left).end) right
else
2023-09-03 12:30:45 +03:00
[ ( redundantConditionResolution, expressionNode ) ]
2021-08-19 21:57:29 +03:00
_ ->
2023-09-03 12:30:45 +03:00
[ ( redundantConditionResolution, expressionNode ) ]
2021-08-19 21:57:29 +03:00
or_isLeftSimplifiableError : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
or_isLeftSimplifiableError checkInfo =
case Evaluate.getBoolean checkInfo checkInfo.left of
2022-04-17 09:59:11 +03:00
Determined True ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2022-09-01 17:15:28 +03:00
{ message = "Comparison is always True"
2021-08-19 21:57:29 +03:00
, details = alwaysSameDetails
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.leftRange.end
, end = checkInfo.rightRange.end
2021-08-19 21:57:29 +03:00
}
]
]
2022-04-17 09:59:11 +03:00
Determined False ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = unnecessaryMessage
, details = unnecessaryDetails
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.leftRange.start
, end = checkInfo.rightRange.start
2021-08-19 21:57:29 +03:00
}
]
]
2022-04-17 09:59:11 +03:00
Undetermined ->
2021-08-19 21:57:29 +03:00
[]
or_isRightSimplifiableError : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
or_isRightSimplifiableError checkInfo =
case Evaluate.getBoolean checkInfo checkInfo.right of
2022-04-17 09:59:11 +03:00
Determined True ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = unnecessaryMessage
, details = unnecessaryDetails
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.leftRange.start
, end = checkInfo.rightRange.start
2021-08-19 21:57:29 +03:00
}
]
]
2022-04-17 09:59:11 +03:00
Determined False ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = unnecessaryMessage
, details = unnecessaryDetails
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.leftRange.end
, end = checkInfo.rightRange.end
2021-08-19 21:57:29 +03:00
}
]
]
2022-04-17 09:59:11 +03:00
Undetermined ->
2021-08-19 21:57:29 +03:00
[]
andChecks : OperatorCheckInfo -> List (Error {})
andChecks operatorCheckInfo =
firstThatReportsError
[ \() ->
List.concat
[ and_isLeftSimplifiableError operatorCheckInfo
, and_isRightSimplifiableError operatorCheckInfo
]
, \() -> findSimilarConditionsError operatorCheckInfo
]
()
2023-09-03 12:30:45 +03:00
and_isLeftSimplifiableError : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
and_isLeftSimplifiableError checkInfo =
case Evaluate.getBoolean checkInfo checkInfo.left of
2022-04-17 09:59:11 +03:00
Determined True ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = unnecessaryMessage
, details = unnecessaryDetails
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.leftRange.start
, end = checkInfo.rightRange.start
2021-08-19 21:57:29 +03:00
}
]
]
2022-04-17 09:59:11 +03:00
Determined False ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2022-09-01 17:15:28 +03:00
{ message = "Comparison is always False"
2021-08-19 21:57:29 +03:00
, details = alwaysSameDetails
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.leftRange.end
, end = checkInfo.rightRange.end
2021-08-19 21:57:29 +03:00
}
]
]
2022-04-17 09:59:11 +03:00
Undetermined ->
2021-08-19 21:57:29 +03:00
[]
2023-09-03 12:30:45 +03:00
and_isRightSimplifiableError : OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
and_isRightSimplifiableError checkInfo =
case Evaluate.getBoolean checkInfo checkInfo.right of
2022-04-17 09:59:11 +03:00
Determined True ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = unnecessaryMessage
, details = unnecessaryDetails
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.leftRange.end
, end = checkInfo.rightRange.end
2021-08-19 21:57:29 +03:00
}
]
]
2022-04-17 09:59:11 +03:00
Determined False ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2022-09-01 17:15:28 +03:00
{ message = "Comparison is always False"
2021-08-19 21:57:29 +03:00
, details = alwaysSameDetails
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2021-08-19 21:57:29 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.leftRange.start
, end = checkInfo.rightRange.start
2021-08-19 21:57:29 +03:00
}
]
]
2022-04-17 09:59:11 +03:00
Undetermined ->
2021-08-19 21:57:29 +03:00
[]
-- EQUALITY
equalityChecks : Bool -> OperatorCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
equalityChecks isEqual checkInfo =
if Evaluate.getBoolean checkInfo checkInfo.right == Determined isEqual then
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "Unnecessary comparison with boolean"
, details = [ "The result of the expression will be the same with or without the comparison." ]
}
2023-09-03 12:30:45 +03:00
(errorToRightRange checkInfo)
[ Fix.removeRange (fixToRightRange checkInfo) ]
2021-08-19 21:57:29 +03:00
]
2023-06-25 21:01:32 +03:00
else if Evaluate.getBoolean checkInfo checkInfo.left == Determined isEqual then
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "Unnecessary comparison with boolean"
, details = [ "The result of the expression will be the same with or without the comparison." ]
}
2023-09-03 12:30:45 +03:00
(errorToLeftRange checkInfo)
[ Fix.removeRange (fixToLeftRange checkInfo) ]
2021-08-19 21:57:29 +03:00
]
else
2023-09-03 12:30:45 +03:00
case
Maybe.map2 Tuple.pair
(AstHelpers.getSpecificFunctionCall ( [ "Basics" ], "not" ) checkInfo.lookupTable checkInfo.left)
(AstHelpers.getSpecificFunctionCall ( [ "Basics" ], "not" ) checkInfo.lookupTable checkInfo.right)
of
Just ( leftNot, rightNot ) ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "Unnecessary negation on both sides"
, details = [ "Since both sides are negated using `not`, they are redundant and can be removed." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.parentRange
2023-09-03 12:30:45 +03:00
[ Fix.removeRange leftNot.fnRange, Fix.removeRange rightNot.fnRange ]
2021-08-19 21:57:29 +03:00
]
_ ->
2022-09-01 17:15:28 +03:00
let
inferred : Infer.Inferred
inferred =
Tuple.first checkInfo.inferredConstants
normalizeAndInfer : Node Expression -> Node Expression
2023-09-03 12:30:45 +03:00
normalizeAndInfer expressionNode =
2022-09-01 17:15:28 +03:00
let
2023-09-03 12:30:45 +03:00
normalizedExpressionNode : Node Expression
normalizedExpressionNode =
Normalize.normalize checkInfo expressionNode
2022-09-01 17:15:28 +03:00
in
2023-09-03 12:30:45 +03:00
case Infer.get (Node.value normalizedExpressionNode) inferred of
2022-09-01 17:15:28 +03:00
Just expr ->
Node Range.emptyRange expr
Nothing ->
2023-09-03 12:30:45 +03:00
normalizedExpressionNode
2022-09-01 17:15:28 +03:00
normalizedLeft : Node Expression
normalizedLeft =
2023-06-25 21:01:32 +03:00
normalizeAndInfer checkInfo.left
2022-09-01 17:15:28 +03:00
normalizedRight : Node Expression
normalizedRight =
2023-06-25 21:01:32 +03:00
normalizeAndInfer checkInfo.right
2022-09-01 17:15:28 +03:00
in
case Normalize.compareWithoutNormalization normalizedLeft normalizedRight of
2021-08-19 21:57:29 +03:00
Normalize.ConfirmedEquality ->
2023-09-03 12:30:45 +03:00
if checkInfo.expectNaN then
[]
else
[ comparisonError isEqual checkInfo ]
2021-08-19 21:57:29 +03:00
Normalize.ConfirmedInequality ->
2023-09-03 12:30:45 +03:00
[ comparisonError (not isEqual) checkInfo ]
2021-08-19 21:57:29 +03:00
Normalize.Unconfirmed ->
[]
alwaysSameDetails : List String
alwaysSameDetails =
[ "This condition will always result in the same value. You may have hardcoded a value or mistyped a condition."
]
unnecessaryMessage : String
unnecessaryMessage =
"Part of the expression is unnecessary"
unnecessaryDetails : List String
unnecessaryDetails =
[ "A part of this condition is unnecessary. You can remove it and it would not impact the behavior of the program."
]
-- COMPARISONS
comparisonChecks : (Float -> Float -> Bool) -> OperatorCheckInfo -> List (Error {})
comparisonChecks operatorFunction operatorCheckInfo =
case
Maybe.map2 operatorFunction
(Normalize.getNumberValue operatorCheckInfo.left)
(Normalize.getNumberValue operatorCheckInfo.right)
of
2022-09-01 17:15:28 +03:00
Just bool ->
2023-09-03 12:30:45 +03:00
[ comparisonError bool operatorCheckInfo ]
2021-08-19 21:57:29 +03:00
Nothing ->
[]
2023-09-03 12:30:45 +03:00
comparisonError : Bool -> QualifyResources { a | parentRange : Range } -> Error {}
comparisonError bool checkInfo =
2022-09-01 17:15:28 +03:00
let
boolAsString : String
boolAsString =
2023-06-25 21:01:32 +03:00
AstHelpers.boolToString bool
2022-09-01 17:15:28 +03:00
in
Rule.errorWithFix
{ message = "Comparison is always " ++ boolAsString
, details =
[ "Based on the values and/or the context, we can determine that the value of this operation will always be " ++ boolAsString ++ "."
]
}
2023-09-03 12:30:45 +03:00
checkInfo.parentRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], boolAsString ) checkInfo))
]
2022-09-01 17:15:28 +03:00
2021-08-19 21:57:29 +03:00
-- IF EXPRESSIONS
2022-09-01 17:15:28 +03:00
targetIfKeyword : Range -> Range
2023-09-03 12:30:45 +03:00
targetIfKeyword ifExpressionRange =
let
ifStart : Location
ifStart =
ifExpressionRange.start
in
{ start = ifStart
, end = { ifStart | column = ifStart.column + 2 }
2021-08-19 21:57:29 +03:00
}
-- BASICS
basicsIdentityChecks : CheckInfo -> List (Error {})
basicsIdentityChecks checkInfo =
[ Rule.errorWithFix
{ message = "`identity` should be removed"
, details = [ "`identity` can be a useful function to be passed as arguments to other functions, but calling it manually with an argument is the same thing as writing the argument on its own." ]
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg })
2021-08-19 21:57:29 +03:00
]
2023-09-03 12:30:45 +03:00
basicsIdentityCompositionErrorMessage : { message : String, details : List String }
basicsIdentityCompositionErrorMessage =
2023-06-25 21:01:32 +03:00
{ message = "`identity` should be removed"
, details = [ "Composing a function with `identity` is the same as simplify referencing the function." ]
}
2023-09-03 12:30:45 +03:00
basicsIdentityCompositionChecks : CompositionCheckInfo -> List (Error {})
basicsIdentityCompositionChecks checkInfo =
if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.later then
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
basicsIdentityCompositionErrorMessage
(Node.range checkInfo.later)
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.earlier })
2021-08-19 21:57:29 +03:00
]
2023-09-03 12:30:45 +03:00
else if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.earlier then
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
basicsIdentityCompositionErrorMessage
(Node.range checkInfo.earlier)
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.later })
2021-08-19 21:57:29 +03:00
]
else
[]
basicsAlwaysChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
basicsAlwaysChecks checkInfo =
case secondArg checkInfo of
2021-08-19 21:57:29 +03:00
Just (Node secondArgRange _) ->
[ Rule.errorWithFix
{ message = "Expression can be replaced by the first argument given to `always`"
, details = [ "The second argument will be ignored because of the `always` call." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceBySubExpressionFix
(Range.combine [ checkInfo.fnRange, Node.range checkInfo.firstArg, secondArgRange ])
checkInfo.firstArg
2021-08-19 21:57:29 +03:00
)
]
Nothing ->
[]
2023-09-03 12:30:45 +03:00
basicsAlwaysCompositionErrorMessage : { message : String, details : List String }
basicsAlwaysCompositionErrorMessage =
2023-06-25 21:01:32 +03:00
{ message = "Function composed with always will be ignored"
, details = [ "`always` will swallow the function composed into it." ]
}
2023-09-03 12:30:45 +03:00
basicsAlwaysCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
basicsAlwaysCompositionChecks checkInfo =
case checkInfo.later.args of
_ :: [] ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
basicsAlwaysCompositionErrorMessage
checkInfo.later.range
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = checkInfo.later.range })
2021-08-19 21:57:29 +03:00
]
2022-04-17 09:59:11 +03:00
_ ->
2023-09-03 12:30:45 +03:00
[]
2022-04-17 09:59:11 +03:00
2021-08-19 21:57:29 +03:00
reportEmptyListSecondArgument : ( ( ModuleName, String ), CheckInfo -> List (Error {}) ) -> ( ( ModuleName, String ), CheckInfo -> List (Error {}) )
reportEmptyListSecondArgument ( ( moduleName, name ), function ) =
( ( moduleName, name )
, \checkInfo ->
2023-06-25 21:01:32 +03:00
case secondArg checkInfo of
2021-08-19 21:57:29 +03:00
Just (Node _ (Expression.ListExpr [])) ->
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Using " ++ qualifiedToString ( moduleName, name ) ++ " on an empty list will result in an empty list"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by an empty list." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
]
_ ->
function checkInfo
)
reportEmptyListFirstArgument : ( ( ModuleName, String ), CheckInfo -> List (Error {}) ) -> ( ( ModuleName, String ), CheckInfo -> List (Error {}) )
reportEmptyListFirstArgument ( ( moduleName, name ), function ) =
( ( moduleName, name )
, \checkInfo ->
case checkInfo.firstArg of
Node _ (Expression.ListExpr []) ->
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Using " ++ qualifiedToString ( moduleName, name ) ++ " on an empty list will result in an empty list"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by an empty list." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
]
_ ->
function checkInfo
)
-- STRING
2023-06-25 21:01:32 +03:00
stringFromListChecks : CheckInfo -> List (Error {})
stringFromListChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value checkInfo.firstArg of
Expression.ListExpr [] ->
[ Rule.errorWithFix
{ message = "Calling " ++ qualifiedToString ( [ "String" ], "fromList" ) ++ " [] will result in " ++ emptyStringAsString
, details = [ "You can replace this call by " ++ emptyStringAsString ++ "." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Just listSingletonArg ->
[ Rule.errorWithFix
{ message = "Calling " ++ qualifiedToString ( [ "String" ], "fromList" ) ++ " with a list with a single char is the same as String.fromChar with the contained char"
, details = [ "You can replace this call by " ++ qualifiedToString ( [ "String" ], "fromChar" ) ++ " with the contained char." ]
}
checkInfo.fnRange
(replaceBySubExpressionFix checkInfo.parentRange listSingletonArg.element
++ [ Fix.insertAt checkInfo.parentRange.start
(qualifiedToString (qualify ( [ "String" ], "fromChar" ) checkInfo) ++ " ")
]
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2023-06-25 21:01:32 +03:00
2021-08-19 21:57:29 +03:00
stringIsEmptyChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
stringIsEmptyChecks checkInfo =
case Node.value checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Expression.Literal str ->
let
2023-09-03 12:30:45 +03:00
replacementValueAsString : String
replacementValueAsString =
2023-06-25 21:01:32 +03:00
AstHelpers.boolToString (str == "")
2021-08-19 21:57:29 +03:00
in
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to String.isEmpty will result in " ++ replacementValueAsString
, details = [ "You can replace this call by " ++ replacementValueAsString ++ "." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], replacementValueAsString ) checkInfo))
]
2021-08-19 21:57:29 +03:00
]
_ ->
[]
stringConcatChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
stringConcatChecks checkInfo =
case Node.value checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Expression.ListExpr [] ->
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Using String.concat on an empty list will result in an empty string"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by an empty string." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ]
2021-08-19 21:57:29 +03:00
]
_ ->
[]
stringWordsChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
stringWordsChecks checkInfo =
case Node.value checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Expression.Literal "" ->
[ Rule.errorWithFix
2022-09-01 17:15:28 +03:00
{ message = "Using String.words on an empty string will result in an empty list"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by an empty list." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
2021-08-19 21:57:29 +03:00
]
_ ->
[]
stringLinesChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
stringLinesChecks checkInfo =
case Node.value checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Expression.Literal "" ->
[ Rule.errorWithFix
2022-09-01 17:15:28 +03:00
{ message = "Using String.lines on an empty string will result in an empty list"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by an empty list." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
2021-08-19 21:57:29 +03:00
]
_ ->
[]
stringReverseChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
stringReverseChecks checkInfo =
case Node.value checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Expression.Literal "" ->
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Using String.reverse on an empty string will result in an empty string"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by an empty string." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ]
2021-08-19 21:57:29 +03:00
]
_ ->
removeAlongWithOtherFunctionCheck
reverseReverseCompositionErrorMessage
2023-09-03 12:30:45 +03:00
(AstHelpers.getSpecificValueOrFunction ( [ "String" ], "reverse" ))
2021-08-19 21:57:29 +03:00
checkInfo
2023-06-25 21:01:32 +03:00
stringSliceChecks : CheckInfo -> List (Error {})
stringSliceChecks checkInfo =
case ( secondArg checkInfo, thirdArg checkInfo ) of
( _, Just (Node _ (Expression.Literal "")) ) ->
[ Rule.errorWithFix
{ message = "Using String.slice on an empty string will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
}
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (thirdArg checkInfo) checkInfo)
]
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
( Just (Node _ (Expression.Integer 0)), _ ) ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Using String.slice with end index 0 will result in an empty string"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by an empty string." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (thirdArg checkInfo) checkInfo)
2021-08-19 21:57:29 +03:00
]
2023-06-25 21:01:32 +03:00
( Just end, _ ) ->
Maybe.map2
(\startInt endInt ->
if
(startInt >= endInt)
&& -- have the same sign
((startInt <= -1 && endInt <= -1)
|| (startInt >= 0 && endInt >= 0)
)
then
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to String.slice will result in " ++ emptyStringAsString
, details = [ "You can replace this slice operation by " ++ emptyStringAsString ++ "." ]
2023-06-25 21:01:32 +03:00
}
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (thirdArg checkInfo) checkInfo)
]
|> Just
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
else
-- either is negative or startInt < endInt
Nothing
)
(Evaluate.getInt checkInfo checkInfo.firstArg)
(Evaluate.getInt checkInfo end)
|> Maybe.withDefault
(if Normalize.areAllTheSame checkInfo checkInfo.firstArg [ end ] then
[ Rule.errorWithFix
{ message = "Using String.slice with equal start and end index will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
}
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (thirdArg checkInfo) checkInfo)
]
|> Just
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
else
Nothing
)
|> Maybe.withDefault []
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
( Nothing, _ ) ->
[]
stringLeftChecks : CheckInfo -> List (Error {})
stringLeftChecks checkInfo =
case ( checkInfo.firstArg, secondArg checkInfo ) of
( _, Just (Node _ (Expression.Literal "")) ) ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Using String.left on an empty string will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ]
2023-06-25 21:01:32 +03:00
]
( Node _ (Expression.Integer 0), _ ) ->
[ Rule.errorWithFix
{ message = "Using String.left with length 0 will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
}
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (secondArg checkInfo) checkInfo)
]
( Node _ (Expression.Negation (Node _ (Expression.Integer _))), _ ) ->
[ Rule.errorWithFix
{ message = "Using String.left with negative length will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (secondArg checkInfo) checkInfo)
2021-08-19 21:57:29 +03:00
]
_ ->
[]
2023-06-25 21:01:32 +03:00
stringRightChecks : CheckInfo -> List (Error {})
stringRightChecks checkInfo =
case ( checkInfo.firstArg, secondArg checkInfo ) of
( _, Just (Node _ (Expression.Literal "")) ) ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Using String.right on an empty string will result in an empty string"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by an empty string." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ]
2021-08-19 21:57:29 +03:00
]
2023-06-25 21:01:32 +03:00
( Node _ (Expression.Integer 0), _ ) ->
[ Rule.errorWithFix
{ message = "Using String.right with length 0 will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
}
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (secondArg checkInfo) checkInfo)
]
( Node _ (Expression.Negation (Node _ (Expression.Integer _))), _ ) ->
[ Rule.errorWithFix
{ message = "Using String.right with negative length will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
}
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (secondArg checkInfo) checkInfo)
]
_ ->
[]
reverseReverseCompositionErrorMessage : { message : String, details : List String }
reverseReverseCompositionErrorMessage =
{ message = "Unnecessary double reversal"
, details = [ "Composing `reverse` with `reverse` cancel each other out." ]
}
stringJoinChecks : CheckInfo -> List (Error {})
stringJoinChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case secondArg checkInfo of
Just (Node _ (Expression.ListExpr [])) ->
[ Rule.errorWithFix
{ message = "Using String.join on an empty list will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
2023-06-25 21:01:32 +03:00
case Node.value checkInfo.firstArg of
Expression.Literal "" ->
[ Rule.errorWithFix
{ message = "Use String.concat instead"
, details = [ "Using String.join with an empty separator is the same as using String.concat." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy { start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).end }
(qualifiedToString (qualify ( [ "String" ], "concat" ) checkInfo))
]
]
_ ->
[]
2023-09-03 12:30:45 +03:00
]
()
2023-06-25 21:01:32 +03:00
stringLengthChecks : CheckInfo -> List (Error {})
stringLengthChecks checkInfo =
case Node.value checkInfo.firstArg of
Expression.Literal str ->
[ Rule.errorWithFix
{ message = "The length of the string is " ++ String.fromInt (String.length str)
, details = [ "The length of the string can be determined by looking at the code." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange (String.fromInt (String.length str)) ]
]
_ ->
[]
stringRepeatChecks : CheckInfo -> List (Error {})
stringRepeatChecks checkInfo =
case secondArg checkInfo of
Just (Node _ (Expression.Literal "")) ->
[ Rule.errorWithFix
{ message = "Using String.repeat with an empty string will result in an empty string"
, details = [ "You can replace this call by an empty string." ]
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy checkInfo.parentRange emptyStringAsString ]
2023-06-25 21:01:32 +03:00
]
_ ->
case Evaluate.getInt checkInfo checkInfo.firstArg of
Just intValue ->
if intValue == 1 then
[ Rule.errorWithFix
{ message = "String.repeat 1 won't do anything"
, details = [ "Using String.repeat with 1 will result in the second argument." ]
}
checkInfo.fnRange
[ Fix.removeRange { start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).end } ]
]
2021-08-19 21:57:29 +03:00
else if intValue < 1 then
[ Rule.errorWithFix
{ message = "String.repeat will result in an empty string"
, details = [ "Using String.repeat with a number less than 1 will result in an empty string. You can replace this call by an empty string." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
(replaceByEmptyFix emptyStringAsString checkInfo.parentRange (secondArg checkInfo) checkInfo)
2021-08-19 21:57:29 +03:00
]
else
[]
_ ->
[]
2022-04-17 09:59:11 +03:00
stringReplaceChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
stringReplaceChecks checkInfo =
case secondArg checkInfo of
2023-09-03 12:30:45 +03:00
Just replacementArg ->
firstThatReportsError
[ \() ->
case Normalize.compare checkInfo checkInfo.firstArg replacementArg of
Normalize.ConfirmedEquality ->
[ Rule.errorWithFix
{ message = "The result of String.replace will be the original string"
, details = [ "The pattern to replace and the replacement are equal, therefore the result of the String.replace call will be the original string." ]
}
checkInfo.fnRange
(toIdentityFix { lastArg = thirdArg checkInfo, resources = checkInfo })
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case thirdArg checkInfo of
Just (Node thirdRange (Expression.Literal "")) ->
2022-04-17 09:59:11 +03:00
[ Rule.errorWithFix
{ message = "The result of String.replace will be the empty string"
, details = [ "Replacing anything on an empty string results in an empty string." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2022-04-17 09:59:11 +03:00
[ Fix.removeRange
2023-06-25 21:01:32 +03:00
{ start = checkInfo.fnRange.start
2022-04-17 09:59:11 +03:00
, end = thirdRange.start
}
]
]
_ ->
2023-09-03 12:30:45 +03:00
[]
, \() ->
case ( Node.value checkInfo.firstArg, Node.value replacementArg, thirdArg checkInfo ) of
( Expression.Literal first, Expression.Literal second, Just (Node thirdRange (Expression.Literal third)) ) ->
if not (String.contains "\u{000D}" first) && String.replace first second third == third then
[ Rule.errorWithFix
{ message = "The result of String.replace will be the original string"
, details = [ "The replacement doesn't haven't any noticeable impact. You can remove the call to String.replace." ]
}
checkInfo.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = thirdRange })
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
else
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
]
()
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
-- MAYBE FUNCTIONS
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
maybeMapChecks : CheckInfo -> List (Error {})
maybeMapChecks checkInfo =
firstThatReportsError
[ \() -> collectionMapChecks maybeCollection checkInfo
, \() -> mapPureChecks { moduleName = [ "Maybe" ], pure = "Just", map = "map" } checkInfo
]
()
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
maybeMapCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
maybeMapCompositionChecks checkInfo =
pureToMapCompositionChecks { moduleName = [ "Maybe" ], pure = "Just", map = "map" } checkInfo
2021-08-19 21:57:29 +03:00
-- RESULT FUNCTIONS
resultMapChecks : CheckInfo -> List (Error {})
resultMapChecks checkInfo =
firstThatReportsError
[ \() -> collectionMapChecks resultCollection checkInfo
2023-09-03 12:30:45 +03:00
, \() -> mapPureChecks { moduleName = [ "Result" ], pure = "Ok", map = "map" } checkInfo
2021-08-19 21:57:29 +03:00
]
()
2023-09-03 12:30:45 +03:00
resultMapCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
resultMapCompositionChecks checkInfo =
pureToMapCompositionChecks { moduleName = [ "Result" ], pure = "Ok", map = "map" } checkInfo
2023-06-25 21:01:32 +03:00
resultMapErrorOnErrErrorInfo : { message : String, details : List String }
resultMapErrorOnErrErrorInfo =
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "Result" ], "mapError" ) ++ " on Err will result in Err with the function applied to the error"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by Err with the function directly applied to the error itself." ]
}
resultMapErrorOnOkErrorInfo : { message : String, details : List String }
resultMapErrorOnOkErrorInfo =
2023-09-03 12:30:45 +03:00
{ message = "Calling " ++ qualifiedToString ( [ "Result" ], "mapError" ) ++ " on a value that is Ok will always return the Ok result value"
, details = [ "You can remove the " ++ qualifiedToString ( [ "Result" ], "mapError" ) ++ " call." ]
2023-06-25 21:01:32 +03:00
}
resultMapErrorChecks : CheckInfo -> List (Error {})
resultMapErrorChecks checkInfo =
let
maybeResultArg : Maybe (Node Expression)
maybeResultArg =
secondArg checkInfo
in
firstThatReportsError
2023-09-03 12:30:45 +03:00
-- TODO use collectionMapChecks
2023-06-25 21:01:32 +03:00
[ \() ->
if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.firstArg then
[ identityError
2023-09-03 12:30:45 +03:00
{ toFix = qualifiedToString ( [ "Result" ], "mapError" ) ++ " identity"
2023-06-25 21:01:32 +03:00
, lastArgName = "result"
, lastArg = maybeResultArg
, resources = checkInfo
}
]
else
[]
, \() ->
2023-09-03 12:30:45 +03:00
mapPureChecks { moduleName = [ "Result" ], pure = "Err", map = "mapError" } checkInfo
2023-06-25 21:01:32 +03:00
, \() ->
case maybeResultArg of
Just resultArg ->
2023-09-03 12:30:45 +03:00
case sameCallInAllBranches ( [ "Result" ], "Ok" ) checkInfo.lookupTable resultArg of
Determined _ ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
resultMapErrorOnOkErrorInfo
checkInfo.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range resultArg })
]
_ ->
[]
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2023-06-25 21:01:32 +03:00
]
()
2023-09-03 12:30:45 +03:00
resultMapErrorCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
resultMapErrorCompositionChecks checkInfo =
2023-09-03 12:30:45 +03:00
case checkInfo.later.args of
(Node errorMappingArgRange _) :: _ ->
case ( checkInfo.earlier.fn, checkInfo.earlier.args ) of
( ( [ "Result" ], "Err" ), [] ) ->
[ Rule.errorWithFix
resultMapErrorOnErrErrorInfo
checkInfo.later.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = errorMappingArgRange }
++ [ case checkInfo.direction of
LeftToRight ->
Fix.insertAt checkInfo.parentRange.end
(" >> " ++ qualifiedToString (qualify ( [ "Result" ], "Err" ) checkInfo))
RightToLeft ->
Fix.insertAt checkInfo.parentRange.start
(qualifiedToString (qualify ( [ "Result" ], "Err" ) checkInfo) ++ " << ")
]
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
( ( [ "Result" ], "Ok" ), [] ) ->
[ Rule.errorWithFix
resultMapErrorOnOkErrorInfo
checkInfo.later.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = checkInfo.earlier.fnRange })
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
[] ->
[]
2023-06-25 21:01:32 +03:00
2021-08-19 21:57:29 +03:00
-- LIST FUNCTIONS
listConcatChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
listConcatChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value checkInfo.firstArg of
Expression.ListExpr list ->
case list of
(Node elementRange _) :: [] ->
[ Rule.errorWithFix
{ message = "Unnecessary use of " ++ qualifiedToString ( [ "List" ], "concat" ) ++ " on a list with 1 element"
, details = [ "The value of the operation will be the element itself. You should replace this expression by that." ]
}
checkInfo.parentRange
[ Fix.removeRange { start = checkInfo.parentRange.start, end = elementRange.start }
, Fix.removeRange { start = elementRange.end, end = checkInfo.parentRange.end }
]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
firstListElement :: restOfListElements ->
firstThatReportsError
[ \() ->
case findEmptyLiteral list of
Just emptyLiteral ->
[ Rule.errorWithFix
{ message = "Found empty list in the list given " ++ qualifiedToString ( [ "List" ], "concat" )
, details = [ "This element is unnecessary and can be removed." ]
}
emptyLiteral.element
[ Fix.removeRange emptyLiteral.removalRange ]
]
Nothing ->
[]
, \() ->
case traverse AstHelpers.getListLiteral list of
Just _ ->
[ Rule.errorWithFix
{ message = "Expression could be simplified to be a single List"
, details = [ "Try moving all the elements into a single list." ]
}
checkInfo.parentRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range checkInfo.firstArg }
++ List.concatMap removeBoundariesFix (firstListElement :: restOfListElements)
)
]
Nothing ->
[]
, \() ->
case findConsecutiveListLiterals firstListElement restOfListElements of
firstFix :: fixesAFterFirst ->
[ Rule.errorWithFix
{ message = "Consecutive literal lists should be merged"
, details = [ "Try moving all the elements from consecutive list literals so that they form a single list." ]
}
checkInfo.fnRange
(firstFix :: fixesAFterFirst)
]
[] ->
[]
2021-08-19 21:57:29 +03:00
]
2023-09-03 12:30:45 +03:00
()
_ ->
[]
2021-08-19 21:57:29 +03:00
_ ->
[]
2023-09-03 12:30:45 +03:00
, \() ->
case AstHelpers.getSpecificFunctionCall ( [ "List" ], "map" ) checkInfo.lookupTable checkInfo.firstArg of
Just listMapArg ->
2022-04-17 09:59:11 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = qualifiedToString ( [ "List" ], "map" ) ++ " and " ++ qualifiedToString ( [ "List" ], "concat" ) ++ " can be combined using " ++ qualifiedToString ( [ "List" ], "concatMap" )
, details = [ qualifiedToString ( [ "List" ], "concatMap" ) ++ " is meant for this exact purpose and will also be faster." ]
2022-04-17 09:59:11 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = listMapArg.nodeRange }
++ [ Fix.replaceRangeBy listMapArg.fnRange
(qualifiedToString (qualify ( [ "List" ], "concatMap" ) checkInfo))
]
)
2022-04-17 09:59:11 +03:00
]
Nothing ->
[]
2023-09-03 12:30:45 +03:00
]
()
findEmptyLiteral : List (Node Expression) -> Maybe { element : Range, removalRange : Range }
findEmptyLiteral elements =
case elements of
[] ->
Nothing
head :: rest ->
let
headRange : Range
headRange =
Node.range head
in
case AstHelpers.getListLiteral head of
Just [] ->
let
end : Location
end =
case rest of
[] ->
headRange.end
(Node nextItem _) :: _ ->
nextItem.start
in
Just
{ element = headRange
, removalRange = { start = headRange.start, end = end }
}
_ ->
findEmptyLiteralHelp rest headRange.end
findEmptyLiteralHelp : List (Node Expression) -> Location -> Maybe { element : Range, removalRange : Range }
findEmptyLiteralHelp elements previousItemEnd =
case elements of
[] ->
Nothing
head :: rest ->
let
headRange : Range
headRange =
Node.range head
in
case AstHelpers.getListLiteral head of
Just [] ->
Just
{ element = headRange
, removalRange = { start = previousItemEnd, end = headRange.end }
}
_ ->
findEmptyLiteralHelp rest headRange.end
2021-08-19 21:57:29 +03:00
findConsecutiveListLiterals : Node Expression -> List (Node Expression) -> List Fix
findConsecutiveListLiterals firstListElement restOfListElements =
case ( firstListElement, restOfListElements ) of
( Node firstRange (Expression.ListExpr _), ((Node secondRange (Expression.ListExpr _)) as second) :: rest ) ->
Fix.replaceRangeBy
{ start = { row = firstRange.end.row, column = firstRange.end.column - 1 }
, end = { row = secondRange.start.row, column = secondRange.start.column + 1 }
}
", "
:: findConsecutiveListLiterals second rest
( _, x :: xs ) ->
findConsecutiveListLiterals x xs
_ ->
[]
listConcatMapChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
listConcatMapChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.firstArg then
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "concatMap" ) ++ " with an identity function is the same as using " ++ qualifiedToString ( [ "List" ], "concat" ) ++ ""
, details = [ "You can replace this call by " ++ qualifiedToString ( [ "List" ], "concat" ) ++ "." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy
{ start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).end }
(qualifiedToString (qualify ( [ "List" ], "concat" ) checkInfo))
]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
else
[]
, \() ->
case AstHelpers.getAlwaysResult checkInfo.lookupTable checkInfo.firstArg of
Just alwaysResult ->
case AstHelpers.getListLiteral alwaysResult of
Just [] ->
[ Rule.errorWithFix
{ message = qualifiedToString ( [ "List" ], "concatMap" ) ++ " will result in on an empty list"
, details = [ "You can replace this call by an empty list." ]
}
checkInfo.fnRange
(replaceByEmptyFix "[]" checkInfo.parentRange (secondArg checkInfo) checkInfo)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
, \() ->
case Node.value (AstHelpers.removeParens checkInfo.firstArg) of
Expression.LambdaExpression lambda ->
case replaceSingleElementListBySingleValue checkInfo.lookupTable lambda.expression of
Just fixes ->
[ Rule.errorWithFix
{ message = "Use " ++ qualifiedToString ( [ "List" ], "map" ) ++ " instead"
, details = [ "The function passed to " ++ qualifiedToString ( [ "List" ], "concatMap" ) ++ " always returns a list with a single element." ]
}
checkInfo.fnRange
(Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "List" ], "map" ) checkInfo))
:: fixes
)
2022-04-17 09:59:11 +03:00
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2021-08-19 21:57:29 +03:00
_ ->
[]
2023-09-03 12:30:45 +03:00
, \() ->
case secondArg checkInfo of
Just (Node listRange (Expression.ListExpr (listElement :: []))) ->
2022-04-17 09:59:11 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "concatMap" ) ++ " on an element with a single item is the same as calling the function directly on that lone element."
, details = [ "You can replace this call by a call to the function directly." ]
2022-04-17 09:59:11 +03:00
}
2023-09-03 12:30:45 +03:00
checkInfo.fnRange
(Fix.removeRange checkInfo.fnRange
:: replaceBySubExpressionFix listRange listElement
)
2022-04-17 09:59:11 +03:00
]
2023-09-03 12:30:45 +03:00
_ ->
2022-04-17 09:59:11 +03:00
[]
2023-09-03 12:30:45 +03:00
]
()
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
listConcatCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
listConcatCompositionChecks checkInfo =
case ( checkInfo.earlier.fn, checkInfo.earlier.args ) of
( ( [ "List" ], "map" ), _ :: [] ) ->
[ Rule.errorWithFix
{ message = qualifiedToString ( [ "List" ], "map" ) ++ " and " ++ qualifiedToString ( [ "List" ], "concat" ) ++ " can be combined using " ++ qualifiedToString ( [ "List" ], "concatMap" ) ++ ""
, details = [ qualifiedToString ( [ "List" ], "concatMap" ) ++ " is meant for this exact purpose and will also be faster." ]
}
-- TODO switch to later.fnRange
checkInfo.later.range
(Fix.replaceRangeBy checkInfo.earlier.fnRange
(qualifiedToString (qualify ( [ "List" ], "concatMap" ) checkInfo))
:: keepOnlyFix { parentRange = checkInfo.parentRange, keep = checkInfo.earlier.range }
)
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
2022-04-17 09:59:11 +03:00
listIndexedMapChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
listIndexedMapChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case AstHelpers.removeParens checkInfo.firstArg of
Node lambdaRange (Expression.LambdaExpression lambda) ->
case Maybe.map AstHelpers.removeParensFromPattern (List.head lambda.args) of
Just (Node _ Pattern.AllPattern) ->
let
rangeToRemove : Range
rangeToRemove =
case lambda.args of
[] ->
Range.emptyRange
_ :: [] ->
-- Only one argument, remove the entire lambda except the expression
{ start = lambdaRange.start, end = (Node.range lambda.expression).start }
(Node firstRange _) :: (Node secondRange _) :: _ ->
{ start = firstRange.start, end = secondRange.start }
in
[ Rule.errorWithFix
{ message = "Use " ++ qualifiedToString ( [ "List" ], "map" ) ++ " instead"
, details = [ "Using " ++ qualifiedToString ( [ "List" ], "indexedMap" ) ++ " while ignoring the first argument is the same thing as calling " ++ qualifiedToString ( [ "List" ], "map" ) ++ "." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "List" ], "map" ) checkInfo))
, Fix.removeRange rangeToRemove
]
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
2022-04-17 09:59:11 +03:00
_ ->
[]
2023-09-03 12:30:45 +03:00
, \() ->
case AstHelpers.getSpecificFunctionCall ( [ "Basics" ], "always" ) checkInfo.lookupTable checkInfo.firstArg of
Just alwaysCall ->
2022-04-17 09:59:11 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Use " ++ qualifiedToString ( [ "List" ], "map" ) ++ " instead"
, details = [ "Using " ++ qualifiedToString ( [ "List" ], "indexedMap" ) ++ " while ignoring the first argument is the same thing as calling " ++ qualifiedToString ( [ "List" ], "map" ) ++ "." ]
2022-04-17 09:59:11 +03:00
}
2023-09-03 12:30:45 +03:00
checkInfo.fnRange
(Fix.replaceRangeBy checkInfo.fnRange
2023-06-25 21:01:32 +03:00
(qualifiedToString (qualify ( [ "List" ], "map" ) checkInfo))
2023-09-03 12:30:45 +03:00
:: replaceBySubExpressionFix alwaysCall.nodeRange alwaysCall.firstArg
)
2022-04-17 09:59:11 +03:00
]
Nothing ->
[]
2023-09-03 12:30:45 +03:00
]
()
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
listAppendEmptyErrorInfo : { message : String, details : List String }
listAppendEmptyErrorInfo =
{ message = "Appending [] doesn't have any effect"
2023-09-03 12:30:45 +03:00
, details = [ "You can remove the " ++ qualifiedToString ( [ "List" ], "append" ) ++ " function and the []." ]
2023-06-25 21:01:32 +03:00
}
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
listAppendChecks : CheckInfo -> List (Error {})
listAppendChecks checkInfo =
case ( checkInfo.firstArg, secondArg checkInfo ) of
2023-09-03 12:30:45 +03:00
( Node _ (Expression.ListExpr []), maybeSecondListArg ) ->
case maybeSecondListArg of
2023-06-25 21:01:32 +03:00
Nothing ->
2022-04-17 09:59:11 +03:00
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ listAppendEmptyErrorInfo
| details = [ "You can replace this call by identity." ]
2022-04-17 09:59:11 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "identity" ) checkInfo))
]
2022-04-17 09:59:11 +03:00
]
2023-09-03 12:30:45 +03:00
Just secondListArg ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
listAppendEmptyErrorInfo
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceBySubExpressionFix checkInfo.parentRange secondListArg)
2023-06-25 21:01:32 +03:00
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
( firstList, Just (Node _ (Expression.ListExpr [])) ) ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
listAppendEmptyErrorInfo
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceBySubExpressionFix checkInfo.parentRange firstList)
2023-06-25 21:01:32 +03:00
]
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
( Node firstListRange (Expression.ListExpr (_ :: _)), Just (Node secondListRange (Expression.ListExpr (_ :: _))) ) ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Appending literal lists could be simplified to be a single List"
, details = [ "Try moving all the elements into a single list." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.removeRange { start = secondListRange.end, end = checkInfo.parentRange.end }
, Fix.replaceRangeBy
{ start = checkInfo.parentRange.start, end = startWithoutBoundary secondListRange }
("[" ++ checkInfo.extractSourceCode (rangeWithoutBoundaries firstListRange) ++ ",")
]
2021-08-19 21:57:29 +03:00
]
_ ->
2023-06-25 21:01:32 +03:00
[]
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
listHeadExistsError : { message : String, details : List String }
listHeadExistsError =
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "head" ) ++ " on a list with a first element will result in Just that element"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by Just the first list element." ]
}
listHeadChecks : CheckInfo -> List (Error {})
listHeadChecks checkInfo =
let
2023-09-03 12:30:45 +03:00
justFirstElementError : Node Expression -> List (Error {})
2023-06-25 21:01:32 +03:00
justFirstElementError keep =
[ Rule.errorWithFix
listHeadExistsError
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceBySubExpressionFix (Node.range listArg) keep
2023-06-25 21:01:32 +03:00
++ [ Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo))
]
)
]
listArg : Node Expression
listArg =
AstHelpers.removeParens checkInfo.firstArg
in
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value listArg of
Expression.ListExpr [] ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "head" ) ++ " on an empty list will result in Nothing"
, details = [ "You can replace this call by Nothing." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) checkInfo))
]
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.ListExpr (head :: _) ->
justFirstElementError head
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.OperatorApplication "::" _ head _ ->
justFirstElementError head
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable listArg of
2023-06-25 21:01:32 +03:00
Just single ->
2023-09-03 12:30:45 +03:00
justFirstElementError single.element
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
Nothing ->
[]
2023-09-03 12:30:45 +03:00
]
()
2023-06-25 21:01:32 +03:00
listTailExistsError : { message : String, details : List String }
listTailExistsError =
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "tail" ) ++ " on a list with some elements will result in Just the elements after the first"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by Just the list elements after the first." ]
}
listEmptyTailExistsError : { message : String, details : List String }
listEmptyTailExistsError =
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "tail" ) ++ " on a list with a single element will result in Just the empty list"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by Just the empty list." ]
}
listTailChecks : CheckInfo -> List (Error {})
listTailChecks checkInfo =
let
listArg : Node Expression
listArg =
AstHelpers.removeParens checkInfo.firstArg
in
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value listArg of
Expression.ListExpr [] ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "tail" ) ++ " on an empty list will result in Nothing"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by Nothing." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) checkInfo))
]
]
2023-09-03 12:30:45 +03:00
Expression.ListExpr ((Node headRange _) :: (Node tailFirstRange _) :: _) ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
listTailExistsError
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.removeRange { start = headRange.start, end = tailFirstRange.start }
2023-06-25 21:01:32 +03:00
, Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo))
]
]
2023-09-03 12:30:45 +03:00
Expression.OperatorApplication "::" _ _ tail ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
listTailExistsError
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceBySubExpressionFix (Node.range listArg) tail
++ [ Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo))
]
)
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable listArg of
2023-06-25 21:01:32 +03:00
Just _ ->
[ Rule.errorWithFix
listEmptyTailExistsError
checkInfo.fnRange
[ Fix.replaceRangeBy (Node.range checkInfo.firstArg) "[]"
, Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo))
]
]
Nothing ->
[]
2023-09-03 12:30:45 +03:00
]
()
2023-06-25 21:01:32 +03:00
listMapChecks : CheckInfo -> List (Error {})
listMapChecks checkInfo =
firstThatReportsError
[ \() -> collectionMapChecks listCollection checkInfo
, \() -> dictToListMapChecks checkInfo
]
()
dictToListMapErrorInfo : { toEntryAspectList : String, tuplePart : String } -> { message : String, details : List String }
dictToListMapErrorInfo info =
let
toEntryAspectListAsQualifiedString : String
toEntryAspectListAsQualifiedString =
qualifiedToString ( [ "Dict" ], info.toEntryAspectList )
in
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "Dict" ], "toList" ) ++ ", then " ++ qualifiedToString ( [ "List" ], "map" ) ++ " " ++ qualifiedToString ( [ "Tuple" ], info.tuplePart ) ++ " is the same as using " ++ toEntryAspectListAsQualifiedString
2023-06-25 21:01:32 +03:00
, details = [ "Using " ++ toEntryAspectListAsQualifiedString ++ " directly is meant for this exact purpose and will also be faster." ]
}
dictToListMapChecks : CheckInfo -> List (Error {})
dictToListMapChecks listMapCheckInfo =
case secondArg listMapCheckInfo of
Just listArgument ->
2023-09-03 12:30:45 +03:00
case AstHelpers.getSpecificFunctionCall ( [ "Dict" ], "toList" ) listMapCheckInfo.lookupTable listArgument of
2023-06-25 21:01:32 +03:00
Just dictToListCall ->
let
error : { toEntryAspectList : String, tuplePart : String } -> Error {}
error info =
Rule.errorWithFix
(dictToListMapErrorInfo info)
listMapCheckInfo.fnRange
(keepOnlyFix { parentRange = Node.range listArgument, keep = Node.range dictToListCall.firstArg }
++ [ Fix.replaceRangeBy
(Range.combine [ listMapCheckInfo.fnRange, Node.range listMapCheckInfo.firstArg ])
(qualifiedToString (qualify ( [ "Dict" ], info.toEntryAspectList ) listMapCheckInfo))
]
)
in
if AstHelpers.isTupleFirstAccess listMapCheckInfo.lookupTable listMapCheckInfo.firstArg then
[ error { tuplePart = "first", toEntryAspectList = "keys" } ]
else if AstHelpers.isTupleSecondAccess listMapCheckInfo.lookupTable listMapCheckInfo.firstArg then
[ error { tuplePart = "second", toEntryAspectList = "values" } ]
else
[]
Nothing ->
[]
Nothing ->
[]
2023-09-03 12:30:45 +03:00
listMapCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
listMapCompositionChecks checkInfo =
2023-06-25 21:01:32 +03:00
case
2023-09-03 12:30:45 +03:00
( ( checkInfo.earlier.fn, checkInfo.earlier.args )
, checkInfo.later.args
)
2023-06-25 21:01:32 +03:00
of
2023-09-03 12:30:45 +03:00
( ( ( [ "Dict" ], "toList" ), [] ), elementMappingArg :: [] ) ->
2023-06-25 21:01:32 +03:00
let
error : { toEntryAspectList : String, tuplePart : String } -> Error {}
error info =
Rule.errorWithFix
(dictToListMapErrorInfo info)
2023-09-03 12:30:45 +03:00
checkInfo.later.fnRange
2023-06-25 21:01:32 +03:00
[ Fix.replaceRangeBy checkInfo.parentRange (qualifiedToString (qualify ( [ "Dict" ], info.toEntryAspectList ) checkInfo)) ]
in
2023-09-03 12:30:45 +03:00
if AstHelpers.isTupleFirstAccess checkInfo.lookupTable elementMappingArg then
2023-06-25 21:01:32 +03:00
[ error { tuplePart = "first", toEntryAspectList = "keys" } ]
2023-09-03 12:30:45 +03:00
else if AstHelpers.isTupleSecondAccess checkInfo.lookupTable elementMappingArg then
2023-06-25 21:01:32 +03:00
[ error { tuplePart = "second", toEntryAspectList = "values" } ]
else
[]
2023-09-03 12:30:45 +03:00
_ ->
[]
2023-06-25 21:01:32 +03:00
listMemberChecks : CheckInfo -> List (Error {})
listMemberChecks checkInfo =
let
needleArg : Node Expression
needleArg =
checkInfo.firstArg
needleArgNormalized : Node Expression
needleArgNormalized =
Normalize.normalize checkInfo needleArg
isNeedle : Node Expression -> Bool
isNeedle element =
Normalize.compareWithoutNormalization
(Normalize.normalize checkInfo element)
needleArgNormalized
== Normalize.ConfirmedEquality
in
case secondArg checkInfo of
Just listArg ->
let
needleRange : Range
needleRange =
Node.range needleArg
listMemberExistsError : List (Error {})
listMemberExistsError =
2023-09-03 12:30:45 +03:00
if checkInfo.expectNaN then
[]
else
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "member" ) ++ " on a list which contains the given element will result in True"
, details = [ "You can replace this call by True." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "True" ) checkInfo))
]
2023-06-25 21:01:32 +03:00
]
singleNonNormalizedEqualElementError : Node Expression -> List (Error {})
singleNonNormalizedEqualElementError element =
let
elementRange : Range
elementRange =
Node.range element
in
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "member" ) ++ " on an list with a single element is equivalent to directly checking for equality"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by checking whether the member to find and the list element are equal." ]
}
checkInfo.fnRange
(List.concat
[ keepOnlyFix
{ parentRange = checkInfo.parentRange
, keep = Range.combine [ needleRange, elementRange ]
}
, [ Fix.replaceRangeBy
(rangeBetweenExclusive ( needleRange, elementRange ))
" == "
]
, parenthesizeIfNeededFix element
]
)
]
in
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable listArg of
Just single ->
if isNeedle single.element then
listMemberExistsError
else
singleNonNormalizedEqualElementError single.element
Nothing ->
[]
, \() ->
case Node.value (AstHelpers.removeParens listArg) of
Expression.ListExpr [] ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "member" ) ++ " on an empty list will result in False"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by False." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "False" ) checkInfo))
]
]
2023-09-03 12:30:45 +03:00
Expression.ListExpr (el0 :: el1 :: el2Up) ->
if List.any isNeedle (el0 :: el1 :: el2Up) then
2023-06-25 21:01:32 +03:00
listMemberExistsError
else
2023-09-03 12:30:45 +03:00
[]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Expression.OperatorApplication "::" _ head tail ->
if List.any isNeedle (head :: getBeforeLastCons tail) then
2023-06-25 21:01:32 +03:00
listMemberExistsError
else
2023-09-03 12:30:45 +03:00
[]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
2023-06-25 21:01:32 +03:00
[]
2023-09-03 12:30:45 +03:00
]
()
2023-06-25 21:01:32 +03:00
Nothing ->
[]
getBeforeLastCons : Node Expression -> List (Node Expression)
2023-09-03 12:30:45 +03:00
getBeforeLastCons (Node _ expression) =
case expression of
2023-06-25 21:01:32 +03:00
Expression.OperatorApplication "::" _ beforeCons afterCons ->
beforeCons :: getBeforeLastCons afterCons
_ ->
[]
listSumChecks : CheckInfo -> List (Error {})
listSumChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value checkInfo.firstArg of
Expression.ListExpr [] ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "sum" ) ++ " on [] will result in 0"
, details = [ "You can replace this call by 0." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "0" ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Just listSingletonArg ->
[ Rule.errorWithFix
{ message = "Summing a list with a single element will result in the element itself"
, details = [ "You can replace this call by the single element itself." ]
}
checkInfo.fnRange
(replaceBySubExpressionFix checkInfo.parentRange listSingletonArg.element)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2023-06-25 21:01:32 +03:00
listProductChecks : CheckInfo -> List (Error {})
listProductChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value checkInfo.firstArg of
Expression.ListExpr [] ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "product" ) ++ " on [] will result in 1"
, details = [ "You can replace this call by 1." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "1" ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Just listSingletonArg ->
[ Rule.errorWithFix
{ message = qualifiedToString ( [ "List" ], "product" ) ++ " on a list with a single element will result in the element itself"
, details = [ "You can replace this call by the single element itself." ]
}
checkInfo.fnRange
(replaceBySubExpressionFix checkInfo.parentRange listSingletonArg.element)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2023-06-25 21:01:32 +03:00
listMinimumChecks : CheckInfo -> List (Error {})
listMinimumChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value checkInfo.firstArg of
Expression.ListExpr [] ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "minimum" ) ++ " on [] will result in Nothing"
, details = [ "You can replace this call by Nothing." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) checkInfo))
]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Just listSingletonArg ->
[ Rule.errorWithFix
{ message = qualifiedToString ( [ "List" ], "minimum" ) ++ " on a list with a single element will result in Just the element itself"
, details = [ "You can replace this call by Just the single element itself." ]
}
checkInfo.fnRange
(Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo))
:: replaceBySubExpressionFix (Node.range checkInfo.firstArg) listSingletonArg.element
)
]
_ ->
[]
]
()
2023-06-25 21:01:32 +03:00
listMaximumChecks : CheckInfo -> List (Error {})
listMaximumChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value checkInfo.firstArg of
Expression.ListExpr [] ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "maximum" ) ++ " on [] will result in Nothing"
, details = [ "You can replace this call by Nothing." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) checkInfo))
]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Just listSingletonArg ->
[ Rule.errorWithFix
{ message = qualifiedToString ( [ "List" ], "maximum" ) ++ " on a list with a single element will result in Just the element itself"
, details = [ "You can replace this call by Just the single element itself." ]
}
checkInfo.fnRange
(Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo))
:: replaceBySubExpressionFix (Node.range checkInfo.firstArg) listSingletonArg.element
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2023-06-25 21:01:32 +03:00
listFoldlChecks : CheckInfo -> List (Error {})
listFoldlChecks checkInfo =
listFoldAnyDirectionChecks "foldl" checkInfo
listFoldrChecks : CheckInfo -> List (Error {})
listFoldrChecks checkInfo =
listFoldAnyDirectionChecks "foldr" checkInfo
listFoldAnyDirectionChecks : String -> CheckInfo -> List (Error {})
listFoldAnyDirectionChecks foldOperationName checkInfo =
case secondArg checkInfo of
Nothing ->
[]
2023-09-03 12:30:45 +03:00
Just initialArg ->
2023-06-25 21:01:32 +03:00
let
2023-09-03 12:30:45 +03:00
maybeListArg : Maybe (Node Expression)
maybeListArg =
2023-06-25 21:01:32 +03:00
thirdArg checkInfo
2023-09-03 12:30:45 +03:00
numberBinaryOperationChecks : { identity : Int, two : String, list : String } -> List (Error {})
numberBinaryOperationChecks operation =
2023-06-25 21:01:32 +03:00
let
2023-09-03 12:30:45 +03:00
fixWith : List Fix -> List (Error {})
fixWith fixes =
2023-06-25 21:01:32 +03:00
let
2023-09-03 12:30:45 +03:00
replacementOperationAsString : String
replacementOperationAsString =
qualifiedToString ( [ "List" ], operation.list )
2023-06-25 21:01:32 +03:00
in
2023-09-03 12:30:45 +03:00
[ Rule.errorWithFix
{ message = "Use " ++ replacementOperationAsString ++ " instead"
, details =
[ "Using " ++ qualifiedToString ( [ "List" ], foldOperationName ) ++ " (" ++ operation.two ++ ") " ++ String.fromInt operation.identity ++ " is the same as using " ++ replacementOperationAsString ++ "." ]
}
checkInfo.fnRange
fixes
]
in
if AstHelpers.getUncomputedNumberValue initialArg == Just (Basics.toFloat operation.identity) then
fixWith
[ Fix.replaceRangeBy
{ start = checkInfo.fnRange.start
, end = (Node.range initialArg).end
}
(qualifiedToString (qualify ( [ "List" ], operation.list ) checkInfo))
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
else
case maybeListArg of
Nothing ->
[]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Just _ ->
if checkInfo.usingRightPizza then
-- list |> fold op initial --> ((list |> List.op) op initial)
fixWith
[ Fix.insertAt (Node.range initialArg).end ")"
, Fix.insertAt (Node.range initialArg).start (operation.two ++ " ")
, Fix.replaceRangeBy
{ start = checkInfo.fnRange.start
, end = (Node.range checkInfo.firstArg).end
}
(qualifiedToString (qualify ( [ "List" ], operation.list ) checkInfo) ++ ")")
, Fix.insertAt checkInfo.parentRange.start "(("
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
else
-- <| or application
-- fold op initial list --> (initial op (List.op list))
fixWith
[ Fix.insertAt checkInfo.parentRange.end ")"
, Fix.insertAt (Node.range initialArg).end
(" "
++ operation.two
++ " ("
++ qualifiedToString (qualify ( [ "List" ], operation.list ) checkInfo)
)
, Fix.removeRange
{ start = checkInfo.fnRange.start
, end = (Node.range initialArg).start
}
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
boolBinaryOperationChecks : { two : String, list : String, determining : Bool } -> Bool -> List (Error {})
boolBinaryOperationChecks operation initialIsDetermining =
if initialIsDetermining == operation.determining then
let
determiningAsString : String
determiningAsString =
AstHelpers.boolToString operation.determining
in
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to " ++ qualifiedToString ( [ "List" ], foldOperationName ) ++ " will result in " ++ determiningAsString
, details = [ "You can replace this call by " ++ determiningAsString ++ "." ]
2023-06-25 21:01:32 +03:00
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceByEmptyFix (qualifiedToString (qualify ( [ "Basics" ], determiningAsString ) checkInfo))
checkInfo.parentRange
(thirdArg checkInfo)
checkInfo
)
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
else
-- initialIsTrue /= operation.determining
let
replacementOperationAsString : String
replacementOperationAsString =
qualifiedToString ( [ "List" ], operation.list ) ++ " identity"
in
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Use " ++ replacementOperationAsString ++ " instead"
, details = [ "Using " ++ qualifiedToString ( [ "List" ], foldOperationName ) ++ " (" ++ operation.two ++ ") " ++ AstHelpers.boolToString (not operation.determining) ++ " is the same as using " ++ replacementOperationAsString ++ "." ]
2023-06-25 21:01:32 +03:00
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy
{ start = checkInfo.fnRange.start, end = (Node.range initialArg).end }
(qualifiedToString (qualify ( [ "List" ], operation.list ) checkInfo)
++ " "
++ qualifiedToString (qualify ( [ "Basics" ], "identity" ) checkInfo)
)
]
]
in
firstThatReportsError
[ \() ->
case maybeListArg of
Just listArg ->
case AstHelpers.getSpecificFunctionCall ( [ "Set" ], "toList" ) checkInfo.lookupTable listArg of
Just setToListCall ->
[ Rule.errorWithFix
{ message = "To fold a set, you don't need to convert to a List"
, details = [ "Using " ++ qualifiedToString ( [ "Set" ], foldOperationName ) ++ " directly is meant for this exact purpose and will also be faster." ]
}
checkInfo.fnRange
(replaceBySubExpressionFix setToListCall.nodeRange setToListCall.firstArg
++ [ Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Set" ], foldOperationName ) checkInfo))
]
)
]
2023-06-25 21:01:32 +03:00
Nothing ->
2023-09-03 12:30:45 +03:00
[]
Nothing ->
[]
, \() ->
case maybeListArg of
Just listArg ->
case AstHelpers.getListLiteral listArg of
Just [] ->
[ Rule.errorWithFix
{ message = "The call to " ++ qualifiedToString ( [ "List" ], foldOperationName ) ++ " will result in the initial accumulator"
, details = [ "You can replace this call by the initial accumulator." ]
2023-06-25 21:01:32 +03:00
}
2023-09-03 12:30:45 +03:00
checkInfo.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range initialArg })
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
_ ->
[]
Nothing ->
[]
, \() ->
case AstHelpers.getAlwaysResult checkInfo.lookupTable checkInfo.firstArg of
Just reduceAlwaysResult ->
if AstHelpers.isIdentity checkInfo.lookupTable reduceAlwaysResult then
[ Rule.errorWithFix
{ message = "The call to " ++ qualifiedToString ( [ "List" ], foldOperationName ) ++ " will result in the initial accumulator"
, details = [ "You can replace this call by the initial accumulator." ]
}
checkInfo.fnRange
(case maybeListArg of
Nothing ->
[ Fix.replaceRangeBy
{ start = checkInfo.fnRange.start
, end = (Node.range checkInfo.firstArg).end
}
(qualifiedToString (qualify ( [ "Basics" ], "always" ) checkInfo))
]
Just _ ->
keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range initialArg }
)
]
else
[]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
, \() ->
if AstHelpers.isSpecificUnappliedBinaryOperation "*" checkInfo checkInfo.firstArg then
2023-06-25 21:01:32 +03:00
numberBinaryOperationChecks { two = "*", list = "product", identity = 1 }
2023-09-03 12:30:45 +03:00
else
[]
, \() ->
if AstHelpers.isSpecificUnappliedBinaryOperation "+" checkInfo checkInfo.firstArg then
2023-06-25 21:01:32 +03:00
numberBinaryOperationChecks { two = "+", list = "sum", identity = 0 }
else
2023-09-03 12:30:45 +03:00
[]
, \() ->
case Evaluate.getBoolean checkInfo initialArg of
Undetermined ->
[]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Determined initialBool ->
if AstHelpers.isSpecificUnappliedBinaryOperation "&&" checkInfo checkInfo.firstArg then
boolBinaryOperationChecks { two = "&&", list = "all", determining = False } initialBool
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
else if AstHelpers.isSpecificUnappliedBinaryOperation "||" checkInfo checkInfo.firstArg then
boolBinaryOperationChecks { two = "||", list = "any", determining = True } initialBool
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
else
[]
]
()
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
listFoldlCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
listFoldlCompositionChecks checkInfo =
foldAndSetToListCompositionChecks "foldl" checkInfo
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
listFoldrCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
listFoldrCompositionChecks checkInfo =
foldAndSetToListCompositionChecks "foldr" checkInfo
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
foldAndSetToListCompositionChecks : String -> CompositionIntoCheckInfo -> List (Error {})
foldAndSetToListCompositionChecks foldOperationName checkInfo =
case ( checkInfo.earlier.fn, checkInfo.earlier.args ) of
( ( [ "Set" ], "toList" ), [] ) ->
[ Rule.errorWithFix
{ message = "To fold a set, you don't need to convert to a List"
, details = [ "Using " ++ qualifiedToString ( [ "Set" ], foldOperationName ) ++ " directly is meant for this exact purpose and will also be faster." ]
}
checkInfo.later.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = checkInfo.later.range }
++ [ Fix.replaceRangeBy checkInfo.later.fnRange
(qualifiedToString (qualify ( [ "Set" ], foldOperationName ) checkInfo))
]
)
]
2023-06-25 21:01:32 +03:00
_ ->
[]
listAllChecks : CheckInfo -> List (Error {})
listAllChecks checkInfo =
2023-09-03 12:30:45 +03:00
let
maybeListArg : Maybe (Node Expression)
maybeListArg =
secondArg checkInfo
in
firstThatReportsError
[ \() ->
case maybeListArg of
Just (Node _ (Expression.ListExpr [])) ->
[ Rule.errorWithFix
{ message = "The call to " ++ qualifiedToString ( [ "List" ], "all" ) ++ " will result in True"
, details = [ "You can replace this call by True." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "True" ) checkInfo))
]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
2023-06-25 21:01:32 +03:00
case Evaluate.isAlwaysBoolean checkInfo checkInfo.firstArg of
Determined True ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to " ++ qualifiedToString ( [ "List" ], "all" ) ++ " will result in True"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by True." ]
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceByBoolWithIrrelevantLastArgFix { lastArg = maybeListArg, replacement = True, checkInfo = checkInfo })
2023-06-25 21:01:32 +03:00
]
_ ->
[]
2023-09-03 12:30:45 +03:00
]
()
2023-06-25 21:01:32 +03:00
listAnyChecks : CheckInfo -> List (Error {})
listAnyChecks checkInfo =
2023-09-03 12:30:45 +03:00
let
maybeListArg : Maybe (Node Expression)
maybeListArg =
secondArg checkInfo
in
firstThatReportsError
[ \() ->
case maybeListArg of
Just (Node _ (Expression.ListExpr [])) ->
[ Rule.errorWithFix
{ message = "The call to " ++ qualifiedToString ( [ "List" ], "any" ) ++ " will result in False"
, details = [ "You can replace this call by False." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "False" ) checkInfo))
]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
2023-06-25 21:01:32 +03:00
case Evaluate.isAlwaysBoolean checkInfo checkInfo.firstArg of
Determined False ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to " ++ qualifiedToString ( [ "List" ], "any" ) ++ " will result in False"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by False." ]
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceByBoolWithIrrelevantLastArgFix { lastArg = maybeListArg, replacement = False, checkInfo = checkInfo })
2023-06-25 21:01:32 +03:00
]
_ ->
[]
2023-09-03 12:30:45 +03:00
, \() ->
case Evaluate.isEqualToSomethingFunction checkInfo.firstArg of
Nothing ->
[]
Just equatedTo ->
[ Rule.errorWithFix
{ message = "Use " ++ qualifiedToString ( [ "List" ], "member" ) ++ " instead"
, details = [ "This call to " ++ qualifiedToString ( [ "List" ], "any" ) ++ " checks for the presence of a value, which what " ++ qualifiedToString ( [ "List" ], "member" ) ++ " is for." ]
}
checkInfo.fnRange
(Fix.replaceRangeBy checkInfo.fnRange (qualifiedToString (qualify ( [ "List" ], "member" ) checkInfo))
:: replaceBySubExpressionFix (Node.range checkInfo.firstArg) equatedTo.something
)
]
]
()
2023-06-25 21:01:32 +03:00
listFilterMapChecks : CheckInfo -> List (Error {})
listFilterMapChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case constructsSpecificInAllBranches ( [ "Maybe" ], "Just" ) checkInfo.lookupTable checkInfo.firstArg of
Determined justConstruction ->
case justConstruction of
NonDirectConstruction fix ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "filterMap" ) ++ " with a function that will always return Just is the same as using " ++ qualifiedToString ( [ "List" ], "map" )
, details = [ "You can remove the `Just`s and replace the call by " ++ qualifiedToString ( [ "List" ], "map" ) ++ "." ]
}
checkInfo.fnRange
(Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "List" ], "map" ) checkInfo))
:: fix
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
DirectConstruction ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "filterMap" ) ++ " with a function that will always return Just is the same as not using " ++ qualifiedToString ( [ "List" ], "filterMap" )
, details = [ "You can remove this call and replace it by the list itself." ]
}
checkInfo.fnRange
(toIdentityFix
{ lastArg = secondArg checkInfo, resources = checkInfo }
)
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
Undetermined ->
[]
, \() ->
case returnsSpecificValueOrFunctionInAllBranches ( [ "Maybe" ], "Nothing" ) checkInfo.lookupTable checkInfo.firstArg of
Determined _ ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "filterMap" ) ++ " with a function that will always return Nothing will result in an empty list"
, details = [ "You can remove this call and replace it by an empty list." ]
}
checkInfo.fnRange
(replaceByEmptyFix "[]" checkInfo.parentRange (secondArg checkInfo) checkInfo)
]
Undetermined ->
[]
, \() ->
case secondArg checkInfo of
Just listArg ->
if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.firstArg then
firstThatReportsError
[ \() ->
case AstHelpers.getSpecificFunctionCall ( [ "List" ], "map" ) checkInfo.lookupTable listArg of
Just listMapCall ->
2022-04-17 09:59:11 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
-- TODO rework error info
{ message = qualifiedToString ( [ "List" ], "map" ) ++ " and " ++ qualifiedToString ( [ "List" ], "filterMap" ) ++ " identity can be combined using " ++ qualifiedToString ( [ "List" ], "filterMap" )
, details = [ qualifiedToString ( [ "List" ], "filterMap" ) ++ " is meant for this exact purpose and will also be faster." ]
2022-04-17 09:59:11 +03:00
}
2023-06-25 21:01:32 +03:00
{ start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).end }
2023-09-03 12:30:45 +03:00
(replaceBySubExpressionFix checkInfo.parentRange listArg
++ [ Fix.replaceRangeBy listMapCall.fnRange
(qualifiedToString (qualify ( [ "List" ], "filterMap" ) checkInfo))
]
2022-04-17 09:59:11 +03:00
)
]
Nothing ->
[]
2023-09-03 12:30:45 +03:00
, \() ->
case listArg of
Node listRange (Expression.ListExpr list) ->
case collectJusts checkInfo.lookupTable list [] of
Just justRanges ->
[ Rule.errorWithFix
{ message = "Unnecessary use of " ++ qualifiedToString ( [ "List" ], "filterMap" ) ++ " identity"
, details = [ "All of the elements in the list are `Just`s, which can be simplified by removing all of the `Just`s." ]
}
{ start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).end }
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = listRange }
++ List.map Fix.removeRange justRanges
)
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
]
()
else
[]
Nothing ->
[]
]
()
2022-04-17 09:59:11 +03:00
collectJusts : ModuleNameLookupTable -> List (Node Expression) -> List Range -> Maybe (List Range)
collectJusts lookupTable list acc =
case list of
[] ->
Just acc
2023-09-03 12:30:45 +03:00
(Node _ element) :: restOfList ->
case element of
Expression.Application ((Node justRange (Expression.FunctionOrValue _ "Just")) :: (Node justArgRange _) :: []) ->
2022-04-17 09:59:11 +03:00
case ModuleNameLookupTable.moduleNameAt lookupTable justRange of
Just [ "Maybe" ] ->
2023-09-03 12:30:45 +03:00
collectJusts lookupTable restOfList ({ start = justRange.start, end = justArgRange.start } :: acc)
2022-04-17 09:59:11 +03:00
_ ->
Nothing
_ ->
Nothing
2023-09-03 12:30:45 +03:00
listFilterMapCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
listFilterMapCompositionChecks checkInfo =
case checkInfo.later.args of
elementToMaybeMappingArg :: [] ->
if AstHelpers.isIdentity checkInfo.lookupTable elementToMaybeMappingArg then
case ( checkInfo.earlier.fn, checkInfo.earlier.args ) of
( ( [ "List" ], "map" ), _ :: [] ) ->
[ Rule.errorWithFix
-- TODO rework error info
{ message = qualifiedToString ( [ "List" ], "map" ) ++ " and " ++ qualifiedToString ( [ "List" ], "filterMap" ) ++ " identity can be combined using " ++ qualifiedToString ( [ "List" ], "filterMap" )
, details = [ qualifiedToString ( [ "List" ], "filterMap" ) ++ " is meant for this exact purpose and will also be faster." ]
}
-- TODO use laterFnRange
checkInfo.later.range
(Fix.replaceRangeBy checkInfo.earlier.fnRange
(qualifiedToString (qualify ( [ "List" ], "filterMap" ) checkInfo))
:: keepOnlyFix { parentRange = checkInfo.parentRange, keep = checkInfo.earlier.range }
)
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
else
2022-04-17 09:59:11 +03:00
[]
2023-09-03 12:30:45 +03:00
_ ->
[]
2021-08-19 21:57:29 +03:00
listRangeChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
listRangeChecks checkInfo =
2023-09-03 12:30:45 +03:00
case secondArg checkInfo of
Just rangeEndArg ->
case ( Evaluate.getInt checkInfo checkInfo.firstArg, Evaluate.getInt checkInfo rangeEndArg ) of
( Just rangeStartValue, Just rangeEndValue ) ->
if rangeStartValue > rangeEndValue then
2022-09-01 17:15:28 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to " ++ qualifiedToString ( [ "List" ], "range" ) ++ " will result in []"
, details = [ "The second argument to " ++ qualifiedToString ( [ "List" ], "range" ) ++ " is bigger than the first one, therefore you can replace this list by an empty list." ]
2022-09-01 17:15:28 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceByEmptyFix "[]" checkInfo.parentRange (Just rangeEndValue) checkInfo)
2022-09-01 17:15:28 +03:00
]
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
else
[]
2023-09-03 12:30:45 +03:00
( Nothing, _ ) ->
[]
( _, Nothing ) ->
2022-09-01 17:15:28 +03:00
[]
2021-08-19 21:57:29 +03:00
Nothing ->
[]
listRepeatChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
listRepeatChecks checkInfo =
case Evaluate.getInt checkInfo checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Just intValue ->
if intValue < 1 then
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = qualifiedToString ( [ "List" ], "repeat" ) ++ " will result in an empty list"
, details = [ "Using " ++ qualifiedToString ( [ "List" ], "repeat" ) ++ " with a number less than 1 will result in an empty list. You can replace this call by an empty list." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
(replaceByEmptyFix "[]" checkInfo.parentRange (secondArg checkInfo) checkInfo)
2021-08-19 21:57:29 +03:00
]
else
[]
_ ->
[]
listReverseChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
listReverseChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value (AstHelpers.removeParens checkInfo.firstArg) of
Expression.ListExpr [] ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "reverse" ) ++ " on [] will result in []"
, details = [ "You can replace this call by []." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
2021-08-19 21:57:29 +03:00
removeAlongWithOtherFunctionCheck
reverseReverseCompositionErrorMessage
2023-09-03 12:30:45 +03:00
(AstHelpers.getSpecificValueOrFunction ( [ "List" ], "reverse" ))
2021-08-19 21:57:29 +03:00
checkInfo
2023-09-03 12:30:45 +03:00
]
()
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
listSortChecks : CheckInfo -> List (Error {})
listSortChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case checkInfo.firstArg of
Node _ (Expression.ListExpr []) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "sort" ) ++ " on [] will result in []"
, details = [ "You can replace this call by []." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Just _ ->
[ Rule.errorWithFix
{ message = "Sorting a list with a single element will result in the list itself"
, details = [ "You can replace this call by the list itself." ]
}
checkInfo.fnRange
(keepOnlyFix
{ parentRange = checkInfo.parentRange
, keep = Node.range checkInfo.firstArg
}
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2023-06-25 21:01:32 +03:00
listSortByChecks : CheckInfo -> List (Error {})
listSortByChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ case secondArg checkInfo of
Just listArg ->
firstThatReportsError
[ \() ->
case listArg of
Node _ (Expression.ListExpr []) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "sortBy" ) ++ " on [] will result in []"
, details = [ "You can replace this call by []." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable listArg of
Just _ ->
[ Rule.errorWithFix
{ message = "Sorting a list with a single element will result in the list itself"
, details = [ "You can replace this call by the list itself." ]
}
checkInfo.fnRange
(keepOnlyFix
{ parentRange = checkInfo.parentRange
, keep = Node.range listArg
}
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
Nothing ->
\() -> []
, \() ->
case AstHelpers.getAlwaysResult checkInfo.lookupTable checkInfo.firstArg of
2023-06-25 21:01:32 +03:00
Just _ ->
[ identityError
2023-09-03 12:30:45 +03:00
{ toFix = qualifiedToString ( [ "List" ], "sortBy" ) ++ " (always a)"
2023-06-25 21:01:32 +03:00
, lastArgName = "list"
, lastArg = secondArg checkInfo
, resources = checkInfo
}
]
Nothing ->
2023-09-03 12:30:45 +03:00
[]
, \() ->
if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.firstArg then
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "sortBy" ) ++ " identity is the same as using " ++ qualifiedToString ( [ "List" ], "sort" )
, details = [ "You can replace this call by " ++ qualifiedToString ( [ "List" ], "sort" ) ++ "." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy
{ start = checkInfo.fnRange.start
, end = (Node.range checkInfo.firstArg).end
}
(qualifiedToString (qualify ( [ "List" ], "sort" ) checkInfo))
]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
else
[]
]
()
2023-06-25 21:01:32 +03:00
listSortWithChecks : CheckInfo -> List (Error {})
listSortWithChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ case secondArg checkInfo of
Just listArg ->
firstThatReportsError
[ \() ->
case listArg of
Node _ (Expression.ListExpr []) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "sortWith" ) ++ " on [] will result in []"
, details = [ "You can replace this call by []." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable listArg of
Just _ ->
[ Rule.errorWithFix
{ message = "Sorting a list with a single element will result in the list itself"
, details = [ "You can replace this call by the list itself." ]
}
checkInfo.fnRange
(keepOnlyFix
{ parentRange = checkInfo.parentRange
, keep = Node.range listArg
}
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
]
Nothing ->
\() -> []
, \() ->
2023-06-25 21:01:32 +03:00
let
alwaysAlwaysOrder : Maybe Order
alwaysAlwaysOrder =
2023-09-03 12:30:45 +03:00
AstHelpers.getAlwaysResult checkInfo.lookupTable checkInfo.firstArg
|> Maybe.andThen (AstHelpers.getAlwaysResult checkInfo.lookupTable)
|> Maybe.andThen (AstHelpers.getOrder checkInfo.lookupTable)
2023-06-25 21:01:32 +03:00
in
case alwaysAlwaysOrder of
Just order ->
let
fixToIdentity : List (Error {})
fixToIdentity =
[ identityError
2023-09-03 12:30:45 +03:00
{ toFix = qualifiedToString ( [ "List" ], "sortWith" ) ++ " (\\_ _ -> " ++ AstHelpers.orderToString order ++ ")"
2023-06-25 21:01:32 +03:00
, lastArgName = "list"
, lastArg = secondArg checkInfo
, resources = checkInfo
}
]
in
case order of
LT ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "sortWith" ) ++ " (\\_ _ -> LT) is the same as using " ++ qualifiedToString ( [ "List" ], "reverse" )
, details = [ "You can replace this call by " ++ qualifiedToString ( [ "List" ], "reverse" ) ++ "." ]
2023-06-25 21:01:32 +03:00
}
checkInfo.fnRange
[ Fix.replaceRangeBy
{ start = checkInfo.fnRange.start
, end = (Node.range checkInfo.firstArg).end
}
(qualifiedToString (qualify ( [ "List" ], "reverse" ) checkInfo))
]
]
EQ ->
fixToIdentity
GT ->
fixToIdentity
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2023-06-25 21:01:32 +03:00
2022-04-17 09:59:11 +03:00
listTakeChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
listTakeChecks checkInfo =
let
2023-09-03 12:30:45 +03:00
maybeListArg : Maybe (Node Expression)
maybeListArg =
2023-06-25 21:01:32 +03:00
secondArg checkInfo
in
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
if AstHelpers.getUncomputedNumberValue checkInfo.firstArg == Just 0 then
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Taking 0 items from a list will result in []"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by []." ]
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceByEmptyFix "[]" checkInfo.parentRange maybeListArg checkInfo)
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
else
2023-06-25 21:01:32 +03:00
[]
2023-09-03 12:30:45 +03:00
, \() ->
case maybeListArg of
Just listArg ->
case determineListLength checkInfo.lookupTable listArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "take" ) ++ " on [] will result in []"
, details = [ "You can replace this call by []." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
]
_ ->
[]
Nothing ->
[]
]
()
2023-06-25 21:01:32 +03:00
listDropChecks : CheckInfo -> List (Error {})
listDropChecks checkInfo =
2023-09-03 12:30:45 +03:00
let
maybeListArg : Maybe (Node Expression)
maybeListArg =
secondArg checkInfo
in
firstThatReportsError
[ \() ->
case Evaluate.getInt checkInfo checkInfo.firstArg of
Just 0 ->
[ -- TODO use identityError
Rule.errorWithFix
(case maybeListArg of
Just _ ->
{ message = "Dropping 0 items from a list will result in the list itself"
, details = [ "You can replace this call by the list itself." ]
}
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
{ message = "Dropping 0 items from a list will result in the list itself"
, details = [ "You can replace this function by identity." ]
}
)
checkInfo.fnRange
(toIdentityFix { lastArg = maybeListArg, resources = checkInfo })
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case maybeListArg of
Just listArg ->
case determineListLength checkInfo.lookupTable listArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "List" ], "drop" ) ++ " on [] will result in []"
, details = [ "You can replace this call by []." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2023-06-25 21:01:32 +03:00
listMapNChecks : { n : Int } -> CheckInfo -> List (Error {})
listMapNChecks { n } checkInfo =
if List.any (\(Node _ list) -> list == Expression.ListExpr []) checkInfo.argsAfterFirst then
let
callReplacement : String
callReplacement =
multiAlways (n - List.length checkInfo.argsAfterFirst) "[]" checkInfo
in
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "map" ++ String.fromInt n ) ++ " with any list being [] will result in []"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by " ++ callReplacement ++ "." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange callReplacement ]
]
else
[]
listUnzipChecks : CheckInfo -> List (Error {})
listUnzipChecks checkInfo =
case Node.value checkInfo.firstArg of
Expression.ListExpr [] ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "List" ], "unzip" ) ++ " on [] will result in ( [], [] )"
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by ( [], [] )." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "( [], [] )" ]
]
_ ->
[]
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
setFromListChecks : CheckInfo -> List (Error {})
setFromListChecks checkInfo =
collectionFromListChecks setCollection checkInfo
++ setFromListSingletonChecks checkInfo
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
setFromListSingletonChecks : CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
setFromListSingletonChecks checkInfo =
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Nothing ->
[]
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
Just listSingleton ->
[ Rule.errorWithFix
setFromListSingletonError
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceBySubExpressionFix (Node.range checkInfo.firstArg) listSingleton.element
2023-06-25 21:01:32 +03:00
++ [ Fix.replaceRangeBy checkInfo.fnRange (qualifiedToString (qualify ( [ "Set" ], "singleton" ) checkInfo)) ]
)
]
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
setFromListSingletonError : { message : String, details : List String }
setFromListSingletonError =
2023-09-03 12:30:45 +03:00
{ message = qualifiedToString ( [ "Set" ], "fromList" ) ++ " with a single element can be replaced using " ++ qualifiedToString ( [ "Set" ], "singleton" )
, details = [ "You can replace this call by " ++ qualifiedToString ( [ "Set" ], "singleton" ) ++ " with the list element itself." ]
2023-06-25 21:01:32 +03:00
}
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
setFromListCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
setFromListCompositionChecks checkInfo =
case ( checkInfo.earlier.fn, checkInfo.earlier.args ) of
( ( [ "List" ], "singleton" ), [] ) ->
[ Rule.errorWithFix
setFromListSingletonError
checkInfo.later.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Set" ], "singleton" ) checkInfo))
2022-04-17 09:59:11 +03:00
]
2023-09-03 12:30:45 +03:00
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
_ ->
2023-06-25 21:01:32 +03:00
[]
2022-04-17 09:59:11 +03:00
2021-08-19 21:57:29 +03:00
subAndCmdBatchChecks : String -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
subAndCmdBatchChecks moduleName checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Node.value checkInfo.firstArg of
Expression.ListExpr [] ->
[ Rule.errorWithFix
{ message = "Replace by " ++ moduleName ++ ".batch"
, details = [ moduleName ++ ".batch [] and " ++ moduleName ++ ".none are equivalent but the latter is more idiomatic in Elm code" ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Platform", moduleName ], "none" ) checkInfo))
]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.ListExpr (arg0 :: arg1 :: arg2Up) ->
neighboringMap
(\arg ->
case AstHelpers.removeParens arg.current of
Node batchRange (Expression.FunctionOrValue _ "none") ->
if ModuleNameLookupTable.moduleNameAt checkInfo.lookupTable batchRange == Just [ "Platform", moduleName ] then
let
argRange : Range
argRange =
Node.range arg.current
in
Just
(Rule.errorWithFix
{ message = "Unnecessary " ++ moduleName ++ ".none"
, details = [ moduleName ++ ".none will be ignored by " ++ moduleName ++ ".batch." ]
}
argRange
(case arg.before of
Just (Node prevRange _) ->
[ Fix.removeRange { start = prevRange.end, end = argRange.end } ]
Nothing ->
case arg.after of
Just (Node nextRange _) ->
[ Fix.removeRange { start = argRange.start, end = nextRange.start } ]
Nothing ->
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Platform", moduleName ], "none" ) checkInfo))
]
)
2021-08-19 21:57:29 +03:00
)
2023-09-03 12:30:45 +03:00
else
Nothing
_ ->
2021-08-19 21:57:29 +03:00
Nothing
2023-09-03 12:30:45 +03:00
)
(arg0 :: arg1 :: arg2Up)
|> List.filterMap identity
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Just listSingletonArg ->
[ Rule.errorWithFix
{ message = "Unnecessary " ++ moduleName ++ ".batch"
, details = [ moduleName ++ ".batch with a single element is equal to that element." ]
}
checkInfo.fnRange
(replaceBySubExpressionFix checkInfo.parentRange listSingletonArg.element)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
]
()
2021-08-19 21:57:29 +03:00
2022-04-17 09:59:11 +03:00
2023-06-25 21:01:32 +03:00
-- HTML.ATTRIBUTES
htmlAttributesClassListFalseElementError : { message : String, details : List String }
htmlAttributesClassListFalseElementError =
2023-09-03 12:30:45 +03:00
{ message = "In a " ++ qualifiedToString ( [ "Html", "Attributes" ], "classList" ) ++ ", a tuple paired with False can be removed"
2023-06-25 21:01:32 +03:00
, details = [ "You can remove the tuple list element where the second part is False." ]
}
htmlAttributesClassListChecks : CheckInfo -> List (Error {})
htmlAttributesClassListChecks checkInfo =
let
listArg : Node Expression
listArg =
checkInfo.firstArg
2023-09-03 12:30:45 +03:00
getTupleWithSpecificSecond : Bool -> Node Expression -> Maybe { range : Range, first : Node Expression }
getTupleWithSpecificSecond specificBool expressionNode =
2023-06-25 21:01:32 +03:00
case AstHelpers.getTuple expressionNode of
Just tuple ->
2023-09-03 12:30:45 +03:00
case AstHelpers.getSpecificBool specificBool checkInfo.lookupTable tuple.second of
Just _ ->
Just { range = tuple.range, first = tuple.first }
2023-06-25 21:01:32 +03:00
Nothing ->
Nothing
Nothing ->
Nothing
2023-09-03 12:30:45 +03:00
in
firstThatReportsError
[ \() ->
case AstHelpers.getListSingleton checkInfo.lookupTable listArg of
Just single ->
case AstHelpers.getTuple single.element of
Just tuple ->
case AstHelpers.getBool checkInfo.lookupTable tuple.second of
Just bool ->
if bool then
[ Rule.errorWithFix
{ message = qualifiedToString ( [ "Html", "Attributes" ], "classList" ) ++ " with a single tuple paired with True can be replaced with " ++ qualifiedToString ( [ "Html", "Attributes" ], "class" )
, details = [ "You can replace this call by " ++ qualifiedToString ( [ "Html", "Attributes" ], "class" ) ++ " with the String from the single tuple list element." ]
}
checkInfo.fnRange
(replaceBySubExpressionFix (Node.range listArg) tuple.first
++ [ Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Html", "Attributes" ], "class" ) checkInfo))
]
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
else
[ Rule.errorWithFix htmlAttributesClassListFalseElementError
checkInfo.fnRange
[ Fix.replaceRangeBy (Node.range listArg) "[]" ]
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
Nothing ->
[]
2023-06-25 21:01:32 +03:00
Nothing ->
2023-09-03 12:30:45 +03:00
[]
, \() ->
case AstHelpers.getListLiteral listArg of
Just (tuple0 :: tuple1 :: tuple2Up) ->
case findMapNeighboring (getTupleWithSpecificSecond False) (tuple0 :: tuple1 :: tuple2Up) of
Just classPart ->
[ Rule.errorWithFix htmlAttributesClassListFalseElementError
checkInfo.fnRange
(listLiteralElementRemoveFix classPart)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
, \() ->
case AstHelpers.getCollapsedCons listArg of
Just classParts ->
case findMapNeighboring (getTupleWithSpecificSecond False) classParts.consed of
Just classPart ->
[ Rule.errorWithFix htmlAttributesClassListFalseElementError
checkInfo.fnRange
(collapsedConsRemoveElementFix
{ toRemove = classPart
, tailRange = Node.range classParts.tail
}
)
]
Nothing ->
[]
Nothing ->
[]
]
()
-- PARSER
oneOfChecks : CheckInfo -> List (Error {})
oneOfChecks checkInfo =
case AstHelpers.getListSingleton checkInfo.lookupTable checkInfo.firstArg of
Just listSingletonArg ->
[ Rule.errorWithFix
{ message = "Unnecessary oneOf"
, details = [ "There is only a single element in the list of elements to try out." ]
}
checkInfo.fnRange
(replaceBySubExpressionFix checkInfo.parentRange listSingletonArg.element)
]
Nothing ->
[]
-- RANDOM
randomUniformChecks : CheckInfo -> List (Error {})
randomUniformChecks checkInfo =
case secondArg checkInfo of
Just otherOptionsArg ->
case AstHelpers.getListLiteral otherOptionsArg of
Just [] ->
let
onlyValueRange : Range
onlyValueRange =
Node.range checkInfo.firstArg
in
[ Rule.errorWithFix
{ message = "Random.uniform with only one possible value can be replaced by Random.constant"
, details = [ "Only a single value can be produced by this Random.uniform call. You can replace the call with Random.constant with the value." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy { start = checkInfo.parentRange.start, end = onlyValueRange.start }
(qualifiedToString (qualify ( [ "Random" ], "constant" ) checkInfo) ++ " ")
, Fix.removeRange { start = onlyValueRange.end, end = checkInfo.parentRange.end }
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
]
_ ->
[]
Nothing ->
[]
randomWeightedChecks : CheckInfo -> List (Error {})
randomWeightedChecks checkInfo =
case secondArg checkInfo of
Just otherOptionsArg ->
case AstHelpers.getListLiteral otherOptionsArg of
Just [] ->
[ Rule.errorWithFix
{ message = "Random.weighted with only one possible value can be replaced by Random.constant"
, details = [ "Only a single value can be produced by this Random.weighted call. You can replace the call with Random.constant with the value." ]
}
checkInfo.fnRange
(case Node.value checkInfo.firstArg of
Expression.TupledExpression (_ :: (Node valuePartRange _) :: []) ->
[ Fix.replaceRangeBy { start = checkInfo.parentRange.start, end = valuePartRange.start }
(qualifiedToString (qualify ( [ "Random" ], "constant" ) checkInfo) ++ " ")
, Fix.removeRange { start = valuePartRange.end, end = checkInfo.parentRange.end }
]
_ ->
let
tupleRange : Range
tupleRange =
Node.range checkInfo.firstArg
in
[ Fix.replaceRangeBy { start = checkInfo.parentRange.start, end = tupleRange.start }
(qualifiedToString (qualify ( [ "Random" ], "constant" ) checkInfo) ++ " (Tuple.first ")
, Fix.replaceRangeBy { start = tupleRange.end, end = checkInfo.parentRange.end }
")"
]
)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
_ ->
2023-06-25 21:01:32 +03:00
[]
2023-09-03 12:30:45 +03:00
Nothing ->
[]
randomListChecks : CheckInfo -> List (Error {})
randomListChecks checkInfo =
let
maybeElementGeneratorArg : Maybe (Node Expression)
maybeElementGeneratorArg =
secondArg checkInfo
2023-06-25 21:01:32 +03:00
in
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case Evaluate.getInt checkInfo checkInfo.firstArg of
Just 1 ->
[ Rule.errorWithFix
{ message = qualifiedToString ( [ "Random" ], "list" ) ++ " 1 can be replaced by " ++ qualifiedToString ( [ "Random" ], "map" ) ++ " " ++ qualifiedToString ( [ "List" ], "singleton" )
, details = [ "This " ++ qualifiedToString ( [ "Random" ], "list" ) ++ " call always produces a list with one generated element. This means you can replace the call with " ++ qualifiedToString ( [ "Random" ], "map" ) ++ " " ++ qualifiedToString ( [ "List" ], "singleton" ) ++ "." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy
(Range.combine [ checkInfo.fnRange, Node.range checkInfo.firstArg ])
(qualifiedToString (qualify ( [ "Random" ], "map" ) checkInfo)
++ " "
++ qualifiedToString (qualify ( [ "List" ], "singleton" ) checkInfo)
)
]
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
Just non1Length ->
if non1Length <= 0 then
let
replacement : String
replacement =
replacementWithIrrelevantLastArg
{ forNoLastArg =
qualifiedToString (qualify ( [ "Random" ], "constant" ) checkInfo)
++ " []"
, lastArg = maybeElementGeneratorArg
, resources = checkInfo
}
callDescription : String
callDescription =
case non1Length of
0 ->
"Random.list 0"
_ ->
"Random.list with a negative length"
in
[ Rule.errorWithFix
{ message = callDescription ++ " can be replaced by Random.constant []"
, details = [ callDescription ++ " always generates an empty list. This means you can replace the call with " ++ replacement ++ "." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange replacement ]
]
else
[]
_ ->
2023-06-25 21:01:32 +03:00
[]
2023-09-03 12:30:45 +03:00
, \() ->
case maybeElementGeneratorArg of
Just elementGeneratorArg ->
case AstHelpers.getSpecificFunctionCall ( [ "Random" ], "constant" ) checkInfo.lookupTable elementGeneratorArg of
Just constantCall ->
let
currentAsString : String
currentAsString =
qualifiedToString ( [ "Random" ], "list" ) ++ " n (" ++ qualifiedToString ( [ "Random" ], "constant" ) ++ " el)"
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
replacementAsString : String
replacementAsString =
qualifiedToString ( [ "Random" ], "constant" ) ++ " (" ++ qualifiedToString ( [ "List" ], "repeat" ) ++ " n el)"
in
[ Rule.errorWithFix
{ message = currentAsString ++ " can be replaced by " ++ replacementAsString
, details = [ currentAsString ++ " generates the same value for each of the n elements. This means you can replace the call with " ++ replacementAsString ++ "." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceBySubExpressionFix constantCall.nodeRange constantCall.firstArg
++ [ Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "List" ], "repeat" ) checkInfo))
, Fix.insertAt checkInfo.parentRange.start
(qualifiedToString (qualify ( [ "Random" ], "constant" ) checkInfo)
++ " ("
)
, Fix.insertAt checkInfo.parentRange.end ")"
]
2023-06-25 21:01:32 +03:00
)
]
Nothing ->
[]
Nothing ->
2023-09-03 12:30:45 +03:00
[]
]
()
randomMapChecks : CheckInfo -> List (Error {})
randomMapChecks checkInfo =
firstThatReportsError
[ \() -> mapIdentityChecks { moduleName = [ "Random" ], represents = "random generator" } checkInfo
, \() -> mapPureChecks { moduleName = [ "Random" ], pure = "constant", map = "map" } checkInfo
, \() -> randomMapAlwaysChecks checkInfo
]
()
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
randomMapCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
randomMapCompositionChecks checkInfo =
firstThatReportsError
[ \() -> pureToMapCompositionChecks { moduleName = [ "Random" ], pure = "constant", map = "map" } checkInfo
, \() -> randomMapAlwaysCompositionChecks checkInfo
]
()
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
randomMapAlwaysErrorInfo : { message : String, details : List String }
randomMapAlwaysErrorInfo =
{ message = "Always mapping to the same value is equivalent to Random.constant"
, details = [ "Since your Random.map call always produces the same value, you can replace the whole call by Random.constant that value." ]
}
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
randomMapAlwaysChecks : CheckInfo -> List (Error {})
randomMapAlwaysChecks checkInfo =
case AstHelpers.getAlwaysResult checkInfo.lookupTable checkInfo.firstArg of
Just (Node alwaysMapResultRange alwaysMapResult) ->
let
( leftParenIfRequired, rightParenIfRequired ) =
if needsParens alwaysMapResult then
( "(", ")" )
else
( "", "" )
in
2022-04-17 09:59:11 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
randomMapAlwaysErrorInfo
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(case secondArg checkInfo of
Nothing ->
[ Fix.replaceRangeBy
{ start = checkInfo.parentRange.start, end = alwaysMapResultRange.start }
(qualifiedToString (qualify ( [ "Basics" ], "always" ) checkInfo)
++ " ("
++ qualifiedToString (qualify ( [ "Random" ], "constant" ) checkInfo)
++ " "
++ leftParenIfRequired
)
, Fix.replaceRangeBy
{ start = alwaysMapResultRange.end, end = checkInfo.parentRange.end }
(rightParenIfRequired ++ ")")
]
Just _ ->
[ Fix.replaceRangeBy
{ start = checkInfo.parentRange.start, end = alwaysMapResultRange.start }
(qualifiedToString (qualify ( [ "Random" ], "constant" ) checkInfo)
++ " "
++ leftParenIfRequired
)
, Fix.replaceRangeBy
{ start = alwaysMapResultRange.end, end = checkInfo.parentRange.end }
rightParenIfRequired
]
)
]
Nothing ->
[]
randomMapAlwaysCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
randomMapAlwaysCompositionChecks checkInfo =
case ( checkInfo.earlier.fn, checkInfo.earlier.args ) of
( ( [ "Basics" ], "always" ), [] ) ->
[ Rule.errorWithFix
randomMapAlwaysErrorInfo
checkInfo.later.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Random" ], "constant" ) checkInfo))
]
2022-04-17 09:59:11 +03:00
]
_ ->
[]
2023-09-03 12:30:45 +03:00
--
2021-08-19 21:57:29 +03:00
type alias Collection =
2023-09-03 12:30:45 +03:00
{ moduleName : ModuleName
2021-08-19 21:57:29 +03:00
, represents : String
2023-06-25 21:01:32 +03:00
, emptyAsString : QualifyResources {} -> String
2021-08-19 21:57:29 +03:00
, emptyDescription : String
, isEmpty : ModuleNameLookupTable -> Node Expression -> Bool
, nameForSize : String
, determineSize : ModuleNameLookupTable -> Node Expression -> Maybe CollectionSize
}
2023-06-25 21:01:32 +03:00
extractQualifyResources : QualifyResources a -> QualifyResources {}
extractQualifyResources resources =
{ importLookup = resources.importLookup
, moduleBindings = resources.moduleBindings
, localBindings = resources.localBindings
}
emptyAsString : QualifyResources a -> { emptiable | emptyAsString : QualifyResources {} -> String } -> String
emptyAsString qualifyResources emptiable =
emptiable.emptyAsString (extractQualifyResources qualifyResources)
2021-08-19 21:57:29 +03:00
listCollection : Collection
listCollection =
2023-09-03 12:30:45 +03:00
{ moduleName = [ "List" ]
2021-08-19 21:57:29 +03:00
, represents = "list"
2023-06-25 21:01:32 +03:00
, emptyAsString = \_ -> "[]"
2021-08-19 21:57:29 +03:00
, emptyDescription = "[]"
2023-09-03 12:30:45 +03:00
, isEmpty = \_ expr -> AstHelpers.getListLiteral expr == Just []
2021-08-19 21:57:29 +03:00
, nameForSize = "length"
, determineSize = determineListLength
}
setCollection : Collection
setCollection =
2023-09-03 12:30:45 +03:00
{ moduleName = [ "Set" ]
2021-08-19 21:57:29 +03:00
, represents = "set"
2023-06-25 21:01:32 +03:00
, emptyAsString =
\resources ->
qualifiedToString (qualify ( [ "Set" ], "empty" ) resources)
2023-09-03 12:30:45 +03:00
, emptyDescription = qualifiedToString ( [ "Set" ], "empty" )
, isEmpty =
\lookupTable expr ->
isJust (AstHelpers.getSpecificValueOrFunction ( [ "Set" ], "empty" ) lookupTable expr)
2021-08-19 21:57:29 +03:00
, nameForSize = "size"
2023-09-03 12:30:45 +03:00
, determineSize = setDetermineSize
2021-08-19 21:57:29 +03:00
}
2023-09-03 12:30:45 +03:00
setDetermineSize :
ModuleNameLookupTable
-> Node Expression
-> Maybe CollectionSize
setDetermineSize lookupTable expressionNode =
findMap (\f -> f ())
[ \() ->
case AstHelpers.getSpecificValueOrFunction ( [ "Set" ], "empty" ) lookupTable expressionNode of
Just _ ->
Just (Exactly 0)
Nothing ->
Nothing
, \() ->
case AstHelpers.getSpecificFunctionCall ( [ "Set" ], "singleton" ) lookupTable expressionNode of
Just _ ->
Just (Exactly 1)
Nothing ->
Nothing
, \() ->
case AstHelpers.getSpecificFunctionCall ( [ "Set" ], "fromList" ) lookupTable expressionNode of
Just fromListCall ->
case AstHelpers.getListLiteral fromListCall.firstArg of
Just [] ->
Just (Exactly 0)
Just (_ :: []) ->
Just (Exactly 1)
Just (el0 :: el1 :: el2Up) ->
case traverse getComparableExpression (el0 :: el1 :: el2Up) of
Nothing ->
Just NotEmpty
Just comparableExpressions ->
comparableExpressions |> unique |> List.length |> Exactly |> Just
Nothing ->
Nothing
_ ->
Nothing
]
2021-08-19 21:57:29 +03:00
dictCollection : Collection
dictCollection =
2023-09-03 12:30:45 +03:00
{ moduleName = [ "Dict" ]
2021-08-19 21:57:29 +03:00
, represents = "Dict"
2023-06-25 21:01:32 +03:00
, emptyAsString =
\resources ->
qualifiedToString (qualify ( [ "Dict" ], "empty" ) resources)
2023-09-03 12:30:45 +03:00
, emptyDescription = qualifiedToString ( [ "Dict" ], "empty" )
, isEmpty =
\lookupTable expr ->
isJust (AstHelpers.getSpecificValueOrFunction ( [ "Dict" ], "empty" ) lookupTable expr)
2021-08-19 21:57:29 +03:00
, nameForSize = "size"
2023-09-03 12:30:45 +03:00
, determineSize = dictDetermineSize
2021-08-19 21:57:29 +03:00
}
2023-09-03 12:30:45 +03:00
dictDetermineSize :
ModuleNameLookupTable
-> Node Expression
-> Maybe CollectionSize
dictDetermineSize lookupTable expressionNode =
findMap (\f -> f ())
[ \() ->
case AstHelpers.getSpecificValueOrFunction ( [ "Dict" ], "empty" ) lookupTable expressionNode of
Just _ ->
Just (Exactly 0)
Nothing ->
Nothing
, \() ->
case AstHelpers.getSpecificFunctionCall ( [ "Dict" ], "singleton" ) lookupTable expressionNode of
Just singletonCall ->
case singletonCall.argsAfterFirst of
_ :: [] ->
Just (Exactly 1)
_ ->
Nothing
Nothing ->
Nothing
, \() ->
case AstHelpers.getSpecificFunctionCall ( [ "Dict" ], "fromList" ) lookupTable expressionNode of
Just fromListCall ->
case AstHelpers.getListLiteral fromListCall.firstArg of
Just [] ->
Just (Exactly 0)
Just (_ :: []) ->
Just (Exactly 1)
Just (el0 :: el1 :: el2Up) ->
case traverse getComparableExpressionInTupleFirst (el0 :: el1 :: el2Up) of
Nothing ->
Just NotEmpty
Just comparableExpressions ->
comparableExpressions |> unique |> List.length |> Exactly |> Just
Nothing ->
Nothing
_ ->
Nothing
]
2021-08-19 21:57:29 +03:00
type alias Mappable =
2023-09-03 12:30:45 +03:00
{ moduleName : ModuleName
2021-08-19 21:57:29 +03:00
, represents : String
2023-06-25 21:01:32 +03:00
, emptyAsString : QualifyResources {} -> String
2021-08-19 21:57:29 +03:00
, emptyDescription : String
, isEmpty : ModuleNameLookupTable -> Node Expression -> Bool
}
type alias Defaultable =
2023-09-03 12:30:45 +03:00
{ moduleName : ModuleName
2021-08-19 21:57:29 +03:00
, represents : String
2023-06-25 21:01:32 +03:00
, emptyAsString : QualifyResources {} -> String
2021-08-19 21:57:29 +03:00
, emptyDescription : String
, isEmpty : ModuleNameLookupTable -> Node Expression -> Bool
2023-06-25 21:01:32 +03:00
, isSomethingConstructor : QualifyResources {} -> String
2021-08-19 21:57:29 +03:00
}
maybeCollection : Defaultable
maybeCollection =
2023-09-03 12:30:45 +03:00
{ moduleName = [ "Maybe" ]
2021-08-19 21:57:29 +03:00
, represents = "maybe"
2023-06-25 21:01:32 +03:00
, emptyAsString =
\resources ->
qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) resources)
2021-08-19 21:57:29 +03:00
, emptyDescription = "Nothing"
2023-09-03 12:30:45 +03:00
, isEmpty =
\lookupTable expr ->
isJust (AstHelpers.getSpecificValueOrFunction ( [ "Maybe" ], "Nothing" ) lookupTable expr)
2023-06-25 21:01:32 +03:00
, isSomethingConstructor =
\resources ->
qualifiedToString (qualify ( [ "Maybe" ], "Just" ) resources)
2021-08-19 21:57:29 +03:00
}
resultCollection : Defaultable
resultCollection =
2023-09-03 12:30:45 +03:00
{ moduleName = [ "Result" ]
2021-08-19 21:57:29 +03:00
, represents = "result"
2023-06-25 21:01:32 +03:00
, emptyAsString =
\resources ->
qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) resources)
2021-08-19 21:57:29 +03:00
, emptyDescription = "an error"
2023-09-03 12:30:45 +03:00
, isEmpty =
\lookupTable expr ->
isJust (AstHelpers.getSpecificFunctionCall ( [ "Result" ], "Err" ) lookupTable expr)
2023-06-25 21:01:32 +03:00
, isSomethingConstructor =
\resources ->
qualifiedToString (qualify ( [ "Result" ], "Ok" ) resources)
2021-08-19 21:57:29 +03:00
}
cmdCollection : Mappable
cmdCollection =
2023-09-03 12:30:45 +03:00
{ moduleName = [ "Platform", "Cmd" ]
2021-08-19 21:57:29 +03:00
, represents = "command"
2023-06-25 21:01:32 +03:00
, emptyAsString =
\resources ->
qualifiedToString (qualify ( [ "Platform", "Cmd" ], "none" ) resources)
2023-09-03 12:30:45 +03:00
, emptyDescription =
-- TODO change to qualifiedToString ( [ "Platform", "Cmd" ], "none" )
"Cmd.none"
, isEmpty =
\lookupTable expr ->
isJust (AstHelpers.getSpecificValueOrFunction ( [ "Platform", "Cmd" ], "none" ) lookupTable expr)
2021-08-19 21:57:29 +03:00
}
subCollection : Mappable
subCollection =
2023-09-03 12:30:45 +03:00
{ moduleName = [ "Platform", "Sub" ]
2021-08-19 21:57:29 +03:00
, represents = "subscription"
2023-06-25 21:01:32 +03:00
, emptyAsString =
\resources ->
qualifiedToString (qualify ( [ "Platform", "Sub" ], "none" ) resources)
2023-09-03 12:30:45 +03:00
, emptyDescription =
-- TODO change to qualifiedToString ( [ "Platform", "Sub" ], "none" )
"Sub.none"
, isEmpty =
\lookupTable expr ->
isJust (AstHelpers.getSpecificValueOrFunction ( [ "Platform", "Sub" ], "none" ) lookupTable expr)
2021-08-19 21:57:29 +03:00
}
collectionMapChecks :
{ a
2023-09-03 12:30:45 +03:00
| moduleName : ModuleName
2021-08-19 21:57:29 +03:00
, represents : String
, emptyDescription : String
2023-06-25 21:01:32 +03:00
, emptyAsString : QualifyResources {} -> String
2021-08-19 21:57:29 +03:00
, isEmpty : ModuleNameLookupTable -> Node Expression -> Bool
}
-> CheckInfo
-> List (Error {})
collectionMapChecks collection checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() -> mapIdentityChecks collection checkInfo
, \() ->
case secondArg checkInfo of
Just collectionArg ->
if collection.isEmpty checkInfo.lookupTable collectionArg then
[ Rule.errorWithFix
-- TODO rework error info
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "map" ) ++ " on " ++ collection.emptyDescription ++ " will result in " ++ collection.emptyDescription
, details = [ "You can replace this call by " ++ collection.emptyDescription ++ "." ]
}
checkInfo.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range collectionArg })
]
else
[]
Nothing ->
[]
]
()
{-| TODO merge with identityError
-}
mapIdentityChecks :
{ a
| moduleName : ModuleName
, represents : String
}
-> CheckInfo
-> List (Error {})
mapIdentityChecks mappable checkInfo =
if AstHelpers.isIdentity checkInfo.lookupTable checkInfo.firstArg then
-- TODO use identityError
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( mappable.moduleName, "map" ) ++ " with an identity function is the same as not using " ++ qualifiedToString ( mappable.moduleName, "map" )
, details = [ "You can remove this call and replace it by the " ++ mappable.represents ++ " itself." ]
}
checkInfo.fnRange
(toIdentityFix
{ lastArg = secondArg checkInfo, resources = checkInfo }
)
]
else
[]
mapPureChecks :
{ a | moduleName : List String, pure : String, map : String }
-> CheckInfo
-> List (Error {})
mapPureChecks mappable checkInfo =
case secondArg checkInfo of
Just mappableArg ->
case sameCallInAllBranches ( mappable.moduleName, mappable.pure ) checkInfo.lookupTable mappableArg of
Determined pureCalls ->
let
mappingArgRange : Range
mappingArgRange =
Node.range checkInfo.firstArg
removePureCalls : List Fix
removePureCalls =
List.concatMap
(\pureCall ->
keepOnlyFix
{ parentRange = pureCall.nodeRange
, keep = Node.range pureCall.firstArg
}
)
pureCalls
in
[ Rule.errorWithFix
-- TODO reword error info to something more like resultMapErrorOnErrErrorInfo
{ message = "Calling " ++ qualifiedToString ( mappable.moduleName, mappable.map ) ++ " on a value that is " ++ mappable.pure
, details = [ "The function can be called without " ++ qualifiedToString ( mappable.moduleName, mappable.map ) ++ "." ]
}
checkInfo.fnRange
(if checkInfo.usingRightPizza then
[ Fix.removeRange { start = checkInfo.fnRange.start, end = mappingArgRange.start }
, Fix.insertAt mappingArgRange.end
(" |> " ++ qualifiedToString (qualify ( mappable.moduleName, mappable.pure ) checkInfo))
]
++ removePureCalls
else
[ Fix.replaceRangeBy
{ start = checkInfo.parentRange.start, end = mappingArgRange.start }
(qualifiedToString (qualify ( mappable.moduleName, mappable.pure ) checkInfo) ++ " (")
, Fix.insertAt checkInfo.parentRange.end ")"
]
++ removePureCalls
)
]
Undetermined ->
[]
Nothing ->
[]
pureToMapCompositionChecks :
{ a | moduleName : ModuleName, pure : String, map : String }
-> CompositionIntoCheckInfo
-> List (Error {})
pureToMapCompositionChecks mappable checkInfo =
case
( checkInfo.earlier.fn == ( mappable.moduleName, mappable.pure )
, checkInfo.later.args
)
of
( True, (Node mapperFunctionRange _) :: _ ) ->
let
fixes : List Fix
fixes =
case checkInfo.direction of
LeftToRight ->
[ Fix.removeRange
{ start = checkInfo.parentRange.start, end = mapperFunctionRange.start }
, Fix.insertAt mapperFunctionRange.end
(" >> " ++ qualifiedToString (qualify ( mappable.moduleName, mappable.pure ) checkInfo))
]
RightToLeft ->
[ Fix.replaceRangeBy
{ start = checkInfo.parentRange.start, end = mapperFunctionRange.start }
(qualifiedToString (qualify ( mappable.moduleName, mappable.pure ) checkInfo) ++ " << ")
, Fix.removeRange { start = mapperFunctionRange.end, end = checkInfo.parentRange.end }
]
in
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
-- TODO reword error info
{ message = "Calling " ++ qualifiedToString ( mappable.moduleName, mappable.map ) ++ " on a value that is " ++ mappable.pure
, details = [ "The function can be called without " ++ qualifiedToString ( mappable.moduleName, mappable.map ) ++ "." ]
2021-08-19 21:57:29 +03:00
}
2023-09-03 12:30:45 +03:00
checkInfo.later.fnRange
fixes
2021-08-19 21:57:29 +03:00
]
_ ->
2023-09-03 12:30:45 +03:00
[]
2021-08-19 21:57:29 +03:00
maybeAndThenChecks : CheckInfo -> List (Error {})
maybeAndThenChecks checkInfo =
2023-06-25 21:01:32 +03:00
let
maybeEmptyAsString : String
maybeEmptyAsString =
emptyAsString checkInfo maybeCollection
2023-09-03 12:30:45 +03:00
maybeMaybeArg : Maybe (Node Expression)
maybeMaybeArg =
secondArg checkInfo
2023-06-25 21:01:32 +03:00
in
2021-08-19 21:57:29 +03:00
firstThatReportsError
2023-09-03 12:30:45 +03:00
[ case maybeMaybeArg of
Just maybeArg ->
firstThatReportsError
[ \() ->
case sameCallInAllBranches ( [ "Maybe" ], "Just" ) checkInfo.lookupTable maybeArg of
Determined justCalls ->
[ Rule.errorWithFix
{ message = "Calling " ++ qualifiedToString ( maybeCollection.moduleName, "andThen" ) ++ " on a value that is known to be Just"
, details = [ "You can remove the Just and just call the function directly." ]
}
checkInfo.fnRange
(Fix.removeRange { start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).start }
:: List.concatMap (\justCall -> replaceBySubExpressionFix justCall.nodeRange justCall.firstArg) justCalls
)
]
Undetermined ->
[]
, \() ->
case sameValueOrFunctionInAllBranches ( [ "Maybe" ], "Nothing" ) checkInfo.lookupTable maybeArg of
Determined _ ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( maybeCollection.moduleName, "andThen" ) ++ " on " ++ maybeEmptyAsString ++ " will result in " ++ maybeEmptyAsString
, details = [ "You can replace this call by " ++ maybeEmptyAsString ++ "." ]
}
checkInfo.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range maybeArg })
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Undetermined ->
[]
2021-08-19 21:57:29 +03:00
]
2023-09-03 12:30:45 +03:00
Nothing ->
\() -> []
2021-08-19 21:57:29 +03:00
, \() ->
2023-09-03 12:30:45 +03:00
case constructsSpecificInAllBranches ( [ "Maybe" ], "Just" ) checkInfo.lookupTable checkInfo.firstArg of
Determined justConstruction ->
case justConstruction of
NonDirectConstruction fix ->
[ Rule.errorWithFix
{ message = "Use " ++ qualifiedToString ( maybeCollection.moduleName, "map" ) ++ " instead"
, details = [ "Using " ++ qualifiedToString ( maybeCollection.moduleName, "andThen" ) ++ " with a function that always returns Just is the same thing as using " ++ qualifiedToString ( maybeCollection.moduleName, "map" ) ++ "." ]
}
checkInfo.fnRange
(Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( maybeCollection.moduleName, "map" ) checkInfo))
:: fix
)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
DirectConstruction ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( maybeCollection.moduleName, "andThen" ) ++ " with a function that will always return Just is the same as not using " ++ qualifiedToString ( maybeCollection.moduleName, "andThen" )
, details = [ "You can remove this call and replace it by the value itself." ]
}
checkInfo.fnRange
(toIdentityFix
{ lastArg = maybeMaybeArg, resources = checkInfo }
)
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Undetermined ->
[]
, \() ->
case returnsSpecificValueOrFunctionInAllBranches ( [ "Maybe" ], "Nothing" ) checkInfo.lookupTable checkInfo.firstArg of
Determined _ ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( maybeCollection.moduleName, "andThen" ) ++ " with a function that will always return Nothing will result in Nothing"
2021-08-19 21:57:29 +03:00
, details = [ "You can remove this call and replace it by Nothing." ]
}
checkInfo.fnRange
2023-06-25 21:01:32 +03:00
(replaceByEmptyFix maybeEmptyAsString checkInfo.parentRange (secondArg checkInfo) checkInfo)
2021-08-19 21:57:29 +03:00
]
2022-04-17 09:59:11 +03:00
Undetermined ->
2021-08-19 21:57:29 +03:00
[]
]
()
resultAndThenChecks : CheckInfo -> List (Error {})
resultAndThenChecks checkInfo =
2023-09-03 12:30:45 +03:00
let
maybeResultArg : Maybe (Node Expression)
maybeResultArg =
secondArg checkInfo
in
2021-08-19 21:57:29 +03:00
firstThatReportsError
2023-09-03 12:30:45 +03:00
[ case maybeResultArg of
Just resultArg ->
firstThatReportsError
[ \() ->
case sameCallInAllBranches ( [ "Result" ], "Ok" ) checkInfo.lookupTable resultArg of
Determined okCalls ->
[ Rule.errorWithFix
{ message = "Calling " ++ qualifiedToString ( resultCollection.moduleName, "andThen" ) ++ " on a value that is known to be Ok"
, details = [ "You can remove the Ok and just call the function directly." ]
}
checkInfo.fnRange
(Fix.removeRange { start = checkInfo.fnRange.start, end = (Node.range checkInfo.firstArg).start }
:: List.concatMap (\okCall -> replaceBySubExpressionFix okCall.nodeRange okCall.firstArg) okCalls
)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Undetermined ->
[]
, \() ->
case sameCallInAllBranches ( [ "Result" ], "Err" ) checkInfo.lookupTable resultArg of
Determined _ ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( resultCollection.moduleName, "andThen" ) ++ " on an error will result in the error"
, details = [ "You can replace this call by the error itself." ]
}
checkInfo.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range resultArg })
]
Undetermined ->
[]
2021-08-19 21:57:29 +03:00
]
2023-09-03 12:30:45 +03:00
Nothing ->
\() -> []
2021-08-19 21:57:29 +03:00
, \() ->
2023-09-03 12:30:45 +03:00
case constructsSpecificInAllBranches ( [ "Result" ], "Ok" ) checkInfo.lookupTable checkInfo.firstArg of
Determined okConstruction ->
case okConstruction of
NonDirectConstruction fix ->
[ Rule.errorWithFix
{ message = "Use " ++ qualifiedToString ( [ "Result" ], "map" ) ++ " instead"
, details = [ "Using " ++ qualifiedToString ( [ "Result" ], "andThen" ) ++ " with a function that always returns Ok is the same thing as using " ++ qualifiedToString ( [ "Result" ], "map" ) ++ "." ]
}
checkInfo.fnRange
(Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( resultCollection.moduleName, "map" ) checkInfo))
:: fix
)
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
DirectConstruction ->
[ Rule.errorWithFix
-- TODO use identityError and replace Just by Ok
{ message = "Using " ++ qualifiedToString ( [ "Result" ], "andThen" ) ++ " with a function that will always return Just is the same as not using " ++ qualifiedToString ( [ "Result" ], "andThen" )
, details = [ "You can remove this call and replace it by the value itself." ]
}
checkInfo.fnRange
(toIdentityFix
{ lastArg = maybeResultArg, resources = checkInfo }
)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Undetermined ->
2021-08-19 21:57:29 +03:00
[]
]
()
resultWithDefaultChecks : CheckInfo -> List (Error {})
resultWithDefaultChecks checkInfo =
2023-09-03 12:30:45 +03:00
case secondArg checkInfo of
Just resultArg ->
firstThatReportsError
[ \() ->
case sameCallInAllBranches ( [ "Result" ], "Ok" ) checkInfo.lookupTable resultArg of
Determined okCalls ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "Result" ], "withDefault" ) ++ " on a value that is Ok will result in that value"
, details = [ "You can replace this call by the value wrapped in Ok." ]
}
checkInfo.fnRange
(List.concatMap (\okCall -> replaceBySubExpressionFix okCall.nodeRange okCall.firstArg) okCalls
++ keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range resultArg }
)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Undetermined ->
[]
, \() ->
case sameCallInAllBranches ( [ "Result" ], "Err" ) checkInfo.lookupTable resultArg of
Determined _ ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "Result" ], "withDefault" ) ++ " on an error will result in the default value"
, details = [ "You can replace this call by the default value." ]
}
checkInfo.fnRange
[ Fix.removeRange { start = checkInfo.parentRange.start, end = (Node.range checkInfo.firstArg).start }
, Fix.removeRange { start = (Node.range checkInfo.firstArg).end, end = checkInfo.parentRange.end }
]
]
Undetermined ->
[]
2021-08-19 21:57:29 +03:00
]
2023-09-03 12:30:45 +03:00
()
2021-08-19 21:57:29 +03:00
Nothing ->
[]
2023-06-25 21:01:32 +03:00
resultToMaybeChecks : CheckInfo -> List (Error {})
resultToMaybeChecks checkInfo =
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case sameCallInAllBranches ( [ "Result" ], "Ok" ) checkInfo.lookupTable checkInfo.firstArg of
Determined okCalls ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "Result" ], "toMaybe" ) ++ " on a value that is Ok will result in Just that value itself"
, details = [ "You can replace this call by the value itself wrapped in Just." ]
}
checkInfo.fnRange
(List.concatMap (\okCall -> replaceBySubExpressionFix okCall.nodeRange okCall.firstArg) okCalls
++ [ Fix.replaceRangeBy checkInfo.fnRange
(qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo))
]
)
]
Undetermined ->
[]
, \() ->
case sameCallInAllBranches ( [ "Result" ], "Err" ) checkInfo.lookupTable checkInfo.firstArg of
Determined _ ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "Result" ], "toMaybe" ) ++ " on an error will result in Nothing"
, details = [ "You can replace this call by Nothing." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) checkInfo))
]
]
Undetermined ->
[]
]
()
resultToMaybeCompositionChecks : CompositionIntoCheckInfo -> List (Error {})
resultToMaybeCompositionChecks checkInfo =
let
resultToMaybeFunctionRange : Range
resultToMaybeFunctionRange =
checkInfo.later.fnRange
in
case ( checkInfo.earlier.fn, checkInfo.earlier.args ) of
( ( [ "Result" ], "Err" ), [] ) ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "Result" ], "toMaybe" ) ++ " on an error will result in Nothing"
, details = [ "You can replace this call by always Nothing." ]
2023-06-25 21:01:32 +03:00
}
2023-09-03 12:30:45 +03:00
resultToMaybeFunctionRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "always" ) checkInfo)
++ " "
++ qualifiedToString (qualify ( [ "Maybe" ], "Nothing" ) checkInfo)
)
]
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
( ( [ "Result" ], "Ok" ), [] ) ->
2023-06-25 21:01:32 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( [ "Result" ], "toMaybe" ) ++ " on a value that is Ok will result in Just that value itself"
, details = [ "You can replace this call by Just." ]
2023-06-25 21:01:32 +03:00
}
2023-09-03 12:30:45 +03:00
resultToMaybeFunctionRange
2023-06-25 21:01:32 +03:00
[ Fix.replaceRangeBy checkInfo.parentRange
2023-09-03 12:30:45 +03:00
(qualifiedToString (qualify ( [ "Maybe" ], "Just" ) checkInfo))
2023-06-25 21:01:32 +03:00
]
]
2023-09-03 12:30:45 +03:00
_ ->
2023-06-25 21:01:32 +03:00
[]
2023-09-03 12:30:45 +03:00
pipelineChecks :
{ commentRanges : List Range
, extractSourceCode : Range -> String
, nodeRange : Range
, pipedInto : Node Expression
, arg : Node Expression
, direction : LeftOrRightDirection
}
-> List (Error {})
pipelineChecks checkInfo =
firstThatReportsError
[ \() -> pipingIntoCompositionChecks { commentRanges = checkInfo.commentRanges, extractSourceCode = checkInfo.extractSourceCode } checkInfo.direction checkInfo.pipedInto
, \() -> fullyAppliedLambdaInPipelineChecks { nodeRange = checkInfo.nodeRange, function = checkInfo.pipedInto, firstArgument = checkInfo.arg }
]
()
fullyAppliedLambdaInPipelineChecks : { nodeRange : Range, firstArgument : Node Expression, function : Node Expression } -> List (Error {})
fullyAppliedLambdaInPipelineChecks checkInfo =
case Node.value checkInfo.function of
Expression.ParenthesizedExpression (Node lambdaRange (Expression.LambdaExpression lambda)) ->
case Node.value (AstHelpers.removeParens checkInfo.firstArgument) of
Expression.OperatorApplication "|>" _ _ _ ->
[]
Expression.OperatorApplication "<|" _ _ _ ->
[]
_ ->
appliedLambdaChecks
{ nodeRange = checkInfo.nodeRange
, lambdaRange = lambdaRange
, lambda = lambda
}
_ ->
[]
type LeftOrRightDirection
= RightToLeft
| LeftToRight
pipingIntoCompositionChecks :
{ commentRanges : List Range, extractSourceCode : Range -> String }
-> LeftOrRightDirection
-> Node Expression
-> List (Error {})
pipingIntoCompositionChecks context compositionDirection expressionNode =
2023-06-25 21:01:32 +03:00
let
2023-09-03 12:30:45 +03:00
( opToFind, replacement ) =
case compositionDirection of
RightToLeft ->
( "<<", "<|" )
LeftToRight ->
( ">>", "|>" )
pipingIntoCompositionChecksHelp : Node Expression -> Maybe { opToReplaceRange : Range, fixes : List Fix, firstStepIsComposition : Bool }
pipingIntoCompositionChecksHelp subExpression =
case Node.value subExpression of
Expression.ParenthesizedExpression inParens ->
case pipingIntoCompositionChecksHelp inParens of
Nothing ->
Nothing
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Just error ->
if error.firstStepIsComposition then
-- parens can safely be removed
Just
{ error
| fixes =
removeBoundariesFix subExpression ++ error.fixes
}
else
-- inside parenthesis is checked separately because
-- the parens here can't safely be removed
Nothing
Expression.OperatorApplication symbol _ left right ->
let
continuedSearch : Maybe { opToReplaceRange : Range, fixes : List Fix, firstStepIsComposition : Bool }
continuedSearch =
case compositionDirection of
RightToLeft ->
pipingIntoCompositionChecksHelp left
LeftToRight ->
pipingIntoCompositionChecksHelp right
in
if symbol == replacement then
Maybe.map (\errors -> { errors | firstStepIsComposition = False })
continuedSearch
else if symbol == opToFind then
let
opToFindRange : Range
opToFindRange =
findOperatorRange
{ operator = opToFind
, commentRanges = context.commentRanges
, extractSourceCode = context.extractSourceCode
, leftRange = Node.range left
, rightRange = Node.range right
}
in
Just
{ opToReplaceRange = opToFindRange
, fixes =
Fix.replaceRangeBy opToFindRange replacement
:: (case continuedSearch of
Nothing ->
[]
Just additionalErrorsFound ->
additionalErrorsFound.fixes
)
, firstStepIsComposition = True
}
else
Nothing
_ ->
Nothing
2023-06-25 21:01:32 +03:00
in
2023-09-03 12:30:45 +03:00
case pipingIntoCompositionChecksHelp expressionNode of
2023-06-25 21:01:32 +03:00
Nothing ->
[]
2023-09-03 12:30:45 +03:00
Just error ->
[ Rule.errorWithFix
{ message = "Use " ++ replacement ++ " instead of " ++ opToFind
, details =
[ "Because of the precedence of operators, using " ++ opToFind ++ " at this location is the same as using " ++ replacement ++ "."
, "Please use " ++ replacement ++ " instead as that is more idiomatic in Elm and generally easier to read."
2023-06-25 21:01:32 +03:00
]
2023-09-03 12:30:45 +03:00
}
error.opToReplaceRange
error.fixes
]
2023-06-25 21:01:32 +03:00
2021-08-19 21:57:29 +03:00
collectionFilterChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionFilterChecks collection checkInfo =
let
collectionEmptyAsString : String
collectionEmptyAsString =
emptyAsString checkInfo collection
2023-09-03 12:30:45 +03:00
maybeCollectionArg : Maybe (Node Expression)
maybeCollectionArg =
secondArg checkInfo
2023-06-25 21:01:32 +03:00
in
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case maybeCollectionArg of
Just collectionArg ->
case collection.determineSize checkInfo.lookupTable collectionArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "filter" ) ++ " on " ++ collectionEmptyAsString ++ " will result in " ++ collectionEmptyAsString
, details = [ "You can replace this call by " ++ collectionEmptyAsString ++ "." ]
}
checkInfo.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range collectionArg })
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
Nothing ->
[]
, \() ->
2023-06-25 21:01:32 +03:00
case Evaluate.isAlwaysBoolean checkInfo checkInfo.firstArg of
2022-04-17 09:59:11 +03:00
Determined True ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "filter" ) ++ " with a function that will always return True is the same as not using " ++ qualifiedToString ( collection.moduleName, "filter" )
2021-08-19 21:57:29 +03:00
, details = [ "You can remove this call and replace it by the " ++ collection.represents ++ " itself." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(toIdentityFix
{ lastArg = maybeCollectionArg, resources = checkInfo }
)
2021-08-19 21:57:29 +03:00
]
2022-04-17 09:59:11 +03:00
Determined False ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "filter" ) ++ " with a function that will always return False will result in " ++ collectionEmptyAsString
2023-06-25 21:01:32 +03:00
, details = [ "You can remove this call and replace it by " ++ collectionEmptyAsString ++ "." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceByEmptyFix collectionEmptyAsString checkInfo.parentRange maybeCollectionArg checkInfo)
2021-08-19 21:57:29 +03:00
]
2022-04-17 09:59:11 +03:00
Undetermined ->
2021-08-19 21:57:29 +03:00
[]
2023-09-03 12:30:45 +03:00
]
()
2021-08-19 21:57:29 +03:00
collectionRemoveChecks : Collection -> CheckInfo -> List (Error {})
2023-09-03 12:30:45 +03:00
collectionRemoveChecks collection checkInfo =
case secondArg checkInfo of
Just collectionArg ->
case collection.determineSize checkInfo.lookupTable collectionArg of
Just (Exactly 0) ->
let
collectionEmptyAsString : String
collectionEmptyAsString =
emptyAsString checkInfo collection
in
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "remove" ) ++ " on " ++ collectionEmptyAsString ++ " will result in " ++ collectionEmptyAsString
, details = [ "You can replace this call by " ++ collectionEmptyAsString ++ "." ]
}
checkInfo.fnRange
(keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range collectionArg })
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
Nothing ->
2021-08-19 21:57:29 +03:00
[]
collectionIntersectChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionIntersectChecks collection checkInfo =
let
2023-09-03 12:30:45 +03:00
maybeCollectionArg : Maybe (Node Expression)
maybeCollectionArg =
2023-06-25 21:01:32 +03:00
secondArg checkInfo
collectionEmptyAsString : String
collectionEmptyAsString =
emptyAsString checkInfo collection
in
2021-08-19 21:57:29 +03:00
firstThatReportsError
[ \() ->
2023-06-25 21:01:32 +03:00
case collection.determineSize checkInfo.lookupTable checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Just (Exactly 0) ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "intersect" ) ++ " on " ++ collectionEmptyAsString ++ " will result in " ++ collectionEmptyAsString
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by " ++ collectionEmptyAsString ++ "." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceByEmptyFix collectionEmptyAsString checkInfo.parentRange maybeCollectionArg checkInfo)
2021-08-19 21:57:29 +03:00
]
_ ->
[]
, \() ->
2023-09-03 12:30:45 +03:00
case maybeCollectionArg of
Just collectionArg ->
case collection.determineSize checkInfo.lookupTable collectionArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "intersect" ) ++ " on " ++ collectionEmptyAsString ++ " will result in " ++ collectionEmptyAsString
, details = [ "You can replace this call by " ++ collectionEmptyAsString ++ "." ]
}
checkInfo.fnRange
(replaceByEmptyFix collectionEmptyAsString checkInfo.parentRange maybeCollectionArg checkInfo)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
Nothing ->
2021-08-19 21:57:29 +03:00
[]
]
()
collectionDiffChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionDiffChecks collection checkInfo =
let
2023-09-03 12:30:45 +03:00
maybeCollectionArg : Maybe (Node Expression)
maybeCollectionArg =
2023-06-25 21:01:32 +03:00
secondArg checkInfo
collectionEmptyAsString : String
collectionEmptyAsString =
emptyAsString checkInfo collection
in
2021-08-19 21:57:29 +03:00
firstThatReportsError
[ \() ->
2023-06-25 21:01:32 +03:00
case collection.determineSize checkInfo.lookupTable checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Just (Exactly 0) ->
[ Rule.errorWithFix
2023-06-25 21:01:32 +03:00
{ message = "Diffing " ++ collectionEmptyAsString ++ " will result in " ++ collectionEmptyAsString
, details = [ "You can replace this call by " ++ collectionEmptyAsString ++ "." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(replaceByEmptyFix collectionEmptyAsString checkInfo.parentRange maybeCollectionArg checkInfo)
2021-08-19 21:57:29 +03:00
]
_ ->
[]
, \() ->
2023-09-03 12:30:45 +03:00
case maybeCollectionArg of
Just collectionArg ->
case collection.determineSize checkInfo.lookupTable collectionArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Diffing a " ++ collection.represents ++ " with " ++ collectionEmptyAsString ++ " will result in the " ++ collection.represents ++ " itself"
, details = [ "You can replace this call by the " ++ collection.represents ++ " itself." ]
}
checkInfo.fnRange
[ Fix.removeRange { start = checkInfo.parentRange.start, end = (Node.range checkInfo.firstArg).start }
, Fix.removeRange { start = (Node.range checkInfo.firstArg).end, end = checkInfo.parentRange.end }
]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
Nothing ->
2021-08-19 21:57:29 +03:00
[]
]
()
collectionUnionChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionUnionChecks collection checkInfo =
2023-09-03 12:30:45 +03:00
let
maybeCollectionArg : Maybe (Node Expression)
maybeCollectionArg =
secondArg checkInfo
in
2021-08-19 21:57:29 +03:00
firstThatReportsError
[ \() ->
2023-06-25 21:01:32 +03:00
case collection.determineSize checkInfo.lookupTable checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Just (Exactly 0) ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "Unnecessary union with " ++ collection.emptyAsString (extractQualifyResources checkInfo)
, details = [ "You can replace this call by the " ++ collection.represents ++ " itself." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
(toIdentityFix
{ lastArg = maybeCollectionArg, resources = checkInfo }
)
2021-08-19 21:57:29 +03:00
]
_ ->
[]
, \() ->
2023-09-03 12:30:45 +03:00
case maybeCollectionArg of
Just collectionArg ->
case collection.determineSize checkInfo.lookupTable collectionArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Unnecessary union with " ++ collection.emptyAsString (extractQualifyResources checkInfo)
, details = [ "You can replace this call by the " ++ collection.represents ++ " itself." ]
}
checkInfo.fnRange
[ Fix.removeRange { start = checkInfo.parentRange.start, end = (Node.range checkInfo.firstArg).start }
, Fix.removeRange { start = (Node.range checkInfo.firstArg).end, end = checkInfo.parentRange.end }
]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
Nothing ->
2021-08-19 21:57:29 +03:00
[]
]
()
collectionInsertChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionInsertChecks collection checkInfo =
2023-09-03 12:30:45 +03:00
case secondArg checkInfo of
Just collectionArg ->
case collection.determineSize checkInfo.lookupTable collectionArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Use " ++ qualifiedToString ( collection.moduleName, "singleton" ) ++ " instead of inserting in " ++ emptyAsString checkInfo collection
, details = [ "You can replace this call by " ++ qualifiedToString ( collection.moduleName, "singleton" ) ++ "." ]
}
checkInfo.fnRange
(replaceBySubExpressionFix checkInfo.parentRange checkInfo.firstArg
++ [ Fix.insertAt checkInfo.parentRange.start
(qualifiedToString (qualify ( collection.moduleName, "singleton" ) checkInfo) ++ " ")
]
)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
2021-08-19 21:57:29 +03:00
[]
collectionMemberChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionMemberChecks collection checkInfo =
2023-09-03 12:30:45 +03:00
case secondArg checkInfo of
Just collectionArg ->
case collection.determineSize checkInfo.lookupTable collectionArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "member" ) ++ " on " ++ collection.emptyDescription ++ " will result in False"
, details = [ "You can replace this call by False." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "False" ) checkInfo))
]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
Nothing ->
2021-08-19 21:57:29 +03:00
[]
collectionIsEmptyChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionIsEmptyChecks collection checkInfo =
case collection.determineSize checkInfo.lookupTable checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Just (Exactly 0) ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to " ++ qualifiedToString ( collection.moduleName, "isEmpty" ) ++ " will result in True"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by True." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "True" ) checkInfo))
]
2021-08-19 21:57:29 +03:00
]
Just _ ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to " ++ qualifiedToString ( collection.moduleName, "isEmpty" ) ++ " will result in False"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by False." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "False" ) checkInfo))
]
2021-08-19 21:57:29 +03:00
]
Nothing ->
[]
collectionSizeChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionSizeChecks collection checkInfo =
case collection.determineSize checkInfo.lookupTable checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Just (Exactly size) ->
[ Rule.errorWithFix
{ message = "The " ++ collection.nameForSize ++ " of the " ++ collection.represents ++ " is " ++ String.fromInt size
, details = [ "The " ++ collection.nameForSize ++ " of the " ++ collection.represents ++ " can be determined by looking at the code." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange (String.fromInt size) ]
2021-08-19 21:57:29 +03:00
]
_ ->
[]
collectionFromListChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionFromListChecks collection checkInfo =
case Node.value checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Expression.ListExpr [] ->
2023-06-25 21:01:32 +03:00
let
collectionEmptyAsString : String
collectionEmptyAsString =
emptyAsString checkInfo collection
in
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to " ++ qualifiedToString ( collection.moduleName, "fromList" ) ++ " will result in " ++ collectionEmptyAsString
2023-06-25 21:01:32 +03:00
, details = [ "You can replace this call by " ++ collectionEmptyAsString ++ "." ]
2021-08-19 21:57:29 +03:00
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange collectionEmptyAsString ]
2021-08-19 21:57:29 +03:00
]
_ ->
[]
collectionToListChecks : Collection -> CheckInfo -> List (Error {})
2023-06-25 21:01:32 +03:00
collectionToListChecks collection checkInfo =
case collection.determineSize checkInfo.lookupTable checkInfo.firstArg of
2021-08-19 21:57:29 +03:00
Just (Exactly 0) ->
[ Rule.errorWithFix
2023-09-03 12:30:45 +03:00
{ message = "The call to " ++ qualifiedToString ( collection.moduleName, "toList" ) ++ " will result in []"
2021-08-19 21:57:29 +03:00
, details = [ "You can replace this call by []." ]
}
2023-06-25 21:01:32 +03:00
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange "[]" ]
2021-08-19 21:57:29 +03:00
]
_ ->
[]
collectionPartitionChecks : Collection -> CheckInfo -> List (Error {})
collectionPartitionChecks collection checkInfo =
2023-06-25 21:01:32 +03:00
let
collectionEmptyAsString : String
collectionEmptyAsString =
emptyAsString checkInfo collection
in
2023-09-03 12:30:45 +03:00
firstThatReportsError
[ \() ->
case secondArg checkInfo of
Just collectionArg ->
case collection.determineSize checkInfo.lookupTable collectionArg of
Just (Exactly 0) ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( collection.moduleName, "partition" ) ++ " on " ++ collection.emptyDescription ++ " will result in ( " ++ collectionEmptyAsString ++ ", " ++ collectionEmptyAsString ++ " )"
, details = [ "You can replace this call by ( " ++ collectionEmptyAsString ++ ", " ++ collectionEmptyAsString ++ " )." ]
}
checkInfo.fnRange
[ Fix.replaceRangeBy checkInfo.parentRange ("( " ++ collectionEmptyAsString ++ ", " ++ collectionEmptyAsString ++ " )") ]
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
_ ->
[]
Nothing ->
[]
, \() ->
2022-09-01 17:15:28 +03:00
case Evaluate.isAlwaysBoolean checkInfo checkInfo.firstArg of
2022-04-17 09:59:11 +03:00
Determined True ->
2023-06-25 21:01:32 +03:00
case secondArg checkInfo of
2023-09-03 12:30:45 +03:00
Just (Node listArgRange _) ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "All elements will go to the first " ++ collection.represents
2023-06-25 21:01:32 +03:00
, details = [ "Since the predicate function always returns True, the second " ++ collection.represents ++ " will always be " ++ collection.emptyDescription ++ "." ]
2021-08-19 21:57:29 +03:00
}
checkInfo.fnRange
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy { start = checkInfo.fnRange.start, end = listArgRange.start } "( "
, Fix.insertAt listArgRange.end (", " ++ collectionEmptyAsString ++ " )")
2021-08-19 21:57:29 +03:00
]
]
Nothing ->
[]
2022-04-17 09:59:11 +03:00
Determined False ->
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "All elements will go to the second " ++ collection.represents
2023-06-25 21:01:32 +03:00
, details = [ "Since the predicate function always returns False, the first " ++ collection.represents ++ " will always be " ++ collection.emptyDescription ++ "." ]
2021-08-19 21:57:29 +03:00
}
checkInfo.fnRange
2023-06-25 21:01:32 +03:00
(case secondArg checkInfo of
2021-08-19 21:57:29 +03:00
Just listArg ->
2023-06-25 21:01:32 +03:00
[ Fix.replaceRangeBy { start = checkInfo.fnRange.start, end = (Node.range listArg).start } ("( " ++ collectionEmptyAsString ++ ", ")
2021-08-19 21:57:29 +03:00
, Fix.insertAt (Node.range listArg).end " )"
]
Nothing ->
2023-06-25 21:01:32 +03:00
[ Fix.replaceRangeBy checkInfo.parentRange
("("
++ qualifiedToString (qualify ( [ "Tuple" ], "pair" ) checkInfo)
++ " "
++ collectionEmptyAsString
++ ")"
)
]
2021-08-19 21:57:29 +03:00
)
]
2022-04-17 09:59:11 +03:00
Undetermined ->
2021-08-19 21:57:29 +03:00
[]
2023-09-03 12:30:45 +03:00
]
()
2021-08-19 21:57:29 +03:00
maybeWithDefaultChecks : CheckInfo -> List (Error {})
maybeWithDefaultChecks checkInfo =
2023-09-03 12:30:45 +03:00
case secondArg checkInfo of
Just maybeArg ->
firstThatReportsError
[ \() ->
case sameCallInAllBranches ( [ "Maybe" ], "Just" ) checkInfo.lookupTable maybeArg of
Determined justCalls ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "Maybe" ], "withDefault" ) ++ " on a value that is Just will result in that value"
, details = [ "You can replace this call by the value wrapped in Just." ]
}
checkInfo.fnRange
(List.concatMap (\justCall -> replaceBySubExpressionFix justCall.nodeRange justCall.firstArg) justCalls
++ keepOnlyFix { parentRange = checkInfo.parentRange, keep = Node.range maybeArg }
)
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Undetermined ->
[]
, \() ->
case sameValueOrFunctionInAllBranches ( [ "Maybe" ], "Nothing" ) checkInfo.lookupTable maybeArg of
Determined _ ->
[ Rule.errorWithFix
{ message = "Using " ++ qualifiedToString ( [ "Maybe" ], "withDefault" ) ++ " on Nothing will result in the default value"
, details = [ "You can replace this call by the default value." ]
}
checkInfo.fnRange
[ Fix.removeRange { start = checkInfo.parentRange.start, end = (Node.range checkInfo.firstArg).start }
, Fix.removeRange { start = (Node.range checkInfo.firstArg).end, end = checkInfo.parentRange.end }
]
]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Undetermined ->
[]
]
()
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
[]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
type CollectionSize
= Exactly Int
| NotEmpty
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
determineListLength : ModuleNameLookupTable -> Node Expression -> Maybe CollectionSize
determineListLength lookupTable expressionNode =
case Node.value (AstHelpers.removeParens expressionNode) of
Expression.ListExpr list ->
Just (Exactly (List.length list))
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.OperatorApplication "::" _ _ right ->
case determineListLength lookupTable right of
Just (Exactly n) ->
Just (Exactly (n + 1))
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
_ ->
Just NotEmpty
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.Application ((Node fnRange (Expression.FunctionOrValue _ "singleton")) :: _ :: []) ->
if ModuleNameLookupTable.moduleNameAt lookupTable fnRange == Just [ "List" ] then
Just (Exactly 1)
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
else
Nothing
2022-04-17 09:59:11 +03:00
_ ->
Nothing
2023-09-03 12:30:45 +03:00
replaceSingleElementListBySingleValue : ModuleNameLookupTable -> Node Expression -> Maybe (List Fix)
replaceSingleElementListBySingleValue lookupTable expressionNode =
case Node.value (AstHelpers.removeParens expressionNode) of
Expression.ListExpr (listElement :: []) ->
Just (replaceBySubExpressionFix (Node.range expressionNode) listElement)
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.Application ((Node fnRange (Expression.FunctionOrValue _ "singleton")) :: _ :: []) ->
if ModuleNameLookupTable.moduleNameAt lookupTable fnRange == Just [ "List" ] then
Just [ Fix.removeRange fnRange ]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
else
Nothing
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.IfBlock _ thenBranch elseBranch ->
combineSingleElementFixes lookupTable [ thenBranch, elseBranch ] []
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.CaseExpression caseOf ->
combineSingleElementFixes lookupTable (List.map Tuple.second caseOf.cases) []
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
_ ->
Nothing
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
combineSingleElementFixes : ModuleNameLookupTable -> List (Node Expression) -> List Fix -> Maybe (List Fix)
combineSingleElementFixes lookupTable nodes soFar =
case nodes of
2022-04-17 09:59:11 +03:00
[] ->
2023-09-03 12:30:45 +03:00
Just soFar
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
node :: restOfNodes ->
case replaceSingleElementListBySingleValue lookupTable node of
Nothing ->
Nothing
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Just fixes ->
combineSingleElementFixes lookupTable restOfNodes (fixes ++ soFar)
2022-04-17 09:59:11 +03:00
2021-08-19 21:57:29 +03:00
-- RECORD UPDATE
removeRecordFields : Range -> Node String -> List (Node Expression.RecordSetter) -> List (Error {})
removeRecordFields recordUpdateRange variable fields =
case fields of
[] ->
-- Not possible
[]
(Node _ ( field, valueWithParens )) :: [] ->
let
value : Node Expression
value =
2022-09-01 17:15:28 +03:00
AstHelpers.removeParens valueWithParens
2021-08-19 21:57:29 +03:00
in
if isUnnecessaryRecordUpdateSetter variable field value then
[ Rule.errorWithFix
{ message = "Unnecessary field assignment"
, details = [ "The field is being set to its own value." ]
}
(Node.range value)
2023-09-03 12:30:45 +03:00
(keepOnlyFix { parentRange = recordUpdateRange, keep = Node.range variable })
2021-08-19 21:57:29 +03:00
]
else
[]
2023-09-03 12:30:45 +03:00
(Node firstRange _) :: (Node secondRange _) :: _ ->
withBeforeMap
(\field ->
2021-08-19 21:57:29 +03:00
let
2023-09-03 12:30:45 +03:00
(Node currentFieldRange ( currentFieldName, valueWithParens )) =
field.current
2021-08-19 21:57:29 +03:00
value : Node Expression
value =
2022-09-01 17:15:28 +03:00
AstHelpers.removeParens valueWithParens
2021-08-19 21:57:29 +03:00
in
2023-09-03 12:30:45 +03:00
if isUnnecessaryRecordUpdateSetter variable currentFieldName value then
2021-08-19 21:57:29 +03:00
Just
(Rule.errorWithFix
{ message = "Unnecessary field assignment"
, details = [ "The field is being set to its own value." ]
}
(Node.range value)
2023-09-03 12:30:45 +03:00
(case field.before of
Just (Node prevRange _) ->
[ Fix.removeRange { start = prevRange.end, end = currentFieldRange.end } ]
2021-08-19 21:57:29 +03:00
Nothing ->
-- It's the first element, so we can remove until the second element
2023-09-03 12:30:45 +03:00
[ Fix.removeRange { start = firstRange.start, end = secondRange.start } ]
2021-08-19 21:57:29 +03:00
)
)
else
Nothing
)
2023-09-03 12:30:45 +03:00
fields
|> List.filterMap identity
2021-08-19 21:57:29 +03:00
isUnnecessaryRecordUpdateSetter : Node String -> Node String -> Node Expression -> Bool
2023-09-03 12:30:45 +03:00
isUnnecessaryRecordUpdateSetter (Node _ variable) (Node _ field) (Node _ value) =
case value of
Expression.RecordAccess (Node _ (Expression.FunctionOrValue [] valueHolder)) (Node _ fieldName) ->
field == fieldName && variable == valueHolder
2021-08-19 21:57:29 +03:00
_ ->
False
2022-09-01 17:15:28 +03:00
-- IF
2023-09-03 12:30:45 +03:00
type alias IfCheckInfo =
{ lookupTable : ModuleNameLookupTable
, inferredConstants : ( Infer.Inferred, List Infer.Inferred )
, importLookup : ImportLookup
, moduleBindings : Set String
, localBindings : RangeDict (Set String)
, nodeRange : Range
, condition : Node Expression
, trueBranch : Node Expression
, falseBranch : Node Expression
}
2022-09-01 17:15:28 +03:00
ifChecks :
2023-09-03 12:30:45 +03:00
IfCheckInfo
-> Maybe { errors : List (Error {}), rangesToIgnore : RangeDict () }
ifChecks checkInfo =
findMap (\f -> f ())
[ \() ->
case Evaluate.getBoolean checkInfo checkInfo.condition of
Determined determinedConditionResultIsTrue ->
let
branch : { expressionNode : Node Expression, name : String }
branch =
if determinedConditionResultIsTrue then
{ expressionNode = checkInfo.trueBranch, name = "then" }
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
else
{ expressionNode = checkInfo.falseBranch, name = "else" }
in
Just
{ errors =
[ Rule.errorWithFix
{ message = "The condition will always evaluate to " ++ AstHelpers.boolToString determinedConditionResultIsTrue
, details = [ "The expression can be replaced by what is inside the '" ++ branch.name ++ "' branch." ]
}
(targetIfKeyword checkInfo.nodeRange)
(replaceBySubExpressionFix checkInfo.nodeRange branch.expressionNode)
]
, rangesToIgnore = RangeDict.singleton (Node.range checkInfo.condition) ()
2022-09-01 17:15:28 +03:00
}
2023-09-03 12:30:45 +03:00
Undetermined ->
Nothing
, \() ->
case ( Evaluate.getBoolean checkInfo checkInfo.trueBranch, Evaluate.getBoolean checkInfo checkInfo.falseBranch ) of
2022-09-01 17:15:28 +03:00
( Determined True, Determined False ) ->
2023-09-03 12:30:45 +03:00
Just
{ errors =
[ Rule.errorWithFix
{ message = "The if expression's value is the same as the condition"
, details = [ "The expression can be replaced by the condition." ]
2022-09-01 17:15:28 +03:00
}
2023-09-03 12:30:45 +03:00
(targetIfKeyword checkInfo.nodeRange)
(replaceBySubExpressionFix checkInfo.nodeRange checkInfo.condition)
2022-09-01 17:15:28 +03:00
]
2023-09-03 12:30:45 +03:00
, rangesToIgnore = RangeDict.empty
}
2022-09-01 17:15:28 +03:00
( Determined False, Determined True ) ->
2023-09-03 12:30:45 +03:00
Just
{ errors =
[ Rule.errorWithFix
{ message = "The if expression's value is the inverse of the condition"
, details = [ "The expression can be replaced by the condition wrapped by `not`." ]
2022-09-01 17:15:28 +03:00
}
2023-09-03 12:30:45 +03:00
(targetIfKeyword checkInfo.nodeRange)
(replaceBySubExpressionFix checkInfo.nodeRange checkInfo.condition
++ [ Fix.insertAt checkInfo.nodeRange.start
(qualifiedToString (qualify ( [ "Basics" ], "not" ) checkInfo) ++ " ")
]
2023-06-25 21:01:32 +03:00
)
2022-09-01 17:15:28 +03:00
]
2023-09-03 12:30:45 +03:00
, rangesToIgnore = RangeDict.empty
}
2022-09-01 17:15:28 +03:00
_ ->
2023-09-03 12:30:45 +03:00
Nothing
, \() ->
case Normalize.compare checkInfo checkInfo.trueBranch checkInfo.falseBranch of
Normalize.ConfirmedEquality ->
Just
{ errors =
[ Rule.errorWithFix
{ message = "The values in both branches is the same."
, details = [ "The expression can be replaced by the contents of either branch." ]
}
(targetIfKeyword checkInfo.nodeRange)
(replaceBySubExpressionFix checkInfo.nodeRange checkInfo.trueBranch)
]
, rangesToIgnore = RangeDict.empty
}
2022-09-01 17:15:28 +03:00
2023-09-03 12:30:45 +03:00
_ ->
Nothing
]
2022-09-01 17:15:28 +03:00
2021-08-19 21:57:29 +03:00
-- CASE OF
2023-09-03 12:30:45 +03:00
caseOfChecks : List (CaseOfCheckInfo -> List (Error {}))
caseOfChecks =
[ sameBodyForCaseOfChecks
, booleanCaseOfChecks
, destructuringCaseOfChecks
]
type alias CaseOfCheckInfo =
{ lookupTable : ModuleNameLookupTable
, customTypesToReportInCases : Set ( ModuleName, ConstructorName )
, extractSourceCode : Range -> String
, inferredConstants : ( Infer.Inferred, List Infer.Inferred )
, parentRange : Range
, caseOf : Expression.CaseBlock
}
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
sameBodyForCaseOfChecks :
CaseOfCheckInfo
-> List (Error {})
sameBodyForCaseOfChecks context =
case context.caseOf.cases of
2021-08-19 21:57:29 +03:00
[] ->
[]
2022-09-01 17:15:28 +03:00
( firstPattern, firstBody ) :: rest ->
let
restPatterns : List (Node Pattern)
restPatterns =
List.map Tuple.first rest
in
2021-08-19 21:57:29 +03:00
if
2022-09-01 17:15:28 +03:00
introducesVariableOrUsesTypeConstructor context (firstPattern :: restPatterns)
|| not (Normalize.areAllTheSame context firstBody (List.map Tuple.second rest))
2021-08-19 21:57:29 +03:00
then
[]
else
let
2022-09-01 17:15:28 +03:00
firstBodyRange : Range
firstBodyRange =
Node.range firstBody
2021-08-19 21:57:29 +03:00
in
2022-09-01 17:15:28 +03:00
[ Rule.errorWithFix
{ message = "Unnecessary case expression"
, details = [ "All the branches of this case expression resolve to the same value. You can remove the case expression and replace it with the body of one of the branches." ]
}
2023-09-03 12:30:45 +03:00
(caseKeyWordRange context.parentRange)
[ Fix.removeRange { start = context.parentRange.start, end = firstBodyRange.start }
, Fix.removeRange { start = firstBodyRange.end, end = context.parentRange.end }
2021-08-19 21:57:29 +03:00
]
2022-09-01 17:15:28 +03:00
]
2021-08-19 21:57:29 +03:00
caseKeyWordRange : Range -> Range
caseKeyWordRange range =
{ start = range.start
, end = { row = range.start.row, column = range.start.column + 4 }
}
2023-09-03 12:30:45 +03:00
introducesVariableOrUsesTypeConstructor :
{ a | lookupTable : ModuleNameLookupTable, customTypesToReportInCases : Set ( ModuleName, ConstructorName ) }
-> List (Node Pattern)
-> Bool
introducesVariableOrUsesTypeConstructor resources nodesToLookAt =
2022-09-01 17:15:28 +03:00
case nodesToLookAt of
[] ->
2021-08-19 21:57:29 +03:00
False
2022-09-01 17:15:28 +03:00
node :: remaining ->
case Node.value node of
Pattern.VarPattern _ ->
True
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
Pattern.RecordPattern _ ->
True
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
Pattern.AsPattern _ _ ->
True
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
Pattern.ParenthesizedPattern pattern ->
2023-09-03 12:30:45 +03:00
introducesVariableOrUsesTypeConstructor resources (pattern :: remaining)
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
Pattern.TuplePattern nodes ->
2023-09-03 12:30:45 +03:00
introducesVariableOrUsesTypeConstructor resources (nodes ++ remaining)
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
Pattern.UnConsPattern first rest ->
2023-09-03 12:30:45 +03:00
introducesVariableOrUsesTypeConstructor resources (first :: rest :: remaining)
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
Pattern.ListPattern nodes ->
2023-09-03 12:30:45 +03:00
introducesVariableOrUsesTypeConstructor resources (nodes ++ remaining)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Pattern.NamedPattern variantQualified nodes ->
case ModuleNameLookupTable.fullModuleNameFor resources.lookupTable node of
2022-09-01 17:15:28 +03:00
Just moduleName ->
2023-09-03 12:30:45 +03:00
if Set.member ( moduleName, variantQualified.name ) resources.customTypesToReportInCases then
introducesVariableOrUsesTypeConstructor resources (nodes ++ remaining)
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
else
True
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
Nothing ->
True
2021-08-19 21:57:29 +03:00
2022-09-01 17:15:28 +03:00
_ ->
2023-09-03 12:30:45 +03:00
introducesVariableOrUsesTypeConstructor resources remaining
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
booleanCaseOfChecks : CaseOfCheckInfo -> List (Error {})
booleanCaseOfChecks checkInfo =
case checkInfo.caseOf.cases of
( firstPattern, Node firstRange _ ) :: ( Node secondPatternRange _, Node secondExprRange _ ) :: [] ->
case AstHelpers.getBoolPattern checkInfo.lookupTable firstPattern of
2021-08-19 21:57:29 +03:00
Just isTrueFirst ->
2023-09-03 12:30:45 +03:00
let
expressionRange : Range
expressionRange =
Node.range checkInfo.caseOf.expression
in
2021-08-19 21:57:29 +03:00
[ Rule.errorWithFix
{ message = "Replace `case..of` by an `if` condition"
, details =
[ "The idiomatic way to check for a condition is to use an `if` expression."
, "Read more about it at: https://guide.elm-lang.org/core_language.html#if-expressions"
]
}
(Node.range firstPattern)
(if isTrueFirst then
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy { start = checkInfo.parentRange.start, end = expressionRange.start } "if "
, Fix.replaceRangeBy { start = expressionRange.end, end = firstRange.start } " then "
2021-08-19 21:57:29 +03:00
, Fix.replaceRangeBy { start = secondPatternRange.start, end = secondExprRange.start } "else "
]
else
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy { start = checkInfo.parentRange.start, end = expressionRange.start } "if not ("
, Fix.replaceRangeBy { start = expressionRange.end, end = firstRange.start } ") then "
2021-08-19 21:57:29 +03:00
, Fix.replaceRangeBy { start = secondPatternRange.start, end = secondExprRange.start } "else "
]
)
]
2023-09-03 12:30:45 +03:00
Nothing ->
2021-08-19 21:57:29 +03:00
[]
_ ->
[]
2023-09-03 12:30:45 +03:00
destructuringCaseOfChecks :
CaseOfCheckInfo
-> List (Error {})
destructuringCaseOfChecks checkInfo =
case checkInfo.caseOf.cases of
( rawSinglePattern, Node bodyRange _ ) :: [] ->
let
singlePattern : Node Pattern
singlePattern =
AstHelpers.removeParensFromPattern rawSinglePattern
in
if isSimpleDestructurePattern singlePattern then
let
exprRange : Range
exprRange =
Node.range checkInfo.caseOf.expression
caseIndentation : String
caseIndentation =
String.repeat (checkInfo.parentRange.start.column - 1) " "
bodyIndentation : String
bodyIndentation =
String.repeat (bodyRange.start.column - 1) " "
in
[ Rule.errorWithFix
{ message = "Use a let expression to destructure data"
, details = [ "It is more idiomatic in Elm to use a let expression to define a new variable rather than to use pattern matching. This will also make the code less indented, therefore easier to read." ]
}
(Node.range singlePattern)
[ Fix.replaceRangeBy { start = checkInfo.parentRange.start, end = exprRange.start }
("let " ++ checkInfo.extractSourceCode (Node.range singlePattern) ++ " = ")
, Fix.replaceRangeBy { start = exprRange.end, end = bodyRange.start }
("\n" ++ caseIndentation ++ "in\n" ++ bodyIndentation)
]
]
else
[]
_ ->
[]
isSimpleDestructurePattern : Node Pattern -> Bool
isSimpleDestructurePattern (Node _ pattern) =
case pattern of
Pattern.TuplePattern _ ->
True
Pattern.RecordPattern _ ->
True
Pattern.VarPattern _ ->
True
_ ->
False
-- NEGATION
negationChecks : { parentRange : Range, negatedExpression : Node Expression } -> List (Error {})
negationChecks checkInfo =
case AstHelpers.removeParens checkInfo.negatedExpression of
Node range (Expression.Negation negatedValue) ->
let
doubleNegationRange : Range
doubleNegationRange =
{ start = checkInfo.parentRange.start
, end = { row = range.start.row, column = range.start.column + 1 }
}
in
[ Rule.errorWithFix
{ message = "Unnecessary double number negation"
, details = [ "Negating a number twice is the same as the number itself." ]
}
doubleNegationRange
(replaceBySubExpressionFix checkInfo.parentRange negatedValue)
]
_ ->
[]
2023-09-03 12:30:45 +03:00
-- FULLY APPLIED PREFIX OPERATORS
2023-09-03 12:30:45 +03:00
fullyAppliedPrefixOperatorChecks :
{ operator : String
, operatorRange : Range
, left : Node Expression
, right : Node Expression
}
-> List (Error {})
fullyAppliedPrefixOperatorChecks checkInfo =
[ Rule.errorWithFix
{ message = "Use the infix form (a + b) over the prefix form ((+) a b)"
, details = [ "The prefix form is generally more unfamiliar to Elm developers, and therefore it is nicer when the infix form is used." ]
}
checkInfo.operatorRange
[ Fix.removeRange { start = checkInfo.operatorRange.start, end = (Node.range checkInfo.left).start }
, Fix.insertAt (Node.range checkInfo.right).start (checkInfo.operator ++ " ")
]
]
-- APPLIED LAMBDA
appliedLambdaChecks : { nodeRange : Range, lambdaRange : Range, lambda : Expression.Lambda } -> List (Error {})
appliedLambdaChecks checkInfo =
case checkInfo.lambda.args of
(Node unitRange Pattern.UnitPattern) :: otherPatterns ->
[ Rule.errorWithFix
{ message = "Unnecessary unit argument"
, details =
[ "This function is expecting a unit, but also passing it directly."
, "Maybe this was made in attempt to make the computation lazy, but in practice the function will be evaluated eagerly."
]
2023-09-03 12:30:45 +03:00
}
unitRange
(case otherPatterns of
[] ->
replaceBySubExpressionFix checkInfo.nodeRange checkInfo.lambda.expression
secondPattern :: _ ->
Fix.removeRange { start = unitRange.start, end = (Node.range secondPattern).start }
:: keepOnlyAndParenthesizeFix { parentRange = checkInfo.nodeRange, keep = checkInfo.lambdaRange }
)
]
2023-09-03 12:30:45 +03:00
(Node allRange Pattern.AllPattern) :: otherPatterns ->
[ Rule.errorWithFix
{ message = "Unnecessary wildcard argument argument"
, details =
[ "This function is being passed an argument that is directly ignored."
, "Maybe this was made in attempt to make the computation lazy, but in practice the function will be evaluated eagerly."
]
}
allRange
(case otherPatterns of
[] ->
replaceBySubExpressionFix checkInfo.nodeRange checkInfo.lambda.expression
secondPattern :: _ ->
Fix.removeRange { start = allRange.start, end = (Node.range secondPattern).start }
:: keepOnlyAndParenthesizeFix { parentRange = checkInfo.nodeRange, keep = checkInfo.lambdaRange }
)
]
_ ->
2023-09-03 12:30:45 +03:00
[ Rule.error
{ message = "Anonymous function is immediately invoked"
, details =
[ "This expression defines a function which then gets called directly afterwards, which overly complexifies the intended computation."
, "While there are reasonable uses for this in languages like JavaScript, the same benefits aren't there in Elm because of not allowing name shadowing."
, "Here are a few ways you can simplify this:"
, """- Remove the lambda and reference the arguments directly instead of giving them new names
- Remove the lambda and use let variables to give names to the current arguments
- Extract the lambda to a named function (at the top-level or defined in a let expression)"""
]
}
checkInfo.lambdaRange
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
-- LET IN
2021-08-19 21:57:29 +03:00
letInChecks : Expression.LetBlock -> List (Error {})
letInChecks letBlock =
case Node.value letBlock.expression of
Expression.LetExpression _ ->
let
letRange : Range
letRange =
letKeyWordRange (Node.range letBlock.expression)
in
[ Rule.errorWithFix
{ message = "Let blocks can be joined together"
, details = [ "Let blocks can contain multiple declarations, and there is no advantage to having multiple chained let expressions rather than one longer let expression." ]
}
letRange
2023-09-03 12:30:45 +03:00
(case listLast letBlock.declarations of
Just (Node lastDeclRange _) ->
[ Fix.replaceRangeBy { start = lastDeclRange.end, end = letRange.end } "\n" ]
Nothing ->
[]
)
]
_ ->
[]
letKeyWordRange : Range -> Range
letKeyWordRange range =
{ start = range.start
, end = { row = range.start.row, column = range.start.column + 3 }
}
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
-- RECORD ACCESS
recordAccessChecks : Range -> Maybe Range -> String -> List (Node Expression.RecordSetter) -> List (Error {})
recordAccessChecks nodeRange recordNameRange fieldName setters =
case
findMap
(\(Node _ ( Node _ setterField, setterValue )) ->
if setterField == fieldName then
Just setterValue
else
Nothing
)
setters
of
Just setter ->
[ Rule.errorWithFix
{ message = "Field access can be simplified"
, details = [ "Accessing the field of a record or record update can be simplified to just that field's value" ]
}
nodeRange
(replaceBySubExpressionFix nodeRange setter)
]
Nothing ->
case recordNameRange of
Just rnr ->
[ Rule.errorWithFix
{ message = "Field access can be simplified"
, details = [ "Accessing the field of an unrelated record update can be simplified to just the original field's value" ]
}
nodeRange
[ Fix.replaceRangeBy { start = nodeRange.start, end = rnr.start } ""
, Fix.replaceRangeBy { start = rnr.end, end = nodeRange.end } ("." ++ fieldName)
]
]
Nothing ->
[]
distributeFieldAccess : String -> Range -> List (Node Expression) -> Node String -> List (Error {})
distributeFieldAccess kind recordRange branches (Node fieldRange fieldName) =
case recordLeavesRanges branches of
Just records ->
[ let
fieldAccessRange : Range
fieldAccessRange =
{ start = recordRange.end, end = fieldRange.end }
in
Rule.errorWithFix
{ message = "Field access can be simplified"
, details = [ "Accessing the field outside " ++ kind ++ " expression can be simplified to access the field inside it" ]
}
fieldAccessRange
(Fix.removeRange fieldAccessRange
:: List.map (\leafRange -> Fix.insertAt leafRange.end ("." ++ fieldName)) records
)
]
Nothing ->
[]
injectRecordAccessIntoLetExpression : Range -> Node Expression -> Node String -> Error {}
injectRecordAccessIntoLetExpression recordRange letBody (Node fieldRange fieldName) =
let
removalRange : Range
removalRange =
{ start = recordRange.end, end = fieldRange.end }
in
Rule.errorWithFix
{ message = "Field access can be simplified"
, details = [ "Accessing the field outside a let/in expression can be simplified to access the field inside it" ]
}
removalRange
(Fix.removeRange removalRange
:: replaceSubExpressionByRecordAccessFix fieldName letBody
)
recordLeavesRanges : List (Node Expression) -> Maybe (List Range)
recordLeavesRanges nodes =
recordLeavesRangesHelp nodes []
recordLeavesRangesHelp : List (Node Expression) -> List Range -> Maybe (List Range)
recordLeavesRangesHelp nodes foundRanges =
case nodes of
[] ->
Just foundRanges
(Node range expr) :: rest ->
case expr of
Expression.IfBlock _ thenBranch elseBranch ->
recordLeavesRangesHelp (thenBranch :: elseBranch :: rest) foundRanges
Expression.LetExpression letIn ->
recordLeavesRangesHelp (letIn.expression :: rest) foundRanges
Expression.ParenthesizedExpression child ->
recordLeavesRangesHelp (child :: rest) foundRanges
Expression.CaseExpression caseOf ->
recordLeavesRangesHelp (List.map Tuple.second caseOf.cases ++ rest) foundRanges
Expression.RecordExpr _ ->
recordLeavesRangesHelp rest (range :: foundRanges)
Expression.RecordUpdateExpression _ _ ->
recordLeavesRangesHelp rest (range :: foundRanges)
_ ->
Nothing
2023-06-25 21:01:32 +03:00
-- FIX HELPERS
parenthesizeIfNeededFix : Node Expression -> List Fix
2023-09-03 12:30:45 +03:00
parenthesizeIfNeededFix (Node expressionRange expression) =
if needsParens expression then
parenthesizeFix expressionRange
2023-06-25 21:01:32 +03:00
else
[]
parenthesizeFix : Range -> List Fix
parenthesizeFix toSurround =
[ Fix.insertAt toSurround.start "("
, Fix.insertAt toSurround.end ")"
]
2023-09-03 12:30:45 +03:00
keepOnlyFix : { parentRange : Range, keep : Range } -> List Fix
keepOnlyFix config =
[ Fix.removeRange
{ start = config.parentRange.start
, end = config.keep.start
}
, Fix.removeRange
{ start = config.keep.end
, end = config.parentRange.end
}
]
keepOnlyAndParenthesizeFix : { parentRange : Range, keep : Range } -> List Fix
keepOnlyAndParenthesizeFix config =
[ Fix.replaceRangeBy { start = config.parentRange.start, end = config.keep.start } "("
, Fix.replaceRangeBy { start = config.keep.end, end = config.parentRange.end } ")"
]
replaceBySubExpressionFix : Range -> Node Expression -> List Fix
replaceBySubExpressionFix outerRange (Node exprRange exprValue) =
if needsParens exprValue then
keepOnlyAndParenthesizeFix { parentRange = outerRange, keep = exprRange }
else
keepOnlyFix { parentRange = outerRange, keep = exprRange }
2023-09-03 12:30:45 +03:00
replaceSubExpressionByRecordAccessFix : String -> Node Expression -> List Fix
replaceSubExpressionByRecordAccessFix fieldName (Node exprRange exprValue) =
if needsParens exprValue then
[ Fix.insertAt exprRange.start "("
, Fix.insertAt exprRange.end (")." ++ fieldName)
]
else
[ Fix.insertAt exprRange.end ("." ++ fieldName) ]
2023-06-25 21:01:32 +03:00
rangeBetweenExclusive : ( Range, Range ) -> Range
rangeBetweenExclusive ( aRange, bRange ) =
2023-09-03 12:30:45 +03:00
case Range.compareLocations aRange.start bRange.start of
2023-06-25 21:01:32 +03:00
GT ->
{ start = bRange.end, end = aRange.start }
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
-- EQ | LT
_ ->
{ start = aRange.end, end = bRange.start }
2023-09-03 12:30:45 +03:00
rangeContainsLocation : Location -> Range -> Bool
rangeContainsLocation location =
\range ->
not
((Range.compareLocations location range.start == LT)
|| (Range.compareLocations location range.end == GT)
)
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
rangeWithoutBoundaries : Range -> Range
rangeWithoutBoundaries range =
{ start = startWithoutBoundary range
, end = endWithoutBoundary range
}
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
startWithoutBoundary : Range -> Location
startWithoutBoundary range =
{ row = range.start.row, column = range.start.column + 1 }
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
endWithoutBoundary : Range -> Location
endWithoutBoundary range =
{ row = range.end.row, column = range.end.column - 1 }
2021-08-19 21:57:29 +03:00
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
removeBoundariesFix : Node a -> List Fix
removeBoundariesFix (Node nodeRange _) =
[ Fix.removeRange (leftBoundaryRange nodeRange)
, Fix.removeRange (rightBoundaryRange nodeRange)
]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
leftBoundaryRange : Range -> Range
leftBoundaryRange range =
{ start = range.start
, end = { row = range.start.row, column = range.start.column + 1 }
}
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
rightBoundaryRange : Range -> Range
rightBoundaryRange range =
{ start = { row = range.end.row, column = range.end.column - 1 }
, end = range.end
}
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
replaceByEmptyFix : String -> Range -> Maybe a -> QualifyResources b -> List Fix
replaceByEmptyFix empty parentRange lastArg qualifyResources =
[ case lastArg of
2021-08-19 21:57:29 +03:00
Just _ ->
Fix.replaceRangeBy parentRange empty
Nothing ->
2023-06-25 21:01:32 +03:00
Fix.replaceRangeBy parentRange
(qualifiedToString (qualify ( [ "Basics" ], "always" ) qualifyResources)
++ " "
++ empty
)
2021-08-19 21:57:29 +03:00
]
2023-09-03 12:30:45 +03:00
replaceByBoolWithIrrelevantLastArgFix :
{ replacement : Bool, lastArg : Maybe a, checkInfo : QualifyResources { b | parentRange : Range } }
-> List Fix
replaceByBoolWithIrrelevantLastArgFix config =
let
replacementAsString : String
replacementAsString =
qualifiedToString (qualify ( [ "Basics" ], AstHelpers.boolToString config.replacement ) config.checkInfo)
in
case config.lastArg of
2021-08-19 21:57:29 +03:00
Just _ ->
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy config.checkInfo.parentRange replacementAsString ]
2021-08-19 21:57:29 +03:00
Nothing ->
2023-09-03 12:30:45 +03:00
[ Fix.replaceRangeBy config.checkInfo.parentRange
2023-06-25 21:01:32 +03:00
("("
2023-09-03 12:30:45 +03:00
++ qualifiedToString (qualify ( [ "Basics" ], "always" ) config.checkInfo)
2023-06-25 21:01:32 +03:00
++ " "
2023-09-03 12:30:45 +03:00
++ replacementAsString
2023-06-25 21:01:32 +03:00
++ ")"
)
2023-09-03 12:30:45 +03:00
]
replacementWithIrrelevantLastArg : { resources : QualifyResources a, lastArg : Maybe arg, forNoLastArg : String } -> String
replacementWithIrrelevantLastArg config =
case config.lastArg of
Just _ ->
config.forNoLastArg
Nothing ->
qualifiedToString (qualify ( [ "Basics" ], "always" ) config.resources)
++ (" (" ++ config.forNoLastArg ++ ")")
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
identityError :
{ toFix : String
, lastArgName : String
, lastArg : Maybe (Node lastArgument)
, resources : QualifyResources { a | fnRange : Range, parentRange : Range }
}
-> Error {}
identityError config =
Rule.errorWithFix
{ message = "Using " ++ config.toFix ++ " will always return the same given " ++ config.lastArgName
, details =
case config.lastArg of
Nothing ->
[ "You can replace this call by identity." ]
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
Just _ ->
[ "You can replace this call by the " ++ config.lastArgName ++ " itself." ]
}
config.resources.fnRange
(toIdentityFix { lastArg = config.lastArg, resources = config.resources })
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
toIdentityFix :
{ lastArg : Maybe (Node lastArgument)
, resources : QualifyResources { a | parentRange : Range }
}
-> List Fix
toIdentityFix config =
case config.lastArg of
Nothing ->
[ Fix.replaceRangeBy config.resources.parentRange
(qualifiedToString (qualify ( [ "Basics" ], "identity" ) config.resources))
]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Just (Node lastArgRange _) ->
keepOnlyFix { parentRange = config.resources.parentRange, keep = lastArgRange }
multiAlways : Int -> String -> QualifyResources a -> String
multiAlways alwaysCount alwaysResultExpressionAsString qualifyResources =
case alwaysCount of
0 ->
alwaysResultExpressionAsString
1 ->
qualifiedToString (qualify ( [ "Basics" ], "always" ) qualifyResources)
++ " "
++ alwaysResultExpressionAsString
alwaysCountPositive ->
"(\\" ++ String.repeat alwaysCountPositive "_ " ++ "-> " ++ alwaysResultExpressionAsString ++ ")"
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
{-| Use in combination with
`findMapNeighboring` where finding returns a record containing the element's Range
Works for patterns and expressions.
-}
listLiteralElementRemoveFix : { before : Maybe (Node element), found : { found | range : Range }, after : Maybe (Node element) } -> List Fix
listLiteralElementRemoveFix toRemove =
case ( toRemove.before, toRemove.after ) of
-- found the only element
( Nothing, Nothing ) ->
[ Fix.removeRange toRemove.found.range ]
-- found first element
2023-09-03 12:30:45 +03:00
( Nothing, Just (Node afterRange _) ) ->
2023-06-25 21:01:32 +03:00
[ Fix.removeRange
{ start = toRemove.found.range.start
2023-09-03 12:30:45 +03:00
, end = afterRange.start
2023-06-25 21:01:32 +03:00
}
]
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
-- found after first element
2023-09-03 12:30:45 +03:00
( Just (Node beforeRange _), _ ) ->
2023-06-25 21:01:32 +03:00
[ Fix.removeRange
2023-09-03 12:30:45 +03:00
{ start = beforeRange.end
2023-06-25 21:01:32 +03:00
, end = toRemove.found.range.end
}
]
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
{-| Use in combination with
`findMapNeighboring` where finding returns a record containing the element's Range
Works for patterns and expressions.
-}
collapsedConsRemoveElementFix :
{ toRemove : { before : Maybe (Node element), after : Maybe (Node element), found : { found | range : Range } }
, tailRange : Range
}
-> List Fix
2023-09-03 12:30:45 +03:00
collapsedConsRemoveElementFix config =
case ( config.toRemove.before, config.toRemove.after ) of
2023-06-25 21:01:32 +03:00
-- found the only consed element
( Nothing, Nothing ) ->
[ Fix.removeRange
2023-09-03 12:30:45 +03:00
{ start = config.toRemove.found.range.start, end = config.tailRange.start }
2023-06-25 21:01:32 +03:00
]
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
-- found first consed element
2023-09-03 12:30:45 +03:00
( Nothing, Just (Node afterRange _) ) ->
2023-06-25 21:01:32 +03:00
[ Fix.removeRange
2023-09-03 12:30:45 +03:00
{ start = config.toRemove.found.range.start
, end = afterRange.start
2023-06-25 21:01:32 +03:00
}
]
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
-- found after first consed element
2023-09-03 12:30:45 +03:00
( Just (Node beforeRange _), _ ) ->
2023-06-25 21:01:32 +03:00
[ Fix.removeRange
2023-09-03 12:30:45 +03:00
{ start = beforeRange.end
, end = config.toRemove.found.range.end
2023-06-25 21:01:32 +03:00
}
]
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
-- STRING
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
wrapInBackticks : String -> String
wrapInBackticks s =
"`" ++ s ++ "`"
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
-- MATCHERS AND PARSERS
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
needsParens : Expression -> Bool
needsParens expr =
case expr of
Expression.Application _ ->
2023-06-25 21:01:32 +03:00
True
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.OperatorApplication _ _ _ _ ->
2023-06-25 21:01:32 +03:00
True
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.IfBlock _ _ _ ->
2023-06-25 21:01:32 +03:00
True
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.Negation _ ->
True
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.LetExpression _ ->
True
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.CaseExpression _ ->
True
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.LambdaExpression _ ->
True
2021-08-19 21:57:29 +03:00
_ ->
2023-09-03 12:30:45 +03:00
False
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
returnsSpecificValueOrFunctionInAllBranches : ( ModuleName, String ) -> ModuleNameLookupTable -> Node Expression -> Match (List Range)
returnsSpecificValueOrFunctionInAllBranches specificQualified lookupTable expressionNode =
constructs (sameValueOrFunctionInAllBranches specificQualified) lookupTable expressionNode
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
constructsSpecificInAllBranches : ( ModuleName, String ) -> ModuleNameLookupTable -> Node Expression -> Match ConstructionKind
constructsSpecificInAllBranches specificFullyQualifiedFn lookupTable expressionNode =
case AstHelpers.getSpecificValueOrFunction specificFullyQualifiedFn lookupTable expressionNode of
Just _ ->
Determined DirectConstruction
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
constructs (sameCallInAllBranches specificFullyQualifiedFn) lookupTable expressionNode
|> Match.map
(\calls ->
NonDirectConstruction
(List.concatMap (\call -> replaceBySubExpressionFix call.nodeRange call.firstArg) calls)
)
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
type ConstructionKind
= -- either
-- - `always specific`
-- - `\a -> ... (specific a)`
-- - `... specific`
DirectConstruction
| -- `a` argument not directly used,
-- e.g. `\a -> ... (specific (f a))` or `\a -> if a then specific b`
NonDirectConstruction (List Fix)
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
constructs :
(ModuleNameLookupTable -> Node Expression -> Match specific)
-> ModuleNameLookupTable
-> Node Expression
-> Match specific
constructs getSpecific lookupTable expressionNode =
case AstHelpers.getSpecificFunctionCall ( [ "Basics" ], "always" ) lookupTable expressionNode of
Just alwaysCall ->
getSpecific lookupTable alwaysCall.firstArg
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
case Node.value (AstHelpers.removeParens expressionNode) of
Expression.LambdaExpression lambda ->
getSpecific lookupTable lambda.expression
2023-06-25 21:01:32 +03:00
_ ->
2023-09-03 12:30:45 +03:00
Undetermined
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
sameCallInAllBranches :
( ModuleName, String )
-> ModuleNameLookupTable
-> Node Expression
->
Match
(List
{ argsAfterFirst : List (Node Expression)
, firstArg : Node Expression
, fnRange : Range
, nodeRange : Range
}
)
sameCallInAllBranches pureFullyQualified lookupTable baseExpressionNode =
sameInAllBranches (AstHelpers.getSpecificFunctionCall pureFullyQualified lookupTable) baseExpressionNode
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
sameValueOrFunctionInAllBranches :
( ModuleName, String )
-> ModuleNameLookupTable
-> Node Expression
-> Match (List Range)
sameValueOrFunctionInAllBranches pureFullyQualified lookupTable baseExpressionNode =
sameInAllBranches (AstHelpers.getSpecificValueOrFunction pureFullyQualified lookupTable) baseExpressionNode
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
sameInAllBranches :
(Node Expression -> Maybe info)
-> Node Expression
-> Match (List info)
sameInAllBranches getSpecific baseExpressionNode =
case getSpecific baseExpressionNode of
Just specific ->
Determined [ specific ]
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
case Node.value (AstHelpers.removeParens baseExpressionNode) of
Expression.LetExpression letIn ->
sameInAllBranches getSpecific letIn.expression
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Expression.IfBlock _ thenBranch elseBranch ->
Match.traverse
(\branchExpression -> sameInAllBranches getSpecific branchExpression)
[ thenBranch, elseBranch ]
|> Match.map List.concat
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Expression.CaseExpression caseOf ->
Match.traverse
(\( _, caseExpression ) -> sameInAllBranches getSpecific caseExpression)
caseOf.cases
|> Match.map List.concat
2023-06-25 21:01:32 +03:00
_ ->
2023-09-03 12:30:45 +03:00
Undetermined
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
getComparableExpressionInTupleFirst : Node Expression -> Maybe (List Expression)
getComparableExpressionInTupleFirst expressionNode =
case AstHelpers.getTuple expressionNode of
Just tuple ->
getComparableExpression tuple.first
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
2023-06-25 21:01:32 +03:00
Nothing
2023-09-03 12:30:45 +03:00
getComparableExpression : Node Expression -> Maybe (List Expression)
getComparableExpression =
getComparableExpressionHelper 1
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
getComparableExpressionHelper : Int -> Node Expression -> Maybe (List Expression)
getComparableExpressionHelper sign (Node _ expression) =
case expression of
Expression.Integer int ->
Just [ Expression.Integer (sign * int) ]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.Hex hex ->
Just [ Expression.Integer (sign * hex) ]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.Floatable float ->
Just [ Expression.Floatable (toFloat sign * float) ]
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.Negation expr ->
getComparableExpressionHelper (-1 * sign) expr
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Expression.Literal string ->
Just [ Expression.Literal string ]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.CharLiteral char ->
Just [ Expression.CharLiteral char ]
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.ParenthesizedExpression expr ->
getComparableExpressionHelper 1 expr
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.TupledExpression exprs ->
exprs
|> traverse (getComparableExpressionHelper 1)
|> Maybe.map List.concat
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
Expression.ListExpr exprs ->
exprs
|> traverse (getComparableExpressionHelper 1)
|> Maybe.map List.concat
2022-04-17 09:59:11 +03:00
_ ->
2023-09-03 12:30:45 +03:00
Nothing
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
-- LIST HELPERS
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
listLast : List a -> Maybe a
listLast list =
case list of
2022-04-17 09:59:11 +03:00
[] ->
2023-09-03 12:30:45 +03:00
Nothing
2022-04-17 09:59:11 +03:00
2023-09-03 12:30:45 +03:00
head :: tail ->
Just (listFilledLast ( head, tail ))
2022-04-17 09:59:11 +03:00
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
listFilledLast : ( a, List a ) -> a
listFilledLast ( head, tail ) =
case tail of
[] ->
head
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
tailHead :: tailTail ->
listFilledLast ( tailHead, tailTail )
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
findMap : (a -> Maybe b) -> List a -> Maybe b
findMap mapper nodes =
case nodes of
[] ->
Nothing
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
node :: rest ->
case mapper node of
Just value ->
Just value
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
findMap mapper rest
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
findMapNeighboringAfter : Maybe a -> (a -> Maybe b) -> List a -> Maybe { before : Maybe a, found : b, after : Maybe a }
findMapNeighboringAfter before tryMap list =
case list of
[] ->
Nothing
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
now :: after ->
case tryMap now of
Just found ->
Just { before = before, found = found, after = after |> List.head }
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
findMapNeighboringAfter (Just now) tryMap after
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
findMapNeighboring : (a -> Maybe b) -> List a -> Maybe { before : Maybe a, found : b, after : Maybe a }
findMapNeighboring tryMap list =
findMapNeighboringAfter Nothing tryMap list
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
neighboringMap : ({ before : Maybe a, current : a, after : Maybe a } -> b) -> List a -> List b
neighboringMap changeWithNeighboring list =
List.map3
(\before current after ->
changeWithNeighboring { before = before, current = current, after = after }
)
(Nothing :: List.map Just list)
list
(List.map Just (List.drop 1 list) ++ [ Nothing ])
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
withBeforeMap : ({ before : Maybe a, current : a } -> b) -> List a -> List b
withBeforeMap changeWithBefore list =
List.map2
(\before current ->
changeWithBefore { before = before, current = current }
)
(Nothing :: List.map Just list)
list
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
traverse : (a -> Maybe b) -> List a -> Maybe (List b)
traverse f list =
traverseHelp f list []
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
traverseHelp : (a -> Maybe b) -> List a -> List b -> Maybe (List b)
traverseHelp f list acc =
case list of
head :: tail ->
case f head of
Just a ->
traverseHelp f tail (a :: acc)
2021-08-19 21:57:29 +03:00
2022-04-17 09:59:11 +03:00
Nothing ->
Nothing
2021-08-19 21:57:29 +03:00
2022-04-17 09:59:11 +03:00
[] ->
2023-09-03 12:30:45 +03:00
Just (List.reverse acc)
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
unique : List a -> List a
unique list =
uniqueHelp [] list []
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
uniqueHelp : List a -> List a -> List a -> List a
uniqueHelp existing remaining accumulator =
case remaining of
2023-06-25 21:01:32 +03:00
[] ->
2023-09-03 12:30:45 +03:00
List.reverse accumulator
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
first :: rest ->
if List.member first existing then
uniqueHelp existing rest accumulator
2021-08-19 21:57:29 +03:00
2023-09-03 12:30:45 +03:00
else
uniqueHelp (first :: existing) rest (first :: accumulator)
2021-08-19 21:57:29 +03:00
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
-- MAYBE HELPERS
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
isJust : Maybe a -> Bool
isJust maybe =
case maybe of
Just _ ->
True
2023-06-25 21:01:32 +03:00
2023-09-03 12:30:45 +03:00
Nothing ->
False