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": [
|
"exposed-modules": [
|
||||||
"Lint",
|
"Lint",
|
||||||
"Lint.Rule",
|
"Lint.Rule",
|
||||||
"Lint.Rule.NoDebug"
|
"Lint.Rule.NoDebug",
|
||||||
|
"Lint.Rule.NoUnusedVariables"
|
||||||
],
|
],
|
||||||
"elm-version": "0.19.0 <= v < 0.20.0",
|
"elm-version": "0.19.0 <= v < 0.20.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"elm/core": "1.0.0 <= v < 2.0.0",
|
"elm/core": "1.0.0 <= v < 2.0.0",
|
||||||
"elm/html": "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",
|
"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"
|
"stil4m/elm-syntax": "7.0.2 <= v < 8.0.0"
|
||||||
},
|
},
|
||||||
"test-dependencies": {
|
"test-dependencies": {
|
||||||
|
@ -9,6 +9,7 @@ import Html.Events exposing (onInput)
|
|||||||
import Lint exposing (Rule, Severity(..), lintSource)
|
import Lint exposing (Rule, Severity(..), lintSource)
|
||||||
import Lint.Error exposing (Error)
|
import Lint.Error exposing (Error)
|
||||||
import Lint.Rule.NoDebug
|
import Lint.Rule.NoDebug
|
||||||
|
import Lint.Rule.NoUnusedVariables
|
||||||
import Result exposing (Result)
|
import Result exposing (Result)
|
||||||
|
|
||||||
|
|
||||||
@ -19,6 +20,7 @@ type Msg
|
|||||||
config : List ( Severity, Rule )
|
config : List ( Severity, Rule )
|
||||||
config =
|
config =
|
||||||
[ ( Critical, Lint.Rule.NoDebug.rule )
|
[ ( Critical, Lint.Rule.NoDebug.rule )
|
||||||
|
, ( Critical, Lint.Rule.NoUnusedVariables.rule )
|
||||||
|
|
||||||
-- , ( Critical, Lint.Rule.DefaultPatternPosition.rule { position = Lint.Rule.DefaultPatternPosition.Last } )
|
-- , ( Critical, Lint.Rule.DefaultPatternPosition.rule { position = Lint.Rule.DefaultPatternPosition.Last } )
|
||||||
-- , ( Critical, Lint.Rule.NoConstantCondition.rule )
|
-- , ( Critical, Lint.Rule.NoConstantCondition.rule )
|
||||||
@ -27,7 +29,6 @@ config =
|
|||||||
-- , ( Critical, Lint.Rule.NoImportingEverything.rule { exceptions = [ "Html" ] } )
|
-- , ( Critical, Lint.Rule.NoImportingEverything.rule { exceptions = [ "Html" ] } )
|
||||||
-- , ( Critical, Lint.Rule.NoNestedLet.rule )
|
-- , ( Critical, Lint.Rule.NoNestedLet.rule )
|
||||||
-- , ( Critical, Lint.Rule.NoUnannotatedFunction.rule )
|
-- , ( Critical, Lint.Rule.NoUnannotatedFunction.rule )
|
||||||
-- , ( Critical, Lint.Rule.NoUnusedVariables.rule )
|
|
||||||
-- , ( Critical, Lint.Rule.NoUselessIf.rule )
|
-- , ( Critical, Lint.Rule.NoUselessIf.rule )
|
||||||
-- , ( Critical, Lint.Rule.NoUselessPatternMatching.rule )
|
-- , ( Critical, Lint.Rule.NoUselessPatternMatching.rule )
|
||||||
-- , ( Warning, Lint.Rule.NoWarningComments.rule )
|
-- , ( Warning, Lint.Rule.NoWarningComments.rule )
|
||||||
|
@ -10,11 +10,13 @@
|
|||||||
"elm/browser": "1.0.1",
|
"elm/browser": "1.0.1",
|
||||||
"elm/core": "1.0.0",
|
"elm/core": "1.0.0",
|
||||||
"elm/html": "1.0.0",
|
"elm/html": "1.0.0",
|
||||||
|
"mgold/elm-nonempty-list": "4.0.0",
|
||||||
"stil4m/elm-syntax": "7.0.2"
|
"stil4m/elm-syntax": "7.0.2"
|
||||||
},
|
},
|
||||||
"indirect": {
|
"indirect": {
|
||||||
"elm/json": "1.0.0",
|
"elm/json": "1.0.0",
|
||||||
"elm/parser": "1.1.0",
|
"elm/parser": "1.1.0",
|
||||||
|
"elm/random": "1.0.0",
|
||||||
"elm/time": "1.0.0",
|
"elm/time": "1.0.0",
|
||||||
"elm/url": "1.0.0",
|
"elm/url": "1.0.0",
|
||||||
"elm/virtual-dom": "1.0.2",
|
"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.File exposing (File)
|
||||||
import Elm.Syntax.Node exposing (Node)
|
import Elm.Syntax.Node exposing (Node)
|
||||||
import Lint.Error exposing (Error)
|
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)
|
import Lint.Rule exposing (Direction, Implementation, Visitor, initialContext)
|
||||||
|
|
||||||
|
|
||||||
@ -127,8 +127,7 @@ parseSource source =
|
|||||||
-}
|
-}
|
||||||
lint : File -> Implementation context -> List Error
|
lint : File -> Implementation context -> List Error
|
||||||
lint file rule =
|
lint file rule =
|
||||||
file.declarations
|
createVisitorsForFile file
|
||||||
|> declarationsIntoVisitors
|
|
||||||
|> lintWithVisitors rule
|
|> 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.Declaration exposing (Declaration(..))
|
||||||
import Elm.Syntax.Expression exposing (Expression(..), Function, FunctionImplementation, LetDeclaration(..))
|
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.Infix exposing (InfixDirection(..))
|
||||||
|
import Elm.Syntax.Module exposing (Module)
|
||||||
import Elm.Syntax.Node exposing (Node, value)
|
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)
|
createExitAndEnterWithChildren : (Direction -> nodeType -> Visitor context) -> nodeType -> List (Visitor context) -> List (Visitor context)
|
||||||
@ -21,11 +25,31 @@ moduleVisitor rule context =
|
|||||||
finalEvaluation 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 Expression -> Visitor context
|
||||||
expressionVisitor direction node rule context =
|
expressionVisitor direction node rule context =
|
||||||
evaluateExpression rule context direction node
|
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 : Function -> Node Expression
|
||||||
functionToExpression { documentation, signature, declaration } =
|
functionToExpression { documentation, signature, declaration } =
|
||||||
let
|
let
|
||||||
@ -71,6 +95,9 @@ expressionToVisitors node =
|
|||||||
ParenthesizedExpression expr ->
|
ParenthesizedExpression expr ->
|
||||||
[ expr ]
|
[ expr ]
|
||||||
|
|
||||||
|
Operator name ->
|
||||||
|
[]
|
||||||
|
|
||||||
OperatorApplication operator direction left right ->
|
OperatorApplication operator direction left right ->
|
||||||
case direction of
|
case direction of
|
||||||
Left ->
|
Left ->
|
||||||
@ -109,6 +136,9 @@ expressionToVisitors node =
|
|||||||
expressions
|
expressions
|
||||||
|
|
||||||
-- TODO Implement the rest
|
-- TODO Implement the rest
|
||||||
|
PrefixOperator name ->
|
||||||
|
[]
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
[]
|
||||||
|
|
||||||
@ -118,24 +148,58 @@ expressionToVisitors node =
|
|||||||
createExitAndEnterWithChildren expressionVisitor node childrenVisitors
|
createExitAndEnterWithChildren expressionVisitor node childrenVisitors
|
||||||
|
|
||||||
|
|
||||||
declarationToVisitors : Declaration -> List (Visitor context)
|
typeAnnotationToVisitor : Node TypeAnnotation -> List (Visitor context)
|
||||||
declarationToVisitors declaration =
|
typeAnnotationToVisitor node =
|
||||||
let
|
let
|
||||||
childrenVisitors =
|
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 ->
|
FunctionDeclaration function ->
|
||||||
functionToExpression function |> expressionToVisitors
|
functionToExpression function |> expressionToVisitors
|
||||||
|
|
||||||
-- TODO Implement the rest
|
-- TODO Implement the rest
|
||||||
|
CustomTypeDeclaration { constructors } ->
|
||||||
|
constructors
|
||||||
|
|> List.concatMap (value >> .arguments)
|
||||||
|
|> List.concatMap typeAnnotationToVisitor
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
[]
|
[]
|
||||||
in
|
in
|
||||||
-- createExitAndEnterWithChildren statementVisitor declaration childrenVisitors
|
createExitAndEnterWithChildren declarationVisitor node childrenVisitors
|
||||||
childrenVisitors
|
|
||||||
|
|
||||||
|
|
||||||
declarationsIntoVisitors : List (Node Declaration) -> List (Visitor context)
|
declarationsIntoVisitors : List (Node Declaration) -> List (Visitor context)
|
||||||
declarationsIntoVisitors declarations =
|
declarationsIntoVisitors declarations =
|
||||||
declarations
|
List.concatMap declarationToVisitors declarations
|
||||||
|> List.concatMap (value >> declarationToVisitors)
|
|
||||||
|> (\allVisitors -> List.append allVisitors [ moduleVisitor ])
|
|
||||||
|
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(..)
|
( Direction(..)
|
||||||
, Implementation, createRule
|
, Implementation, createRule
|
||||||
, Visitor, LintResult
|
, Visitor, LintResult
|
||||||
, evaluateExpression, finalEvaluation, initialContext
|
, evaluateDeclaration, evaluateExpression, evaluateImport, evaluateModuleDefinition, evaluateTypeAnnotation, finalEvaluation, initialContext
|
||||||
)
|
)
|
||||||
|
|
||||||
{-| This module contains functions that are used for writing rules.
|
{-| 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.Expression exposing (Expression)
|
||||||
import Elm.Syntax.File exposing (File)
|
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.Node exposing (Node)
|
||||||
|
import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation)
|
||||||
import Lint.Error exposing (Error)
|
import Lint.Error exposing (Error)
|
||||||
|
|
||||||
|
|
||||||
@ -85,7 +89,11 @@ type Implementation context
|
|||||||
|
|
||||||
|
|
||||||
type alias Visitors 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 )
|
, visitEnd : context -> ( List Error, context )
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +104,11 @@ createRule initContext createVisitors =
|
|||||||
{ initContext = initContext
|
{ initContext = initContext
|
||||||
, visitors =
|
, visitors =
|
||||||
createVisitors
|
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 )
|
, visitEnd = \ctx -> ( [], ctx )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -107,11 +119,31 @@ initialContext (Implementation { initContext }) =
|
|||||||
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 context -> context -> Direction -> Node Expression -> ( List Error, context )
|
||||||
evaluateExpression (Implementation { visitors }) =
|
evaluateExpression (Implementation { visitors }) =
|
||||||
visitors.visitExpression
|
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 context -> context -> ( List Error, context )
|
||||||
finalEvaluation (Implementation { visitors }) =
|
finalEvaluation (Implementation { visitors }) =
|
||||||
visitors.visitEnd
|
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.Error exposing (Error)
|
||||||
import Lint.Rule exposing (LintResult)
|
import Lint.Rule exposing (LintResult)
|
||||||
import Lint.Rule.NoDebug exposing (rule)
|
import Lint.Rule.NoDebug exposing (rule)
|
||||||
import Test exposing (Test, describe, only, test)
|
import Test exposing (Test, describe, test)
|
||||||
import TestUtil exposing (expectErrors, ruleTester)
|
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