Backport work from review-unused

This commit is contained in:
Jeroen Engels 2020-05-14 21:01:53 +02:00
parent 7d2a08d335
commit f1e3e725a8
4 changed files with 203 additions and 65 deletions

View File

@ -277,20 +277,29 @@ exposedElements nodes =
(\node -> (\node ->
case Node.value node of case Node.value node of
Exposing.FunctionExpose name -> Exposing.FunctionExpose name ->
Just <| ( name, { range = Node.range node, exposedElement = Function } ) Just ( name, { range = untilEndOfVariable name (Node.range node), exposedElement = Function } )
Exposing.TypeOrAliasExpose name -> Exposing.TypeOrAliasExpose name ->
Just <| ( name, { range = Node.range node, exposedElement = TypeOrTypeAlias } ) Just ( name, { range = untilEndOfVariable name (Node.range node), exposedElement = TypeOrTypeAlias } )
Exposing.TypeExpose { name } -> Exposing.TypeExpose { name } ->
Just <| ( name, { range = Node.range node, exposedElement = ExposedType } ) Just ( name, { range = untilEndOfVariable name (Node.range node), exposedElement = ExposedType } )
Exposing.InfixExpose name -> Exposing.InfixExpose _ ->
Nothing Nothing
) )
|> Dict.fromList |> Dict.fromList
untilEndOfVariable : String -> Range -> Range
untilEndOfVariable name range =
if range.start.row == range.end.row then
range
else
{ range | end = { row = range.start.row, column = range.start.column + String.length name } }
-- DECLARATION LIST VISITOR -- DECLARATION LIST VISITOR

View File

@ -592,6 +592,34 @@ type alias B = A.OtherType
] ]
) )
] ]
, test "should report the correct range when exports are on multiple lines" <|
\() ->
[ """module A
exposing ( Card
, Link
, init
, toElement
)
type Card = Card
type Link = Link
init = 1
""", """
module Exposed exposing (..)
import A
a = A.Card A.init A.toElement
""" ]
|> Review.Test.runOnModulesWithProjectData package_ rule
|> Review.Test.expectErrorsForModules
[ ( "A"
, [ Review.Test.error
{ message = "Exposed type or type alias `Link` is never used outside this module."
, details = details
, under = "Link"
}
|> Review.Test.atExactly { start = { row = 3, column = 7 }, end = { row = 3, column = 11 } }
]
)
]
] ]

View File

@ -10,15 +10,15 @@ module NoUnused.Variables exposing (rule)
-} -}
import Dict exposing (Dict) import Dict exposing (Dict)
import Elm.Syntax.Declaration exposing (Declaration(..)) import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..)) import Elm.Syntax.Exposing as Exposing exposing (Exposing)
import Elm.Syntax.Expression exposing (Expression(..), Function, FunctionImplementation, LetDeclaration(..)) import Elm.Syntax.Expression as Expression exposing (Expression, Function, FunctionImplementation)
import Elm.Syntax.Import exposing (Import) import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.Module as Module exposing (Module(..)) import Elm.Syntax.Module as Module exposing (Module)
import Elm.Syntax.Node as Node exposing (Node(..)) import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern as Pattern exposing (Pattern) import Elm.Syntax.Pattern as Pattern exposing (Pattern)
import Elm.Syntax.Range exposing (Range) import Elm.Syntax.Range exposing (Range)
import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation(..)) import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
import NoUnused.NonemptyList as NonemptyList exposing (Nonempty) import NoUnused.NonemptyList as NonemptyList exposing (Nonempty)
import Review.Fix as Fix exposing (Fix) import Review.Fix as Fix exposing (Fix)
import Review.Rule as Rule exposing (Direction, Error, Rule) import Review.Rule as Rule exposing (Direction, Error, Rule)
@ -27,10 +27,6 @@ import Set exposing (Set)
{-| Report variables or types that are declared or imported but never used. {-| Report variables or types that are declared or imported but never used.
**NOTE**: Since `elm-review` only works in the scope of a single file, this rule
will not report variables that are exposed but not used anywhere in the project.
If you wish those to be reported, check out [`elm-xref`](https://github.com/zwilias/elm-xref).
config = config =
[ NoUnused.Variables.rule [ NoUnused.Variables.rule
] ]
@ -232,25 +228,25 @@ fix declaredModules { variableType, rangeToRemove } =
moduleDefinitionVisitor : Node Module -> Context -> ( List nothing, Context ) moduleDefinitionVisitor : Node Module -> Context -> ( List nothing, Context )
moduleDefinitionVisitor (Node _ moduleNode) context = moduleDefinitionVisitor (Node _ moduleNode) context =
case Module.exposingList moduleNode of case Module.exposingList moduleNode of
All _ -> Exposing.All _ ->
( [], { context | exposesEverything = True } ) ( [], { context | exposesEverything = True } )
Explicit list -> Exposing.Explicit list ->
let let
names = names =
List.filterMap List.filterMap
(\(Node _ node) -> (\(Node _ node) ->
case node of case node of
FunctionExpose name -> Exposing.FunctionExpose name ->
Just name Just name
TypeOrAliasExpose name -> Exposing.TypeOrAliasExpose name ->
Just name Just name
TypeExpose { name } -> Exposing.TypeExpose { name } ->
Just name Just name
InfixExpose name -> Exposing.InfixExpose name ->
-- Just name -- Just name
Nothing Nothing
) )
@ -260,13 +256,33 @@ moduleDefinitionVisitor (Node _ moduleNode) context =
importVisitor : Node Import -> Context -> ( List (Error {}), Context ) importVisitor : Node Import -> Context -> ( List (Error {}), Context )
importVisitor ((Node _ { exposingList, moduleAlias, moduleName }) as node) context = importVisitor ((Node _ import_) as node) context =
case exposingList of let
errors : List (Error {})
errors =
case import_.moduleAlias of
Just moduleAlias ->
if Node.value moduleAlias == Node.value import_.moduleName then
[ Rule.errorWithFix
{ message = "Module `Html` is aliased as `Html`"
, details = [ "The alias is the same as the module name, and brings no useful value" ]
}
(Node.range moduleAlias)
[ Fix.removeRange <| moduleAliasRange node (Node.range moduleAlias) ]
]
else
[]
Nothing -> Nothing ->
( [], registerModuleNameOrAlias node context ) []
in
case import_.exposingList of
Nothing ->
( errors, registerModuleNameOrAlias node context )
Just declaredImports -> Just declaredImports ->
( [] ( errors
, List.foldl , List.foldl
(\( name, variableInfo ) context_ -> register variableInfo name context_) (\( name, variableInfo ) context_ -> register variableInfo name context_)
(registerModuleAlias node context) (registerModuleAlias node context)
@ -324,19 +340,19 @@ moduleAliasRange (Node _ { moduleName }) range =
expressionVisitor : Node Expression -> Direction -> Context -> ( List (Error {}), Context ) expressionVisitor : Node Expression -> Direction -> Context -> ( List (Error {}), Context )
expressionVisitor (Node range value) direction context = expressionVisitor (Node range value) direction context =
case ( direction, value ) of case ( direction, value ) of
( Rule.OnEnter, FunctionOrValue [] name ) -> ( Rule.OnEnter, Expression.FunctionOrValue [] name ) ->
( [], markAsUsed name context ) ( [], markAsUsed name context )
( Rule.OnEnter, FunctionOrValue moduleName name ) -> ( Rule.OnEnter, Expression.FunctionOrValue moduleName name ) ->
( [], markModuleAsUsed (getModuleName moduleName) context ) ( [], markModuleAsUsed (getModuleName moduleName) context )
( Rule.OnEnter, OperatorApplication name _ _ _ ) -> ( Rule.OnEnter, Expression.OperatorApplication name _ _ _ ) ->
( [], markAsUsed name context ) ( [], markAsUsed name context )
( Rule.OnEnter, PrefixOperator name ) -> ( Rule.OnEnter, Expression.PrefixOperator name ) ->
( [], markAsUsed name context ) ( [], markAsUsed name context )
( Rule.OnEnter, LetExpression { declarations, expression } ) -> ( Rule.OnEnter, Expression.LetExpression { declarations, expression } ) ->
let let
letBlockContext : LetBlockContext letBlockContext : LetBlockContext
letBlockContext = letBlockContext =
@ -351,7 +367,7 @@ expressionVisitor (Node range value) direction context =
List.foldl List.foldl
(\declaration context_ -> (\declaration context_ ->
case Node.value declaration of case Node.value declaration of
LetFunction function -> Expression.LetFunction function ->
let let
namesUsedInArgumentPatterns : { types : List String, modules : List String } namesUsedInArgumentPatterns : { types : List String, modules : List String }
namesUsedInArgumentPatterns = namesUsedInArgumentPatterns =
@ -365,7 +381,7 @@ expressionVisitor (Node range value) direction context =
|> registerFunction letBlockContext function |> registerFunction letBlockContext function
|> markUsedTypesAndModules namesUsedInArgumentPatterns |> markUsedTypesAndModules namesUsedInArgumentPatterns
LetDestructuring pattern _ -> Expression.LetDestructuring pattern _ ->
context_ context_
) )
{ context | scopes = NonemptyList.cons emptyScope context.scopes } { context | scopes = NonemptyList.cons emptyScope context.scopes }
@ -373,7 +389,7 @@ expressionVisitor (Node range value) direction context =
in in
( [], newContext ) ( [], newContext )
( Rule.OnEnter, LambdaExpression { args } ) -> ( Rule.OnEnter, Expression.LambdaExpression { args } ) ->
let let
namesUsedInArgumentPatterns : { types : List String, modules : List String } namesUsedInArgumentPatterns : { types : List String, modules : List String }
namesUsedInArgumentPatterns = namesUsedInArgumentPatterns =
@ -383,10 +399,10 @@ expressionVisitor (Node range value) direction context =
in in
( [], markUsedTypesAndModules namesUsedInArgumentPatterns context ) ( [], markUsedTypesAndModules namesUsedInArgumentPatterns context )
( Rule.OnExit, RecordUpdateExpression expr _ ) -> ( Rule.OnExit, Expression.RecordUpdateExpression expr _ ) ->
( [], markAsUsed (Node.value expr) context ) ( [], markAsUsed (Node.value expr) context )
( Rule.OnExit, CaseExpression { cases } ) -> ( Rule.OnExit, Expression.CaseExpression { cases } ) ->
let let
usedVariables : { types : List String, modules : List String } usedVariables : { types : List String, modules : List String }
usedVariables = usedVariables =
@ -401,7 +417,7 @@ expressionVisitor (Node range value) direction context =
, markUsedTypesAndModules usedVariables context , markUsedTypesAndModules usedVariables context
) )
( Rule.OnExit, LetExpression _ ) -> ( Rule.OnExit, Expression.LetExpression _ ) ->
let let
( errors, remainingUsed ) = ( errors, remainingUsed ) =
makeReport (NonemptyList.head context.scopes) makeReport (NonemptyList.head context.scopes)
@ -545,7 +561,7 @@ getUsedModulesFromPattern patternNode =
declarationVisitor : Node Declaration -> Direction -> Context -> ( List nothing, Context ) declarationVisitor : Node Declaration -> Direction -> Context -> ( List nothing, Context )
declarationVisitor node direction context = declarationVisitor node direction context =
case ( direction, Node.value node ) of case ( direction, Node.value node ) of
( Rule.OnEnter, FunctionDeclaration function ) -> ( Rule.OnEnter, Declaration.FunctionDeclaration function ) ->
let let
functionImplementation : FunctionImplementation functionImplementation : FunctionImplementation
functionImplementation = functionImplementation =
@ -579,7 +595,7 @@ declarationVisitor node direction context =
in in
( [], newContext ) ( [], newContext )
( Rule.OnEnter, CustomTypeDeclaration { name, documentation, constructors } ) -> ( Rule.OnEnter, Declaration.CustomTypeDeclaration { name, documentation, constructors } ) ->
let let
variablesFromConstructorArguments : { types : List String, modules : List String } variablesFromConstructorArguments : { types : List String, modules : List String }
variablesFromConstructorArguments = variablesFromConstructorArguments =
@ -610,7 +626,7 @@ declarationVisitor node direction context =
|> markUsedTypesAndModules variablesFromConstructorArguments |> markUsedTypesAndModules variablesFromConstructorArguments
) )
( Rule.OnEnter, AliasDeclaration { name, typeAnnotation, documentation } ) -> ( Rule.OnEnter, Declaration.AliasDeclaration { name, typeAnnotation, documentation } ) ->
let let
namesUsedInTypeAnnotation : { types : List String, modules : List String } namesUsedInTypeAnnotation : { types : List String, modules : List String }
namesUsedInTypeAnnotation = namesUsedInTypeAnnotation =
@ -627,7 +643,7 @@ declarationVisitor node direction context =
|> markUsedTypesAndModules namesUsedInTypeAnnotation |> markUsedTypesAndModules namesUsedInTypeAnnotation
) )
( Rule.OnEnter, PortDeclaration { name, typeAnnotation } ) -> ( Rule.OnEnter, Declaration.PortDeclaration { name, typeAnnotation } ) ->
let let
namesUsedInTypeAnnotation : { types : List String, modules : List String } namesUsedInTypeAnnotation : { types : List String, modules : List String }
namesUsedInTypeAnnotation = namesUsedInTypeAnnotation =
@ -644,10 +660,10 @@ declarationVisitor node direction context =
(Node.value name) (Node.value name)
) )
( Rule.OnEnter, InfixDeclaration _ ) -> ( Rule.OnEnter, Declaration.InfixDeclaration _ ) ->
( [], context ) ( [], context )
( Rule.OnEnter, Destructuring _ _ ) -> ( Rule.OnEnter, Declaration.Destructuring _ _ ) ->
( [], context ) ( [], context )
( Rule.OnExit, _ ) -> ( Rule.OnExit, _ ) ->
@ -764,10 +780,10 @@ registerFunction letBlockContext function context =
collectFromExposing : Node Exposing -> List ( String, VariableInfo ) collectFromExposing : Node Exposing -> List ( String, VariableInfo )
collectFromExposing exposingNode = collectFromExposing exposingNode =
case Node.value exposingNode of case Node.value exposingNode of
All _ -> Exposing.All _ ->
[] []
Explicit list -> Exposing.Explicit list ->
let let
listWithPreviousRange : List (Maybe Range) listWithPreviousRange : List (Maybe Range)
listWithPreviousRange = listWithPreviousRange =
@ -807,27 +823,60 @@ collectFromExposing exposingNode =
{ range | start = previousRange.end } { range | start = previousRange.end }
in in
case value of case value of
FunctionExpose name -> Exposing.FunctionExpose name ->
Just ( name, { variableType = ImportedItem ImportedVariable, under = range, rangeToRemove = rangeToRemove } ) Just
( name
, { variableType = ImportedItem ImportedVariable
, under = untilEndOfVariable name range
, rangeToRemove = rangeToRemove
}
)
InfixExpose name -> Exposing.InfixExpose name ->
Just ( name, { variableType = ImportedItem ImportedOperator, under = range, rangeToRemove = rangeToRemove } ) Just
( name
, { variableType = ImportedItem ImportedOperator
, under = untilEndOfVariable name range
, rangeToRemove = rangeToRemove
}
)
TypeOrAliasExpose name -> Exposing.TypeOrAliasExpose name ->
Just ( name, { variableType = ImportedItem ImportedType, under = range, rangeToRemove = rangeToRemove } ) Just
( name
, { variableType = ImportedItem ImportedType
, under = untilEndOfVariable name range
, rangeToRemove = rangeToRemove
}
)
TypeExpose { name, open } -> Exposing.TypeExpose { name, open } ->
case open of case open of
Just openRange -> Just openRange ->
-- TODO Change this behavior once we know the contents of the open range, using dependencies or the interfaces of the other modules -- TODO Change this behavior once we know the contents of the open range, using dependencies or the interfaces of the other modules
Nothing Nothing
Nothing -> Nothing ->
Just ( name, { variableType = ImportedItem ImportedType, under = range, rangeToRemove = rangeToRemove } ) Just
( name
, { variableType = ImportedItem ImportedType
, under = range
, rangeToRemove = rangeToRemove
}
)
) )
|> List.filterMap identity |> List.filterMap identity
untilEndOfVariable : String -> Range -> Range
untilEndOfVariable name range =
if range.start.row == range.end.row then
range
else
{ range | end = { row = range.start.row, column = range.start.column + String.length name } }
collectNamesFromTypeAnnotation : Node TypeAnnotation -> { types : List String, modules : List String } collectNamesFromTypeAnnotation : Node TypeAnnotation -> { types : List String, modules : List String }
collectNamesFromTypeAnnotation node = collectNamesFromTypeAnnotation node =
{ types = collectTypesFromTypeAnnotation node { types = collectTypesFromTypeAnnotation node
@ -838,10 +887,10 @@ collectNamesFromTypeAnnotation node =
collectTypesFromTypeAnnotation : Node TypeAnnotation -> List String collectTypesFromTypeAnnotation : Node TypeAnnotation -> List String
collectTypesFromTypeAnnotation node = collectTypesFromTypeAnnotation node =
case Node.value node of case Node.value node of
FunctionTypeAnnotation a b -> TypeAnnotation.FunctionTypeAnnotation a b ->
collectTypesFromTypeAnnotation a ++ collectTypesFromTypeAnnotation b collectTypesFromTypeAnnotation a ++ collectTypesFromTypeAnnotation b
Typed nameNode params -> TypeAnnotation.Typed nameNode params ->
let let
name : List String name : List String
name = name =
@ -854,34 +903,34 @@ collectTypesFromTypeAnnotation node =
in in
name ++ List.concatMap collectTypesFromTypeAnnotation params name ++ List.concatMap collectTypesFromTypeAnnotation params
Record list -> TypeAnnotation.Record list ->
list list
|> List.map (Node.value >> Tuple.second) |> List.map (Node.value >> Tuple.second)
|> List.concatMap collectTypesFromTypeAnnotation |> List.concatMap collectTypesFromTypeAnnotation
GenericRecord name list -> TypeAnnotation.GenericRecord name list ->
list list
|> Node.value |> Node.value
|> List.map (Node.value >> Tuple.second) |> List.map (Node.value >> Tuple.second)
|> List.concatMap collectTypesFromTypeAnnotation |> List.concatMap collectTypesFromTypeAnnotation
Tupled list -> TypeAnnotation.Tupled list ->
List.concatMap collectTypesFromTypeAnnotation list List.concatMap collectTypesFromTypeAnnotation list
GenericType _ -> TypeAnnotation.GenericType _ ->
[] []
Unit -> TypeAnnotation.Unit ->
[] []
collectModuleNamesFromTypeAnnotation : Node TypeAnnotation -> List String collectModuleNamesFromTypeAnnotation : Node TypeAnnotation -> List String
collectModuleNamesFromTypeAnnotation node = collectModuleNamesFromTypeAnnotation node =
case Node.value node of case Node.value node of
FunctionTypeAnnotation a b -> TypeAnnotation.FunctionTypeAnnotation a b ->
collectModuleNamesFromTypeAnnotation a ++ collectModuleNamesFromTypeAnnotation b collectModuleNamesFromTypeAnnotation a ++ collectModuleNamesFromTypeAnnotation b
Typed nameNode params -> TypeAnnotation.Typed nameNode params ->
let let
name : List String name : List String
name = name =
@ -894,24 +943,24 @@ collectModuleNamesFromTypeAnnotation node =
in in
name ++ List.concatMap collectModuleNamesFromTypeAnnotation params name ++ List.concatMap collectModuleNamesFromTypeAnnotation params
Record list -> TypeAnnotation.Record list ->
list list
|> List.map (Node.value >> Tuple.second) |> List.map (Node.value >> Tuple.second)
|> List.concatMap collectModuleNamesFromTypeAnnotation |> List.concatMap collectModuleNamesFromTypeAnnotation
GenericRecord name list -> TypeAnnotation.GenericRecord name list ->
list list
|> Node.value |> Node.value
|> List.map (Node.value >> Tuple.second) |> List.map (Node.value >> Tuple.second)
|> List.concatMap collectModuleNamesFromTypeAnnotation |> List.concatMap collectModuleNamesFromTypeAnnotation
Tupled list -> TypeAnnotation.Tupled list ->
List.concatMap collectModuleNamesFromTypeAnnotation list List.concatMap collectModuleNamesFromTypeAnnotation list
GenericType _ -> TypeAnnotation.GenericType _ ->
[] []
Unit -> TypeAnnotation.Unit ->
[] []

View File

@ -555,7 +555,7 @@ import Foo
[ Review.Test.error [ Review.Test.error
{ message = "Imported type `C` is not used" { message = "Imported type `C` is not used"
, details = details , details = details
, under = "C\n ," , under = "C"
} }
|> Review.Test.whenFixed """module SomeModule exposing (d) |> Review.Test.whenFixed """module SomeModule exposing (d)
import Foo import Foo
@ -591,6 +591,41 @@ import Foo
, a , a
)""" )"""
] ]
, test "should report unused imported functions (multiple imports on several lines, function first)" <|
\() ->
"""module SomeModule exposing (d)
import Foo
exposing
( a
, b
)
d = 1"""
|> Review.Test.run rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Imported variable `a` is not used"
, details = details
, under = "a"
}
|> Review.Test.whenFixed
"""module SomeModule exposing (d)
import Foo
exposing
( b
)
d = 1"""
, Review.Test.error
{ message = "Imported variable `b` is not used"
, details = details
, under = "b"
}
|> Review.Test.whenFixed """module SomeModule exposing (d)
import Foo
exposing
( a
)
d = 1"""
]
, test "should report unused operator import" <| , test "should report unused operator import" <|
\() -> \() ->
"""module SomeModule exposing (a) """module SomeModule exposing (a)
@ -738,6 +773,23 @@ import Html.Styled exposing (Html)
a : Html a : Html
a = ()""" a = ()"""
] ]
, test "should report import alias if the name is the same thing as the module name" <|
\() ->
"""module SomeModule exposing (a)
import Html as Html
a = Html.div"""
|> Review.Test.run rule
|> Review.Test.expectErrors
[ Review.Test.error
{ message = "Module `Html` is aliased as `Html`"
, details = [ "The alias is the same as the module name, and brings no useful value" ]
, under = "Html"
}
|> Review.Test.atExactly { start = { row = 2, column = 16 }, end = { row = 2, column = 20 } }
|> Review.Test.whenFixed """module SomeModule exposing (a)
import Html
a = Html.div"""
]
, test "should not report import that exposes a used exposed type" <| , test "should not report import that exposes a used exposed type" <|
\() -> \() ->
"""module SomeModule exposing (a) """module SomeModule exposing (a)