mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-12-18 07:01:48 +03:00
484 lines
13 KiB
Elm
484 lines
13 KiB
Elm
module NoUnused.Patterns exposing (rule)
|
|
|
|
{-| Report useless patterns and pattern values that are not used.
|
|
|
|
|
|
# Rule
|
|
|
|
@docs rule
|
|
|
|
-}
|
|
|
|
import Elm.Syntax.Expression as Expression exposing (Expression)
|
|
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 (Range)
|
|
import Elm.Writer as Writer
|
|
import NoUnused.Patterns.NameVisitor as NameVisitor
|
|
import Review.Fix as Fix exposing (Fix)
|
|
import Review.Rule as Rule exposing (Rule)
|
|
import Set exposing (Set)
|
|
|
|
|
|
{-| Report useless patterns and pattern values that are not used.
|
|
|
|
config =
|
|
[ NoUnused.Patterns.rule
|
|
]
|
|
|
|
This rule looks within let..in blocks and case branches to find any patterns that are unused. It will report any useless patterns as well as any pattern values that are not used.
|
|
|
|
|
|
## Fail
|
|
|
|
Value `something` is not used:
|
|
|
|
case maybe of
|
|
Just something ->
|
|
True
|
|
|
|
Nothing ->
|
|
False
|
|
|
|
|
|
## Success
|
|
|
|
case maybe of
|
|
Just _ ->
|
|
True
|
|
|
|
Nothing ->
|
|
False
|
|
|
|
|
|
## Try it out
|
|
|
|
You can try this rule out by running the following command:
|
|
|
|
```bash
|
|
elm-review --template jfmengels/review-unused/example --rules NoUnused.Patterns
|
|
```
|
|
|
|
-}
|
|
rule : Rule
|
|
rule =
|
|
Rule.newModuleRuleSchema "NoUnused.Patterns" initialContext
|
|
|> Rule.withExpressionEnterVisitor expressionEnterVisitor
|
|
|> Rule.withExpressionExitVisitor expressionExitVisitor
|
|
|> NameVisitor.withValueVisitor valueVisitor
|
|
|> Rule.fromModuleRuleSchema
|
|
|
|
|
|
expressionEnterVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context )
|
|
expressionEnterVisitor node context =
|
|
case Node.value node of
|
|
Expression.LetExpression { declarations } ->
|
|
( [], rememberLetDeclarationList declarations context )
|
|
|
|
Expression.CaseExpression { cases } ->
|
|
( [], rememberCaseList cases context )
|
|
|
|
_ ->
|
|
( [], context )
|
|
|
|
|
|
expressionExitVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context )
|
|
expressionExitVisitor node context =
|
|
case Node.value node of
|
|
Expression.LetExpression { declarations } ->
|
|
errorsForLetDeclarationList declarations context
|
|
|
|
Expression.CaseExpression { cases } ->
|
|
errorsForCaseList cases context
|
|
|
|
_ ->
|
|
( [], context )
|
|
|
|
|
|
valueVisitor : Node ( ModuleName, String ) -> Context -> ( List (Rule.Error {}), Context )
|
|
valueVisitor (Node _ ( moduleName, value )) context =
|
|
case moduleName of
|
|
[] ->
|
|
( [], useValue value context )
|
|
|
|
_ ->
|
|
( [], context )
|
|
|
|
|
|
|
|
--- ON ENTER
|
|
|
|
|
|
rememberCaseList : List Expression.Case -> Context -> Context
|
|
rememberCaseList list context =
|
|
List.foldl rememberCase context list
|
|
|
|
|
|
rememberCase : Expression.Case -> Context -> Context
|
|
rememberCase ( pattern, _ ) context =
|
|
rememberPattern pattern context
|
|
|
|
|
|
rememberLetDeclarationList : List (Node Expression.LetDeclaration) -> Context -> Context
|
|
rememberLetDeclarationList list context =
|
|
List.foldl rememberLetDeclaration context list
|
|
|
|
|
|
rememberLetDeclaration : Node Expression.LetDeclaration -> Context -> Context
|
|
rememberLetDeclaration (Node _ letDeclaration) context =
|
|
case letDeclaration of
|
|
Expression.LetFunction _ ->
|
|
context
|
|
|
|
Expression.LetDestructuring pattern _ ->
|
|
rememberPattern pattern context
|
|
|
|
|
|
rememberPatternList : List (Node Pattern) -> Context -> Context
|
|
rememberPatternList list context =
|
|
List.foldl rememberPattern context list
|
|
|
|
|
|
rememberPattern : Node Pattern -> Context -> Context
|
|
rememberPattern (Node _ pattern) context =
|
|
case pattern of
|
|
Pattern.AllPattern ->
|
|
context
|
|
|
|
Pattern.VarPattern value ->
|
|
rememberValue value context
|
|
|
|
Pattern.TuplePattern patterns ->
|
|
rememberPatternList patterns context
|
|
|
|
Pattern.RecordPattern values ->
|
|
rememberValueList values context
|
|
|
|
Pattern.UnConsPattern first second ->
|
|
context
|
|
|> rememberPattern first
|
|
|> rememberPattern second
|
|
|
|
Pattern.ListPattern patterns ->
|
|
rememberPatternList patterns context
|
|
|
|
Pattern.NamedPattern _ patterns ->
|
|
rememberPatternList patterns context
|
|
|
|
Pattern.AsPattern inner name ->
|
|
context
|
|
|> rememberPattern inner
|
|
|> rememberValue (Node.value name)
|
|
|
|
Pattern.ParenthesizedPattern inner ->
|
|
rememberPattern inner context
|
|
|
|
_ ->
|
|
context
|
|
|
|
|
|
rememberValueList : List (Node String) -> Context -> Context
|
|
rememberValueList list context =
|
|
List.foldl (Node.value >> rememberValue) context list
|
|
|
|
|
|
|
|
--- ON EXIT
|
|
|
|
|
|
singularDetails : List String
|
|
singularDetails =
|
|
[ "You should either use this value somewhere, or remove it at the location I pointed at." ]
|
|
|
|
|
|
pluralDetails : List String
|
|
pluralDetails =
|
|
[ "You should either use these values somewhere, or remove them at the location I pointed at." ]
|
|
|
|
|
|
removeDetails : List String
|
|
removeDetails =
|
|
[ "You should remove it at the location I pointed at." ]
|
|
|
|
|
|
andThen :
|
|
(value -> Context -> ( List (Rule.Error {}), Context ))
|
|
-> value
|
|
-> ( List (Rule.Error {}), Context )
|
|
-> ( List (Rule.Error {}), Context )
|
|
andThen function value ( errors, context ) =
|
|
let
|
|
( newErrors, newContext ) =
|
|
function value context
|
|
in
|
|
( newErrors ++ errors, newContext )
|
|
|
|
|
|
errorsForCaseList : List Expression.Case -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForCaseList list context =
|
|
List.foldl (andThen errorsForCase) ( [], context ) list
|
|
|
|
|
|
errorsForCase : Expression.Case -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForCase ( pattern, _ ) context =
|
|
errorsForPattern Matching pattern context
|
|
|
|
|
|
errorsForLetDeclarationList : List (Node Expression.LetDeclaration) -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForLetDeclarationList list context =
|
|
List.foldl (andThen errorsForLetDeclaration) ( [], context ) list
|
|
|
|
|
|
errorsForLetDeclaration : Node Expression.LetDeclaration -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForLetDeclaration (Node _ letDeclaration) context =
|
|
case letDeclaration of
|
|
Expression.LetFunction _ ->
|
|
( [], context )
|
|
|
|
Expression.LetDestructuring pattern _ ->
|
|
errorsForPattern Destructuring pattern context
|
|
|
|
|
|
type PatternUse
|
|
= Destructuring
|
|
| Matching
|
|
|
|
|
|
errorsForPatternList : PatternUse -> List (Node Pattern) -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForPatternList use list context =
|
|
List.foldl (andThen (errorsForPattern use)) ( [], context ) list
|
|
|
|
|
|
errorsForPattern : PatternUse -> Node Pattern -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForPattern use (Node range pattern) context =
|
|
case pattern of
|
|
Pattern.AllPattern ->
|
|
( [], context )
|
|
|
|
Pattern.VarPattern value ->
|
|
errorsForValue value range context
|
|
|
|
Pattern.RecordPattern values ->
|
|
errorsForRecordValueList range values context
|
|
|
|
Pattern.TuplePattern [ Node _ Pattern.AllPattern, Node _ Pattern.AllPattern ] ->
|
|
errorsForUselessTuple range context
|
|
|
|
Pattern.TuplePattern [ Node _ Pattern.AllPattern, Node _ Pattern.AllPattern, Node _ Pattern.AllPattern ] ->
|
|
errorsForUselessTuple range context
|
|
|
|
Pattern.TuplePattern patterns ->
|
|
errorsForPatternList use patterns context
|
|
|
|
Pattern.UnConsPattern first second ->
|
|
errorsForPatternList use [ first, second ] context
|
|
|
|
Pattern.ListPattern patterns ->
|
|
errorsForPatternList use patterns context
|
|
|
|
Pattern.NamedPattern _ patterns ->
|
|
if use == Destructuring && List.all isAllPattern patterns then
|
|
errorsForUselessNamePattern range context
|
|
|
|
else
|
|
errorsForPatternList use patterns context
|
|
|
|
Pattern.AsPattern inner name ->
|
|
context
|
|
|> errorsForAsPattern range inner name
|
|
|> andThen (errorsForPattern use) inner
|
|
|
|
Pattern.ParenthesizedPattern inner ->
|
|
errorsForPattern use inner context
|
|
|
|
_ ->
|
|
( [], context )
|
|
|
|
|
|
errorsForUselessNamePattern : Range -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForUselessNamePattern range context =
|
|
( [ Rule.errorWithFix
|
|
{ message = "Named pattern is not needed."
|
|
, details = removeDetails
|
|
}
|
|
range
|
|
[ Fix.replaceRangeBy range "_" ]
|
|
]
|
|
, context
|
|
)
|
|
|
|
|
|
errorsForUselessTuple : Range -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForUselessTuple range context =
|
|
( [ Rule.errorWithFix
|
|
{ message = "Tuple pattern is not needed."
|
|
, details = removeDetails
|
|
}
|
|
range
|
|
[ Fix.replaceRangeBy range "_" ]
|
|
]
|
|
, context
|
|
)
|
|
|
|
|
|
errorsForRecordValueList : Range -> List (Node String) -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForRecordValueList recordRange list context =
|
|
let
|
|
( unused, used ) =
|
|
List.partition (isNodeInContext context) list
|
|
in
|
|
case unused of
|
|
[] ->
|
|
( [], context )
|
|
|
|
firstNode :: restNodes ->
|
|
let
|
|
first : String
|
|
first =
|
|
Node.value firstNode
|
|
|
|
rest : List String
|
|
rest =
|
|
List.map Node.value restNodes
|
|
|
|
( errorRange, fix ) =
|
|
case used of
|
|
[] ->
|
|
( recordRange, Fix.replaceRangeBy recordRange "_" )
|
|
|
|
_ ->
|
|
( Range.combine (List.map Node.range unused)
|
|
, Node Range.emptyRange (Pattern.RecordPattern used)
|
|
|> Writer.writePattern
|
|
|> Writer.write
|
|
|> Fix.replaceRangeBy recordRange
|
|
)
|
|
in
|
|
( [ Rule.errorWithFix
|
|
{ message = listToMessage first rest
|
|
, details = listToDetails first rest
|
|
}
|
|
errorRange
|
|
[ fix ]
|
|
]
|
|
, List.foldl forgetNode context unused
|
|
)
|
|
|
|
|
|
isNodeInContext : Context -> Node String -> Bool
|
|
isNodeInContext context (Node _ value) =
|
|
Set.member value context
|
|
|
|
|
|
listToMessage : String -> List String -> String
|
|
listToMessage first rest =
|
|
case List.reverse rest of
|
|
[] ->
|
|
"Value `" ++ first ++ "` is not used."
|
|
|
|
last :: middle ->
|
|
"Values `" ++ String.join "`, `" (first :: middle) ++ "` and `" ++ last ++ "` are not used."
|
|
|
|
|
|
listToDetails : String -> List String -> List String
|
|
listToDetails _ rest =
|
|
case rest of
|
|
[] ->
|
|
singularDetails
|
|
|
|
_ ->
|
|
pluralDetails
|
|
|
|
|
|
errorsForAsPattern : Range -> Node Pattern -> Node String -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForAsPattern patternRange inner (Node range name) context =
|
|
if Set.member name context then
|
|
let
|
|
fix : List Fix
|
|
fix =
|
|
[ inner
|
|
|> Writer.writePattern
|
|
|> Writer.write
|
|
|> Fix.replaceRangeBy patternRange
|
|
]
|
|
in
|
|
( [ Rule.errorWithFix
|
|
{ message = "Pattern alias `" ++ name ++ "` is not used."
|
|
, details = singularDetails
|
|
}
|
|
range
|
|
fix
|
|
]
|
|
, Set.remove name context
|
|
)
|
|
|
|
else if isAllPattern inner then
|
|
( [ Rule.errorWithFix
|
|
{ message = "Pattern `_` is not needed."
|
|
, details = removeDetails
|
|
}
|
|
(Node.range inner)
|
|
[ Fix.replaceRangeBy patternRange name ]
|
|
]
|
|
, Set.remove name context
|
|
)
|
|
|
|
else
|
|
( [], context )
|
|
|
|
|
|
isAllPattern : Node Pattern -> Bool
|
|
isAllPattern (Node _ pattern) =
|
|
case pattern of
|
|
Pattern.AllPattern ->
|
|
True
|
|
|
|
_ ->
|
|
False
|
|
|
|
|
|
forgetNode : Node String -> Context -> Context
|
|
forgetNode (Node _ value) context =
|
|
Set.remove value context
|
|
|
|
|
|
|
|
--- CONTEXT
|
|
|
|
|
|
type alias Context =
|
|
Set String
|
|
|
|
|
|
initialContext : Context
|
|
initialContext =
|
|
Set.empty
|
|
|
|
|
|
errorsForValue : String -> Range -> Context -> ( List (Rule.Error {}), Context )
|
|
errorsForValue value range context =
|
|
if Set.member value context then
|
|
( [ Rule.errorWithFix
|
|
{ message = "Value `" ++ value ++ "` is not used."
|
|
, details = singularDetails
|
|
}
|
|
range
|
|
[ Fix.replaceRangeBy range "_" ]
|
|
]
|
|
, Set.remove value context
|
|
)
|
|
|
|
else
|
|
( [], context )
|
|
|
|
|
|
rememberValue : String -> Context -> Context
|
|
rememberValue value context =
|
|
Set.insert value context
|
|
|
|
|
|
useValue : String -> Context -> Context
|
|
useValue value context =
|
|
Set.remove value context
|