elm-review/src/Lint/Rule/NoUnusedVariables.elm

464 lines
12 KiB
Elm
Raw Normal View History

2018-11-22 21:19:19 +03:00
module Lint.Rule.NoUnusedVariables exposing (rule)
{-|
@docs rule
# Fail
a n =
n + 1
b =
a 2
# Success
a n =
n + 1
-}
import Dict exposing (Dict)
import Elm.Syntax.Declaration exposing (Declaration(..))
import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..))
import Elm.Syntax.Expression exposing (Expression(..), Function, LetDeclaration(..))
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.Module as Module exposing (Module(..))
import Elm.Syntax.Node exposing (Node, range, value)
import Elm.Syntax.Range exposing (Range)
import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation(..))
2019-06-16 21:39:52 +03:00
import Lint.Direction as Direction exposing (Direction)
import Lint.Error as Error exposing (Error)
2019-06-24 01:41:56 +03:00
import Lint.Rule as Rule exposing (Rule)
2018-11-22 21:19:19 +03:00
import List.Nonempty as Nonempty exposing (Nonempty)
import Set exposing (Set)
2018-11-26 13:22:43 +03:00
{-| Reports variables that are declared but never used.
rules =
[ NoUnusedVariables.rule
]
-}
rule : Rule
2019-06-15 22:14:40 +03:00
rule =
2019-06-24 01:32:27 +03:00
Rule.newRuleSchema "NoUnusedVariables"
|> Rule.withInitialContext initialContext
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|> Rule.withImportVisitor importVisitor
|> Rule.withExpressionVisitor expressionVisitor
|> Rule.withDeclarationVisitor declarationVisitor
|> Rule.withFinalEvaluation finalEvaluation
|> Rule.fromSchema
type alias Context =
{ scopes : Nonempty Scope
, exposesEverything : Bool
}
type alias Scope =
{ declared : Dict String ( VariableType, Range )
, used : Set String
}
2018-11-26 13:22:43 +03:00
2018-11-26 21:16:04 +03:00
type VariableType
2018-11-26 20:39:46 +03:00
= Variable
| ImportedModule
2018-11-26 21:16:04 +03:00
| ImportedVariable
| ImportedType
| ImportedOperator
2018-11-26 20:39:46 +03:00
| ModuleAlias
| Type
2018-11-26 21:09:18 +03:00
| Port
2018-11-26 20:39:46 +03:00
2019-06-24 01:32:27 +03:00
initialContext : Context
initialContext =
{ scopes = Nonempty.fromElement emptyScope
, exposesEverything = False
2018-11-22 21:19:19 +03:00
}
emptyScope : Scope
emptyScope =
Scope Dict.empty Set.empty
2018-11-26 21:16:04 +03:00
error : VariableType -> Range -> String -> Error
2018-11-26 20:39:46 +03:00
error variableType range_ name =
Error.create
2018-11-26 21:09:18 +03:00
(variableTypeToString variableType ++ " `" ++ name ++ "` is not used" ++ variableTypeWarning variableType)
2018-11-26 20:39:46 +03:00
range_
2018-11-26 21:16:04 +03:00
variableTypeToString : VariableType -> String
2018-11-26 20:39:46 +03:00
variableTypeToString value =
case value of
Variable ->
"Variable"
ImportedModule ->
"Imported module"
2018-11-26 21:16:04 +03:00
ImportedVariable ->
"Imported variable"
ImportedType ->
"Imported type"
ImportedOperator ->
"Imported operator"
2018-11-26 20:39:46 +03:00
ModuleAlias ->
"Module alias"
Type ->
"Type"
2018-11-22 21:19:19 +03:00
2018-11-26 21:09:18 +03:00
Port ->
"Port"
2018-11-26 21:16:04 +03:00
variableTypeWarning : VariableType -> String
2018-11-26 21:09:18 +03:00
variableTypeWarning value =
case value of
Variable ->
""
ImportedModule ->
""
2018-11-26 21:16:04 +03:00
ImportedVariable ->
""
ImportedType ->
""
ImportedOperator ->
""
2018-11-26 21:09:18 +03:00
ModuleAlias ->
""
Type ->
""
Port ->
" (Warning: Removing this port may break your application if it is used in the JS code)"
2018-11-22 21:19:19 +03:00
2019-06-24 01:32:27 +03:00
moduleDefinitionVisitor : Node Module -> Context -> ( List Error, Context )
moduleDefinitionVisitor moduleNode context =
2018-11-22 21:19:19 +03:00
case Module.exposingList (value moduleNode) of
All _ ->
( [], { context | exposesEverything = True } )
2018-11-22 21:19:19 +03:00
Explicit list ->
let
names =
List.filterMap
(\node ->
case value node of
FunctionExpose name ->
Just name
TypeOrAliasExpose name ->
Just name
TypeExpose { name } ->
Just name
InfixExpose name ->
-- Just name
Nothing
)
list
in
( [], markAllAsUsed names context )
2018-11-22 21:19:19 +03:00
2019-06-24 01:32:27 +03:00
importVisitor : Node Import -> Context -> ( List Error, Context )
importVisitor node context =
2018-11-22 21:19:19 +03:00
let
2018-11-26 13:22:43 +03:00
exposed =
node
|> value
2018-11-22 21:19:19 +03:00
|> .exposingList
in
2018-11-26 13:22:43 +03:00
case Maybe.map value exposed of
2018-11-26 20:12:36 +03:00
Nothing ->
let
2018-11-26 20:39:46 +03:00
( variableType, moduleName ) =
case value node |> .moduleAlias of
Just moduleAlias ->
( ModuleAlias, moduleAlias )
Nothing ->
( ImportedModule, value node |> .moduleName )
2018-11-26 20:12:36 +03:00
in
( []
, register
2018-11-26 20:39:46 +03:00
variableType
2018-11-26 20:12:36 +03:00
(range moduleName)
(value moduleName |> getModuleName)
context
2018-11-26 20:12:36 +03:00
)
2018-11-22 21:19:19 +03:00
2018-11-26 20:12:36 +03:00
Just declaredImports ->
( []
, List.foldl
(\( variableType, range, name ) context_ -> register variableType range name context_)
context
2018-11-26 20:12:36 +03:00
(collectFromExposing declaredImports)
)
2018-11-22 21:19:19 +03:00
2019-06-24 01:32:27 +03:00
expressionVisitor : Node Expression -> Direction -> Context -> ( List Error, Context )
expressionVisitor node direction context =
2018-11-26 13:22:43 +03:00
case ( direction, value node ) of
2019-06-16 21:39:52 +03:00
( Direction.Enter, FunctionOrValue [] name ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
2019-06-16 21:39:52 +03:00
( Direction.Enter, FunctionOrValue moduleName name ) ->
( [], markAsUsed (getModuleName moduleName) context )
2018-11-22 21:19:19 +03:00
2019-06-16 21:39:52 +03:00
( Direction.Enter, OperatorApplication name _ _ _ ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
2019-06-16 21:39:52 +03:00
( Direction.Enter, PrefixOperator name ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
2019-06-16 21:39:52 +03:00
( Direction.Enter, LetExpression { declarations } ) ->
2018-11-26 13:22:43 +03:00
let
newContext =
List.foldl
(\declaration context_ ->
2018-11-26 13:22:43 +03:00
case value declaration of
LetFunction function ->
registerFunction function context_
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
LetDestructuring pattern _ ->
context_
2018-11-26 13:22:43 +03:00
)
{ context | scopes = Nonempty.cons emptyScope context.scopes }
2018-11-26 13:22:43 +03:00
declarations
in
( [], newContext )
2019-06-16 21:39:52 +03:00
( Direction.Exit, LetExpression _ ) ->
2018-11-26 13:22:43 +03:00
let
( errors, remainingUsed ) =
makeReport (Nonempty.head context.scopes)
2018-11-26 13:22:43 +03:00
contextWithPoppedScope =
{ context | scopes = Nonempty.pop context.scopes }
2018-11-26 13:22:43 +03:00
in
( errors
, markAllAsUsed remainingUsed contextWithPoppedScope
2018-11-26 13:22:43 +03:00
)
_ ->
( [], context )
2018-11-22 21:19:19 +03:00
2019-06-24 01:32:27 +03:00
declarationVisitor : Node Declaration -> Direction -> Context -> ( List Error, Context )
declarationVisitor node direction context =
2018-11-22 21:19:19 +03:00
case ( direction, value node ) of
2019-06-16 21:39:52 +03:00
( Direction.Enter, FunctionDeclaration function ) ->
2018-11-22 21:19:19 +03:00
let
declaration =
value function.declaration
namesUsedInSignature =
function.signature
|> Maybe.map (value >> .typeAnnotation >> collectNamesFromTypeAnnotation)
|> Maybe.withDefault []
newContext =
context
2018-11-26 20:39:46 +03:00
|> register Variable (range declaration.name) (value declaration.name)
2018-11-22 21:19:19 +03:00
|> markAllAsUsed namesUsedInSignature
in
( [], newContext )
2019-06-16 21:39:52 +03:00
( Direction.Enter, CustomTypeDeclaration { name } ) ->
( [], register Type (range name) (value name) context )
2018-11-22 21:19:19 +03:00
2019-06-16 21:39:52 +03:00
( Direction.Enter, AliasDeclaration { name } ) ->
( [], register Type (range name) (value name) context )
2018-11-22 21:19:19 +03:00
2019-06-16 21:39:52 +03:00
( Direction.Enter, PortDeclaration { name, typeAnnotation } ) ->
2018-11-26 21:09:18 +03:00
( []
, context
2018-11-26 21:09:18 +03:00
|> markAllAsUsed (collectNamesFromTypeAnnotation typeAnnotation)
|> register Port (range name) (value name)
)
2019-06-16 21:39:52 +03:00
( Direction.Enter, InfixDeclaration _ ) ->
( [], context )
2018-11-26 21:09:18 +03:00
2019-06-16 21:39:52 +03:00
( Direction.Enter, Destructuring _ _ ) ->
( [], context )
2018-11-26 21:09:18 +03:00
2019-06-16 21:39:52 +03:00
( Direction.Exit, _ ) ->
( [], context )
2018-11-22 21:19:19 +03:00
finalEvaluation : Context -> List Error
finalEvaluation context =
if context.exposesEverything then
[]
else
context.scopes
|> Nonempty.head
|> makeReport
|> Tuple.first
2018-11-26 13:22:43 +03:00
registerFunction : Function -> Context -> Context
registerFunction function context =
2018-11-26 13:22:43 +03:00
let
declaration =
value function.declaration
in
register Variable (range declaration.name) (value declaration.name) context
2018-11-26 13:22:43 +03:00
2018-11-26 21:16:04 +03:00
collectFromExposing : Exposing -> List ( VariableType, Range, String )
2018-11-26 13:22:43 +03:00
collectFromExposing exposing_ =
case exposing_ of
All _ ->
[]
Explicit list ->
List.filterMap
(\node ->
case value node of
FunctionExpose name ->
2018-11-26 21:16:04 +03:00
Just ( ImportedVariable, range node, name )
2018-11-26 13:22:43 +03:00
InfixExpose name ->
2018-11-26 21:16:04 +03:00
Just ( ImportedOperator, range node, name )
2018-11-26 13:22:43 +03:00
2018-11-26 20:12:36 +03:00
TypeOrAliasExpose name ->
2018-11-26 21:16:04 +03:00
Just ( ImportedType, range node, name )
2018-11-26 20:12:36 +03:00
TypeExpose { name, open } ->
case open of
Just openRange ->
Nothing
Nothing ->
2018-11-26 21:16:04 +03:00
Just ( ImportedType, range node, name )
2018-11-26 13:22:43 +03:00
)
list
2018-11-22 21:19:19 +03:00
collectNamesFromTypeAnnotation : Node TypeAnnotation -> List String
collectNamesFromTypeAnnotation node =
case value node of
FunctionTypeAnnotation a b ->
collectNamesFromTypeAnnotation a ++ collectNamesFromTypeAnnotation b
Typed nameNode params ->
let
name =
2018-11-26 21:09:18 +03:00
case value nameNode of
( [], str ) ->
str
( moduleName, _ ) ->
getModuleName moduleName
2018-11-22 21:19:19 +03:00
in
name :: List.concatMap collectNamesFromTypeAnnotation params
Record list ->
list
|> List.map (value >> Tuple.second)
|> List.concatMap collectNamesFromTypeAnnotation
GenericRecord name list ->
list
|> value
|> List.map (value >> Tuple.second)
|> List.concatMap collectNamesFromTypeAnnotation
2018-11-26 21:09:18 +03:00
Tupled list ->
List.concatMap collectNamesFromTypeAnnotation list
GenericType _ ->
[]
Unit ->
2018-11-22 21:19:19 +03:00
[]
2018-11-26 21:16:04 +03:00
register : VariableType -> Range -> String -> Context -> Context
register variableType range name context =
2018-11-22 21:19:19 +03:00
let
scopes =
mapNonemptyHead
(\scope ->
2018-11-26 20:39:46 +03:00
{ scope | declared = Dict.insert name ( variableType, range ) scope.declared }
2018-11-22 21:19:19 +03:00
)
context.scopes
2018-11-22 21:19:19 +03:00
in
{ context | scopes = scopes }
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
markAllAsUsed : List String -> Context -> Context
markAllAsUsed names context =
List.foldl markAsUsed context names
2018-11-26 13:22:43 +03:00
2018-11-22 21:19:19 +03:00
markAsUsed : String -> Context -> Context
markAsUsed name context =
2018-11-22 21:19:19 +03:00
let
scopes =
mapNonemptyHead
(\scope ->
{ scope | used = Set.insert name scope.used }
)
context.scopes
2018-11-22 21:19:19 +03:00
in
{ context | scopes = scopes }
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
getModuleName : List String -> String
getModuleName name =
String.join "." name
2018-11-22 21:19:19 +03:00
makeReport : Scope -> ( List Error, List String )
makeReport { declared, used } =
let
2019-06-24 01:32:27 +03:00
nonUsedVars : List String
2018-11-22 21:19:19 +03:00
nonUsedVars =
Set.diff used (Set.fromList <| Dict.keys declared)
|> Set.toList
2019-06-24 01:32:27 +03:00
errors : List Error
2018-11-22 21:19:19 +03:00
errors =
Dict.filter (\key _ -> not <| Set.member key used) declared
|> Dict.toList
2018-11-26 20:39:46 +03:00
|> List.map (\( key, ( variableType, range ) ) -> error variableType range key)
2018-11-22 21:19:19 +03:00
in
( errors, nonUsedVars )
mapNonemptyHead : (a -> a) -> Nonempty a -> Nonempty a
mapNonemptyHead fn nonempty =
let
newHead =
fn (Nonempty.head nonempty)
in
Nonempty.replaceHead newHead nonempty