elm-review/tests/NoUnused/Variables.elm

1173 lines
37 KiB
Elm
Raw Normal View History

module NoUnused.Variables exposing (rule)
2018-11-22 21:19:19 +03:00
{-| Report variables or types that are declared or imported but never used inside of a module.
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)
2020-05-14 22:01:53 +03:00
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Exposing as Exposing exposing (Exposing)
import Elm.Syntax.Expression as Expression exposing (Expression, Function, FunctionImplementation)
2018-11-22 21:19:19 +03:00
import Elm.Syntax.Import exposing (Import)
2020-05-14 22:01:53 +03:00
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)
2020-05-14 22:01:53 +03:00
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
import NoUnused.NonemptyList as NonemptyList exposing (Nonempty)
2020-04-04 13:58:26 +03:00
import Review.Fix as Fix exposing (Fix)
import Review.Rule as Rule exposing (Direction, Error, Rule)
2018-11-22 21:19:19 +03:00
import Set exposing (Set)
2020-03-27 22:48:27 +03:00
{-| Report variables or types that are declared or imported but never used.
config =
2020-03-27 22:48:27 +03:00
[ NoUnused.Variables.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-03-27 22:48:27 +03:00
Rule.newModuleRuleSchema "NoUnused.Variables" 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
2020-03-27 22:48:27 +03:00
, inTheDeclarationOf : Maybe String
2019-06-24 01:32:27 +03:00
, 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
2020-03-27 22:48:27 +03:00
| ModuleAlias { originalNameOfTheImport : String, exposesSomething : Bool }
2018-11-26 20:39:46 +03:00
| 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 =
2020-03-27 22:48:27 +03:00
{ scopes = NonemptyList.fromElement emptyScope
, inTheDeclarationOf = Nothing
2019-06-24 01:32:27 +03:00
, 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
2020-03-27 22:48:27 +03:00
error : Dict String VariableInfo -> VariableInfo -> String -> Error {}
error declaredModules variableInfo name =
2020-04-04 13:58:26 +03:00
Rule.errorWithFix
2020-03-27 22:48:27 +03:00
{ message = variableTypeToString variableInfo.variableType ++ " `" ++ name ++ "` is not used" ++ variableTypeWarning variableInfo.variableType
, details = [ "You should either use this value somewhere, or remove it at the location I pointed at." ]
}
2020-03-27 22:48:27 +03:00
variableInfo.under
2020-04-04 13:58:26 +03:00
(fix declaredModules variableInfo)
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"
2020-03-27 22:48:27 +03:00
ModuleAlias _ ->
2018-11-26 20:39:46 +03:00
"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
""
2020-03-27 22:48:27 +03:00
ModuleAlias _ ->
2018-11-26 21:09:18 +03:00
""
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
2020-04-04 13:58:26 +03:00
fix : Dict String VariableInfo -> VariableInfo -> List Fix
fix declaredModules { variableType, rangeToRemove } =
2020-03-27 22:48:27 +03:00
let
shouldOfferFix : Bool
shouldOfferFix =
case variableType of
TopLevelVariable ->
True
LetVariable ->
True
ImportedModule ->
True
ImportedItem _ ->
True
ModuleAlias { originalNameOfTheImport, exposesSomething } ->
not exposesSomething
|| not (Dict.member originalNameOfTheImport declaredModules)
Type ->
True
Port ->
True
in
if shouldOfferFix then
2020-04-04 13:58:26 +03:00
[ Fix.removeRange rangeToRemove ]
2020-03-27 22:48:27 +03:00
else
2020-04-04 13:58:26 +03:00
[]
2020-03-27 22:48:27 +03:00
moduleDefinitionVisitor : Node Module -> Context -> ( List nothing, Context )
moduleDefinitionVisitor (Node _ moduleNode) context =
case Module.exposingList moduleNode of
2020-05-14 22:01:53 +03:00
Exposing.All _ ->
( [], { context | exposesEverything = True } )
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
Exposing.Explicit list ->
2018-11-22 21:19:19 +03:00
let
names =
List.filterMap
(\(Node _ node) ->
case node of
2020-05-14 22:01:53 +03:00
Exposing.FunctionExpose name ->
2018-11-22 21:19:19 +03:00
Just name
2020-05-14 22:01:53 +03:00
Exposing.TypeOrAliasExpose name ->
2018-11-22 21:19:19 +03:00
Just name
2020-05-14 22:01:53 +03:00
Exposing.TypeExpose { name } ->
2018-11-22 21:19:19 +03:00
Just name
2020-05-14 22:01:53 +03:00
Exposing.InfixExpose name ->
2018-11-22 21:19:19 +03:00
-- Just name
Nothing
)
list
in
( [], markAllAsUsed names context )
2018-11-22 21:19:19 +03:00
2020-03-27 22:48:27 +03:00
importVisitor : Node Import -> Context -> ( List (Error {}), Context )
2020-05-14 22:01:53 +03:00
importVisitor ((Node _ import_) as node) context =
let
errors : List (Error {})
errors =
case import_.moduleAlias of
Just moduleAlias ->
if Node.value moduleAlias == Node.value import_.moduleName then
[ Rule.errorWithFix
{ message = "Module `Html` is aliased as `Html`"
, details = [ "The alias is the same as the module name, and brings no useful value" ]
}
(Node.range moduleAlias)
[ Fix.removeRange <| moduleAliasRange node (Node.range moduleAlias) ]
]
else
[]
Nothing ->
[]
in
case import_.exposingList of
2018-11-26 20:12:36 +03:00
Nothing ->
2020-05-14 22:01:53 +03:00
( errors, registerModuleNameOrAlias node context )
2018-11-22 21:19:19 +03:00
2018-11-26 20:12:36 +03:00
Just declaredImports ->
2020-05-14 22:01:53 +03:00
( errors
2018-11-26 20:12:36 +03:00
, List.foldl
(\( name, variableInfo ) context_ -> register variableInfo name context_)
2020-03-27 22:48:27 +03:00
(registerModuleAlias node context)
2018-11-26 20:12:36 +03:00
(collectFromExposing declaredImports)
)
2018-11-22 21:19:19 +03:00
2020-03-27 22:48:27 +03:00
registerModuleNameOrAlias : Node Import -> Context -> Context
registerModuleNameOrAlias ((Node range { exposingList, moduleAlias, moduleName }) as node) context =
case moduleAlias of
Just _ ->
registerModuleAlias node context
Nothing ->
register
{ variableType = ImportedModule
, under = Node.range moduleName
, rangeToRemove = untilStartOfNextLine range
}
(getModuleName <| Node.value moduleName)
context
registerModuleAlias : Node Import -> Context -> Context
registerModuleAlias ((Node range { exposingList, moduleAlias, moduleName }) as node) context =
case moduleAlias of
Just moduleAlias_ ->
register
{ variableType =
ModuleAlias
{ originalNameOfTheImport = getModuleName <| Node.value moduleName
, exposesSomething = exposingList /= Nothing
}
, under = Node.range moduleAlias_
, rangeToRemove =
case exposingList of
Nothing ->
untilStartOfNextLine range
Just _ ->
moduleAliasRange node (Node.range moduleAlias_)
}
(getModuleName <| Node.value moduleAlias_)
context
Nothing ->
context
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
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Expression.FunctionOrValue [] name ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Expression.FunctionOrValue moduleName name ) ->
( [], markModuleAsUsed (getModuleName moduleName) context )
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Expression.OperatorApplication name _ _ _ ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Expression.PrefixOperator name ) ->
( [], markAsUsed name context )
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Expression.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
2020-05-14 22:01:53 +03:00
Expression.LetFunction function ->
2020-03-27 22:48:27 +03:00
let
namesUsedInArgumentPatterns : { types : List String, modules : List String }
namesUsedInArgumentPatterns =
function.declaration
|> Node.value
|> .arguments
|> List.map getUsedVariablesFromPattern
|> foldUsedTypesAndModules
in
context_
|> registerFunction letBlockContext function
|> markUsedTypesAndModules namesUsedInArgumentPatterns
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
Expression.LetDestructuring pattern _ ->
context_
2018-11-26 13:22:43 +03:00
)
2020-03-27 22:48:27 +03:00
{ context | scopes = NonemptyList.cons emptyScope context.scopes }
2018-11-26 13:22:43 +03:00
declarations
in
( [], newContext )
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Expression.LambdaExpression { args } ) ->
2020-03-27 22:48:27 +03:00
let
namesUsedInArgumentPatterns : { types : List String, modules : List String }
namesUsedInArgumentPatterns =
args
|> List.map getUsedVariablesFromPattern
|> foldUsedTypesAndModules
in
( [], markUsedTypesAndModules namesUsedInArgumentPatterns context )
2020-05-14 22:01:53 +03:00
( Rule.OnExit, Expression.RecordUpdateExpression expr _ ) ->
( [], markAsUsed (Node.value expr) context )
2020-05-14 22:01:53 +03:00
( Rule.OnExit, Expression.CaseExpression { cases } ) ->
let
usedVariables : { types : List String, modules : List String }
usedVariables =
cases
|> List.map
(\( patternNode, expressionNode ) ->
getUsedVariablesFromPattern patternNode
)
|> foldUsedTypesAndModules
in
( []
, markUsedTypesAndModules usedVariables context
)
2020-05-14 22:01:53 +03:00
( Rule.OnExit, Expression.LetExpression _ ) ->
2018-11-26 13:22:43 +03:00
let
( errors, remainingUsed ) =
2020-03-27 22:48:27 +03:00
makeReport (NonemptyList.head context.scopes)
2018-11-26 13:22:43 +03:00
contextWithPoppedScope =
2020-03-27 22:48:27 +03:00
{ context | scopes = NonemptyList.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
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Declaration.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
2020-03-27 22:48:27 +03:00
namesUsedInArgumentPatterns : { types : List String, modules : List String }
namesUsedInArgumentPatterns =
function.declaration
|> Node.value
|> .arguments
|> List.map getUsedVariablesFromPattern
|> foldUsedTypesAndModules
newContext : Context
2018-11-22 21:19:19 +03:00
newContext =
2020-03-27 22:48:27 +03:00
{ context | inTheDeclarationOf = Just <| Node.value functionImplementation.name }
|> register
{ variableType = TopLevelVariable
, under = Node.range functionImplementation.name
2020-03-27 22:48:27 +03:00
, rangeToRemove = rangeToRemoveForNodeWithDocumentation node function.documentation
}
(Node.value functionImplementation.name)
|> markUsedTypesAndModules namesUsedInSignature
2020-03-27 22:48:27 +03:00
|> markUsedTypesAndModules namesUsedInArgumentPatterns
2018-11-22 21:19:19 +03:00
in
( [], newContext )
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Declaration.CustomTypeDeclaration { name, documentation, 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
2020-03-27 22:48:27 +03:00
, rangeToRemove = rangeToRemoveForNodeWithDocumentation node documentation
}
(Node.value name)
|> markUsedTypesAndModules variablesFromConstructorArguments
)
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Declaration.AliasDeclaration { name, typeAnnotation, documentation } ) ->
let
namesUsedInTypeAnnotation : { types : List String, modules : List String }
namesUsedInTypeAnnotation =
collectNamesFromTypeAnnotation typeAnnotation
in
( []
, context
|> register
{ variableType = Type
, under = Node.range name
2020-03-27 22:48:27 +03:00
, rangeToRemove = rangeToRemoveForNodeWithDocumentation node documentation
}
(Node.value name)
|> markUsedTypesAndModules namesUsedInTypeAnnotation
)
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Declaration.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
)
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Declaration.InfixDeclaration _ ) ->
( [], context )
2018-11-26 21:09:18 +03:00
2020-05-14 22:01:53 +03:00
( Rule.OnEnter, Declaration.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
2020-03-27 22:48:27 +03:00
rangeToRemoveForNodeWithDocumentation : Node Declaration -> Maybe (Node a) -> Range
rangeToRemoveForNodeWithDocumentation (Node nodeRange _) documentation =
case documentation of
Nothing ->
untilStartOfNextLine nodeRange
Just (Node documentationRange _) ->
untilStartOfNextLine
{ start = documentationRange.start
, end = nodeRange.end
}
finalEvaluation : Context -> List (Error {})
finalEvaluation context =
if context.exposesEverything then
[]
else
let
rootScope : Scope
rootScope =
2020-03-27 22:48:27 +03:00
NonemptyList.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
2020-03-27 22:48:27 +03:00
|> List.map (\( key, variableInfo ) -> error context.declaredModules 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
2020-05-14 22:01:53 +03:00
Exposing.All _ ->
2018-11-26 13:22:43 +03:00
[]
2020-05-14 22:01:53 +03:00
Exposing.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
2020-05-14 22:01:53 +03:00
Exposing.FunctionExpose name ->
Just
( name
, { variableType = ImportedItem ImportedVariable
, under = untilEndOfVariable name range
, rangeToRemove = rangeToRemove
}
)
Exposing.InfixExpose name ->
Just
( name
, { variableType = ImportedItem ImportedOperator
, under = untilEndOfVariable name range
, rangeToRemove = rangeToRemove
}
)
Exposing.TypeOrAliasExpose name ->
Just
( name
, { variableType = ImportedItem ImportedType
, under = untilEndOfVariable name range
, rangeToRemove = rangeToRemove
}
)
Exposing.TypeExpose { name, open } ->
case open of
Just openRange ->
2020-03-27 22:48:27 +03:00
-- TODO Change this behavior once we know the contents of the open range, using dependencies or the interfaces of the other modules
Nothing
Nothing ->
2020-05-14 22:01:53 +03:00
Just
( name
, { variableType = ImportedItem ImportedType
, under = range
, rangeToRemove = rangeToRemove
}
)
)
|> List.filterMap identity
2018-11-26 13:22:43 +03:00
2020-05-14 22:01:53 +03:00
untilEndOfVariable : String -> Range -> Range
untilEndOfVariable name range =
if range.start.row == range.end.row then
range
else
{ range | end = { row = range.start.row, column = range.start.column + String.length name } }
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
2020-05-14 22:01:53 +03:00
TypeAnnotation.FunctionTypeAnnotation a b ->
collectTypesFromTypeAnnotation a ++ collectTypesFromTypeAnnotation b
2020-05-14 22:01:53 +03:00
TypeAnnotation.Typed nameNode params ->
let
name : List String
name =
case Node.value nameNode of
( [], str ) ->
[ str ]
( moduleName, _ ) ->
[]
in
name ++ List.concatMap collectTypesFromTypeAnnotation params
2020-05-14 22:01:53 +03:00
TypeAnnotation.Record list ->
list
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectTypesFromTypeAnnotation
2020-05-14 22:01:53 +03:00
TypeAnnotation.GenericRecord name list ->
list
|> Node.value
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectTypesFromTypeAnnotation
2020-05-14 22:01:53 +03:00
TypeAnnotation.Tupled list ->
List.concatMap collectTypesFromTypeAnnotation list
2020-05-14 22:01:53 +03:00
TypeAnnotation.GenericType _ ->
[]
2020-05-14 22:01:53 +03:00
TypeAnnotation.Unit ->
[]
collectModuleNamesFromTypeAnnotation : Node TypeAnnotation -> List String
collectModuleNamesFromTypeAnnotation node =
case Node.value node of
2020-05-14 22:01:53 +03:00
TypeAnnotation.FunctionTypeAnnotation a b ->
collectModuleNamesFromTypeAnnotation a ++ collectModuleNamesFromTypeAnnotation b
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
TypeAnnotation.Typed nameNode params ->
2018-11-22 21:19:19 +03:00
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
2020-05-14 22:01:53 +03:00
TypeAnnotation.Record list ->
2018-11-22 21:19:19 +03:00
list
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectModuleNamesFromTypeAnnotation
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
TypeAnnotation.GenericRecord name list ->
2018-11-22 21:19:19 +03:00
list
|> Node.value
|> List.map (Node.value >> Tuple.second)
|> List.concatMap collectModuleNamesFromTypeAnnotation
2018-11-22 21:19:19 +03:00
2020-05-14 22:01:53 +03:00
TypeAnnotation.Tupled list ->
List.concatMap collectModuleNamesFromTypeAnnotation list
2018-11-26 21:09:18 +03:00
2020-05-14 22:01:53 +03:00
TypeAnnotation.GenericType _ ->
2018-11-26 21:09:18 +03:00
[]
2020-05-14 22:01:53 +03:00
TypeAnnotation.Unit ->
2018-11-22 21:19:19 +03:00
[]
register : VariableInfo -> String -> Context -> Context
register variableInfo name context =
case variableInfo.variableType of
TopLevelVariable ->
2020-03-27 22:48:27 +03:00
-- The main function is "exposed" by default
if name == "main" then
context
else
registerVariable variableInfo name context
LetVariable ->
registerVariable variableInfo name context
ImportedModule ->
registerModule variableInfo name context
ImportedItem _ ->
registerVariable variableInfo name context
2020-03-27 22:48:27 +03:00
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 =
2020-03-27 22:48:27 +03:00
NonemptyList.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 =
2020-03-27 22:48:27 +03:00
if context.inTheDeclarationOf == Just name then
context
else
let
scopes : Nonempty Scope
scopes =
NonemptyList.mapHead
(\scope ->
{ scope | used = Set.insert name scope.used }
)
context.scopes
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
2020-03-27 22:48:27 +03:00
|> List.map (\( key, variableInfo ) -> error Dict.empty variableInfo key)
2018-11-22 21:19:19 +03:00
in
( errors, nonUsedVars )
-- RANGE MANIPULATION
2020-03-27 22:48:27 +03:00
{-| Include everything until the line after the end.
-}
untilStartOfNextLine : Range -> Range
untilStartOfNextLine range =
if range.end.column == 1 then
range
else
{ range | end = { row = range.end.row + 1, column = 1 } }
{-| 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 =
2020-03-27 22:48:27 +03:00
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