mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-12-25 18:51:41 +03:00
Make scope register top level variables
This commit is contained in:
parent
0a9a4d8853
commit
28e9dfc401
@ -27,6 +27,7 @@ rule =
|
|||||||
}
|
}
|
||||||
|> Rule.withDependenciesVisitor (Scope.dependenciesVisitor scopeSetterGetter Nothing)
|
|> Rule.withDependenciesVisitor (Scope.dependenciesVisitor scopeSetterGetter Nothing)
|
||||||
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|
|> Rule.withModuleDefinitionVisitor moduleDefinitionVisitor
|
||||||
|
|> Rule.withDeclarationListVisitor (Scope.declarationListVisitor scopeSetterGetter Nothing)
|
||||||
|> Rule.withImportVisitor (Scope.importVisitor scopeSetterGetter Nothing)
|
|> Rule.withImportVisitor (Scope.importVisitor scopeSetterGetter Nothing)
|
||||||
|> Rule.withExpressionVisitor expressionVisitor
|
|> Rule.withExpressionVisitor expressionVisitor
|
||||||
|> Rule.fromSchema
|
|> Rule.fromSchema
|
||||||
|
@ -2,6 +2,7 @@ module NonemptyList exposing
|
|||||||
( Nonempty(..)
|
( Nonempty(..)
|
||||||
, fromElement
|
, fromElement
|
||||||
, head
|
, head
|
||||||
|
, any
|
||||||
, cons, pop
|
, cons, pop
|
||||||
, mapHead
|
, mapHead
|
||||||
)
|
)
|
||||||
@ -26,7 +27,12 @@ available.
|
|||||||
|
|
||||||
# Access
|
# Access
|
||||||
|
|
||||||
@docs head, sample
|
@docs head
|
||||||
|
|
||||||
|
|
||||||
|
# Inspect
|
||||||
|
|
||||||
|
@docs any
|
||||||
|
|
||||||
|
|
||||||
# Convert
|
# Convert
|
||||||
@ -96,6 +102,13 @@ head (Nonempty x xs) =
|
|||||||
x
|
x
|
||||||
|
|
||||||
|
|
||||||
|
{-| Determine if any elements satisfy the predicate.
|
||||||
|
-}
|
||||||
|
any : (a -> Bool) -> Nonempty a -> Bool
|
||||||
|
any f (Nonempty x xs) =
|
||||||
|
f x || List.any f xs
|
||||||
|
|
||||||
|
|
||||||
{-| Add another element as the head of the list, pushing the previous head to the tail.
|
{-| Add another element as the head of the list, pushing the previous head to the tail.
|
||||||
-}
|
-}
|
||||||
cons : a -> Nonempty a -> Nonempty a
|
cons : a -> Nonempty a -> Nonempty a
|
||||||
|
231
src/Scope.elm
231
src/Scope.elm
@ -1,17 +1,31 @@
|
|||||||
module Scope exposing (Context, SetterGetter, dependenciesVisitor, importVisitor, initialContext, realFunctionOrType)
|
module Scope exposing
|
||||||
|
( Context, SetterGetter
|
||||||
|
, initialContext, dependenciesVisitor, importVisitor, declarationListVisitor
|
||||||
|
, realFunctionOrType
|
||||||
|
)
|
||||||
|
|
||||||
{-| Report variables or types that are declared or imported but never used.
|
{-| Report variables or types that are declared or imported but never used.
|
||||||
|
|
||||||
|
|
||||||
# Rule
|
# Definition
|
||||||
|
|
||||||
@docs rule
|
@docs Context, SetterGetter
|
||||||
|
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
@docs initialContext, dependenciesVisitor, importVisitor, declarationListVisitor
|
||||||
|
|
||||||
|
|
||||||
|
# Access
|
||||||
|
|
||||||
|
@docs realFunctionOrType
|
||||||
|
|
||||||
-}
|
-}
|
||||||
|
|
||||||
import Dict exposing (Dict)
|
import Dict exposing (Dict)
|
||||||
import Elm.Docs
|
import Elm.Docs
|
||||||
import Elm.Syntax.Declaration exposing (Declaration(..))
|
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
|
||||||
import Elm.Syntax.Exposing as Exposing exposing (Exposing, TopLevelExpose)
|
import Elm.Syntax.Exposing as Exposing exposing (Exposing, TopLevelExpose)
|
||||||
import Elm.Syntax.Expression exposing (Expression(..), Function, FunctionImplementation, LetDeclaration(..))
|
import Elm.Syntax.Expression exposing (Expression(..), Function, FunctionImplementation, LetDeclaration(..))
|
||||||
import Elm.Syntax.Import exposing (Import)
|
import Elm.Syntax.Import exposing (Import)
|
||||||
@ -26,50 +40,136 @@ import Review.Rule as Rule exposing (Direction, Error, Rule)
|
|||||||
import Set exposing (Set)
|
import Set exposing (Set)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- DEFINITION
|
||||||
|
|
||||||
|
|
||||||
type Context
|
type Context
|
||||||
= Context InnerContext
|
= Context InnerContext
|
||||||
|
|
||||||
|
|
||||||
type alias InnerContext =
|
type alias InnerContext =
|
||||||
{ scopes : Nonempty Scope
|
{ scopes : Nonempty (Dict String VariableInfo)
|
||||||
, importAliases : Dict String (List String)
|
, importAliases : Dict String (List String)
|
||||||
, importedFunctionOrTypes : Dict String (List String)
|
, importedFunctionOrTypes : Dict String (List String)
|
||||||
, dependencies : Dict String Elm.Docs.Module
|
, dependencies : Dict String Elm.Docs.Module
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type alias SetterGetter context =
|
||||||
|
{ setter : Context -> context -> context
|
||||||
|
, getter : context -> Context
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- USAGE
|
||||||
|
|
||||||
|
|
||||||
initialContext : Context
|
initialContext : Context
|
||||||
initialContext =
|
initialContext =
|
||||||
Context
|
Context
|
||||||
{ scopes = Nonempty.fromElement emptyScope
|
{ scopes = Nonempty.fromElement Dict.empty
|
||||||
, importAliases = Dict.empty
|
, importAliases = Dict.empty
|
||||||
, importedFunctionOrTypes = Dict.empty
|
, importedFunctionOrTypes = Dict.empty
|
||||||
, dependencies = Dict.empty
|
, dependencies = Dict.empty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
realFunctionOrType : List String -> String -> Context -> ( List String, String )
|
dependenciesVisitor : SetterGetter context -> Maybe (Dict String Elm.Docs.Module -> context -> context) -> Dict String Elm.Docs.Module -> context -> context
|
||||||
realFunctionOrType moduleName functionOrType (Context context) =
|
dependenciesVisitor { setter, getter } maybeVisitor =
|
||||||
if List.length moduleName == 0 then
|
let
|
||||||
( Dict.get functionOrType context.importedFunctionOrTypes
|
visitor : Dict String Elm.Docs.Module -> context -> context
|
||||||
|> Maybe.withDefault moduleName
|
visitor =
|
||||||
, functionOrType
|
case maybeVisitor of
|
||||||
)
|
Nothing ->
|
||||||
|
\dependencies newContext -> newContext
|
||||||
|
|
||||||
else if List.length moduleName == 1 then
|
Just fn ->
|
||||||
( Dict.get (String.join "." moduleName) context.importAliases
|
fn
|
||||||
|> Maybe.withDefault moduleName
|
in
|
||||||
, functionOrType
|
\dependencies outerContext ->
|
||||||
)
|
outerContext
|
||||||
|
|> getter
|
||||||
else
|
|> unbox
|
||||||
( moduleName, functionOrType )
|
|> (\innerContext -> { innerContext | dependencies = dependencies })
|
||||||
|
|> Context
|
||||||
|
|> (\newContext -> setter newContext outerContext)
|
||||||
|
|> visitor dependencies
|
||||||
|
|
||||||
|
|
||||||
type alias SetterGetter context =
|
declarationListVisitor : SetterGetter context -> Maybe (List (Node Declaration) -> context -> ( List Error, context )) -> List (Node Declaration) -> context -> ( List Error, context )
|
||||||
{ setter : Context -> context -> context
|
declarationListVisitor { setter, getter } maybeVisitor =
|
||||||
, getter : context -> Context
|
let
|
||||||
}
|
visitor : List (Node Declaration) -> context -> ( List Error, context )
|
||||||
|
visitor =
|
||||||
|
case maybeVisitor of
|
||||||
|
Nothing ->
|
||||||
|
\declarations newContext -> ( [], newContext )
|
||||||
|
|
||||||
|
Just fn ->
|
||||||
|
fn
|
||||||
|
in
|
||||||
|
\declarations outerContext ->
|
||||||
|
outerContext
|
||||||
|
|> getter
|
||||||
|
|> unbox
|
||||||
|
|> (\innerContext -> List.foldl registerDeclaration innerContext declarations)
|
||||||
|
|> Context
|
||||||
|
|> (\newContext -> setter newContext outerContext)
|
||||||
|
|> visitor declarations
|
||||||
|
|
||||||
|
|
||||||
|
registerDeclaration : Node Declaration -> InnerContext -> InnerContext
|
||||||
|
registerDeclaration declaration innerContext =
|
||||||
|
case declarationNameNode declaration of
|
||||||
|
Just nameNode ->
|
||||||
|
registerVariable
|
||||||
|
{ variableType = TopLevelVariable
|
||||||
|
, node = nameNode
|
||||||
|
}
|
||||||
|
(Node.value nameNode)
|
||||||
|
innerContext
|
||||||
|
|
||||||
|
Nothing ->
|
||||||
|
innerContext
|
||||||
|
|
||||||
|
|
||||||
|
declarationNameNode : Node Declaration -> Maybe (Node String)
|
||||||
|
declarationNameNode (Node _ declaration) =
|
||||||
|
case declaration of
|
||||||
|
Declaration.FunctionDeclaration function ->
|
||||||
|
function.declaration
|
||||||
|
|> Node.value
|
||||||
|
|> .name
|
||||||
|
|> Just
|
||||||
|
|
||||||
|
Declaration.CustomTypeDeclaration type_ ->
|
||||||
|
Just type_.name
|
||||||
|
|
||||||
|
Declaration.AliasDeclaration alias_ ->
|
||||||
|
Just alias_.name
|
||||||
|
|
||||||
|
Declaration.PortDeclaration port_ ->
|
||||||
|
Just port_.name
|
||||||
|
|
||||||
|
Declaration.InfixDeclaration _ ->
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
Declaration.Destructuring _ _ ->
|
||||||
|
Nothing
|
||||||
|
|
||||||
|
|
||||||
|
registerVariable : VariableInfo -> String -> InnerContext -> InnerContext
|
||||||
|
registerVariable variableInfo name context =
|
||||||
|
let
|
||||||
|
scopes : Nonempty (Dict String VariableInfo)
|
||||||
|
scopes =
|
||||||
|
Nonempty.mapHead
|
||||||
|
(Dict.insert name variableInfo)
|
||||||
|
context.scopes
|
||||||
|
in
|
||||||
|
{ context | scopes = scopes }
|
||||||
|
|
||||||
|
|
||||||
importVisitor : SetterGetter context -> Maybe (Node Import -> context -> ( List Error, context )) -> Node Import -> context -> ( List Error, context )
|
importVisitor : SetterGetter context -> Maybe (Node Import -> context -> ( List Error, context )) -> Node Import -> context -> ( List Error, context )
|
||||||
@ -95,28 +195,6 @@ importVisitor { setter, getter } maybeVisitor =
|
|||||||
|> visitor node
|
|> visitor node
|
||||||
|
|
||||||
|
|
||||||
dependenciesVisitor : SetterGetter context -> Maybe (Dict String Elm.Docs.Module -> context -> context) -> Dict String Elm.Docs.Module -> context -> context
|
|
||||||
dependenciesVisitor { setter, getter } maybeVisitor =
|
|
||||||
let
|
|
||||||
visitor : Dict String Elm.Docs.Module -> context -> context
|
|
||||||
visitor =
|
|
||||||
case maybeVisitor of
|
|
||||||
Nothing ->
|
|
||||||
\dependencies newContext -> newContext
|
|
||||||
|
|
||||||
Just fn ->
|
|
||||||
fn
|
|
||||||
in
|
|
||||||
\dependencies outerContext ->
|
|
||||||
outerContext
|
|
||||||
|> getter
|
|
||||||
|> unbox
|
|
||||||
|> (\innerContext -> { innerContext | dependencies = dependencies })
|
|
||||||
|> Context
|
|
||||||
|> (\newContext -> setter newContext outerContext)
|
|
||||||
|> visitor dependencies
|
|
||||||
|
|
||||||
|
|
||||||
registerImportAlias : Import -> InnerContext -> InnerContext
|
registerImportAlias : Import -> InnerContext -> InnerContext
|
||||||
registerImportAlias import_ innerContext =
|
registerImportAlias import_ innerContext =
|
||||||
case import_.moduleAlias of
|
case import_.moduleAlias of
|
||||||
@ -127,7 +205,7 @@ registerImportAlias import_ innerContext =
|
|||||||
{ innerContext
|
{ innerContext
|
||||||
| importAliases =
|
| importAliases =
|
||||||
Dict.insert
|
Dict.insert
|
||||||
(Node.value alias_ |> String.join ".")
|
(Node.value alias_ |> getModuleName)
|
||||||
(Node.value import_.moduleName)
|
(Node.value import_.moduleName)
|
||||||
innerContext.importAliases
|
innerContext.importAliases
|
||||||
}
|
}
|
||||||
@ -145,7 +223,7 @@ registerExposed import_ innerContext =
|
|||||||
moduleName =
|
moduleName =
|
||||||
Node.value import_.moduleName
|
Node.value import_.moduleName
|
||||||
in
|
in
|
||||||
case Dict.get (String.join "." moduleName) innerContext.dependencies of
|
case Dict.get (getModuleName moduleName) innerContext.dependencies of
|
||||||
Just module_ ->
|
Just module_ ->
|
||||||
let
|
let
|
||||||
nameWithModuleName : { r | name : String } -> ( String, List String )
|
nameWithModuleName : { r | name : String } -> ( String, List String )
|
||||||
@ -214,16 +292,9 @@ unbox (Context context) =
|
|||||||
context
|
context
|
||||||
|
|
||||||
|
|
||||||
type alias Scope =
|
|
||||||
{ declared : Dict String VariableInfo
|
|
||||||
, used : Set String
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type alias VariableInfo =
|
type alias VariableInfo =
|
||||||
{ variableType : VariableType
|
{ variableType : VariableType
|
||||||
, under : Range
|
, node : Node String
|
||||||
, rangeToRemove : Range
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -243,13 +314,6 @@ type ImportType
|
|||||||
| ImportedOperator
|
| ImportedOperator
|
||||||
|
|
||||||
|
|
||||||
emptyScope : Scope
|
|
||||||
emptyScope =
|
|
||||||
{ declared = Dict.empty
|
|
||||||
, used = Set.empty
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
getUsedTypesFromPattern : Node Pattern -> List String
|
getUsedTypesFromPattern : Node Pattern -> List String
|
||||||
getUsedTypesFromPattern patternNode =
|
getUsedTypesFromPattern patternNode =
|
||||||
case Node.value patternNode of
|
case Node.value patternNode of
|
||||||
@ -448,6 +512,45 @@ collectModuleNamesFromTypeAnnotation node =
|
|||||||
[]
|
[]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- 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 (Dict String VariableInfo) -> Bool
|
||||||
|
isInScope name scopes =
|
||||||
|
Nonempty.any (Dict.member name) scopes
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- MISC
|
||||||
|
|
||||||
|
|
||||||
getModuleName : List String -> String
|
getModuleName : List String -> String
|
||||||
getModuleName name =
|
getModuleName name =
|
||||||
String.join "." name
|
String.join "." name
|
||||||
|
@ -125,14 +125,14 @@ a = button
|
|||||||
}
|
}
|
||||||
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 11 } }
|
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 11 } }
|
||||||
]
|
]
|
||||||
, test "should not report the use of `button` when it has been imported implicitly and the dependency is not known" <|
|
, test "should not report the use of `button` when it has been imported using `exposing (..)` and the dependency is not known" <|
|
||||||
\() ->
|
\() ->
|
||||||
testRule """
|
testRule """
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
a = button
|
a = button
|
||||||
"""
|
"""
|
||||||
|> Review.Test.expectNoErrors
|
|> Review.Test.expectNoErrors
|
||||||
, test "should report the use of `button` when it has been imported implicitly and the dependency is known" <|
|
, test "should report the use of `button` when it has been imported using `exposing (..)` and the dependency is known" <|
|
||||||
\() ->
|
\() ->
|
||||||
testRuleWithHtmlDependency """
|
testRuleWithHtmlDependency """
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
@ -146,6 +146,41 @@ a = button
|
|||||||
}
|
}
|
||||||
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 11 } }
|
|> Review.Test.atExactly { start = { row = 5, column = 5 }, end = { row = 5, column = 11 } }
|
||||||
]
|
]
|
||||||
|
, test "should not report the use of `button` when it has been imported using `exposing (..)` and the dependency is known, but it has been redefined at the top-level" <|
|
||||||
|
\() ->
|
||||||
|
testRuleWithHtmlDependency """
|
||||||
|
import Html exposing (..)
|
||||||
|
a = button
|
||||||
|
button = 1
|
||||||
|
"""
|
||||||
|
|> Review.Test.expectNoErrors
|
||||||
|
, Test.skip <|
|
||||||
|
test "should not report the use of `button` when it has been imported using `exposing (..)` and the dependency is known, but it has been redefined in an accessible let..in declaration" <|
|
||||||
|
\() ->
|
||||||
|
testRuleWithHtmlDependency """
|
||||||
|
import Html exposing (..)
|
||||||
|
a = let
|
||||||
|
button = 1
|
||||||
|
in button
|
||||||
|
"""
|
||||||
|
|> Review.Test.expectNoErrors
|
||||||
|
, test "should report the use of `button` when it has been imported using `exposing (..)` and the dependency is known, and it has been redefined in an out-of-scope let..in declaration" <|
|
||||||
|
\() ->
|
||||||
|
testRuleWithHtmlDependency """
|
||||||
|
import Html exposing (..)
|
||||||
|
a = let
|
||||||
|
button = 1
|
||||||
|
in 2
|
||||||
|
b = button
|
||||||
|
"""
|
||||||
|
|> Review.Test.expectErrors
|
||||||
|
[ Review.Test.error
|
||||||
|
{ message = message
|
||||||
|
, details = details
|
||||||
|
, under = "button"
|
||||||
|
}
|
||||||
|
|> Review.Test.atExactly { start = { row = 8, column = 5 }, end = { row = 8, column = 11 } }
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user