Add NoUnusedVariables rule

This commit is contained in:
Jeroen Engels 2018-11-22 19:19:19 +01:00
parent d93474a62e
commit 06d6e5f247
9 changed files with 920 additions and 20 deletions

View File

@ -7,16 +7,18 @@
"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": {
"elm-explorations/test": "1.1.0 <= v < 2.0.0"
}
}
}

View File

@ -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 )

View File

@ -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",

View File

@ -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

View File

@ -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 ]

View File

@ -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

View 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

View File

@ -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)

View 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