mirror of
https://github.com/jfmengels/elm-review.git
synced 2024-11-28 00:56:51 +03:00
476 lines
14 KiB
Elm
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 >> (++)) []
|