Use Scope2 instead of Scope

This commit is contained in:
Jeroen Engels 2020-03-27 17:43:16 +01:00
parent ab360b6db1
commit 69c6f4e515
5 changed files with 57 additions and 873 deletions

View File

@ -4,11 +4,11 @@ import Elm.Syntax.Expression exposing (Expression(..))
import Elm.Syntax.Module as Module exposing (Module)
import Elm.Syntax.Node as Node exposing (Node)
import Review.Rule as Rule exposing (Direction, Error, Rule)
import Scope
import Scope2 as Scope
type alias Context =
{ scope : Scope.Context
{ scope : Scope.ModuleContext
, allowed : Allowed
}
@ -21,7 +21,7 @@ type Allowed
rule : Rule
rule =
Rule.newModuleRuleSchema "NoHtmlButton" initialContext
|> Scope.addVisitors
|> Scope.addModuleVisitors
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|> Rule.withExpressionVisitor expressionVisitor
|> Rule.fromModuleRuleSchema
@ -29,7 +29,7 @@ rule =
initialContext : Context
initialContext =
{ scope = Scope.initialContext
{ scope = Scope.initialModuleContext
, allowed = HtmlButtonIsForbidden
}

View File

@ -1,679 +0,0 @@
module Scope exposing
( Context
, initialContext, addVisitors
, realFunctionOrType
)
{-| Report variables or types that are declared or imported but never used.
# Definition
@docs Context
# Usage
@docs initialContext, addVisitors
# Access
@docs realFunctionOrType
-}
import Dict exposing (Dict)
import Elm.Docs
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Exposing as Exposing exposing (Exposing, TopLevelExpose)
import Elm.Syntax.Expression as Expression exposing (Expression)
import Elm.Syntax.Import exposing (Import)
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
import Elm.Syntax.Range as Range
import NonemptyList exposing (Nonempty)
import Review.Project.Dependency as Dependency exposing (Dependency)
import Review.Rule as Rule exposing (Direction)
-- DEFINITION
type Context
= Context InnerContext
type alias InnerContext =
{ scopes : Nonempty Scope
, importAliases : Dict String (List String)
, importedFunctionOrTypes : Dict String (List String)
, dependenciesModules : Dict String Elm.Docs.Module
}
type alias Scope =
{ names : Dict String VariableInfo
, cases : List ( Node Expression, Dict String VariableInfo )
, caseToExit : Node Expression
}
-- USAGE
initialContext : Context
initialContext =
Context
{ scopes = NonemptyList.fromElement emptyScope
, importAliases = Dict.empty
, importedFunctionOrTypes = Dict.empty
, dependenciesModules = Dict.empty
}
emptyScope : Scope
emptyScope =
{ names = Dict.empty
, cases = []
, caseToExit = Node Range.emptyRange (Expression.Literal "root")
}
addVisitors :
Rule.ModuleRuleSchema { anything | canCollectProjectData : () } { context | scope : Context }
-> Rule.ModuleRuleSchema { anything | canCollectProjectData : (), hasAtLeastOneVisitor : () } { context | scope : Context }
addVisitors schema =
schema
|> Rule.withDependenciesModuleVisitor
(mapInnerContext dependenciesVisitor)
|> Rule.withImportVisitor
(mapInnerContext importVisitor |> pairWithNoErrors)
|> Rule.withDeclarationListVisitor
(mapInnerContext declarationListVisitor |> pairWithNoErrors)
|> Rule.withDeclarationVisitor
(\visitedElement direction outerContext ->
let
innerContext : InnerContext
innerContext =
outerContext.scope
|> unbox
|> declarationVisitor visitedElement direction
in
( [], { outerContext | scope = Context innerContext } )
)
|> Rule.withExpressionVisitor
(\visitedElement direction outerContext ->
let
innerContext : InnerContext
innerContext =
outerContext.scope
|> unbox
|> popScope visitedElement direction
|> expressionVisitor visitedElement direction
in
( [], { outerContext | scope = Context innerContext } )
)
mapInnerContext : (visitedElement -> InnerContext -> InnerContext) -> visitedElement -> { context | scope : Context } -> { context | scope : Context }
mapInnerContext visitor visitedElement outerContext =
let
innerContext : InnerContext
innerContext =
outerContext.scope
|> unbox
|> visitor visitedElement
in
{ outerContext | scope = Context innerContext }
pairWithNoErrors : (visited -> context -> context) -> visited -> context -> ( List nothing, context )
pairWithNoErrors fn visited context =
( [], fn visited context )
-- DEPENDENCIES
dependenciesVisitor : Dict String Dependency -> InnerContext -> InnerContext
dependenciesVisitor dependencies innerContext =
let
dependenciesModules : Dict String Elm.Docs.Module
dependenciesModules =
dependencies
|> Dict.values
|> List.concatMap Dependency.modules
|> List.map (\dependencyModule -> ( dependencyModule.name, dependencyModule ))
|> Dict.fromList
in
{ innerContext | dependenciesModules = dependenciesModules }
|> registerPrelude
registerPrelude : InnerContext -> InnerContext
registerPrelude innerContext =
List.foldl registerExposed innerContext elmCorePrelude
elmCorePrelude : List Import
elmCorePrelude =
let
explicit : List TopLevelExpose -> Maybe Exposing
explicit exposed =
exposed
|> List.map (Node Range.emptyRange)
|> Exposing.Explicit
|> Just
in
-- These are the default imports implicitly added by the Elm compiler
-- https://package.elm-lang.org/packages/elm/core/latest
[ createFakeImport
{ moduleName = [ "Basics" ]
, moduleAlias = Nothing
, exposingList = Just <| Exposing.All Range.emptyRange
}
, createFakeImport
{ moduleName = [ "List" ]
, moduleAlias = Nothing
, exposingList =
explicit
[ Exposing.TypeExpose { name = "List", open = Nothing }
, Exposing.InfixExpose "::"
]
}
, createFakeImport
{ moduleName = [ "Maybe" ]
, moduleAlias = Nothing
, exposingList =
explicit
[ Exposing.TypeExpose { name = "Maybe", open = Just Range.emptyRange }
]
}
, createFakeImport
{ moduleName = [ "Result" ]
, moduleAlias = Nothing
, exposingList =
explicit
[ Exposing.TypeExpose { name = "Result", open = Just Range.emptyRange }
]
}
, createFakeImport
{ moduleName = [ "String" ]
, moduleAlias = Nothing
, exposingList =
explicit
[ Exposing.TypeExpose { name = "Char", open = Nothing }
]
}
, createFakeImport
{ moduleName = [ "Char" ]
, moduleAlias = Nothing
, exposingList = Nothing
}
, createFakeImport
{ moduleName = [ "Tuple" ]
, moduleAlias = Nothing
, exposingList = Nothing
}
, createFakeImport
{ moduleName = [ "Debug" ]
, moduleAlias = Nothing
, exposingList = Nothing
}
, createFakeImport
{ moduleName = [ "Platform" ]
, moduleAlias = Nothing
, exposingList =
explicit
[ Exposing.TypeExpose { name = "Program", open = Nothing }
]
}
, createFakeImport
{ moduleName = [ "Platform", "Cmd" ]
, moduleAlias = Just "Cmd"
, exposingList =
explicit
[ Exposing.TypeExpose { name = "Cmd", open = Nothing }
]
}
, createFakeImport
{ moduleName = [ "Platform", "Sub" ]
, moduleAlias = Just "Sub"
, exposingList =
explicit
[ Exposing.TypeExpose { name = "Sub", open = Nothing }
]
}
]
createFakeImport : { moduleName : List String, exposingList : Maybe Exposing, moduleAlias : Maybe String } -> Import
createFakeImport { moduleName, moduleAlias, exposingList } =
{ moduleName = Node Range.emptyRange moduleName
, moduleAlias = moduleAlias |> Maybe.map (List.singleton >> Node Range.emptyRange)
, exposingList = exposingList |> Maybe.map (Node Range.emptyRange)
}
declarationListVisitor : List (Node Declaration) -> InnerContext -> InnerContext
declarationListVisitor declarations innerContext =
List.foldl registerDeclaration innerContext declarations
registerDeclaration : Node Declaration -> InnerContext -> InnerContext
registerDeclaration declaration innerContext =
case declarationNameNode declaration of
Just ( variableType, nameNode ) ->
innerContext.scopes
|> registerVariable
{ variableType = variableType
, node = nameNode
}
(Node.value nameNode)
|> updateScope innerContext
Nothing ->
innerContext
declarationNameNode : Node Declaration -> Maybe ( VariableType, Node String )
declarationNameNode (Node _ declaration) =
case declaration of
Declaration.FunctionDeclaration function ->
Just
( TopLevelVariable
, function.declaration
|> Node.value
|> .name
)
Declaration.CustomTypeDeclaration type_ ->
Just ( TopLevelVariable, type_.name )
Declaration.AliasDeclaration alias_ ->
Just ( TopLevelVariable, alias_.name )
Declaration.PortDeclaration port_ ->
Just ( Port, port_.name )
Declaration.InfixDeclaration _ ->
Nothing
Declaration.Destructuring _ _ ->
Nothing
registerVariable : VariableInfo -> String -> Nonempty Scope -> Nonempty Scope
registerVariable variableInfo name scopes =
NonemptyList.mapHead
(\scope -> { scope | names = Dict.insert name variableInfo scope.names })
scopes
updateScope : InnerContext -> Nonempty Scope -> InnerContext
updateScope context scopes =
{ context | scopes = scopes }
importVisitor : Node Import -> InnerContext -> InnerContext
importVisitor (Node range import_) innerContext =
innerContext
|> registerImportAlias import_
|> registerExposed import_
registerImportAlias : Import -> InnerContext -> InnerContext
registerImportAlias import_ innerContext =
case import_.moduleAlias of
Nothing ->
innerContext
Just alias_ ->
{ innerContext
| importAliases =
Dict.insert
(Node.value alias_ |> getModuleName)
(Node.value import_.moduleName)
innerContext.importAliases
}
registerExposed : Import -> InnerContext -> InnerContext
registerExposed import_ innerContext =
case import_.exposingList |> Maybe.map Node.value of
Nothing ->
innerContext
Just exposing_ ->
let
moduleName : List String
moduleName =
Node.value import_.moduleName
module_ : Elm.Docs.Module
module_ =
Dict.get (getModuleName moduleName) innerContext.dependenciesModules
|> Maybe.withDefault
{ name = getModuleName moduleName
, comment = ""
, unions = []
, values = []
, aliases = []
, binops = []
}
in
case exposing_ of
Exposing.All _ ->
let
nameWithModuleName : { r | name : String } -> ( String, List String )
nameWithModuleName { name } =
( name, moduleName )
exposedValues : Dict String (List String)
exposedValues =
List.concat
[ List.map nameWithModuleName module_.unions
, List.map nameWithModuleName module_.values
, List.map nameWithModuleName module_.aliases
, List.map nameWithModuleName module_.binops
]
|> Dict.fromList
in
{ innerContext
| importedFunctionOrTypes =
Dict.union innerContext.importedFunctionOrTypes exposedValues
}
Exposing.Explicit topLevelExposeList ->
let
exposedValues : Dict String (List String)
exposedValues =
topLevelExposeList
|> List.concatMap (namesFromExposingList module_)
|> List.map (\name -> ( name, moduleName ))
|> Dict.fromList
in
{ innerContext
| importedFunctionOrTypes =
Dict.union innerContext.importedFunctionOrTypes exposedValues
}
namesFromExposingList : Elm.Docs.Module -> Node TopLevelExpose -> List String
namesFromExposingList module_ topLevelExpose =
case Node.value topLevelExpose of
Exposing.InfixExpose operator ->
[ operator ]
Exposing.FunctionExpose function ->
[ function ]
Exposing.TypeOrAliasExpose type_ ->
[ type_ ]
Exposing.TypeExpose { name, open } ->
case open of
Just _ ->
name
:: (module_.unions
|> List.filter (\union -> union.name == name)
|> List.concatMap .tags
|> List.map Tuple.first
)
Nothing ->
[ name ]
unbox : Context -> InnerContext
unbox (Context context) =
context
type alias VariableInfo =
{ variableType : VariableType
, node : Node String
}
type VariableType
= TopLevelVariable
| FunctionParameter
| LetVariable
| PatternVariable
| Port
declarationVisitor : Node Declaration -> Rule.Direction -> InnerContext -> InnerContext
declarationVisitor declaration direction context =
case ( direction, Node.value declaration ) of
( Rule.OnEnter, Declaration.FunctionDeclaration function ) ->
let
newScope : Scope
newScope =
{ emptyScope | names = parameters <| .arguments <| Node.value function.declaration }
in
context.scopes
|> NonemptyList.cons newScope
|> updateScope context
( Rule.OnExit, Declaration.FunctionDeclaration function ) ->
{ context | scopes = NonemptyList.pop context.scopes }
_ ->
context
parameters : List (Node Pattern) -> Dict String VariableInfo
parameters patterns =
List.concatMap collectNamesFromPattern patterns
|> List.map
(\node ->
( Node.value node
, { node = node
, variableType = FunctionParameter
}
)
)
|> Dict.fromList
collectNamesFromPattern : Node Pattern -> List (Node String)
collectNamesFromPattern pattern =
case Node.value pattern of
Pattern.AllPattern ->
[]
Pattern.UnitPattern ->
[]
Pattern.CharPattern _ ->
[]
Pattern.StringPattern _ ->
[]
Pattern.IntPattern _ ->
[]
Pattern.HexPattern _ ->
[]
Pattern.FloatPattern _ ->
[]
Pattern.TuplePattern subPatterns ->
List.concatMap collectNamesFromPattern subPatterns
Pattern.RecordPattern names ->
names
Pattern.UnConsPattern left right ->
List.concatMap collectNamesFromPattern [ left, right ]
Pattern.ListPattern subPatterns ->
List.concatMap collectNamesFromPattern subPatterns
Pattern.VarPattern name ->
[ Node (Node.range pattern) name ]
Pattern.NamedPattern _ subPatterns ->
List.concatMap collectNamesFromPattern subPatterns
Pattern.AsPattern subPattern alias_ ->
alias_ :: collectNamesFromPattern subPattern
Pattern.ParenthesizedPattern subPattern ->
collectNamesFromPattern subPattern
popScope : Node Expression -> Direction -> InnerContext -> InnerContext
popScope ((Node range value) as node) direction context =
let
currentScope : Scope
currentScope =
NonemptyList.head context.scopes
in
case direction of
Rule.OnEnter ->
let
caseExpression : Maybe ( Node Expression, Dict String VariableInfo )
caseExpression =
findInList (\( expressionNode, _ ) -> node == expressionNode) currentScope.cases
in
case caseExpression of
Nothing ->
context
Just ( _, names ) ->
{ context | scopes = NonemptyList.cons { emptyScope | names = names, caseToExit = node } context.scopes }
Rule.OnExit ->
if node == currentScope.caseToExit then
{ context | scopes = NonemptyList.pop context.scopes }
else
context
expressionVisitor : Node Expression -> Direction -> InnerContext -> InnerContext
expressionVisitor ((Node range value) as node) direction context =
case ( direction, value ) of
( Rule.OnEnter, Expression.LetExpression { declarations, expression } ) ->
List.foldl
(\declaration scopes ->
case Node.value declaration of
Expression.LetFunction function ->
let
nameNode : Node String
nameNode =
function.declaration
|> Node.value
|> .name
in
registerVariable
{ variableType = LetVariable, node = nameNode }
-- TODO Check if the name as 2nd arg is not redundant with the 1st argument's node field
(Node.value nameNode)
scopes
Expression.LetDestructuring pattern _ ->
scopes
)
(NonemptyList.cons emptyScope context.scopes)
declarations
|> updateScope context
( Rule.OnExit, Expression.LetExpression _ ) ->
{ context | scopes = NonemptyList.pop context.scopes }
( Rule.OnEnter, Expression.CaseExpression caseBlock ) ->
let
cases : List ( Node Expression, Dict String VariableInfo )
cases =
caseBlock.cases
|> List.map
(\( pattern, expression ) ->
( expression
, collectNamesFromPattern pattern
|> List.map
(\node_ ->
( Node.value node_
, { node = node_
, variableType = PatternVariable
}
)
)
|> Dict.fromList
)
)
in
{ context | scopes = NonemptyList.mapHead (\scope -> { scope | cases = cases }) context.scopes }
( Rule.OnExit, Expression.CaseExpression caseBlock ) ->
{ context | scopes = NonemptyList.mapHead (\scope -> { scope | cases = [] }) context.scopes }
_ ->
context
findInList : (a -> Bool) -> List a -> Maybe a
findInList predicate list =
case list of
[] ->
Nothing
a :: rest ->
if predicate a then
Just a
else
findInList predicate rest
-- ACCESS
realFunctionOrType : List String -> String -> Context -> ( List String, String )
realFunctionOrType moduleName functionOrType (Context context) =
if List.length moduleName == 0 then
( if isInScope functionOrType context.scopes then
[]
else
case Dict.get functionOrType context.importedFunctionOrTypes of
Just importedFunctionOrType ->
importedFunctionOrType
Nothing ->
[]
, functionOrType
)
else if List.length moduleName == 1 then
( Dict.get (getModuleName moduleName) context.importAliases
|> Maybe.withDefault moduleName
, functionOrType
)
else
( moduleName, functionOrType )
isInScope : String -> Nonempty Scope -> Bool
isInScope name scopes =
NonemptyList.any (.names >> Dict.member name) scopes
-- MISC
getModuleName : List String -> String
getModuleName name =
String.join "." name

View File

@ -1,20 +1,22 @@
module Scope2 exposing
( ProjectContext, ModuleContext
, addProjectVisitors, initialProjectContext, fromProjectToModule, fromModuleToProject, foldProjectContexts
( ModuleContext, addModuleVisitors, initialModuleContext
, ProjectContext, addProjectVisitors
, initialProjectContext, fromProjectToModule, fromModuleToProject, foldProjectContexts
, realFunctionOrType
)
{-| Report variables or types that are declared or imported but never used.
{-| Collect and infer information automatically for you
# Definition
# Adding to a module rule
@docs ProjectContext, ModuleContext
@docs ModuleContext, addModuleVisitors, initialModuleContext
# Usage
# Adding to a project rule
@docs addProjectVisitors, addModuleVisitors, initialProjectContext, fromProjectToModule, fromModuleToProject, foldProjectContexts
@docs ProjectContext, addProjectVisitors
@docs initialProjectContext, fromProjectToModule, fromModuleToProject, foldProjectContexts
# Access
@ -23,8 +25,6 @@ module Scope2 exposing
-}
-- TODO Re-add the nice "can't make mistakes" addVisitors
import Dict exposing (Dict)
import Elm.Docs
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
@ -45,36 +45,7 @@ import Review.Rule as Rule exposing (Direction)
-- DEFINITION
{-
TODO To make everything less error-prone:
Wrap the following in a helper from Scope:
Scope.addVisitors setterGetter
({ moduleVisitor =
\schema ->
schema
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
, fromProjectToModule = fromProjectToModule
, fromModuleToProject = fromModuleToProject
, foldProjectContexts = foldProjectContexts
})
Need to fine-tune the details on how that would work obviously.
-}
type ProjectContext
= ProjectContext InnerProjectContext
type alias InnerProjectContext =
{ dependenciesModules : Dict String Elm.Docs.Module
, modules : Dict ModuleName Elm.Docs.Module
}
-- MODULE VISITOR
type ModuleContext
@ -96,17 +67,25 @@ type alias InnerModuleContext =
}
type alias Scope =
{ names : Dict String VariableInfo
, cases : List ( Node Expression, Dict String VariableInfo )
, caseToExit : Node Expression
initialModuleContext : ModuleContext
initialModuleContext =
fromProjectToModule initialProjectContext
-- PROJECT VISITOR
type ProjectContext
= ProjectContext InnerProjectContext
type alias InnerProjectContext =
{ dependenciesModules : Dict String Elm.Docs.Module
, modules : Dict ModuleName Elm.Docs.Module
}
-- USAGE
initialProjectContext : ProjectContext
initialProjectContext =
ProjectContext
@ -158,6 +137,17 @@ foldProjectContexts (ProjectContext a) (ProjectContext b) =
}
-- SCOPE
type alias Scope =
{ names : Dict String VariableInfo
, cases : List ( Node Expression, Dict String VariableInfo )
, caseToExit : Node Expression
}
emptyScope : Scope
emptyScope =
{ names = Dict.empty
@ -173,12 +163,22 @@ addProjectVisitors schema =
schema
|> Rule.withContextFromImportedModules
|> Rule.withDependenciesProjectVisitor (mapInnerProjectContext dependenciesVisitor)
|> Rule.withModuleVisitor addModuleVisitors
|> Rule.withModuleVisitor internalAddModuleVisitors
addModuleVisitors : Rule.ModuleRuleSchema anything { moduleContext | scope : ModuleContext } -> Rule.ModuleRuleSchema { anything | hasAtLeastOneVisitor : () } { moduleContext | scope : ModuleContext }
addModuleVisitors :
Rule.ModuleRuleSchema { schemaState | canCollectProjectData : () } { moduleContext | scope : ModuleContext }
-> Rule.ModuleRuleSchema { schemaState | canCollectProjectData : (), hasAtLeastOneVisitor : () } { moduleContext | scope : ModuleContext }
addModuleVisitors schema =
schema
|> Rule.withDependenciesModuleVisitor (mapInnerModuleContext dependenciesVisitor)
|> internalAddModuleVisitors
internalAddModuleVisitors : Rule.ModuleRuleSchema schemaState { moduleContext | scope : ModuleContext } -> Rule.ModuleRuleSchema { schemaState | hasAtLeastOneVisitor : () } { moduleContext | scope : ModuleContext }
internalAddModuleVisitors schema =
schema
-- TODO Add project visitors information
|> Rule.withModuleDefinitionVisitor
(mapInnerModuleContext moduleDefinitionVisitor |> pairWithNoErrors)
|> Rule.withImportVisitor
@ -267,7 +267,7 @@ pairWithNoErrors fn visited context =
-- DEPENDENCIES
dependenciesVisitor : Dict String Dependency -> InnerProjectContext -> InnerProjectContext
dependenciesVisitor : Dict String Dependency -> { context | dependenciesModules : Dict String Elm.Docs.Module } -> { context | dependenciesModules : Dict String Elm.Docs.Module }
dependenciesVisitor dependencies innerContext =
let
dependenciesModules : Dict String Elm.Docs.Module
@ -593,7 +593,7 @@ exposedElements nodes =
importVisitor : Node Import -> InnerModuleContext -> InnerModuleContext
importVisitor (Node range import_) innerContext =
importVisitor (Node _ import_) innerContext =
innerContext
|> registerImportAlias import_
|> registerImportExposed import_
@ -851,7 +851,7 @@ popScope ((Node range value) as node) direction context =
expressionVisitor : Node Expression -> Direction -> InnerModuleContext -> InnerModuleContext
expressionVisitor ((Node range value) as node) direction context =
expressionVisitor (Node _ value) direction context =
case ( direction, value ) of
( Rule.OnEnter, Expression.LetExpression { declarations, expression } ) ->
List.foldl

View File

@ -14,7 +14,7 @@ import Test exposing (Test, test)
all : Test
all =
Test.describe "Scope"
Test.describe "Scope (project rule)"
[ realFunctionOrTypeTests
]

View File

@ -1,137 +0,0 @@
module ScopeTest exposing (all)
import Dependencies
import Elm.Syntax.Expression as Expression exposing (Expression)
import Elm.Syntax.Node as Node exposing (Node)
import Review.Project as Project exposing (Project)
import Review.Rule as Rule exposing (Error, Rule)
import Review.Test exposing (ReviewResult)
import Scope
import Test exposing (Test, test)
type alias Context =
{ scope : Scope.Context
, text : String
}
project : Project
project =
Project.new
|> Project.addDependency Dependencies.elmCore
|> Project.addDependency Dependencies.elmHtml
testRule : Rule -> String -> ReviewResult
testRule rule string =
"module A exposing (..)\n\n"
++ string
|> Review.Test.runWithProjectData project rule
baseRule :
Rule.ModuleRuleSchema
{ hasAtLeastOneVisitor : ()
, canCollectProjectData : ()
}
Context
baseRule =
Rule.newModuleRuleSchema "TestRule" initialContext
|> Scope.addVisitors
initialContext : Context
initialContext =
{ scope = Scope.initialContext
, text = ""
}
all : Test
all =
Test.describe "Scope"
[ Test.describe "Scope.realFunctionOrType"
[ test "should indicate that module from which a function or value comes from" <|
\() ->
let
rule : Rule
rule =
baseRule
|> Rule.withExpressionVisitor expressionVisitor
|> Rule.withFinalModuleEvaluation finalEvaluation
|> Rule.fromModuleRuleSchema
expressionVisitor : Node Expression -> Rule.Direction -> Context -> ( List (Error {}), Context )
expressionVisitor node direction context =
case ( direction, Node.value node ) of
( Rule.OnEnter, Expression.FunctionOrValue moduleName name ) ->
let
nameInCode : String
nameInCode =
case moduleName of
[] ->
"<nothing>." ++ name
_ ->
String.join "." moduleName ++ "." ++ name
realName : String
realName =
case Scope.realFunctionOrType moduleName name context.scope of
( [], name_ ) ->
"<nothing>." ++ name_
( moduleName_, name_ ) ->
String.join "." moduleName_ ++ "." ++ name_
in
( [], { context | text = context.text ++ "\n" ++ nameInCode ++ " -> " ++ realName } )
_ ->
( [], context )
finalEvaluation : Context -> List (Error {})
finalEvaluation context =
[ Rule.error { message = context.text, details = [ "details" ] }
{ start = { row = 1, column = 1 }
, end = { row = 1, column = 7 }
}
]
in
testRule rule """
import Bar as Baz exposing (baz)
import Foo
import Foo.Bar
import Html exposing (..)
import Http exposing (get)
a = b
Foo.bar
Foo.Bar
Baz.foo
baz
button
Http.get
get
always
Just
"""
|> Review.Test.expectErrors
[ Review.Test.error
{ message = """
<nothing>.b -> <nothing>.b
Foo.bar -> Foo.bar
Foo.Bar -> Foo.Bar
Baz.foo -> Bar.foo
<nothing>.baz -> Bar.baz
<nothing>.button -> Html.button
Http.get -> Http.get
<nothing>.get -> Http.get
<nothing>.always -> Basics.always
<nothing>.Just -> Maybe.Just"""
, details = [ "details" ]
, under = "module"
}
]
]
]