elm-review/tests/Review/Rule/NoUnusedVariables.elm

1007 lines
31 KiB
Elm
Raw Normal View History

module Review.Rule.NoUnusedVariables exposing (rule)
2018-11-22 21:19:19 +03:00
2019-07-03 15:19:29 +03:00
{-| Forbid variables or types that are declared or imported but never used.
2018-11-22 21:19:19 +03:00
2019-07-03 15:19:29 +03:00
# Rule
@docs rule
2018-11-22 21:19:19 +03:00
-}
import Dict exposing (Dict)
import Elm.Syntax.Declaration exposing (Declaration(..))
import Elm.Syntax.Exposing exposing (Exposing(..), TopLevelExpose(..))
import Elm.Syntax.Expression exposing (Expression(..), Function, FunctionImplementation, LetDeclaration(..))
2018-11-22 21:19:19 +03:00
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.Module as Module exposing (Module(..))
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
2018-11-22 21:19:19 +03:00
import Elm.Syntax.Range exposing (Range)
import Elm.Syntax.TypeAnnotation exposing (TypeAnnotation(..))
import NonemptyList as Nonempty exposing (Nonempty)
import Review.Fix as Fix
import Review.Rule as Rule exposing (Direction, Error, Rule)
2018-11-22 21:19:19 +03:00
import Set exposing (Set)
2019-07-03 15:19:29 +03:00
{-| Forbid variables or types that are declared or imported but never used.
2018-11-26 13:22:43 +03:00
config =
2019-07-25 15:35:58 +03:00
[ NoUnusedVariables.rule
2018-11-26 13:22:43 +03:00
]
## Fail
2019-08-27 20:18:56 +03:00
module A exposing (a)
a n =
n + 1
b =
a 2
## Success
2019-08-27 20:18:56 +03:00
module A exposing (a)
a n =
n + 1
2018-11-26 13:22:43 +03:00
-}
rule : Rule
2019-06-15 22:14:40 +03:00
rule =
2020-01-19 22:37:19 +03:00
Rule.newModuleRuleSchema "NoUnusedVariables" initialContext
2019-06-24 01:32:27 +03:00
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|> Rule.withImportVisitor importVisitor
|> Rule.withExpressionVisitor expressionVisitor
|> Rule.withDeclarationVisitor declarationVisitor
|> Rule.withFinalModuleEvaluation finalEvaluation
2020-01-19 22:37:19 +03:00
|> Rule.fromModuleRuleSchema
2019-06-24 01:32:27 +03:00
type alias Context =
{ scopes : Nonempty Scope
, exposesEverything : Bool
, constructorNameToTypeName : Dict String String
, declaredModules : Dict String VariableInfo
, usedModules : Set String
2019-06-24 01:32:27 +03:00
}
type alias Scope =
{ declared : Dict String VariableInfo
2019-06-24 01:32:27 +03:00
, used : Set String
}
2018-11-26 13:22:43 +03:00
type alias VariableInfo =
{ variableType : VariableType
, under : Range
, rangeToRemove : Range
}
2018-11-26 21:16:04 +03:00
type VariableType
= TopLevelVariable
| LetVariable
2018-11-26 20:39:46 +03:00
| ImportedModule
| ImportedItem ImportType
2018-11-26 20:39:46 +03:00
| ModuleAlias
| Type
2018-11-26 21:09:18 +03:00
| Port
2018-11-26 20:39:46 +03:00
type LetBlockContext
= HasMultipleDeclarations
| HasNoOtherDeclarations Range
type ImportType
= ImportedVariable
| ImportedType
| ImportedOperator
2019-06-24 01:32:27 +03:00
initialContext : Context
initialContext =
{ scopes = Nonempty.fromElement emptyScope
, exposesEverything = False
, constructorNameToTypeName = Dict.empty
, declaredModules = Dict.empty
, usedModules = Set.empty
2018-11-22 21:19:19 +03:00
}
emptyScope : Scope
emptyScope =
{ declared = Dict.empty
, used = Set.empty
}
2018-11-22 21:19:19 +03:00
error : VariableInfo -> String -> Error {}
error { variableType, under, rangeToRemove } name =
Rule.error
{ message = variableTypeToString variableType ++ " `" ++ name ++ "` is not used" ++ variableTypeWarning variableType
, details = [ "Since it is not being used, I recommend removing it. It should make the code clearer to read for other people." ]
}
under
|> Rule.withFixes [ Fix.removeRange rangeToRemove ]
2018-11-26 20:39:46 +03:00
2018-11-26 21:16:04 +03:00
variableTypeToString : VariableType -> String
variableTypeToString variableType =
case variableType of
TopLevelVariable ->
"Top-level variable"
LetVariable ->
"`let in` variable"
2018-11-26 20:39:46 +03:00
ImportedModule ->
"Imported module"
ImportedItem ImportedVariable ->
2018-11-26 21:16:04 +03:00
"Imported variable"
ImportedItem ImportedType ->
2018-11-26 21:16:04 +03:00
"Imported type"
ImportedItem ImportedOperator ->
2018-11-26 21:16:04 +03:00
"Imported operator"
2018-11-26 20:39:46 +03:00
ModuleAlias ->
"Module alias"
Type ->
"Type"
2018-11-22 21:19:19 +03:00
2018-11-26 21:09:18 +03:00
Port ->
"Port"
2018-11-26 21:16:04 +03:00
variableTypeWarning : VariableType -> String
2018-11-26 21:09:18 +03:00
variableTypeWarning value =
case value of
TopLevelVariable ->
""
LetVariable ->
2018-11-26 21:09:18 +03:00
""
ImportedModule ->
""
ImportedItem _ ->
2018-11-26 21:16:04 +03:00
""
2018-11-26 21:09:18 +03:00
ModuleAlias ->
""
Type ->
""
Port ->
" (Warning: Removing this port may break your application if it is used in the JS code)"
2018-11-22 21:19:19 +03:00
moduleDefinitionVisitor : Node Module -> Context -> ( List nothing, Context )
moduleDefinitionVisitor (Node _ moduleNode) context =
case Module.exposingList moduleNode of
2018-11-22 21:19:19 +03:00
All _ ->
( [], { context | exposesEverything = True } )
2018-11-22 21:19:19 +03:00
Explicit list ->
let
names =
List.filterMap
(\(Node _ node) ->
case node of
2018-11-22 21:19:19 +03:00
FunctionExpose name ->
Just name
TypeOrAliasExpose name ->
Just name
TypeExpose { name } ->
Just name
InfixExpose name ->
-- Just name
Nothing
)
list
in
( [], markAllAsUsed names context )
2018-11-22 21:19:19 +03:00
importVisitor : Node Import -> Context -> ( List nothing, Context )
importVisitor ((Node range { exposingList, moduleAlias, moduleName }) as importNode) context =
case exposingList of
2018-11-26 20:12:36 +03:00
Nothing ->
let
( variableType, Node nameNodeRange nameNodeValue, rangeToRemove ) =
case moduleAlias of
Just moduleAlias_ ->
( ModuleAlias, moduleAlias_, moduleAliasRange importNode (Node.range moduleAlias_) )
2018-11-26 20:39:46 +03:00
Nothing ->
( ImportedModule, moduleName, range )
2018-11-26 20:12:36 +03:00
in
( []
, register
{ variableType = variableType, under = nameNodeRange, rangeToRemove = rangeToRemove }
(getModuleName nameNodeValue)
context
2018-11-26 20:12:36 +03:00
)
2018-11-22 21:19:19 +03:00
2018-11-26 20:12:36 +03:00
Just declaredImports ->
2019-07-25 10:14:33 +03:00
let
contextWithoutImports : Context
contextWithoutImports =
case moduleAlias of
Just (Node moduleAliasRange_ value) ->
2019-07-25 10:14:33 +03:00
register
{ variableType = ModuleAlias
, under = moduleAliasRange_
, rangeToRemove = moduleAliasRange importNode moduleAliasRange_
}
(getModuleName value)
2019-07-25 10:14:33 +03:00
context
Nothing ->
context
in
2018-11-26 20:12:36 +03:00
( []
, List.foldl
(\( name, variableInfo ) context_ -> register variableInfo name context_)
2019-07-25 10:14:33 +03:00
contextWithoutImports
2018-11-26 20:12:36 +03:00
(collectFromExposing declaredImports)
)
2018-11-22 21:19:19 +03:00
moduleAliasRange : Node Import -> Range -> Range
moduleAliasRange (Node _ { moduleName }) range =
{ range | start = (Node.range moduleName).end }
expressionVisitor : Node Expression -> Direction -> Context -> ( List (Error {}), Context )
expressionVisitor (Node range value) direction context =
case ( direction, value ) of
2019-06-26 12:42:17 +03:00
( Rule.OnEnter, FunctionOrValue [] name ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
2019-06-26 12:42:17 +03:00
( Rule.OnEnter, FunctionOrValue moduleName name ) ->
( [], markModuleAsUsed (getModuleName moduleName) context )
2018-11-22 21:19:19 +03:00
2019-06-26 12:42:17 +03:00
( Rule.OnEnter, OperatorApplication name _ _ _ ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
2019-06-26 12:42:17 +03:00
( Rule.OnEnter, PrefixOperator name ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
( Rule.OnEnter, LetExpression { declarations, expression } ) ->
2018-11-26 13:22:43 +03:00
let
letBlockContext : LetBlockContext
letBlockContext =
if List.length declarations == 1 then
HasNoOtherDeclarations <| rangeUpUntil range (Node.range expression |> .start)
else
HasMultipleDeclarations
newContext : Context
2018-11-26 13:22:43 +03:00
newContext =
List.foldl
(\declaration context_ ->
case Node.value declaration of
2018-11-26 13:22:43 +03:00
LetFunction function ->
registerFunction letBlockContext function context_
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
LetDestructuring pattern _ ->
context_
2018-11-26 13:22:43 +03:00
)
{ context | scopes = Nonempty.cons emptyScope context.scopes }
2018-11-26 13:22:43 +03:00
declarations
in
( [], newContext )
( Rule.OnExit, RecordUpdateExpression expr _ ) ->
( [], markAsUsed (Node.value expr) context )
( Rule.OnExit, CaseExpression { cases } ) ->
let
usedVariables : { types : List String, modules : List String }
usedVariables =
cases
|> List.map
(\( patternNode, expressionNode ) ->
getUsedVariablesFromPattern patternNode
)
|> foldUsedTypesAndModules
in
( []
, markUsedTypesAndModules usedVariables context
)
2019-06-26 12:42:17 +03:00
( Rule.OnExit, LetExpression _ ) ->
2018-11-26 13:22:43 +03:00
let
( errors, remainingUsed ) =
makeReport (Nonempty.head context.scopes)
2018-11-26 13:22:43 +03:00
contextWithPoppedScope =
{ context | scopes = Nonempty.pop context.scopes }
2018-11-26 13:22:43 +03:00
in
( errors
, markAllAsUsed remainingUsed contextWithPoppedScope
2018-11-26 13:22:43 +03:00
)
_ ->
( [], context )
2018-11-22 21:19:19 +03:00
getUsedVariablesFromPattern : Node Pattern -> { types : List String, modules : List String }
getUsedVariablesFromPattern patternNode =
{ types = getUsedTypesFromPattern patternNode
, modules = getUsedModulesFromPattern patternNode
}
getUsedTypesFromPattern : Node Pattern -> List String
getUsedTypesFromPattern patternNode =
case Node.value patternNode of
Pattern.AllPattern ->
[]
Pattern.UnitPattern ->
[]
Pattern.CharPattern _ ->
[]
Pattern.StringPattern _ ->
[]
Pattern.IntPattern _ ->
[]
Pattern.HexPattern _ ->
[]
Pattern.FloatPattern _ ->
[]
Pattern.TuplePattern patterns ->
List.concatMap getUsedTypesFromPattern patterns
Pattern.RecordPattern _ ->
[]
Pattern.UnConsPattern pattern1 pattern2 ->
List.concatMap getUsedTypesFromPattern [ pattern1, pattern2 ]
Pattern.ListPattern patterns ->
List.concatMap getUsedTypesFromPattern patterns
Pattern.VarPattern _ ->
[]
Pattern.NamedPattern qualifiedNameRef patterns ->
let
usedVariable : List String
usedVariable =
case qualifiedNameRef.moduleName of
[] ->
[ qualifiedNameRef.name ]
moduleName ->
[]
in
usedVariable ++ List.concatMap getUsedTypesFromPattern patterns
Pattern.AsPattern pattern alias_ ->
getUsedTypesFromPattern pattern
Pattern.ParenthesizedPattern pattern ->
getUsedTypesFromPattern pattern
getUsedModulesFromPattern : Node Pattern -> List String
getUsedModulesFromPattern patternNode =
case Node.value patternNode of
Pattern.AllPattern ->
[]
Pattern.UnitPattern ->
[]
Pattern.CharPattern _ ->
[]
Pattern.StringPattern _ ->
[]
Pattern.IntPattern _ ->
[]
Pattern.HexPattern _ ->
[]
Pattern.FloatPattern _ ->
[]
Pattern.TuplePattern patterns ->
List.concatMap getUsedModulesFromPattern patterns
Pattern.RecordPattern _ ->
[]
Pattern.UnConsPattern pattern1 pattern2 ->
List.concatMap getUsedModulesFromPattern [ pattern1, pattern2 ]
Pattern.ListPattern patterns ->
List.concatMap getUsedModulesFromPattern patterns
Pattern.VarPattern _ ->
[]
Pattern.NamedPattern qualifiedNameRef patterns ->
let
usedVariable : List String
usedVariable =
case qualifiedNameRef.moduleName of
[] ->
[]
moduleName ->
[ getModuleName moduleName ]
in
usedVariable ++ List.concatMap getUsedModulesFromPattern patterns
Pattern.AsPattern pattern alias_ ->
getUsedModulesFromPattern pattern
Pattern.ParenthesizedPattern pattern ->
getUsedModulesFromPattern pattern
declarationVisitor : Node Declaration -> Direction -> Context -> ( List nothing, Context )
2019-06-24 01:32:27 +03:00
declarationVisitor node direction context =
case ( direction, Node.value node ) of
2019-06-26 12:42:17 +03:00
( Rule.OnEnter, FunctionDeclaration function ) ->
2018-11-22 21:19:19 +03:00
let
functionImplementation : FunctionImplementation
functionImplementation =
Node.value function.declaration
2018-11-22 21:19:19 +03:00
namesUsedInSignature : { types : List String, modules : List String }
2018-11-22 21:19:19 +03:00
namesUsedInSignature =
function.signature
|> Maybe.map (Node.value >> .typeAnnotation >> collectNamesFromTypeAnnotation)
|> Maybe.withDefault { types = [], modules = [] }
2018-11-22 21:19:19 +03:00
newContext : Context
2018-11-22 21:19:19 +03:00
newContext =
context
|> register
{ variableType = TopLevelVariable
, under = Node.range functionImplementation.name
, rangeToRemove = Node.range node
}
(Node.value functionImplementation.name)
|> markUsedTypesAndModules namesUsedInSignature
2018-11-22 21:19:19 +03:00
in
( [], newContext )
( Rule.OnEnter, CustomTypeDeclaration { name, constructors } ) ->
let
variablesFromConstructorArguments : { types : List String, modules : List String }
variablesFromConstructorArguments =
constructors
|> List.concatMap (Node.value >> .arguments)
|> List.map collectNamesFromTypeAnnotation
|> foldUsedTypesAndModules
typeName : String
typeName =
Node.value name
constructorsForType : Dict String String
constructorsForType =
constructors
|> List.map (Node.value >> .name >> Node.value)
|> List.map (\constructorName -> ( constructorName, typeName ))
|> Dict.fromList
in
( []
, { context | constructorNameToTypeName = Dict.union constructorsForType context.constructorNameToTypeName }
|> register
{ variableType = Type
, under = Node.range name
, rangeToRemove = Node.range node
}
(Node.value name)
|> markUsedTypesAndModules variablesFromConstructorArguments
)
2018-11-22 21:19:19 +03:00
( Rule.OnEnter, AliasDeclaration { name, typeAnnotation } ) ->
let
namesUsedInTypeAnnotation : { types : List String, modules : List String }
namesUsedInTypeAnnotation =
collectNamesFromTypeAnnotation typeAnnotation
in
( []
, context
|> register
{ variableType = Type
, under = Node.range name
, rangeToRemove = Node.range node
}
(Node.value name)
|> markUsedTypesAndModules namesUsedInTypeAnnotation
)
2018-11-22 21:19:19 +03:00
2019-06-26 12:42:17 +03:00
( Rule.OnEnter, PortDeclaration { name, typeAnnotation } ) ->
let
namesUsedInTypeAnnotation : { types : List String, modules : List String }
namesUsedInTypeAnnotation =
collectNamesFromTypeAnnotation typeAnnotation
in
2018-11-26 21:09:18 +03:00
( []
, context
|> markUsedTypesAndModules namesUsedInTypeAnnotation
|> register
{ variableType = Port
, under = Node.range name
, rangeToRemove = Node.range node
}
(Node.value name)
2018-11-26 21:09:18 +03:00
)
2019-06-26 12:42:17 +03:00
( Rule.OnEnter, InfixDeclaration _ ) ->
( [], context )
2018-11-26 21:09:18 +03:00
2019-06-26 12:42:17 +03:00
( Rule.OnEnter, Destructuring _ _ ) ->
( [], context )
2018-11-26 21:09:18 +03:00
2019-06-26 12:42:17 +03:00
( Rule.OnExit, _ ) ->
( [], context )
2018-11-22 21:19:19 +03:00
foldUsedTypesAndModules : List { types : List String, modules : List String } -> { types : List String, modules : List String }
foldUsedTypesAndModules =
List.foldl (\a b -> { types = a.types ++ b.types, modules = a.modules ++ b.modules }) { types = [], modules = [] }
markUsedTypesAndModules : { types : List String, modules : List String } -> Context -> Context
markUsedTypesAndModules { types, modules } context =
context
|> markAllAsUsed types
|> markAllModulesAsUsed modules
finalEvaluation : Context -> List (Error {})
finalEvaluation context =
if context.exposesEverything then
[]
else
let
rootScope : Scope
rootScope =
Nonempty.head context.scopes
namesOfCustomTypesUsedByCallingAConstructor : Set String
namesOfCustomTypesUsedByCallingAConstructor =
context.constructorNameToTypeName
|> Dict.filter (\usedName _ -> Set.member usedName rootScope.used)
|> Dict.values
|> Set.fromList
newRootScope : Scope
newRootScope =
{ rootScope | used = Set.union namesOfCustomTypesUsedByCallingAConstructor rootScope.used }
moduleErrors : List (Error {})
moduleErrors =
context.declaredModules
|> Dict.filter (\key _ -> not <| Set.member key context.usedModules)
|> Dict.toList
|> List.map (\( key, variableInfo ) -> error variableInfo key)
in
List.concat
[ newRootScope
|> makeReport
|> Tuple.first
, moduleErrors
]
2018-11-26 13:22:43 +03:00
registerFunction : LetBlockContext -> Function -> Context -> Context
registerFunction letBlockContext function context =
2018-11-26 13:22:43 +03:00
let
declaration : FunctionImplementation
2018-11-26 13:22:43 +03:00
declaration =
Node.value function.declaration
namesUsedInSignature : { types : List String, modules : List String }
namesUsedInSignature =
case Maybe.map Node.value function.signature of
Just signature ->
collectNamesFromTypeAnnotation signature.typeAnnotation
Nothing ->
{ types = [], modules = [] }
functionRange : Range
functionRange =
case function.signature of
Just signature ->
mergeRanges
(Node.range function.declaration)
(Node.range signature)
Nothing ->
Node.range function.declaration
2018-11-26 13:22:43 +03:00
in
context
|> register
{ variableType = LetVariable
, under = Node.range declaration.name
, rangeToRemove =
case letBlockContext of
HasMultipleDeclarations ->
functionRange
HasNoOtherDeclarations letDeclarationsRange ->
-- If there are no other declarations in the let in block,
-- we also need to remove the `let in` keywords.
letDeclarationsRange
}
(Node.value declaration.name)
|> markUsedTypesAndModules namesUsedInSignature
2018-11-26 13:22:43 +03:00
collectFromExposing : Node Exposing -> List ( String, VariableInfo )
collectFromExposing exposingNode =
case Node.value exposingNode of
2018-11-26 13:22:43 +03:00
All _ ->
[]
Explicit list ->
let
listWithPreviousRange : List (Maybe Range)
listWithPreviousRange =
Nothing
:: (list
|> List.map (Node.range >> Just)
|> List.take (List.length list - 1)
)
listWithNextRange : List Range
listWithNextRange =
(list
|> List.map Node.range
|> List.drop 1
)
++ [ { start = { row = 0, column = 0 }, end = { row = 0, column = 0 } } ]
in
list
|> List.map3 (\prev next current -> ( prev, current, next )) listWithPreviousRange listWithNextRange
|> List.indexedMap
(\index ( maybePreviousRange, Node range value, nextRange ) ->
let
rangeToRemove : Range
rangeToRemove =
if List.length list == 1 then
Node.range exposingNode
else if index == 0 then
{ range | end = nextRange.start }
else
case maybePreviousRange of
Nothing ->
range
Just previousRange ->
{ range | start = previousRange.end }
in
case value of
FunctionExpose name ->
Just ( name, { variableType = ImportedItem ImportedVariable, under = range, rangeToRemove = rangeToRemove } )
InfixExpose name ->
Just ( name, { variableType = ImportedItem ImportedOperator, under = range, rangeToRemove = rangeToRemove } )
TypeOrAliasExpose name ->
Just ( name, { variableType = ImportedItem ImportedType, under = range, rangeToRemove = rangeToRemove } )
TypeExpose { name, open } ->
case open of
Just openRange ->
Nothing
Nothing ->
Just ( name, { variableType = ImportedItem ImportedType, under = range, rangeToRemove = rangeToRemove } )
)
|> List.filterMap identity
2018-11-26 13:22:43 +03:00
collectNamesFromTypeAnnotation : Node TypeAnnotation -> { types : List String, modules : List String }
2018-11-22 21:19:19 +03:00
collectNamesFromTypeAnnotation node =
{ types = collectTypesFromTypeAnnotation node
, modules = collectModuleNamesFromTypeAnnotation node
}
collectTypesFromTypeAnnotation : Node TypeAnnotation -> List String
collectTypesFromTypeAnnotation node =
case Node.value node of
FunctionTypeAnnotation a b ->
collectTypesFromTypeAnnotation a ++ collectTypesFromTypeAnnotation b
Typed nameNode params ->
let
name : List String
name =
case Node.value nameNode of
( [], str ) ->
[ str ]
( moduleName, _ ) ->
[]
in
name ++ List.concatMap collectTypesFromTypeAnnotation params
Record list ->
list
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectTypesFromTypeAnnotation
GenericRecord name list ->
list
|> Node.value
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectTypesFromTypeAnnotation
Tupled list ->
List.concatMap collectTypesFromTypeAnnotation list
GenericType _ ->
[]
Unit ->
[]
collectModuleNamesFromTypeAnnotation : Node TypeAnnotation -> List String
collectModuleNamesFromTypeAnnotation node =
case Node.value node of
2018-11-22 21:19:19 +03:00
FunctionTypeAnnotation a b ->
collectModuleNamesFromTypeAnnotation a ++ collectModuleNamesFromTypeAnnotation b
2018-11-22 21:19:19 +03:00
Typed nameNode params ->
let
name : List String
2018-11-22 21:19:19 +03:00
name =
case Node.value nameNode of
2018-11-26 21:09:18 +03:00
( [], str ) ->
[]
2018-11-26 21:09:18 +03:00
( moduleName, _ ) ->
[ getModuleName moduleName ]
2018-11-22 21:19:19 +03:00
in
name ++ List.concatMap collectModuleNamesFromTypeAnnotation params
2018-11-22 21:19:19 +03:00
Record list ->
list
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectModuleNamesFromTypeAnnotation
2018-11-22 21:19:19 +03:00
GenericRecord name list ->
list
|> Node.value
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectModuleNamesFromTypeAnnotation
2018-11-22 21:19:19 +03:00
2018-11-26 21:09:18 +03:00
Tupled list ->
List.concatMap collectModuleNamesFromTypeAnnotation list
2018-11-26 21:09:18 +03:00
GenericType _ ->
[]
Unit ->
2018-11-22 21:19:19 +03:00
[]
register : VariableInfo -> String -> Context -> Context
register variableInfo name context =
case variableInfo.variableType of
TopLevelVariable ->
registerVariable variableInfo name context
LetVariable ->
registerVariable variableInfo name context
ImportedModule ->
registerModule variableInfo name context
ImportedItem _ ->
registerVariable variableInfo name context
ModuleAlias ->
registerModule variableInfo name context
Type ->
registerVariable variableInfo name context
Port ->
registerVariable variableInfo name context
registerModule : VariableInfo -> String -> Context -> Context
registerModule variableInfo name context =
{ context | declaredModules = Dict.insert name variableInfo context.declaredModules }
registerVariable : VariableInfo -> String -> Context -> Context
registerVariable variableInfo name context =
let
scopes : Nonempty Scope
scopes =
Nonempty.mapHead
(\scope ->
{ scope | declared = Dict.insert name variableInfo scope.declared }
)
context.scopes
in
{ context | scopes = scopes }
2018-11-22 21:19:19 +03:00
2018-11-26 13:22:43 +03:00
markAllAsUsed : List String -> Context -> Context
markAllAsUsed names context =
List.foldl markAsUsed context names
2018-11-26 13:22:43 +03:00
2018-11-22 21:19:19 +03:00
markAsUsed : String -> Context -> Context
markAsUsed name context =
2018-11-22 21:19:19 +03:00
let
scopes : Nonempty Scope
2018-11-22 21:19:19 +03:00
scopes =
Nonempty.mapHead
2018-11-22 21:19:19 +03:00
(\scope ->
{ scope | used = Set.insert name scope.used }
)
context.scopes
2018-11-22 21:19:19 +03:00
in
{ context | scopes = scopes }
2018-11-22 21:19:19 +03:00
markAllModulesAsUsed : List String -> Context -> Context
markAllModulesAsUsed names context =
{ context | usedModules = Set.union (Set.fromList names) context.usedModules }
markModuleAsUsed : String -> Context -> Context
markModuleAsUsed name context =
{ context | usedModules = Set.insert name context.usedModules }
2018-11-26 13:22:43 +03:00
getModuleName : List String -> String
getModuleName name =
String.join "." name
2018-11-22 21:19:19 +03:00
makeReport : Scope -> ( List (Error {}), List String )
2018-11-22 21:19:19 +03:00
makeReport { declared, used } =
let
2019-06-24 01:32:27 +03:00
nonUsedVars : List String
2018-11-22 21:19:19 +03:00
nonUsedVars =
Set.diff used (Set.fromList <| Dict.keys declared)
|> Set.toList
errors : List (Error {})
2018-11-22 21:19:19 +03:00
errors =
Dict.filter (\key _ -> not <| Set.member key used) declared
|> Dict.toList
|> List.map (\( key, variableInfo ) -> error variableInfo key)
2018-11-22 21:19:19 +03:00
in
( errors, nonUsedVars )
-- RANGE MANIPULATION
{-| Create a new range that starts at the start of the range that starts first,
and ends at the end of the range that starts last. If the two ranges are distinct
and there is code in between, that code will be included in the resulting range.
range : Range
range =
Fix.mergeRanges
(Node.range node1)
(Node.range node2)
-}
mergeRanges : Range -> Range -> Range
mergeRanges a b =
let
start : { row : Int, column : Int }
start =
case comparePosition a.start b.start of
LT ->
a.start
EQ ->
a.start
GT ->
b.start
end : { row : Int, column : Int }
end =
case comparePosition a.end b.end of
LT ->
b.end
EQ ->
b.end
GT ->
a.end
in
{ start = start, end = end }
{-| Make a range stop at a position. If the position is not inside the range,
then the range won't change.
range : Range
range =
Fix.rangeUpUntil
(Node.range node)
(node |> Node.value |> .typeAnnotation |> Node.range |> .start)
-}
rangeUpUntil : Range -> { row : Int, column : Int } -> Range
rangeUpUntil range position =
let
positionAsInt_ : Int
positionAsInt_ =
positionAsInt position
in
if positionAsInt range.start <= positionAsInt_ && positionAsInt range.end >= positionAsInt_ then
{ range | end = position }
else
range
positionAsInt : { row : Int, column : Int } -> Int
positionAsInt { row, column } =
-- This is a quick and simple heuristic to be able to sort ranges.
-- It is entirely based on the assumption that no line is longer than
-- 1.000.000 characters long. Then, as long as ranges don't overlap,
-- this should work fine.
row * 1000000 + column
comparePosition : { row : Int, column : Int } -> { row : Int, column : Int } -> Order
comparePosition a b =
let
order : Order
order =
compare a.row b.row
in
case order of
EQ ->
compare a.column b.column
_ ->
order