mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-11-23 23:05:35 +03:00
Add NoUnusedVariables rule
This commit is contained in:
parent
d93474a62e
commit
06d6e5f247
4
elm.json
4
elm.json
@ -7,13 +7,15 @@
|
||||
"exposed-modules": [
|
||||
"Lint",
|
||||
"Lint.Rule",
|
||||
"Lint.Rule.NoDebug"
|
||||
"Lint.Rule.NoDebug",
|
||||
"Lint.Rule.NoUnusedVariables"
|
||||
],
|
||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||
"dependencies": {
|
||||
"elm/core": "1.0.0 <= v < 2.0.0",
|
||||
"elm/html": "1.0.0 <= v < 2.0.0",
|
||||
"elm/regex": "1.0.0 <= v < 2.0.0",
|
||||
"mgold/elm-nonempty-list": "4.0.0 <= v < 5.0.0",
|
||||
"stil4m/elm-syntax": "7.0.2 <= v < 8.0.0"
|
||||
},
|
||||
"test-dependencies": {
|
||||
|
@ -9,6 +9,7 @@ import Html.Events exposing (onInput)
|
||||
import Lint exposing (Rule, Severity(..), lintSource)
|
||||
import Lint.Error exposing (Error)
|
||||
import Lint.Rule.NoDebug
|
||||
import Lint.Rule.NoUnusedVariables
|
||||
import Result exposing (Result)
|
||||
|
||||
|
||||
@ -19,6 +20,7 @@ type Msg
|
||||
config : List ( Severity, Rule )
|
||||
config =
|
||||
[ ( Critical, Lint.Rule.NoDebug.rule )
|
||||
, ( Critical, Lint.Rule.NoUnusedVariables.rule )
|
||||
|
||||
-- , ( Critical, Lint.Rule.DefaultPatternPosition.rule { position = Lint.Rule.DefaultPatternPosition.Last } )
|
||||
-- , ( Critical, Lint.Rule.NoConstantCondition.rule )
|
||||
@ -27,7 +29,6 @@ config =
|
||||
-- , ( Critical, Lint.Rule.NoImportingEverything.rule { exceptions = [ "Html" ] } )
|
||||
-- , ( Critical, Lint.Rule.NoNestedLet.rule )
|
||||
-- , ( Critical, Lint.Rule.NoUnannotatedFunction.rule )
|
||||
-- , ( Critical, Lint.Rule.NoUnusedVariables.rule )
|
||||
-- , ( Critical, Lint.Rule.NoUselessIf.rule )
|
||||
-- , ( Critical, Lint.Rule.NoUselessPatternMatching.rule )
|
||||
-- , ( Warning, Lint.Rule.NoWarningComments.rule )
|
||||
|
@ -10,11 +10,13 @@
|
||||
"elm/browser": "1.0.1",
|
||||
"elm/core": "1.0.0",
|
||||
"elm/html": "1.0.0",
|
||||
"mgold/elm-nonempty-list": "4.0.0",
|
||||
"stil4m/elm-syntax": "7.0.2"
|
||||
},
|
||||
"indirect": {
|
||||
"elm/json": "1.0.0",
|
||||
"elm/parser": "1.1.0",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.2",
|
||||
|
@ -51,7 +51,7 @@ import Elm.Syntax.Expression exposing (Expression)
|
||||
import Elm.Syntax.File exposing (File)
|
||||
import Elm.Syntax.Node exposing (Node)
|
||||
import Lint.Error exposing (Error)
|
||||
import Lint.NodeToVisitor exposing (declarationsIntoVisitors, expressionToVisitors)
|
||||
import Lint.NodeToVisitor exposing (createVisitorsForFile, expressionToVisitors)
|
||||
import Lint.Rule exposing (Direction, Implementation, Visitor, initialContext)
|
||||
|
||||
|
||||
@ -127,8 +127,7 @@ parseSource source =
|
||||
-}
|
||||
lint : File -> Implementation context -> List Error
|
||||
lint file rule =
|
||||
file.declarations
|
||||
|> declarationsIntoVisitors
|
||||
createVisitorsForFile file
|
||||
|> lintWithVisitors rule
|
||||
|
||||
|
||||
|
@ -1,10 +1,14 @@
|
||||
module Lint.NodeToVisitor exposing (declarationsIntoVisitors, expressionToVisitors)
|
||||
module Lint.NodeToVisitor exposing (createVisitorsForFile, expressionToVisitors)
|
||||
|
||||
import Elm.Syntax.Declaration exposing (Declaration(..))
|
||||
import Elm.Syntax.Expression exposing (Expression(..), Function, FunctionImplementation, LetDeclaration(..))
|
||||
import Elm.Syntax.File exposing (File)
|
||||
import Elm.Syntax.Import exposing (Import)
|
||||
import Elm.Syntax.Infix exposing (InfixDirection(..))
|
||||
import Elm.Syntax.Module exposing (Module)
|
||||
import Elm.Syntax.Node exposing (Node, value)
|
||||
import Lint.Rule exposing (Direction(..), Visitor, evaluateExpression, finalEvaluation)
|
||||
import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation(..))
|
||||
import Lint.Rule exposing (Direction(..), Visitor, evaluateDeclaration, evaluateExpression, evaluateImport, evaluateModuleDefinition, evaluateTypeAnnotation, finalEvaluation)
|
||||
|
||||
|
||||
createExitAndEnterWithChildren : (Direction -> nodeType -> Visitor context) -> nodeType -> List (Visitor context) -> List (Visitor context)
|
||||
@ -21,11 +25,31 @@ moduleVisitor rule context =
|
||||
finalEvaluation rule context
|
||||
|
||||
|
||||
moduleDefinitionVisitor : Node Module -> Visitor context
|
||||
moduleDefinitionVisitor node rule context =
|
||||
evaluateModuleDefinition rule context node
|
||||
|
||||
|
||||
importVisitor : Node Import -> Visitor context
|
||||
importVisitor node rule context =
|
||||
evaluateImport rule context node
|
||||
|
||||
|
||||
expressionVisitor : Direction -> Node Expression -> Visitor context
|
||||
expressionVisitor direction node rule context =
|
||||
evaluateExpression rule context direction node
|
||||
|
||||
|
||||
declarationVisitor : Direction -> Node Declaration -> Visitor context
|
||||
declarationVisitor direction node rule context =
|
||||
evaluateDeclaration rule context direction node
|
||||
|
||||
|
||||
typeAnnotationVisitor : Direction -> Node TypeAnnotation -> Visitor context
|
||||
typeAnnotationVisitor direction node rule context =
|
||||
evaluateTypeAnnotation rule context direction node
|
||||
|
||||
|
||||
functionToExpression : Function -> Node Expression
|
||||
functionToExpression { documentation, signature, declaration } =
|
||||
let
|
||||
@ -71,6 +95,9 @@ expressionToVisitors node =
|
||||
ParenthesizedExpression expr ->
|
||||
[ expr ]
|
||||
|
||||
Operator name ->
|
||||
[]
|
||||
|
||||
OperatorApplication operator direction left right ->
|
||||
case direction of
|
||||
Left ->
|
||||
@ -109,6 +136,9 @@ expressionToVisitors node =
|
||||
expressions
|
||||
|
||||
-- TODO Implement the rest
|
||||
PrefixOperator name ->
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
@ -118,24 +148,58 @@ expressionToVisitors node =
|
||||
createExitAndEnterWithChildren expressionVisitor node childrenVisitors
|
||||
|
||||
|
||||
declarationToVisitors : Declaration -> List (Visitor context)
|
||||
declarationToVisitors declaration =
|
||||
typeAnnotationToVisitor : Node TypeAnnotation -> List (Visitor context)
|
||||
typeAnnotationToVisitor node =
|
||||
let
|
||||
childrenVisitors =
|
||||
case declaration of
|
||||
case value node of
|
||||
GenericType _ ->
|
||||
[]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
in
|
||||
createExitAndEnterWithChildren typeAnnotationVisitor node childrenVisitors
|
||||
|
||||
|
||||
declarationToVisitors : Node Declaration -> List (Visitor context)
|
||||
declarationToVisitors node =
|
||||
let
|
||||
childrenVisitors =
|
||||
case value node of
|
||||
FunctionDeclaration function ->
|
||||
functionToExpression function |> expressionToVisitors
|
||||
|
||||
-- TODO Implement the rest
|
||||
CustomTypeDeclaration { constructors } ->
|
||||
constructors
|
||||
|> List.concatMap (value >> .arguments)
|
||||
|> List.concatMap typeAnnotationToVisitor
|
||||
|
||||
_ ->
|
||||
[]
|
||||
in
|
||||
-- createExitAndEnterWithChildren statementVisitor declaration childrenVisitors
|
||||
childrenVisitors
|
||||
createExitAndEnterWithChildren declarationVisitor node childrenVisitors
|
||||
|
||||
|
||||
declarationsIntoVisitors : List (Node Declaration) -> List (Visitor context)
|
||||
declarationsIntoVisitors declarations =
|
||||
declarations
|
||||
|> List.concatMap (value >> declarationToVisitors)
|
||||
|> (\allVisitors -> List.append allVisitors [ moduleVisitor ])
|
||||
List.concatMap declarationToVisitors declarations
|
||||
|
||||
|
||||
importsIntoVisitors : List (Node Import) -> List (Visitor context)
|
||||
importsIntoVisitors imports =
|
||||
List.map importVisitor imports
|
||||
|
||||
|
||||
moduleDefinitionIntoVisitor : Node Module -> Visitor context
|
||||
moduleDefinitionIntoVisitor moduleNode =
|
||||
moduleDefinitionVisitor moduleNode
|
||||
|
||||
|
||||
createVisitorsForFile : File -> List (Visitor context)
|
||||
createVisitorsForFile file =
|
||||
[ moduleDefinitionIntoVisitor file.moduleDefinition ]
|
||||
++ importsIntoVisitors file.imports
|
||||
++ declarationsIntoVisitors file.declarations
|
||||
++ [ moduleVisitor ]
|
||||
|
@ -2,7 +2,7 @@ module Lint.Rule exposing
|
||||
( Direction(..)
|
||||
, Implementation, createRule
|
||||
, Visitor, LintResult
|
||||
, evaluateExpression, finalEvaluation, initialContext
|
||||
, evaluateDeclaration, evaluateExpression, evaluateImport, evaluateModuleDefinition, evaluateTypeAnnotation, finalEvaluation, initialContext
|
||||
)
|
||||
|
||||
{-| This module contains functions that are used for writing rules.
|
||||
@ -24,9 +24,13 @@ module Lint.Rule exposing
|
||||
|
||||
-}
|
||||
|
||||
import Elm.Syntax.Declaration exposing (Declaration)
|
||||
import Elm.Syntax.Expression exposing (Expression)
|
||||
import Elm.Syntax.File exposing (File)
|
||||
import Elm.Syntax.Import exposing (Import)
|
||||
import Elm.Syntax.Module exposing (Module)
|
||||
import Elm.Syntax.Node exposing (Node)
|
||||
import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation)
|
||||
import Lint.Error exposing (Error)
|
||||
|
||||
|
||||
@ -85,7 +89,11 @@ type Implementation context
|
||||
|
||||
|
||||
type alias Visitors context =
|
||||
{ visitExpression : context -> Direction -> Node Expression -> ( List Error, context )
|
||||
{ visitModuleDefinition : context -> Node Module -> ( List Error, context )
|
||||
, visitImport : context -> Node Import -> ( List Error, context )
|
||||
, visitExpression : context -> Direction -> Node Expression -> ( List Error, context )
|
||||
, visitDeclaration : context -> Direction -> Node Declaration -> ( List Error, context )
|
||||
, visitTypeAnnotation : context -> Direction -> Node TypeAnnotation -> ( List Error, context )
|
||||
, visitEnd : context -> ( List Error, context )
|
||||
}
|
||||
|
||||
@ -96,7 +104,11 @@ createRule initContext createVisitors =
|
||||
{ initContext = initContext
|
||||
, visitors =
|
||||
createVisitors
|
||||
{ visitExpression = \ctx direction node -> ( [], ctx )
|
||||
{ visitModuleDefinition = \ctx node -> ( [], ctx )
|
||||
, visitImport = \ctx node -> ( [], ctx )
|
||||
, visitExpression = \ctx direction node -> ( [], ctx )
|
||||
, visitDeclaration = \ctx direction node -> ( [], ctx )
|
||||
, visitTypeAnnotation = \ctx direction node -> ( [], ctx )
|
||||
, visitEnd = \ctx -> ( [], ctx )
|
||||
}
|
||||
}
|
||||
@ -107,11 +119,31 @@ initialContext (Implementation { initContext }) =
|
||||
initContext
|
||||
|
||||
|
||||
evaluateModuleDefinition : Implementation context -> context -> Node Module -> ( List Error, context )
|
||||
evaluateModuleDefinition (Implementation { visitors }) =
|
||||
visitors.visitModuleDefinition
|
||||
|
||||
|
||||
evaluateImport : Implementation context -> context -> Node Import -> ( List Error, context )
|
||||
evaluateImport (Implementation { visitors }) =
|
||||
visitors.visitImport
|
||||
|
||||
|
||||
evaluateExpression : Implementation context -> context -> Direction -> Node Expression -> ( List Error, context )
|
||||
evaluateExpression (Implementation { visitors }) =
|
||||
visitors.visitExpression
|
||||
|
||||
|
||||
evaluateDeclaration : Implementation context -> context -> Direction -> Node Declaration -> ( List Error, context )
|
||||
evaluateDeclaration (Implementation { visitors }) =
|
||||
visitors.visitDeclaration
|
||||
|
||||
|
||||
evaluateTypeAnnotation : Implementation context -> context -> Direction -> Node TypeAnnotation -> ( List Error, context )
|
||||
evaluateTypeAnnotation (Implementation { visitors }) =
|
||||
visitors.visitTypeAnnotation
|
||||
|
||||
|
||||
finalEvaluation : Implementation context -> context -> ( List Error, context )
|
||||
finalEvaluation (Implementation { visitors }) =
|
||||
visitors.visitEnd
|
||||
|
512
src/Lint/Rule/NoUnusedVariables.elm
Normal file
512
src/Lint/Rule/NoUnusedVariables.elm
Normal file
@ -0,0 +1,512 @@
|
||||
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)
|
||||
|
||||
|
||||
type alias Scope =
|
||||
{ declared : Dict String Range
|
||||
, used : Set String
|
||||
}
|
||||
|
||||
|
||||
type alias Context =
|
||||
{ scopes : Nonempty Scope
|
||||
, exposesEverything : Bool
|
||||
}
|
||||
|
||||
|
||||
emptyScope : Scope
|
||||
emptyScope =
|
||||
Scope Dict.empty Set.empty
|
||||
|
||||
|
||||
{-| Reports variables that are declared but never used.
|
||||
|
||||
rules =
|
||||
[ NoUnusedVariables.rule
|
||||
]
|
||||
|
||||
-}
|
||||
rule : Rule
|
||||
rule input =
|
||||
lint input implementation
|
||||
|
||||
|
||||
implementation : Implementation Context
|
||||
implementation =
|
||||
createRule
|
||||
(Context (Nonempty.fromElement emptyScope) False)
|
||||
(\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
|
||||
newContext =
|
||||
value node
|
||||
|> .exposingList
|
||||
|> Maybe.map (value >> collectFromExposing)
|
||||
|> Maybe.withDefault []
|
||||
|> List.foldl (\( range, name ) context -> register range name context) ctx
|
||||
in
|
||||
( [], newContext )
|
||||
|
||||
|
||||
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 )
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
list
|
||||
|
||||
|
||||
markAllAsUsed : List String -> Context -> Context
|
||||
markAllAsUsed names ctx =
|
||||
List.foldl markAsUsed ctx names
|
||||
|
||||
|
||||
error : Range -> String -> Error
|
||||
error range_ name =
|
||||
Error "NoUnusedVariables" ("Variable `" ++ name ++ "` is not used") range_
|
||||
|
||||
|
||||
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
|
||||
|> register (range declaration.name) (value declaration.name)
|
||||
|> markAllAsUsed namesUsedInSignature
|
||||
in
|
||||
( [], newContext )
|
||||
|
||||
( Enter, CustomTypeDeclaration { name } ) ->
|
||||
( [], register (range name) (value name) ctx )
|
||||
|
||||
( Enter, AliasDeclaration { name } ) ->
|
||||
( [], register (range name) (value name) ctx )
|
||||
|
||||
_ ->
|
||||
( [], ctx )
|
||||
|
||||
|
||||
collectNamesFromTypeAnnotation : Node TypeAnnotation -> List String
|
||||
collectNamesFromTypeAnnotation node =
|
||||
case value node of
|
||||
FunctionTypeAnnotation a b ->
|
||||
collectNamesFromTypeAnnotation a ++ collectNamesFromTypeAnnotation b
|
||||
|
||||
Typed nameNode params ->
|
||||
let
|
||||
name =
|
||||
nameNode
|
||||
|> value
|
||||
|> Tuple.second
|
||||
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
|
||||
|
||||
_ ->
|
||||
[]
|
||||
|
||||
|
||||
register : Range -> String -> Context -> Context
|
||||
register range name ctx =
|
||||
let
|
||||
scopes =
|
||||
mapNonemptyHead
|
||||
(\scope ->
|
||||
{ scope | declared = Dict.insert name range scope.declared }
|
||||
)
|
||||
ctx.scopes
|
||||
in
|
||||
{ ctx | scopes = scopes }
|
||||
|
||||
|
||||
markAsUsed : String -> Context -> Context
|
||||
markAsUsed name ctx =
|
||||
let
|
||||
scopes =
|
||||
mapNonemptyHead
|
||||
(\scope ->
|
||||
{ scope | used = Set.insert name scope.used }
|
||||
)
|
||||
ctx.scopes
|
||||
in
|
||||
{ ctx | scopes = scopes }
|
||||
|
||||
|
||||
visitExpression : Context -> Direction -> Node Expression -> ( List Error, Context )
|
||||
visitExpression ctx direction node =
|
||||
case ( direction, value node ) of
|
||||
( Enter, FunctionOrValue [] name ) ->
|
||||
( [], markAsUsed name ctx )
|
||||
|
||||
( Enter, OperatorApplication name _ _ _ ) ->
|
||||
( [], markAsUsed name ctx )
|
||||
|
||||
( Enter, PrefixOperator name ) ->
|
||||
( [], markAsUsed name ctx )
|
||||
|
||||
( Enter, LetExpression { declarations } ) ->
|
||||
let
|
||||
newContext =
|
||||
List.foldl
|
||||
(\declaration context ->
|
||||
case value declaration of
|
||||
LetFunction function ->
|
||||
registerFunction function context
|
||||
|
||||
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 )
|
||||
|
||||
|
||||
registerFunction : Function -> Context -> Context
|
||||
registerFunction function ctx =
|
||||
let
|
||||
declaration =
|
||||
value function.declaration
|
||||
in
|
||||
register (range declaration.name) (value declaration.name) ctx
|
||||
|
||||
|
||||
visitEnd : Context -> ( List Error, Context )
|
||||
visitEnd ctx =
|
||||
let
|
||||
errors =
|
||||
if ctx.exposesEverything then
|
||||
[]
|
||||
|
||||
else
|
||||
ctx.scopes
|
||||
|> Nonempty.head
|
||||
|> makeReport
|
||||
|> Tuple.first
|
||||
in
|
||||
( errors, ctx )
|
||||
|
||||
|
||||
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
|
||||
|> List.map (\( key, node ) -> error node key)
|
||||
in
|
||||
( errors, nonUsedVars )
|
||||
|
||||
|
||||
|
||||
-- ( Enter, Variable names ) ->
|
||||
-- case names of
|
||||
-- [ name ] ->
|
||||
-- ( [], { ctx | scopes = addUsedToStack ctx.scopes [ name ] } )
|
||||
--
|
||||
-- _ ->
|
||||
-- ( [], ctx )
|
||||
--
|
||||
-- ( Enter, LetExpression declarations ) ->
|
||||
-- let
|
||||
-- variables =
|
||||
-- List.map Tuple.first declarations
|
||||
-- |> List.filterMap variableName
|
||||
-- |> List.concat
|
||||
-- |> Set.fromList
|
||||
--
|
||||
-- newScope =
|
||||
-- Scope variables Set.empty
|
||||
-- in
|
||||
-- ( [], { ctx | scopes = newScope :: ctx.scopes } )
|
||||
--
|
||||
-- ( Exit, LetExpression _ ) ->
|
||||
-- let
|
||||
-- ( errors, variablesUsedButNotFromThisScope ) =
|
||||
-- ctx.scopes
|
||||
-- |> List.head
|
||||
-- |> makeReport
|
||||
--
|
||||
-- newScopes =
|
||||
-- List.drop 1 ctx.scopes
|
||||
-- in
|
||||
-- ( errors, { ctx | scopes = addUsedToStack newScopes (Set.toList variablesUsedButNotFromThisScope) } )
|
||||
-- addUsedToStack : List Scope -> List String -> List Scope
|
||||
-- addUsedToStack scopes variables =
|
||||
-- let
|
||||
-- lastScope =
|
||||
-- case List.head scopes of
|
||||
-- Nothing ->
|
||||
-- Debug.log "Unexpected Empty scope stack" emptyScope
|
||||
--
|
||||
-- Just scope ->
|
||||
-- { scope | used = Set.union scope.used (Set.fromList variables) }
|
||||
-- in
|
||||
-- lastScope :: List.drop 1 scopes
|
||||
--
|
||||
--
|
||||
-- addFoundToStack : List Scope -> List String -> List Scope
|
||||
-- addFoundToStack scopes variables =
|
||||
-- let
|
||||
-- lastScope =
|
||||
-- case List.head scopes of
|
||||
-- Nothing ->
|
||||
-- Debug.log "Unexpected Empty scope stack" emptyScope
|
||||
--
|
||||
-- Just scope ->
|
||||
-- { scope | declared = Set.union scope.declared (Set.fromList variables) }
|
||||
-- in
|
||||
-- lastScope :: List.drop 1 scopes
|
||||
-- makeReport : Maybe Scope -> ( List Error, Set String )
|
||||
-- makeReport maybeScope =
|
||||
-- case maybeScope of
|
||||
-- Nothing ->
|
||||
-- Debug.log "Unexpected Empty scope stack" ( [], Set.empty )
|
||||
--
|
||||
-- Just scope ->
|
||||
-- let
|
||||
-- notUsed =
|
||||
-- Set.diff scope.declared scope.used
|
||||
--
|
||||
-- variablesUsedButNotFromThisScope =
|
||||
-- Set.diff scope.used scope.declared
|
||||
--
|
||||
-- errors =
|
||||
-- Set.diff scope.declared scope.used
|
||||
-- |> Set.toList
|
||||
-- |> List.sort
|
||||
-- |> List.map error
|
||||
-- in
|
||||
-- ( errors, variablesUsedButNotFromThisScope )
|
||||
--
|
||||
-- variableName : Expression -> Maybe (List String)
|
||||
-- variableName expr =
|
||||
-- case expr of
|
||||
-- Variable names ->
|
||||
-- Just names
|
||||
--
|
||||
-- Application var _ ->
|
||||
-- variableName var
|
||||
--
|
||||
-- _ ->
|
||||
-- Nothing
|
||||
--
|
||||
--
|
||||
-- getExported : ExportSet -> Set String
|
||||
-- getExported exportType =
|
||||
-- case exportType of
|
||||
-- -- Ignore as this case is handled by `exposesEverything`
|
||||
-- AllExport ->
|
||||
-- Set.empty
|
||||
--
|
||||
-- SubsetExport exports ->
|
||||
-- List.map getExported exports
|
||||
-- |> List.foldl Set.union Set.empty
|
||||
--
|
||||
-- FunctionExport name ->
|
||||
-- Set.singleton name
|
||||
--
|
||||
-- TypeExport name _ ->
|
||||
-- Set.singleton name
|
||||
--
|
||||
--
|
||||
-- addExposedVariables : Context -> Ast.Statement.ExportSet -> Context
|
||||
-- addExposedVariables ctx exportType =
|
||||
-- { ctx
|
||||
-- | scopes =
|
||||
-- getExported exportType
|
||||
-- |> Set.toList
|
||||
-- |> addUsedToStack ctx.scopes
|
||||
-- }
|
||||
--
|
||||
--
|
||||
-- statementFn : Context -> Direction Statement -> ( List Error, Context )
|
||||
-- statementFn ctx node =
|
||||
-- case node of
|
||||
-- Enter (FunctionDeclaration name args body) ->
|
||||
-- ( [], { ctx | scopes = addFoundToStack ctx.scopes [ name ] } )
|
||||
--
|
||||
-- Enter (ModuleDeclaration names AllExport) ->
|
||||
-- ( [], { ctx | exposesEverything = True } )
|
||||
--
|
||||
-- Enter (PortModuleDeclaration names AllExport) ->
|
||||
-- ( [], { ctx | exposesEverything = True } )
|
||||
--
|
||||
-- Enter (ImportStatement module_ alias_ (Just (SubsetExport imported))) ->
|
||||
-- let
|
||||
-- variables =
|
||||
-- List.foldl
|
||||
-- (\var res ->
|
||||
-- case var of
|
||||
-- FunctionExport name ->
|
||||
-- name :: res
|
||||
--
|
||||
-- _ ->
|
||||
-- res
|
||||
-- )
|
||||
-- []
|
||||
-- imported
|
||||
-- in
|
||||
-- ( [], { ctx | scopes = addFoundToStack ctx.scopes variables } )
|
||||
--
|
||||
-- Enter (ModuleDeclaration names exportType) ->
|
||||
-- ( [], addExposedVariables ctx exportType )
|
||||
--
|
||||
-- Enter (PortModuleDeclaration names exportType) ->
|
||||
-- ( [], addExposedVariables ctx exportType )
|
||||
--
|
||||
-- _ ->
|
||||
-- ( [], ctx )
|
||||
--
|
||||
--
|
||||
-- moduleEndFn : Context -> ( List Error, Context )
|
||||
-- moduleEndFn ctx =
|
||||
-- let
|
||||
-- ( errors, _ ) =
|
||||
-- if ctx.exposesEverything then
|
||||
-- ( [], Set.empty )
|
||||
--
|
||||
-- else
|
||||
-- ctx.scopes
|
||||
-- |> List.head
|
||||
-- |> makeReport
|
||||
-- in
|
||||
-- ( errors, ctx )
|
||||
|
||||
|
||||
mapNonemptyHead : (a -> a) -> Nonempty a -> Nonempty a
|
||||
mapNonemptyHead fn nonempty =
|
||||
let
|
||||
newHead =
|
||||
fn (Nonempty.head nonempty)
|
||||
in
|
||||
Nonempty.replaceHead newHead nonempty
|
@ -5,7 +5,7 @@ import Lint exposing (Rule)
|
||||
import Lint.Error exposing (Error)
|
||||
import Lint.Rule exposing (LintResult)
|
||||
import Lint.Rule.NoDebug exposing (rule)
|
||||
import Test exposing (Test, describe, only, test)
|
||||
import Test exposing (Test, describe, test)
|
||||
import TestUtil exposing (expectErrors, ruleTester)
|
||||
|
||||
|
||||
|
288
tests/NoUnusedVariablesTest.elm
Normal file
288
tests/NoUnusedVariablesTest.elm
Normal file
@ -0,0 +1,288 @@
|
||||
module NoUnusedVariablesTest exposing (all)
|
||||
|
||||
import Elm.Syntax.Range exposing (Location, Range)
|
||||
import Lint exposing (Rule)
|
||||
import Lint.Error exposing (Error)
|
||||
import Lint.Rule exposing (LintResult)
|
||||
import Lint.Rule.NoUnusedVariables exposing (rule)
|
||||
import Test exposing (Test, describe, test)
|
||||
import TestUtil exposing (expectErrors, ruleTester)
|
||||
|
||||
|
||||
testRule : String -> LintResult
|
||||
testRule =
|
||||
ruleTester rule
|
||||
|
||||
|
||||
error : String -> Range -> Error
|
||||
error =
|
||||
Error "NoUnusedVariables"
|
||||
|
||||
|
||||
location : Int -> Int -> Int -> Range
|
||||
location row columnStart columnEnd =
|
||||
{ start = { row = row, column = columnStart }
|
||||
, end = { row = row, column = columnEnd }
|
||||
}
|
||||
|
||||
|
||||
tests : List Test
|
||||
tests =
|
||||
[ test "should not report exposed top-level variables" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
a = 1"""
|
||||
|> expectErrors []
|
||||
, test "should not report used top-level variables" <|
|
||||
\() ->
|
||||
testRule """module A exposing (b)
|
||||
a n = 1
|
||||
b = a 1"""
|
||||
|> expectErrors []
|
||||
, test "should report unused top-level variables" <|
|
||||
\() ->
|
||||
testRule """module A exposing (b)
|
||||
a = 1"""
|
||||
|> expectErrors [ error "Variable `a` is not used" (location 2 1 2) ]
|
||||
, test "should report unused top-level variables even if they are annotated" <|
|
||||
\() ->
|
||||
testRule """module A exposing (b)
|
||||
a: Int
|
||||
a = 1"""
|
||||
|> expectErrors [ error "Variable `a` is not used" (location 3 1 2) ]
|
||||
, test "should not report unused top-level variables if everything is exposed" <|
|
||||
\() ->
|
||||
testRule """module A exposing (..)
|
||||
a n = 1
|
||||
b = a 1"""
|
||||
|> expectErrors []
|
||||
, test "should not report unused top-level variables that are exposed by name" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a, b)
|
||||
a = 1
|
||||
b = 2"""
|
||||
|> expectErrors []
|
||||
, test "should not report unused top-level variables that are exposed by name, but report others" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a, b)
|
||||
a = 1
|
||||
b = 2
|
||||
c = 3"""
|
||||
|> expectErrors [ error "Variable `c` is not used" (location 4 1 2) ]
|
||||
, test "should not report unused top-level variables if everything is exposed (port module)" <|
|
||||
\() ->
|
||||
testRule """port module A exposing (..)
|
||||
a n = 1
|
||||
b = a 1"""
|
||||
|> expectErrors []
|
||||
, test "should not report unused top-level variables that are exposed by name (port module)" <|
|
||||
\() ->
|
||||
testRule """port module A exposing (a, b)
|
||||
a = 1
|
||||
b = 2"""
|
||||
|> expectErrors []
|
||||
, test "should not report unused top-level variables that are exposed by name, but report others (port module)" <|
|
||||
\() ->
|
||||
testRule """port module A exposing (a, b)
|
||||
a = 1
|
||||
b = 2
|
||||
c = 3"""
|
||||
|> expectErrors [ error "Variable `c` is not used" (location 4 1 2) ]
|
||||
, test "should report unused variables from let declarations" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
a = let b = 1
|
||||
in 2"""
|
||||
|> expectErrors [ error "Variable `b` is not used" (location 2 9 10) ]
|
||||
, test "should report unused variables from let even if they are exposed by name" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a, b)
|
||||
a = let b = 1
|
||||
in 2"""
|
||||
|> expectErrors [ error "Variable `b` is not used" (location 2 9 10) ]
|
||||
, test "should report unused functions from let even if they are exposed by name" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
a = let b param = 1
|
||||
in 2"""
|
||||
|> expectErrors [ error "Variable `b` is not used" (location 2 9 10) ]
|
||||
, test "should report unused variables from let even if everything is exposed" <|
|
||||
\() ->
|
||||
testRule """module A exposing (..)
|
||||
a = let b = 1
|
||||
in 2"""
|
||||
|> expectErrors [ error "Variable `b` is not used" (location 2 9 10) ]
|
||||
, test "should not report top-level variables used inside a let expression" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
b = 1
|
||||
a = let c = 1
|
||||
in b + c"""
|
||||
|> expectErrors []
|
||||
, test "should not report top-level variables used inside let declarations" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
b = 1
|
||||
a = let c = b
|
||||
in c"""
|
||||
|> expectErrors []
|
||||
, test "should not report top-level variables used in nested lets" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
b = 1
|
||||
a = let
|
||||
c = b
|
||||
d = let
|
||||
e = 1
|
||||
in
|
||||
b + c + e
|
||||
in
|
||||
d"""
|
||||
|> expectErrors []
|
||||
, test "should not report variables from let declarations that are used in the expression" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
a = let c = 1
|
||||
in c"""
|
||||
|> expectErrors []
|
||||
, test "should not report unused function parameters" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
a n = 1"""
|
||||
|> expectErrors []
|
||||
, test "should report unused imported functions" <|
|
||||
\() ->
|
||||
testRule """module A exposing (b)
|
||||
import Foo exposing (a)"""
|
||||
|> expectErrors [ error "Variable `a` is not used" (location 2 22 23) ]
|
||||
, test "should report unused imported functions (multiple imports)" <|
|
||||
\() ->
|
||||
testRule """module A exposing (d)
|
||||
import Foo exposing (C, a, b)"""
|
||||
|> expectErrors
|
||||
[ error "Variable `a` is not used" (location 2 25 26)
|
||||
, error "Variable `b` is not used" (location 2 28 29)
|
||||
]
|
||||
|
||||
-- Needs to be improved, every case should create a new scope stack
|
||||
-- Right now, every parameter is considered used, which is not great
|
||||
, test "should not report unused pattern matching parameters" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
a = case thing of
|
||||
Foo b c -> []"""
|
||||
|> expectErrors []
|
||||
|
||||
-- Should B and C be reported if they are not used? Probably.
|
||||
, test "should report unused custom type declarations" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
type A = B | C"""
|
||||
|> expectErrors [ error "Variable `A` is not used" (location 2 6 7) ]
|
||||
, test "should report unused type aliases declarations" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
type alias A = { a : B }"""
|
||||
|> expectErrors [ error "Variable `A` is not used" (location 2 12 13) ]
|
||||
, test "should not report type used in a signature" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
type alias A = { a : B }
|
||||
a : A
|
||||
a = {a = 1}"""
|
||||
|> expectErrors []
|
||||
, test "should not report type used in a signature with multiple arguments" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
type alias A = { a : B }
|
||||
a : String -> A
|
||||
a str = {a = str}"""
|
||||
|> expectErrors []
|
||||
, test "should not report type used in a signature with parameterized types (as generic type)" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
type alias A = { a : B }
|
||||
a : A B
|
||||
a = []"""
|
||||
|> expectErrors []
|
||||
, test "should not report type used in a signature with parameterized types (as parameter)" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
type alias A = { a : B }
|
||||
a : List A
|
||||
a = []"""
|
||||
|> expectErrors []
|
||||
, test "should not report type used in a signature with a record" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
type alias A = { a : B }
|
||||
a : { c: A }
|
||||
a str = {c = str}"""
|
||||
|> expectErrors []
|
||||
, test "should not report type used in a signature with a generic record" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
type alias A = { a : B }
|
||||
a : { r | c: A }
|
||||
a str = {c = str}"""
|
||||
|> expectErrors []
|
||||
, test "should not report type if it's exposed" <|
|
||||
\() ->
|
||||
testRule """module A exposing (A)
|
||||
type A a = B a"""
|
||||
|> expectErrors []
|
||||
, test "should not report custom type if it's exposed with its sub-types" <|
|
||||
\() ->
|
||||
testRule """module A exposing (A(..))
|
||||
type A = B | C | D"""
|
||||
|> expectErrors []
|
||||
, test "should report unused variable even if it's present in a generic type" <|
|
||||
\() ->
|
||||
testRule """module A exposing (A)
|
||||
a = 1
|
||||
type A a = B a"""
|
||||
|> expectErrors [ error "Variable `a` is not used" (location 2 1 2) ]
|
||||
, test "should report unused variable even if it's present in a generic record type" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
r = 1
|
||||
a : { r | c: A }
|
||||
a str = {c = str}"""
|
||||
|> expectErrors [ error "Variable `r` is not used" (location 2 1 2) ]
|
||||
, test "should report unused operator import" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
import Parser exposing ((</>))"""
|
||||
|> expectErrors [ error "Variable `</>` is not used" (location 2 25 30) ]
|
||||
, test "should not report used operator (infix)" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
import Parser exposing ((</>))
|
||||
a = 1 </> 2"""
|
||||
|> expectErrors []
|
||||
, test "should not report used operator (prefix)" <|
|
||||
\() ->
|
||||
testRule """module A exposing (a)
|
||||
import Parser exposing ((</>))
|
||||
a = (</>) 2"""
|
||||
|> expectErrors []
|
||||
|
||||
-- ##################################################################################################
|
||||
-- , test "should report unused opaque types" <|
|
||||
-- \() ->
|
||||
-- testRule """module A exposing (a)
|
||||
-- type A = A Int"""
|
||||
-- |> expectErrors [ error "Variable `A` is not used" (location 2 6 7) ]
|
||||
-- , test "should not report used opaque types" <|
|
||||
-- \() ->
|
||||
-- testRule """module A exposing (a)
|
||||
-- type A = A Int
|
||||
-- a : A
|
||||
-- a = 1"""
|
||||
-- |> expectErrors [ error "Variable `A` is not used" (location 2 6 7) ]
|
||||
]
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "NoUnusedVariables" tests
|
Loading…
Reference in New Issue
Block a user