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

454 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(..))
import Lint exposing (Rule, lint)
import Lint.Error exposing (Error)
import Lint.Rule exposing (Direction(..), Implementation, createRule)
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
rule input =
lint input implementation
2018-11-26 20:39:46 +03:00
type Value
= Variable
| ImportedModule
| ModuleAlias
| Type
2018-11-26 21:09:18 +03:00
| Port
2018-11-26 20:39:46 +03:00
2018-11-22 21:19:19 +03:00
type alias Scope =
2018-11-26 20:39:46 +03:00
{ declared : Dict String ( Value, Range )
2018-11-22 21:19:19 +03:00
, used : Set String
}
type alias Context =
{ scopes : Nonempty Scope
, exposesEverything : Bool
}
emptyScope : Scope
emptyScope =
Scope Dict.empty Set.empty
2018-11-26 20:39:46 +03:00
error : Value -> Range -> String -> Error
error variableType range_ name =
Error
"NoUnusedVariables"
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_
variableTypeToString : Value -> String
variableTypeToString value =
case value of
Variable ->
"Variable"
ImportedModule ->
"Imported module"
ModuleAlias ->
"Module alias"
Type ->
"Type"
2018-11-22 21:19:19 +03:00
2018-11-26 21:09:18 +03:00
Port ->
"Port"
variableTypeWarning : Value -> String
variableTypeWarning value =
case value of
Variable ->
""
ImportedModule ->
""
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
2018-11-26 13:22:43 +03:00
initialContext : Context
initialContext =
{ scopes = Nonempty.fromElement emptyScope
, exposesEverything = False
}
2018-11-22 21:19:19 +03:00
implementation : Implementation Context
implementation =
createRule
2018-11-26 13:22:43 +03:00
initialContext
2018-11-22 21:19:19 +03:00
(\v ->
{ v
| visitModuleDefinition = visitModuleDefinition
, visitImport = visitImport
, visitDeclaration = visitDeclaration
, visitExpression = visitExpression
, visitEnd = visitEnd
}
)
visitModuleDefinition : Context -> Node Module -> ( List Error, Context )
visitModuleDefinition ctx moduleNode =
case Module.exposingList (value moduleNode) of
All _ ->
( [], { ctx | exposesEverything = True } )
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 ctx )
visitImport : Context -> Node Import -> ( List Error, Context )
visitImport ctx node =
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)
ctx
)
2018-11-22 21:19:19 +03:00
2018-11-26 20:12:36 +03:00
Just declaredImports ->
( []
, List.foldl
2018-11-26 20:39:46 +03:00
(\( range, name ) context -> register Variable range name context)
2018-11-26 20:12:36 +03:00
ctx
(collectFromExposing declaredImports)
)
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
visitExpression : Context -> Direction -> Node Expression -> ( List Error, Context )
visitExpression ctx direction node =
case ( direction, value node ) of
( Enter, FunctionOrValue [] name ) ->
( [], markAsUsed name ctx )
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
( Enter, FunctionOrValue moduleName name ) ->
( [], markAsUsed (getModuleName moduleName) ctx )
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
( Enter, OperatorApplication name _ _ _ ) ->
( [], markAsUsed name ctx )
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
( Enter, PrefixOperator name ) ->
( [], markAsUsed name ctx )
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
( Enter, LetExpression { declarations } ) ->
let
newContext =
List.foldl
(\declaration context ->
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
)
{ ctx | scopes = Nonempty.cons emptyScope ctx.scopes }
declarations
in
( [], newContext )
( Exit, LetExpression _ ) ->
let
( errors, remainingUsed ) =
makeReport (Nonempty.head ctx.scopes)
ctxWithPoppedScope =
{ ctx | scopes = Nonempty.pop ctx.scopes }
in
( errors
, markAllAsUsed remainingUsed ctxWithPoppedScope
)
_ ->
( [], ctx )
2018-11-22 21:19:19 +03:00
visitDeclaration : Context -> Direction -> Node Declaration -> ( List Error, Context )
visitDeclaration ctx direction node =
case ( direction, value node ) of
( Enter, FunctionDeclaration function ) ->
let
declaration =
value function.declaration
namesUsedInSignature =
function.signature
|> Maybe.map (value >> .typeAnnotation >> collectNamesFromTypeAnnotation)
|> Maybe.withDefault []
newContext =
ctx
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 )
( Enter, CustomTypeDeclaration { name } ) ->
2018-11-26 20:39:46 +03:00
( [], register Type (range name) (value name) ctx )
2018-11-22 21:19:19 +03:00
( Enter, AliasDeclaration { name } ) ->
2018-11-26 20:39:46 +03:00
( [], register Type (range name) (value name) ctx )
2018-11-22 21:19:19 +03:00
2018-11-26 21:09:18 +03:00
( Enter, PortDeclaration { name, typeAnnotation } ) ->
( []
, ctx
|> markAllAsUsed (collectNamesFromTypeAnnotation typeAnnotation)
|> register Port (range name) (value name)
)
( Enter, InfixDeclaration _ ) ->
( [], ctx )
( Enter, Destructuring _ _ ) ->
( [], ctx )
( Exit, _ ) ->
2018-11-22 21:19:19 +03:00
( [], ctx )
2018-11-26 13:22:43 +03:00
visitEnd : Context -> ( List Error, Context )
visitEnd ctx =
let
errors =
if ctx.exposesEverything then
[]
else
ctx.scopes
|> Nonempty.head
2018-11-26 20:12:36 +03:00
|> makeReport
|> Tuple.first
2018-11-26 13:22:43 +03:00
in
( errors, ctx )
registerFunction : Function -> Context -> Context
registerFunction function ctx =
let
declaration =
value function.declaration
in
2018-11-26 20:39:46 +03:00
register Variable (range declaration.name) (value declaration.name) ctx
2018-11-26 13:22:43 +03:00
collectFromExposing : Exposing -> List ( Range, String )
collectFromExposing exposing_ =
case exposing_ of
All _ ->
[]
Explicit list ->
List.filterMap
(\node ->
case value node of
FunctionExpose name ->
Just ( range node, name )
InfixExpose name ->
Just ( range node, name )
2018-11-26 20:12:36 +03:00
TypeOrAliasExpose name ->
Just ( range node, name )
TypeExpose { name, open } ->
case open of
Just openRange ->
Nothing
Nothing ->
Just ( 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 20:39:46 +03:00
register : Value -> Range -> String -> Context -> Context
register variableType range name ctx =
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
)
ctx.scopes
in
{ ctx | scopes = scopes }
2018-11-26 13:22:43 +03:00
markAllAsUsed : List String -> Context -> Context
markAllAsUsed names ctx =
List.foldl markAsUsed ctx names
2018-11-22 21:19:19 +03:00
markAsUsed : String -> Context -> Context
markAsUsed name ctx =
let
scopes =
mapNonemptyHead
(\scope ->
{ scope | used = Set.insert name scope.used }
)
ctx.scopes
in
{ ctx | scopes = scopes }
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
nonUsedVars =
Set.diff used (Set.fromList <| Dict.keys declared)
|> Set.toList
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