elm-review/tests/Vendor/NameVisitor.elm
2020-08-09 18:56:12 +02:00

476 lines
14 KiB
Elm

module Vendor.NameVisitor exposing (withNameVisitor, withValueVisitor, withTypeVisitor, withValueAndTypeVisitors)
{-| Visit each name in the module.
A "name" is a `Node ( ModuleName, String )` and represents a value or type reference. Here are some examples:
- `Json.Encode.Value` -> `( [ "Json", "Encode" ], "Value" )`
- `Html.Attributes.class` -> `( [ "Html", "Attributes" ], "class" )`
- `Page` -> `( [], "Page" )`
- `view` -> `( [], "view" )`
These can appear in many places throughout declarations and expressions, and picking them out each time is a lot of work. Instead of writing 1000 lines of code and tests each time, you can write one `nameVisitor` and plug it straight into your module schema, or separate `valueVisitor` and `typeVisitor`s.
@docs withNameVisitor, withValueVisitor, withTypeVisitor, withValueAndTypeVisitors
## Scope
This makes no attempt to resolve module names from imports, it just returns what's written in the code. It would be trivial to connect [elm-review-scope] with the name visitor if you want to do this.
[elm-review-scope]: http://github.com/jfmengels/elm-review-scope/
## Version
Version: 0.3.0
-}
import Elm.Syntax.Declaration as Declaration exposing (Declaration)
import Elm.Syntax.Expression as Expression exposing (Expression)
import Elm.Syntax.ModuleName exposing (ModuleName)
import Elm.Syntax.Node as Node exposing (Node(..))
import Elm.Syntax.Pattern as Pattern exposing (Pattern)
import Elm.Syntax.Signature exposing (Signature)
import Elm.Syntax.Type as Type
import Elm.Syntax.TypeAnnotation as TypeAnnotation exposing (TypeAnnotation)
import Review.Rule as Rule exposing (Error)
type Visitor context
= NameVisitor (VisitorFunction context)
| ValueVisitor (VisitorFunction context)
| TypeVisitor (VisitorFunction context)
| ValueAndTypeVisitor (VisitorFunction context) (VisitorFunction context)
type alias VisitorFunction context =
Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
type Name
= Value (Node ( ModuleName, String ))
| Type (Node ( ModuleName, String ))
{-| This will apply the `nameVisitor` to every value and type in the module, you will get no information about whether the name is a value or type.
rule : Rule
rule =
Rule.newModuleRuleSchema "NoInconsistentAliases" initialContext
|> NameVisitor.withNameVisitor nameVisitor
|> Rule.fromModuleRuleSchema
nameVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
nameVisitor node context =
-- Do what you want with the name
( [], context )
-}
withNameVisitor :
(Node ( ModuleName, String ) -> context -> ( List (Error {}), context ))
-> Rule.ModuleRuleSchema state context
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } context
withNameVisitor nameVisitor rule =
let
visitor =
NameVisitor nameVisitor
in
rule
|> Rule.withDeclarationListVisitor (declarationListVisitor visitor)
|> Rule.withExpressionEnterVisitor (expressionVisitor visitor)
{-| This will apply the `valueVisitor` to every value in the module, and ignore any types.
rule : Rule
rule =
Rule.newModuleRuleSchema "NoInconsistentAliases" initialContext
|> NameVisitor.withValueVisitor valueVisitor
|> Rule.fromModuleRuleSchema
valueVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
valueVisitor node context =
-- Do what you want with the value
( [], context )
-}
withValueVisitor :
(Node ( ModuleName, String ) -> context -> ( List (Error {}), context ))
-> Rule.ModuleRuleSchema state context
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } context
withValueVisitor valueVisitor rule =
let
visitor =
ValueVisitor valueVisitor
in
rule
|> Rule.withDeclarationListVisitor (declarationListVisitor visitor)
|> Rule.withExpressionEnterVisitor (expressionVisitor visitor)
{-| This will apply the `typeVisitor` to every type in the module, and ignore any values.
rule : Rule
rule =
Rule.newModuleRuleSchema "NoInconsistentAliases" initialContext
|> NameVisitor.withTypeVisitor typeVisitor
|> Rule.fromModuleRuleSchema
typeVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
typeVisitor node context =
-- Do what you want with the type
( [], context )
-}
withTypeVisitor :
(Node ( ModuleName, String ) -> context -> ( List (Error {}), context ))
-> Rule.ModuleRuleSchema state context
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } context
withTypeVisitor typeVisitor rule =
let
visitor =
TypeVisitor typeVisitor
in
rule
|> Rule.withDeclarationListVisitor (declarationListVisitor visitor)
|> Rule.withExpressionEnterVisitor (expressionVisitor visitor)
{-| This will apply the `valueVisitor` to every value and the `typeVisitor` to every type in the module.
rule : Rule
rule =
Rule.newModuleRuleSchema "NoInconsistentAliases" initialContext
|> NameVisitor.withValueAndTypeVisitors
{ valueVisitor = valueVisitor
, typeVisitor = typeVisitor
}
|> Rule.fromModuleRuleSchema
valueVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
valueVisitor node context =
-- Do what you want with the value
( [], context )
typeVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
typeVisitor node context =
-- Do what you want with the type
( [], context )
-}
withValueAndTypeVisitors :
{ valueVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
, typeVisitor : Node ( ModuleName, String ) -> context -> ( List (Error {}), context )
}
-> Rule.ModuleRuleSchema state context
-> Rule.ModuleRuleSchema { state | hasAtLeastOneVisitor : () } context
withValueAndTypeVisitors { valueVisitor, typeVisitor } rule =
let
visitor =
ValueAndTypeVisitor valueVisitor typeVisitor
in
rule
|> Rule.withDeclarationListVisitor (declarationListVisitor visitor)
|> Rule.withExpressionEnterVisitor (expressionVisitor visitor)
--- VISITORS
declarationListVisitor :
Visitor context
-> (List (Node Declaration) -> context -> ( List (Error {}), context ))
declarationListVisitor visitor list context =
visitDeclarationList list
|> folder visitor context
expressionVisitor : Visitor context -> (Node Expression -> context -> ( List (Error {}), context ))
expressionVisitor visitor node context =
visitExpression node
|> folder visitor context
--- FOLDER
folder :
Visitor context
-> context
-> List Name
-> ( List (Error {}), context )
folder visitor context list =
List.foldl (folderHelper visitor) ( [], context ) list
folderHelper :
Visitor context
-> Name
-> ( List (Error {}), context )
-> ( List (Error {}), context )
folderHelper visitor name ( errors, context ) =
let
( newErrors, newContext ) =
applyVisitor visitor name context
in
( newErrors ++ errors, newContext )
applyVisitor : Visitor context -> Name -> context -> ( List (Error {}), context )
applyVisitor visitor name context =
case name of
Value node ->
applyValueVisitor visitor node context
Type node ->
applyTypeVisitor visitor node context
applyValueVisitor : Visitor context -> VisitorFunction context
applyValueVisitor visitor =
case visitor of
NameVisitor function ->
function
ValueVisitor function ->
function
TypeVisitor _ ->
noopVisitor
ValueAndTypeVisitor function _ ->
function
applyTypeVisitor : Visitor context -> VisitorFunction context
applyTypeVisitor visitor =
case visitor of
NameVisitor function ->
function
ValueVisitor _ ->
noopVisitor
TypeVisitor function ->
function
ValueAndTypeVisitor _ function ->
function
noopVisitor : VisitorFunction context
noopVisitor _ context =
( [], context )
--- PRIVATE
visitDeclarationList : List (Node Declaration) -> List Name
visitDeclarationList nodes =
fastConcatMap visitDeclaration nodes
visitDeclaration : Node Declaration -> List Name
visitDeclaration node =
case Node.value node of
Declaration.FunctionDeclaration { signature, declaration } ->
visitMaybeSignature signature
++ visitFunctionImplementation declaration
Declaration.AliasDeclaration { typeAnnotation } ->
visitTypeAnnotation typeAnnotation
Declaration.CustomTypeDeclaration { constructors } ->
visitValueConstructorList constructors
Declaration.PortDeclaration { typeAnnotation } ->
visitTypeAnnotation typeAnnotation
_ ->
[]
visitMaybeSignature : Maybe (Node Signature) -> List Name
visitMaybeSignature maybeNode =
case maybeNode of
Just node ->
visitSignature node
Nothing ->
[]
visitSignature : Node Signature -> List Name
visitSignature node =
visitTypeAnnotation (node |> Node.value |> .typeAnnotation)
visitFunctionImplementation : Node Expression.FunctionImplementation -> List Name
visitFunctionImplementation node =
visitPatternList (node |> Node.value |> .arguments)
visitValueConstructorList : List (Node Type.ValueConstructor) -> List Name
visitValueConstructorList list =
fastConcatMap visitValueConstructor list
visitValueConstructor : Node Type.ValueConstructor -> List Name
visitValueConstructor node =
visitTypeAnnotationList (node |> Node.value |> .arguments)
visitTypeAnnotationList : List (Node TypeAnnotation) -> List Name
visitTypeAnnotationList list =
fastConcatMap visitTypeAnnotation list
visitTypeAnnotation : Node TypeAnnotation -> List Name
visitTypeAnnotation node =
case Node.value node of
TypeAnnotation.GenericType _ ->
[]
TypeAnnotation.Typed call types ->
visitType call
++ visitTypeAnnotationList types
TypeAnnotation.Unit ->
[]
TypeAnnotation.Tupled list ->
visitTypeAnnotationList list
TypeAnnotation.Record list ->
visitRecordFieldList list
TypeAnnotation.GenericRecord _ list ->
visitRecordFieldList (Node.value list)
TypeAnnotation.FunctionTypeAnnotation argument return ->
visitTypeAnnotation argument
++ visitTypeAnnotation return
visitRecordFieldList : List (Node TypeAnnotation.RecordField) -> List Name
visitRecordFieldList list =
fastConcatMap visitRecordField list
visitRecordField : Node TypeAnnotation.RecordField -> List Name
visitRecordField node =
visitTypeAnnotation (node |> Node.value |> Tuple.second)
visitExpression : Node Expression -> List Name
visitExpression (Node range expression) =
case expression of
Expression.FunctionOrValue moduleName function ->
visitValue (Node range ( moduleName, function ))
Expression.LetExpression { declarations } ->
visitLetDeclarationList declarations
Expression.CaseExpression { cases } ->
visitCaseList cases
Expression.LambdaExpression { args } ->
visitPatternList args
Expression.RecordUpdateExpression name _ ->
visitValue (Node.map (\function -> ( [], function )) name)
_ ->
[]
visitLetDeclarationList : List (Node Expression.LetDeclaration) -> List Name
visitLetDeclarationList list =
fastConcatMap visitLetDeclaration list
visitLetDeclaration : Node Expression.LetDeclaration -> List Name
visitLetDeclaration node =
case Node.value node of
Expression.LetFunction { signature, declaration } ->
visitMaybeSignature signature
++ visitFunctionImplementation declaration
Expression.LetDestructuring pattern _ ->
visitPattern pattern
visitCaseList : List Expression.Case -> List Name
visitCaseList list =
fastConcatMap visitCase list
visitCase : Expression.Case -> List Name
visitCase ( pattern, _ ) =
visitPattern pattern
visitPatternList : List (Node Pattern) -> List Name
visitPatternList list =
fastConcatMap visitPattern list
visitPattern : Node Pattern -> List Name
visitPattern node =
case Node.value node of
Pattern.TuplePattern patterns ->
visitPatternList patterns
Pattern.UnConsPattern head rest ->
visitPattern head ++ visitPattern rest
Pattern.ListPattern list ->
visitPatternList list
Pattern.NamedPattern { moduleName, name } _ ->
let
{ start } =
Node.range node
newEnd =
{ start | column = start.column + (name :: moduleName |> String.join "." |> String.length) }
range =
{ start = start, end = newEnd }
in
visitValue (Node range ( moduleName, name ))
Pattern.AsPattern pattern _ ->
visitPattern pattern
Pattern.ParenthesizedPattern pattern ->
visitPattern pattern
_ ->
[]
visitValue : Node ( ModuleName, String ) -> List Name
visitValue node =
[ Value node ]
visitType : Node ( ModuleName, String ) -> List Name
visitType node =
[ Type node ]
--- High Performance List
fastConcatMap : (a -> List b) -> List a -> List b
fastConcatMap fn =
List.foldr (fn >> (++)) []