Report unused import aliases that are named like a type

This commit is contained in:
Jeroen Engels 2019-07-25 10:40:23 +02:00
parent 527ee1c43a
commit 45a10ebb3a
2 changed files with 232 additions and 52 deletions

View File

@ -64,6 +64,8 @@ type alias Context =
{ scopes : Nonempty Scope { scopes : Nonempty Scope
, exposesEverything : Bool , exposesEverything : Bool
, constructorNameToTypeName : Dict String String , constructorNameToTypeName : Dict String String
, declaredModules : Dict String ( VariableType, Range )
, usedModules : Set String
} }
@ -89,12 +91,16 @@ initialContext =
{ scopes = Nonempty.fromElement emptyScope { scopes = Nonempty.fromElement emptyScope
, exposesEverything = False , exposesEverything = False
, constructorNameToTypeName = Dict.empty , constructorNameToTypeName = Dict.empty
, declaredModules = Dict.empty
, usedModules = Set.empty
} }
emptyScope : Scope emptyScope : Scope
emptyScope = emptyScope =
Scope Dict.empty Set.empty { declared = Dict.empty
, used = Set.empty
}
error : VariableType -> Range -> String -> Error error : VariableType -> Range -> String -> Error
@ -249,7 +255,7 @@ expressionVisitor node direction context =
( [], markAsUsed name context ) ( [], markAsUsed name context )
( Rule.OnEnter, FunctionOrValue moduleName name ) -> ( Rule.OnEnter, FunctionOrValue moduleName name ) ->
( [], markAsUsed (getModuleName moduleName) context ) ( [], markModuleAsUsed (getModuleName moduleName) context )
( Rule.OnEnter, OperatorApplication name _ _ _ ) -> ( Rule.OnEnter, OperatorApplication name _ _ _ ) ->
( [], markAsUsed name context ) ( [], markAsUsed name context )
@ -280,16 +286,17 @@ expressionVisitor node direction context =
( Rule.OnExit, CaseExpression { cases } ) -> ( Rule.OnExit, CaseExpression { cases } ) ->
let let
usedVariables : List String usedVariables : { types : List String, modules : List String }
usedVariables = usedVariables =
List.concatMap cases
(\( patternNode, expressionNode ) -> |> List.map
getUsedVariablesFromPattern patternNode (\( patternNode, expressionNode ) ->
) getUsedVariablesFromPattern patternNode
cases )
|> foldUsedTypesAndModules
in in
( [] ( []
, markAllAsUsed usedVariables context , markUsedTypesAndModules usedVariables context
) )
( Rule.OnExit, LetExpression _ ) -> ( Rule.OnExit, LetExpression _ ) ->
@ -308,8 +315,15 @@ expressionVisitor node direction context =
( [], context ) ( [], context )
getUsedVariablesFromPattern : Node Pattern -> List String getUsedVariablesFromPattern : Node Pattern -> { types : List String, modules : List String }
getUsedVariablesFromPattern patternNode = getUsedVariablesFromPattern patternNode =
{ types = getUsedTypesFromPattern patternNode
, modules = getUsedModulesFromPattern patternNode
}
getUsedTypesFromPattern : Node Pattern -> List String
getUsedTypesFromPattern patternNode =
case Node.value patternNode of case Node.value patternNode of
Pattern.AllPattern -> Pattern.AllPattern ->
[] []
@ -333,38 +347,97 @@ getUsedVariablesFromPattern patternNode =
[] []
Pattern.TuplePattern patterns -> Pattern.TuplePattern patterns ->
List.concatMap getUsedVariablesFromPattern patterns List.concatMap getUsedTypesFromPattern patterns
Pattern.RecordPattern _ -> Pattern.RecordPattern _ ->
[] []
Pattern.UnConsPattern pattern1 pattern2 -> Pattern.UnConsPattern pattern1 pattern2 ->
List.concatMap getUsedVariablesFromPattern [ pattern1, pattern2 ] List.concatMap getUsedTypesFromPattern [ pattern1, pattern2 ]
Pattern.ListPattern patterns -> Pattern.ListPattern patterns ->
List.concatMap getUsedVariablesFromPattern patterns List.concatMap getUsedTypesFromPattern patterns
Pattern.VarPattern _ -> Pattern.VarPattern _ ->
[] []
Pattern.NamedPattern qualifiedNameRef patterns -> Pattern.NamedPattern qualifiedNameRef patterns ->
let let
usedVariable : String usedVariable : List String
usedVariable = usedVariable =
case qualifiedNameRef.moduleName of case qualifiedNameRef.moduleName of
[] -> [] ->
qualifiedNameRef.name [ qualifiedNameRef.name ]
moduleName -> moduleName ->
getModuleName moduleName []
in in
usedVariable :: List.concatMap getUsedVariablesFromPattern patterns usedVariable ++ List.concatMap getUsedTypesFromPattern patterns
Pattern.AsPattern pattern alias_ -> Pattern.AsPattern pattern alias_ ->
getUsedVariablesFromPattern pattern getUsedTypesFromPattern pattern
Pattern.ParenthesizedPattern pattern -> Pattern.ParenthesizedPattern pattern ->
getUsedVariablesFromPattern pattern getUsedTypesFromPattern pattern
getUsedModulesFromPattern : Node Pattern -> List String
getUsedModulesFromPattern patternNode =
case Node.value patternNode of
Pattern.AllPattern ->
[]
Pattern.UnitPattern ->
[]
Pattern.CharPattern _ ->
[]
Pattern.StringPattern _ ->
[]
Pattern.IntPattern _ ->
[]
Pattern.HexPattern _ ->
[]
Pattern.FloatPattern _ ->
[]
Pattern.TuplePattern patterns ->
List.concatMap getUsedModulesFromPattern patterns
Pattern.RecordPattern _ ->
[]
Pattern.UnConsPattern pattern1 pattern2 ->
List.concatMap getUsedModulesFromPattern [ pattern1, pattern2 ]
Pattern.ListPattern patterns ->
List.concatMap getUsedModulesFromPattern patterns
Pattern.VarPattern _ ->
[]
Pattern.NamedPattern qualifiedNameRef patterns ->
let
usedVariable : List String
usedVariable =
case qualifiedNameRef.moduleName of
[] ->
[]
moduleName ->
[ getModuleName moduleName ]
in
usedVariable ++ List.concatMap getUsedModulesFromPattern patterns
Pattern.AsPattern pattern alias_ ->
getUsedModulesFromPattern pattern
Pattern.ParenthesizedPattern pattern ->
getUsedModulesFromPattern pattern
declarationVisitor : Node Declaration -> Direction -> Context -> ( List Error, Context ) declarationVisitor : Node Declaration -> Direction -> Context -> ( List Error, Context )
@ -376,27 +449,28 @@ declarationVisitor node direction context =
functionImplementation = functionImplementation =
Node.value function.declaration Node.value function.declaration
namesUsedInSignature : List String namesUsedInSignature : { types : List String, modules : List String }
namesUsedInSignature = namesUsedInSignature =
function.signature function.signature
|> Maybe.map (Node.value >> .typeAnnotation >> collectNamesFromTypeAnnotation) |> Maybe.map (Node.value >> .typeAnnotation >> collectNamesFromTypeAnnotation)
|> Maybe.withDefault [] |> Maybe.withDefault { types = [], modules = [] }
newContext : Context newContext : Context
newContext = newContext =
context context
|> register Variable (Node.range functionImplementation.name) (Node.value functionImplementation.name) |> register Variable (Node.range functionImplementation.name) (Node.value functionImplementation.name)
|> markAllAsUsed namesUsedInSignature |> markUsedTypesAndModules namesUsedInSignature
in in
( [], newContext ) ( [], newContext )
( Rule.OnEnter, CustomTypeDeclaration { name, constructors } ) -> ( Rule.OnEnter, CustomTypeDeclaration { name, constructors } ) ->
let let
variablesFromConstructorArguments : List String variablesFromConstructorArguments : { types : List String, modules : List String }
variablesFromConstructorArguments = variablesFromConstructorArguments =
constructors constructors
|> List.concatMap (Node.value >> .arguments) |> List.concatMap (Node.value >> .arguments)
|> List.concatMap collectNamesFromTypeAnnotation |> List.map collectNamesFromTypeAnnotation
|> foldUsedTypesAndModules
typeName : String typeName : String
typeName = typeName =
@ -412,20 +486,30 @@ declarationVisitor node direction context =
( [] ( []
, { context | constructorNameToTypeName = Dict.union constructorsForType context.constructorNameToTypeName } , { context | constructorNameToTypeName = Dict.union constructorsForType context.constructorNameToTypeName }
|> register Type (Node.range name) (Node.value name) |> register Type (Node.range name) (Node.value name)
|> markAllAsUsed variablesFromConstructorArguments |> markUsedTypesAndModules variablesFromConstructorArguments
) )
( Rule.OnEnter, AliasDeclaration { name, typeAnnotation } ) -> ( Rule.OnEnter, AliasDeclaration { name, typeAnnotation } ) ->
let
namesUsedInTypeAnnotation : { types : List String, modules : List String }
namesUsedInTypeAnnotation =
collectNamesFromTypeAnnotation typeAnnotation
in
( [] ( []
, context , context
|> register Type (Node.range name) (Node.value name) |> register Type (Node.range name) (Node.value name)
|> markAllAsUsed (collectNamesFromTypeAnnotation typeAnnotation) |> markUsedTypesAndModules namesUsedInTypeAnnotation
) )
( Rule.OnEnter, PortDeclaration { name, typeAnnotation } ) -> ( Rule.OnEnter, PortDeclaration { name, typeAnnotation } ) ->
let
namesUsedInTypeAnnotation : { types : List String, modules : List String }
namesUsedInTypeAnnotation =
collectNamesFromTypeAnnotation typeAnnotation
in
( [] ( []
, context , context
|> markAllAsUsed (collectNamesFromTypeAnnotation typeAnnotation) |> markUsedTypesAndModules namesUsedInTypeAnnotation
|> register Port (Node.range name) (Node.value name) |> register Port (Node.range name) (Node.value name)
) )
@ -439,6 +523,18 @@ declarationVisitor node direction context =
( [], context ) ( [], context )
foldUsedTypesAndModules : List { types : List String, modules : List String } -> { types : List String, modules : List String }
foldUsedTypesAndModules =
List.foldl (\a b -> { types = a.types ++ b.types, modules = a.modules ++ b.modules }) { types = [], modules = [] }
markUsedTypesAndModules : { types : List String, modules : List String } -> Context -> Context
markUsedTypesAndModules { types, modules } context =
context
|> markAllAsUsed types
|> markAllModulesAsUsed modules
finalEvaluation : Context -> List Error finalEvaluation : Context -> List Error
finalEvaluation context = finalEvaluation context =
if context.exposesEverything then if context.exposesEverything then
@ -460,10 +556,20 @@ finalEvaluation context =
newRootScope : Scope newRootScope : Scope
newRootScope = newRootScope =
{ rootScope | used = Set.union namesOfCustomTypesUsedByCallingAConstructor rootScope.used } { rootScope | used = Set.union namesOfCustomTypesUsedByCallingAConstructor rootScope.used }
moduleErrors : List Error
moduleErrors =
context.declaredModules
|> Dict.filter (\key _ -> not <| Set.member key context.usedModules)
|> Dict.toList
|> List.map (\( key, ( variableType, range ) ) -> error variableType range key)
in in
newRootScope List.concat
|> makeReport [ newRootScope
|> Tuple.first |> makeReport
|> Tuple.first
, moduleErrors
]
registerFunction : Function -> Context -> Context registerFunction : Function -> Context -> Context
@ -473,18 +579,18 @@ registerFunction function context =
declaration = declaration =
Node.value function.declaration Node.value function.declaration
namesUsedInSignature : List String namesUsedInSignature : { types : List String, modules : List String }
namesUsedInSignature = namesUsedInSignature =
case Maybe.map Node.value function.signature of case Maybe.map Node.value function.signature of
Just signature -> Just signature ->
collectNamesFromTypeAnnotation signature.typeAnnotation collectNamesFromTypeAnnotation signature.typeAnnotation
Nothing -> Nothing ->
[] { types = [], modules = [] }
in in
context context
|> register Variable (Node.range declaration.name) (Node.value declaration.name) |> register Variable (Node.range declaration.name) (Node.value declaration.name)
|> markAllAsUsed namesUsedInSignature |> markUsedTypesAndModules namesUsedInSignature
collectFromExposing : Exposing -> List ( VariableType, Range, String ) collectFromExposing : Exposing -> List ( VariableType, Range, String )
@ -517,38 +623,85 @@ collectFromExposing exposing_ =
list list
collectNamesFromTypeAnnotation : Node TypeAnnotation -> List String collectNamesFromTypeAnnotation : Node TypeAnnotation -> { types : List String, modules : List String }
collectNamesFromTypeAnnotation node = collectNamesFromTypeAnnotation node =
{ types = collectTypesFromTypeAnnotation node
, modules = collectModuleNamesFromTypeAnnotation node
}
collectTypesFromTypeAnnotation : Node TypeAnnotation -> List String
collectTypesFromTypeAnnotation node =
case Node.value node of case Node.value node of
FunctionTypeAnnotation a b -> FunctionTypeAnnotation a b ->
collectNamesFromTypeAnnotation a ++ collectNamesFromTypeAnnotation b collectTypesFromTypeAnnotation a ++ collectTypesFromTypeAnnotation b
Typed nameNode params -> Typed nameNode params ->
let let
name : String name : List String
name = name =
case Node.value nameNode of case Node.value nameNode of
( [], str ) -> ( [], str ) ->
str [ str ]
( moduleName, _ ) -> ( moduleName, _ ) ->
getModuleName moduleName []
in in
name :: List.concatMap collectNamesFromTypeAnnotation params name ++ List.concatMap collectTypesFromTypeAnnotation params
Record list -> Record list ->
list list
|> List.map (Node.value >> Tuple.second) |> List.map (Node.value >> Tuple.second)
|> List.concatMap collectNamesFromTypeAnnotation |> List.concatMap collectTypesFromTypeAnnotation
GenericRecord name list -> GenericRecord name list ->
list list
|> Node.value |> Node.value
|> List.map (Node.value >> Tuple.second) |> List.map (Node.value >> Tuple.second)
|> List.concatMap collectNamesFromTypeAnnotation |> List.concatMap collectTypesFromTypeAnnotation
Tupled list -> Tupled list ->
List.concatMap collectNamesFromTypeAnnotation list List.concatMap collectTypesFromTypeAnnotation list
GenericType _ ->
[]
Unit ->
[]
collectModuleNamesFromTypeAnnotation : Node TypeAnnotation -> List String
collectModuleNamesFromTypeAnnotation node =
case Node.value node of
FunctionTypeAnnotation a b ->
collectModuleNamesFromTypeAnnotation a ++ collectModuleNamesFromTypeAnnotation b
Typed nameNode params ->
let
name : List String
name =
case Node.value nameNode of
( [], str ) ->
[]
( moduleName, _ ) ->
[ getModuleName moduleName ]
in
name ++ List.concatMap collectModuleNamesFromTypeAnnotation params
Record list ->
list
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectModuleNamesFromTypeAnnotation
GenericRecord name list ->
list
|> Node.value
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectModuleNamesFromTypeAnnotation
Tupled list ->
List.concatMap collectModuleNamesFromTypeAnnotation list
GenericType _ -> GenericType _ ->
[] []
@ -559,16 +712,20 @@ collectNamesFromTypeAnnotation node =
register : VariableType -> Range -> String -> Context -> Context register : VariableType -> Range -> String -> Context -> Context
register variableType range name context = register variableType range name context =
let if variableType == ImportedModule || variableType == ModuleAlias then
scopes : Nonempty Scope { context | declaredModules = Dict.insert name ( variableType, range ) context.declaredModules }
scopes =
mapNonemptyHead else
(\scope -> let
{ scope | declared = Dict.insert name ( variableType, range ) scope.declared } scopes : Nonempty Scope
) scopes =
context.scopes mapNonemptyHead
in (\scope ->
{ context | scopes = scopes } { scope | declared = Dict.insert name ( variableType, range ) scope.declared }
)
context.scopes
in
{ context | scopes = scopes }
markAllAsUsed : List String -> Context -> Context markAllAsUsed : List String -> Context -> Context
@ -590,6 +747,16 @@ markAsUsed name context =
{ context | scopes = scopes } { context | scopes = scopes }
markAllModulesAsUsed : List String -> Context -> Context
markAllModulesAsUsed names context =
{ context | usedModules = Set.union (Set.fromList names) context.usedModules }
markModuleAsUsed : String -> Context -> Context
markModuleAsUsed name context =
{ context | usedModules = Set.insert name context.usedModules }
getModuleName : List String -> String getModuleName : List String -> String
getModuleName name = getModuleName name =
String.join "." name String.join "." name

View File

@ -357,6 +357,19 @@ a = ()"""
} }
|> Lint.Test.atExactly { start = { row = 2, column = 34 }, end = { row = 2, column = 38 } } |> Lint.Test.atExactly { start = { row = 2, column = 34 }, end = { row = 2, column = 38 } }
] ]
, test "should report unused import alias even if it is named like an exposed type" <|
\() ->
testRule """module A exposing (a)
import Html.Styled as Html exposing (Html)
a : Html
a = ()"""
|> Lint.Test.expectErrors
[ Lint.Test.error
{ message = "Module alias `Html` is not used"
, under = "Html"
}
|> Lint.Test.atExactly { start = { row = 2, column = 23 }, end = { row = 2, column = 27 } }
]
, test "should not report import that exposes a used exposed type" <| , test "should not report import that exposes a used exposed type" <|
\() -> \() ->
testRule """module A exposing (a) testRule """module A exposing (a)