Backport elm-review-unused

This commit is contained in:
Jeroen Engels 2021-01-24 16:44:12 +01:00
parent 63819d3fb7
commit 4669fe3960
6 changed files with 570 additions and 210 deletions

File diff suppressed because one or more lines are too long

View File

@ -22,6 +22,7 @@ import Elm.Syntax.Pattern as Pattern exposing (Pattern)
import Elm.Syntax.Range exposing (Range)
import Elm.Syntax.Type as Type
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
import NoUnused.RangeDict as RangeDict exposing (RangeDict)
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
import Review.Rule as Rule exposing (Error, Rule)
import Set exposing (Set)
@ -187,7 +188,7 @@ type alias ModuleContext =
, declaredTypesWithConstructors : Dict CustomTypeName (Dict ConstructorName (Node ConstructorName))
, usedFunctionsOrValues : Dict ModuleNameAsString (Set ConstructorName)
, phantomVariables : Dict ModuleName (List ( CustomTypeName, Int ))
, ignoreBlocks : List (Dict RangeAsString (Set ( ModuleName, String )))
, ignoreBlocks : List (RangeDict (Set ( ModuleName, String )))
, constructorsToIgnore : List (Set ( ModuleName, String ))
, wasUsedInLocationThatNeedsItself : Set ( ModuleNameAsString, ConstructorName )
, wasUsedInComparisons : Set ( ModuleNameAsString, ConstructorName )
@ -195,10 +196,6 @@ type alias ModuleContext =
}
type alias RangeAsString =
String
initialProjectContext : List { moduleName : String, typeName : String, index : Int } -> ProjectContext
initialProjectContext phantomTypes =
{ exposedModules = Set.empty
@ -532,7 +529,7 @@ expressionVisitor node moduleContext =
newModuleContext =
case List.head moduleContext.ignoreBlocks of
Just expressionsWhereToIgnoreCases ->
case Dict.get (rangeAsString (Node.range node)) expressionsWhereToIgnoreCases of
case RangeDict.get (Node.range node) expressionsWhereToIgnoreCases of
Just constructorsToIgnore ->
{ moduleContext | constructorsToIgnore = constructorsToIgnore :: moduleContext.constructorsToIgnore }
@ -559,7 +556,7 @@ expressionExitVisitor node moduleContext =
in
case List.head newModuleContext.ignoreBlocks of
Just rangesWhereToIgnoreConstructors ->
if Dict.member (rangeAsString (Node.range node)) rangesWhereToIgnoreConstructors then
if RangeDict.member (Node.range node) rangesWhereToIgnoreConstructors then
( []
, { newModuleContext | constructorsToIgnore = List.drop 1 newModuleContext.constructorsToIgnore }
)
@ -611,11 +608,11 @@ expressionVisitorHelp node moduleContext =
Expression.CaseExpression { cases } ->
let
newCases : Dict RangeAsString (Set ( ModuleName, String ))
newCases : RangeDict (Set ( ModuleName, String ))
newCases =
cases
|> List.map (\( pattern, body ) -> ( rangeAsString (Node.range body), constructorsInPattern moduleContext.lookupTable pattern ))
|> Dict.fromList
|> List.map (\( pattern, body ) -> ( Node.range body, constructorsInPattern moduleContext.lookupTable pattern ))
|> RangeDict.fromList
in
( []
, { moduleContext | ignoreBlocks = newCases :: moduleContext.ignoreBlocks }
@ -945,14 +942,3 @@ listAtIndex index list =
( n, _ :: rest ) ->
listAtIndex (n - 1) rest
rangeAsString : Range -> RangeAsString
rangeAsString range =
[ range.start.row
, range.start.column
, range.end.row
, range.end.column
]
|> List.map String.fromInt
|> String.join "_"

View File

@ -16,6 +16,7 @@ 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 NoUnused.RangeDict as RangeDict exposing (RangeDict)
import Review.Fix as Fix exposing (Fix)
import Review.Rule as Rule exposing (Rule)
import Set exposing (Set)
@ -70,14 +71,97 @@ rule =
|> Rule.fromModuleRuleSchema
expressionEnterVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context )
type alias Context =
{ scopes : List Scope
, scopesToCreate : RangeDict (List FoundPattern)
}
type alias Scope =
{ declared : List FoundPattern
, used : Set String
}
type FoundPattern
= SingleValue
{ name : String
, range : Range
, message : String
, details : List String
, fix : List Fix
}
| RecordPattern
{ fields : List (Node String)
, recordRange : Range
}
| SimplifiablePattern (Rule.Error {})
initialContext : Context
initialContext =
{ scopes = []
, scopesToCreate = RangeDict.empty
}
-- EXPRESSION ENTER VISITOR
expressionEnterVisitor : Node Expression -> Context -> ( List nothing, Context )
expressionEnterVisitor node context =
let
newContext : Context
newContext =
case RangeDict.get (Node.range node) context.scopesToCreate of
Just declared ->
{ context | scopes = { declared = declared, used = Set.empty } :: context.scopes }
Nothing ->
context
in
expressionEnterVisitorHelp node newContext
expressionEnterVisitorHelp : Node Expression -> Context -> ( List nothing, Context )
expressionEnterVisitorHelp node context =
case Node.value node of
Expression.LetExpression { declarations } ->
( [], rememberLetDeclarationList declarations context )
let
findPatternsInLetDeclaration : Node Expression.LetDeclaration -> List FoundPattern
findPatternsInLetDeclaration letDeclaration =
case Node.value letDeclaration of
Expression.LetFunction _ ->
[]
Expression.LetDestructuring pattern _ ->
findPatterns Destructuring pattern
in
( []
, { context
| scopes =
{ declared = List.concatMap findPatternsInLetDeclaration declarations
, used = Set.empty
}
:: context.scopes
-- Will only be used to remove on exit, we are already adding the declared patterns to the scope above
, scopesToCreate = RangeDict.insert (Node.range node) [] context.scopesToCreate
}
)
Expression.CaseExpression { cases } ->
( [], rememberCaseList cases context )
( []
, { context
| scopes = { declared = [], used = Set.empty } :: context.scopes
, scopesToCreate =
List.foldl
(\( pattern, expr ) scopesToCreate -> RangeDict.insert (Node.range expr) (findPatterns Matching pattern) scopesToCreate)
context.scopesToCreate
cases
}
)
_ ->
( [], context )
@ -85,17 +169,136 @@ expressionEnterVisitor node context =
expressionExitVisitor : Node Expression -> Context -> ( List (Rule.Error {}), Context )
expressionExitVisitor node context =
case Node.value node of
Expression.LetExpression { declarations } ->
errorsForLetDeclarationList declarations context
case RangeDict.get (Node.range node) context.scopesToCreate of
Just _ ->
report context
Expression.CaseExpression { cases } ->
errorsForCaseList cases context
Nothing ->
( [], context )
report : Context -> ( List (Rule.Error {}), Context )
report context =
case context.scopes of
headScope :: restOfScopes ->
let
{ singles, records, simplifiablePatterns } =
findDeclaredPatterns headScope
allDeclared : List String
allDeclared =
List.concat
[ List.map .name singles
, List.concatMap (.fields >> List.map Node.value) records
]
nonUsedVars : Set String
nonUsedVars =
allDeclared
|> Set.fromList
|> Set.diff headScope.used
errors : List (Rule.Error {})
errors =
List.concat
[ singleErrors
, recordErrors
, simplifiablePatterns
]
singleErrors : List (Rule.Error {})
singleErrors =
List.filter (\{ name } -> not <| Set.member name headScope.used) singles
|> List.map
(\pattern ->
Rule.errorWithFix
{ message = pattern.message
, details = pattern.details
}
pattern.range
pattern.fix
)
recordErrors : List (Rule.Error {})
recordErrors =
records
|> List.concatMap
(\{ fields, recordRange } ->
let
( unused, used ) =
List.partition (isNodeInContext context) fields
in
case unused of
[] ->
[]
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 ]
]
)
in
( errors
, List.foldl
useValue
{ context | scopes = restOfScopes }
(Set.toList nonUsedVars)
)
_ ->
( [], context )
findDeclaredPatterns :
Scope
->
{ singles : List { name : String, range : Range, message : String, details : List String, fix : List Fix }
, records : List { fields : List (Node String), recordRange : Range }
, simplifiablePatterns : List (Rule.Error {})
}
findDeclaredPatterns scope =
List.foldl
(\foundPattern acc ->
case foundPattern of
SingleValue v ->
{ acc | singles = v :: acc.singles }
RecordPattern v ->
{ acc | records = v :: acc.records }
SimplifiablePattern simplifiablePatternError ->
{ acc | simplifiablePatterns = simplifiablePatternError :: acc.simplifiablePatterns }
)
{ singles = [], records = [], simplifiablePatterns = [] }
scope.declared
valueVisitor : Node ( ModuleName, String ) -> Context -> ( List (Rule.Error {}), Context )
valueVisitor (Node _ ( moduleName, value )) context =
case moduleName of
@ -110,96 +313,109 @@ valueVisitor (Node _ ( moduleName, value )) 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 =
findPatterns : PatternUse -> Node Pattern -> List FoundPattern
findPatterns use (Node range pattern) =
case pattern of
Pattern.AllPattern ->
context
Pattern.VarPattern name ->
[ SingleValue
{ name = name
, message = "Value `" ++ name ++ "` is not used."
, details = singularReplaceDetails
, range = range
, fix = [ Fix.replaceRangeBy range "_" ]
}
]
Pattern.VarPattern value ->
rememberValue value context
Pattern.TuplePattern [ Node _ Pattern.AllPattern, Node _ Pattern.AllPattern ] ->
[ SimplifiablePattern
(Rule.errorWithFix
{ message = "Tuple pattern is not needed."
, details = redundantDetails
}
range
[ Fix.replaceRangeBy range "_" ]
)
]
Pattern.TuplePattern [ Node _ Pattern.AllPattern, Node _ Pattern.AllPattern, Node _ Pattern.AllPattern ] ->
[ SimplifiablePattern
(Rule.errorWithFix
{ message = "Tuple pattern is not needed."
, details = redundantDetails
}
range
[ Fix.replaceRangeBy range "_" ]
)
]
Pattern.TuplePattern patterns ->
rememberPatternList patterns context
List.concatMap (findPatterns use) patterns
Pattern.RecordPattern values ->
rememberValueList values context
Pattern.RecordPattern fields ->
[ RecordPattern
{ fields = fields
, recordRange = range
}
]
Pattern.UnConsPattern first second ->
context
|> rememberPattern first
|> rememberPattern second
findPatterns use first ++ findPatterns use second
Pattern.ListPattern patterns ->
rememberPatternList patterns context
List.concatMap (findPatterns use) patterns
Pattern.NamedPattern _ patterns ->
rememberPatternList patterns context
if use == Destructuring && List.all isAllPattern patterns then
[ SimplifiablePattern
(Rule.errorWithFix
{ message = "Named pattern is not needed."
, details = redundantDetails
}
range
[ Fix.replaceRangeBy range "_" ]
)
]
else
List.concatMap (findPatterns use) patterns
Pattern.AsPattern inner name ->
context
|> rememberPattern inner
|> rememberValue (Node.value name)
findPatternForAsPattern range inner name :: findPatterns use inner
Pattern.ParenthesizedPattern inner ->
rememberPattern inner context
findPatterns use inner
_ ->
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." ]
singularRemoveDetails : List String
singularRemoveDetails =
[ "You should either use this value somewhere or remove it." ]
singularReplaceDetails : List String
singularReplaceDetails =
[ "You should either use this value somewhere or replace it with '_'." ]
pluralDetails : List String
pluralDetails =
[ "You should either use these values somewhere, or remove them at the location I pointed at." ]
[ "You should either use these values somewhere or remove them." ]
redundantDetails : List String
redundantDetails =
[ "This pattern is redundant and should be replaced with '_'." ]
removeDetails : List String
removeDetails =
[ "You should remove it at the location I pointed at." ]
[ "This pattern is redundant and should be removed." ]
andThen :
@ -215,31 +431,6 @@ andThen function value ( errors, context ) =
( 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
@ -300,7 +491,7 @@ errorsForUselessNamePattern : Range -> Context -> ( List (Rule.Error {}), Contex
errorsForUselessNamePattern range context =
( [ Rule.errorWithFix
{ message = "Named pattern is not needed."
, details = removeDetails
, details = redundantDetails
}
range
[ Fix.replaceRangeBy range "_" ]
@ -313,7 +504,7 @@ errorsForUselessTuple : Range -> Context -> ( List (Rule.Error {}), Context )
errorsForUselessTuple range context =
( [ Rule.errorWithFix
{ message = "Tuple pattern is not needed."
, details = removeDetails
, details = redundantDetails
}
range
[ Fix.replaceRangeBy range "_" ]
@ -366,11 +557,6 @@ errorsForRecordValueList recordRange list context =
)
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
@ -385,7 +571,7 @@ listToDetails : String -> List String -> List String
listToDetails _ rest =
case rest of
[] ->
singularDetails
singularRemoveDetails
_ ->
pluralDetails
@ -393,7 +579,7 @@ listToDetails _ rest =
errorsForAsPattern : Range -> Node Pattern -> Node String -> Context -> ( List (Rule.Error {}), Context )
errorsForAsPattern patternRange inner (Node range name) context =
if Set.member name context then
if isUnused name context then
let
fix : List Fix
fix =
@ -405,12 +591,12 @@ errorsForAsPattern patternRange inner (Node range name) context =
in
( [ Rule.errorWithFix
{ message = "Pattern alias `" ++ name ++ "` is not used."
, details = singularDetails
, details = singularRemoveDetails
}
range
fix
]
, Set.remove name context
, useValue name context
)
else if isAllPattern inner then
@ -421,13 +607,44 @@ errorsForAsPattern patternRange inner (Node range name) context =
(Node.range inner)
[ Fix.replaceRangeBy patternRange name ]
]
, Set.remove name context
, useValue name context
)
else
( [], context )
findPatternForAsPattern : Range -> Node Pattern -> Node String -> FoundPattern
findPatternForAsPattern patternRange inner (Node range name) =
if isAllPattern inner then
SimplifiablePattern
(Rule.errorWithFix
{ message = "Pattern `_` is not needed."
, details = removeDetails
}
(Node.range inner)
[ Fix.replaceRangeBy patternRange name ]
)
else
let
fix : List Fix
fix =
[ inner
|> Writer.writePattern
|> Writer.write
|> Fix.replaceRangeBy patternRange
]
in
SingleValue
{ name = name
, message = "Pattern alias `" ++ name ++ "` is not used."
, details = singularRemoveDetails
, range = range
, fix = fix
}
isAllPattern : Node Pattern -> Bool
isAllPattern (Node _ pattern) =
case pattern of
@ -440,44 +657,46 @@ isAllPattern (Node _ pattern) =
forgetNode : Node String -> Context -> Context
forgetNode (Node _ value) context =
Set.remove value context
--- CONTEXT
type alias Context =
Set String
initialContext : Context
initialContext =
Set.empty
useValue value context
errorsForValue : String -> Range -> Context -> ( List (Rule.Error {}), Context )
errorsForValue value range context =
if Set.member value context then
errorsForValue name range context =
if isUnused name context then
( [ Rule.errorWithFix
{ message = "Value `" ++ value ++ "` is not used."
, details = singularDetails
{ message = "Value `" ++ name ++ "` is not used."
, details = singularReplaceDetails
}
range
[ Fix.replaceRangeBy range "_" ]
]
, Set.remove value context
, useValue name 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
useValue name context =
case context.scopes of
[] ->
context
headScope :: restOfScopes ->
{ context | scopes = { headScope | used = Set.insert name headScope.used } :: restOfScopes }
isNodeInContext : Context -> Node String -> Bool
isNodeInContext context (Node _ value) =
isUnused value context
isUnused : String -> Context -> Bool
isUnused name context =
case context.scopes of
[] ->
False
headScope :: _ ->
not <| Set.member name headScope.used

View File

@ -5,9 +5,21 @@ import Review.Test
import Test exposing (Test, describe, test)
details : List String
details =
[ "You should either use this value somewhere, or remove it at the location I pointed at."
useOrRemoveDetails : List String
useOrRemoveDetails =
[ "You should either use this value somewhere or remove it."
]
useOrReplaceDetails : List String
useOrReplaceDetails =
[ "You should either use this value somewhere or replace it with '_'."
]
redundantReplaceDetails : List String
redundantReplaceDetails =
[ "This pattern is redundant and should be replaced with '_'."
]
@ -49,7 +61,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `bish` is not used."
, details = details
, details = useOrReplaceDetails
, under = "bish"
}
|> Review.Test.whenFixed
@ -64,7 +76,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `bash` is not used."
, details = details
, details = useOrReplaceDetails
, under = "bash"
}
|> Review.Test.whenFixed
@ -93,7 +105,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `one` is not used."
, details = details
, details = useOrReplaceDetails
, under = "one"
}
|> Review.Test.whenFixed
@ -108,7 +120,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `first` is not used."
, details = details
, details = useOrReplaceDetails
, under = "first"
}
|> Review.Test.whenFixed
@ -123,7 +135,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `two` is not used."
, details = details
, details = useOrReplaceDetails
, under = "two"
}
|> Review.Test.whenFixed
@ -138,7 +150,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `more` is not used."
, details = details
, details = useOrReplaceDetails
, under = "more"
}
|> Review.Test.whenFixed
@ -167,7 +179,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `one` is not used."
, details = details
, details = useOrReplaceDetails
, under = "one"
}
|> Review.Test.whenFixed
@ -182,7 +194,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `first` is not used."
, details = details
, details = useOrReplaceDetails
, under = "first"
}
|> Review.Test.whenFixed
@ -197,7 +209,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `two` is not used."
, details = details
, details = useOrReplaceDetails
, under = "two"
}
|> Review.Test.whenFixed
@ -212,7 +224,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `more` is not used."
, details = details
, details = useOrReplaceDetails
, under = "more"
}
|> Review.Test.whenFixed
@ -224,6 +236,70 @@ foo =
one :: [] -> 1
first :: two :: [] -> 2
_ :: _ :: _ -> 3
"""
]
, test "report unused values with the same name as found in other branches" <|
\() ->
"""
module A exposing (..)
foo =
case value of
Thing foo ->
Just foo
OtherThing foo ->
Nothing
"""
|> Review.Test.run rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `foo` is not used."
, details = useOrReplaceDetails
, under = "foo"
}
|> Review.Test.atExactly { start = { row = 7, column = 20 }, end = { row = 7, column = 23 } }
|> Review.Test.whenFixed
"""
module A exposing (..)
foo =
case value of
Thing foo ->
Just foo
OtherThing _ ->
Nothing
"""
]
, test "report unused values with the same name as found in different functions" <|
\() ->
"""
module A exposing (..)
foo =
case bar of
bish ->
bish
bar =
case bar of
bish ->
Nothing
"""
|> Review.Test.run rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `bish` is not used."
, details = useOrReplaceDetails
, under = "bish"
}
|> Review.Test.atExactly { start = { row = 9, column = 9 }, end = { row = 9, column = 13 } }
|> Review.Test.whenFixed
"""
module A exposing (..)
foo =
case bar of
bish ->
bish
bar =
case bar of
_ ->
Nothing
"""
]
]
@ -275,7 +351,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `right` is not used."
, details = details
, details = useOrReplaceDetails
, under = "right"
}
|> Review.Test.whenFixed
@ -303,7 +379,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Pattern `_` is not needed."
, details = [ "You should remove it at the location I pointed at." ]
, details = [ "This pattern is redundant and should be removed." ]
, under = "_"
}
|> Review.Test.whenFixed
@ -314,6 +390,52 @@ foo =
(bar) = 1
in
bar
"""
]
, test "should report unused values even if the same name is used in different scopes" <|
\() ->
"""
module A exposing (..)
foo =
case a of
A ->
let
( left, right ) =
tupleValue
in
left + right
B ->
let
( left, right ) =
tupleValue
in
left
"""
|> Review.Test.run rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `right` is not used."
, details = useOrReplaceDetails
, under = "right"
}
|> Review.Test.atExactly { start = { row = 13, column = 21 }, end = { row = 13, column = 26 } }
|> Review.Test.whenFixed
"""
module A exposing (..)
foo =
case a of
A ->
let
( left, right ) =
tupleValue
in
left + right
B ->
let
( left, _ ) =
tupleValue
in
left
"""
]
]
@ -371,7 +493,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Pattern alias `bosh` is not used."
, details = details
, details = useOrRemoveDetails
, under = "bosh"
}
|> Review.Test.whenFixed
@ -396,7 +518,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `bash` is not used."
, details = details
, details = useOrRemoveDetails
, under = "bash"
}
|> Review.Test.whenFixed
@ -421,7 +543,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `bash` is not used."
, details = details
, details = useOrRemoveDetails
, under = "bash"
}
|> Review.Test.whenFixed
@ -434,7 +556,7 @@ foo =
"""
, Review.Test.error
{ message = "Pattern alias `bosh` is not used."
, details = details
, details = useOrRemoveDetails
, under = "bosh"
}
|> Review.Test.whenFixed
@ -458,7 +580,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Pattern `_` is not needed."
, details = [ "You should remove it at the location I pointed at." ]
, details = [ "This pattern is redundant and should be removed." ]
, under = "_"
}
|> Review.Test.whenFixed
@ -484,7 +606,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Pattern alias `bish` is not used."
, details = details
, details = useOrRemoveDetails
, under = "bish"
}
|> Review.Test.whenFixed
@ -516,7 +638,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `first` is not used."
, details = details
, details = useOrReplaceDetails
, under = "first"
}
|> Review.Test.whenFixed
@ -529,7 +651,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `second` is not used."
, details = details
, details = useOrReplaceDetails
, under = "second"
}
|> Review.Test.whenFixed
@ -561,7 +683,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `bish` is not used."
, details = details
, details = useOrReplaceDetails
, under = "bish"
}
|> Review.Test.whenFixed
@ -590,7 +712,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `bish` is not used."
, details = details
, details = useOrReplaceDetails
, under = "bish"
}
|> Review.Test.whenFixed
@ -634,7 +756,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Named pattern is not needed."
, details = [ "You should remove it at the location I pointed at." ]
, details = redundantReplaceDetails
, under = "Singular _"
}
|> Review.Test.whenFixed
@ -649,7 +771,7 @@ foo =
"""
, Review.Test.error
{ message = "Named pattern is not needed."
, details = [ "You should remove it at the location I pointed at." ]
, details = redundantReplaceDetails
, under = "Pair _ _"
}
|> Review.Test.whenFixed
@ -677,7 +799,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Named pattern is not needed."
, details = [ "You should remove it at the location I pointed at." ]
, details = redundantReplaceDetails
, under = "Singular _"
}
|> Review.Test.whenFixed
@ -691,7 +813,7 @@ foo =
"""
, Review.Test.error
{ message = "Named pattern is not needed."
, details = [ "You should remove it at the location I pointed at." ]
, details = redundantReplaceDetails
, under = "Pair _ _"
}
|> Review.Test.whenFixed
@ -724,7 +846,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Values `bish` and `bash` are not used."
, details = [ "You should either use these values somewhere, or remove them at the location I pointed at." ]
, details = [ "You should either use these values somewhere or remove them." ]
, under = "{ bish, bash }"
}
|> Review.Test.whenFixed
@ -753,7 +875,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Values `bish` and `bosh` are not used."
, details = [ "You should either use these values somewhere, or remove them at the location I pointed at." ]
, details = [ "You should either use these values somewhere or remove them." ]
, under = "bish, bash, bosh"
}
|> Review.Test.whenFixed
@ -782,7 +904,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Values `bash` and `bosh` are not used."
, details = [ "You should either use these values somewhere, or remove them at the location I pointed at." ]
, details = [ "You should either use these values somewhere or remove them." ]
, under = "bash, bosh"
}
|> Review.Test.whenFixed
@ -816,7 +938,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `bish` is not used."
, details = details
, details = useOrReplaceDetails
, under = "bish"
}
|> Review.Test.whenFixed
@ -831,7 +953,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `bosh` is not used."
, details = details
, details = useOrReplaceDetails
, under = "bosh"
}
|> Review.Test.whenFixed
@ -860,7 +982,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Tuple pattern is not needed."
, details = [ "You should remove it at the location I pointed at." ]
, details = redundantReplaceDetails
, under = "( _, _ )"
}
|> Review.Test.whenFixed
@ -889,7 +1011,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Tuple pattern is not needed."
, details = [ "You should remove it at the location I pointed at." ]
, details = redundantReplaceDetails
, under = "( _, _, _ )"
}
|> Review.Test.whenFixed
@ -932,7 +1054,7 @@ foo =
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Value `first` is not used."
, details = details
, details = useOrReplaceDetails
, under = "first"
}
|> Review.Test.whenFixed
@ -945,7 +1067,7 @@ foo =
"""
, Review.Test.error
{ message = "Value `rest` is not used."
, details = details
, details = useOrReplaceDetails
, under = "rest"
}
|> Review.Test.whenFixed

View File

@ -0,0 +1,46 @@
module NoUnused.RangeDict exposing (RangeDict, empty, fromList, get, insert, member)
import Dict exposing (Dict)
import Elm.Syntax.Range exposing (Range)
type alias RangeDict v =
Dict String v
empty : RangeDict v
empty =
Dict.empty
insert : Range -> v -> RangeDict v -> RangeDict v
insert range =
Dict.insert (rangeAsString range)
fromList : List ( Range, v ) -> RangeDict v
fromList values =
values
|> List.map (Tuple.mapFirst rangeAsString)
|> Dict.fromList
get : Range -> RangeDict v -> Maybe v
get range =
Dict.get (rangeAsString range)
member : Range -> RangeDict v -> Bool
member range =
Dict.member (rangeAsString range)
rangeAsString : Range -> String
rangeAsString range =
[ range.start.row
, range.start.column
, range.end.row
, range.end.column
]
|> List.map String.fromInt
|> String.join "_"

View File

@ -22,6 +22,7 @@ import Elm.Syntax.Pattern as Pattern exposing (Pattern)
import Elm.Syntax.Range exposing (Range)
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
import NoUnused.NonemptyList as NonemptyList exposing (Nonempty)
import NoUnused.RangeDict as RangeDict exposing (RangeDict)
import Review.Fix as Fix exposing (Fix)
import Review.ModuleNameLookupTable as ModuleNameLookupTable exposing (ModuleNameLookupTable)
import Review.Project.Dependency as Dependency exposing (Dependency)
@ -101,7 +102,7 @@ type alias ModuleContext =
{ lookupTable : ModuleNameLookupTable
, scopes : Nonempty Scope
, inTheDeclarationOf : List String
, declarations : Dict RangeAsString String
, declarations : RangeDict String
, exposesEverything : Bool
, isApplication : Bool
, constructorNameToTypeName : Dict String String
@ -143,10 +144,6 @@ type alias ModuleThatExposesEverything =
}
type alias RangeAsString =
String
type DeclaredModuleType
= ImportedModule
| ModuleAlias { originalNameOfTheImport : String, exposesSomething : Bool }
@ -571,7 +568,7 @@ expressionEnterVisitor node context =
let
newContext : ModuleContext
newContext =
case Dict.get (rangeAsString (Node.range node)) context.declarations of
case RangeDict.get (Node.range node) context.declarations of
Just functionName ->
{ context | inTheDeclarationOf = functionName :: context.inTheDeclarationOf }
@ -643,11 +640,12 @@ expressionEnterVisitorHelp (Node range value) context =
|> List.map (getUsedVariablesFromPattern context)
|> foldUsedTypesAndModules
markAsInTheDeclarationOf : a -> { b | declarations : RangeDict a } -> { b | declarations : RangeDict a }
markAsInTheDeclarationOf name ctx =
{ ctx
| declarations =
Dict.insert
(function.declaration |> Node.value |> .expression |> Node.range |> rangeAsString)
RangeDict.insert
(function.declaration |> Node.value |> .expression |> Node.range)
name
ctx.declarations
}
@ -720,7 +718,7 @@ expressionExitVisitor node context =
let
newContext : ModuleContext
newContext =
if Dict.member (rangeAsString (Node.range node)) context.declarations then
if RangeDict.member (Node.range node) context.declarations then
{ context | inTheDeclarationOf = List.drop 1 context.inTheDeclarationOf }
else
@ -1615,14 +1613,3 @@ comparePosition a b =
_ ->
order
rangeAsString : Range -> RangeAsString
rangeAsString range =
[ range.start.row
, range.start.column
, range.end.row
, range.end.column
]
|> List.map String.fromInt
|> String.join "_"