aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/graphql-go/graphql/language/visitor
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/graphql-go/graphql/language/visitor')
-rw-r--r--vendor/github.com/graphql-go/graphql/language/visitor/visitor.go873
1 files changed, 873 insertions, 0 deletions
diff --git a/vendor/github.com/graphql-go/graphql/language/visitor/visitor.go b/vendor/github.com/graphql-go/graphql/language/visitor/visitor.go
new file mode 100644
index 00000000..9a1c2ac2
--- /dev/null
+++ b/vendor/github.com/graphql-go/graphql/language/visitor/visitor.go
@@ -0,0 +1,873 @@
+package visitor
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/graphql-go/graphql/language/ast"
+ "github.com/graphql-go/graphql/language/typeInfo"
+ "reflect"
+)
+
+const (
+ ActionNoChange = ""
+ ActionBreak = "BREAK"
+ ActionSkip = "SKIP"
+ ActionUpdate = "UPDATE"
+)
+
+type KeyMap map[string][]string
+
+// note that the keys are in Capital letters, equivalent to the ast.Node field Names
+var QueryDocumentKeys = KeyMap{
+ "Name": []string{},
+ "Document": []string{"Definitions"},
+ "OperationDefinition": []string{
+ "Name",
+ "VariableDefinitions",
+ "Directives",
+ "SelectionSet",
+ },
+ "VariableDefinition": []string{
+ "Variable",
+ "Type",
+ "DefaultValue",
+ },
+ "Variable": []string{"Name"},
+ "SelectionSet": []string{"Selections"},
+ "Field": []string{
+ "Alias",
+ "Name",
+ "Arguments",
+ "Directives",
+ "SelectionSet",
+ },
+ "Argument": []string{
+ "Name",
+ "Value",
+ },
+
+ "FragmentSpread": []string{
+ "Name",
+ "Directives",
+ },
+ "InlineFragment": []string{
+ "TypeCondition",
+ "Directives",
+ "SelectionSet",
+ },
+ "FragmentDefinition": []string{
+ "Name",
+ "TypeCondition",
+ "Directives",
+ "SelectionSet",
+ },
+
+ "IntValue": []string{},
+ "FloatValue": []string{},
+ "StringValue": []string{},
+ "BooleanValue": []string{},
+ "EnumValue": []string{},
+ "ListValue": []string{"Values"},
+ "ObjectValue": []string{"Fields"},
+ "ObjectField": []string{
+ "Name",
+ "Value",
+ },
+
+ "Directive": []string{
+ "Name",
+ "Arguments",
+ },
+
+ "Named": []string{"Name"},
+ "List": []string{"Type"},
+ "NonNull": []string{"Type"},
+
+ "SchemaDefinition": []string{
+ "Directives",
+ "OperationTypes",
+ },
+ "OperationTypeDefinition": []string{"Type"},
+
+ "ScalarDefinition": []string{
+ "Name",
+ "Directives",
+ },
+ "ObjectDefinition": []string{
+ "Name",
+ "Interfaces",
+ "Directives",
+ "Fields",
+ },
+ "FieldDefinition": []string{
+ "Name",
+ "Arguments",
+ "Type",
+ "Directives",
+ },
+ "InputValueDefinition": []string{
+ "Name",
+ "Type",
+ "DefaultValue",
+ "Directives",
+ },
+ "InterfaceDefinition": []string{
+ "Name",
+ "Directives",
+ "Fields",
+ },
+ "UnionDefinition": []string{
+ "Name",
+ "Directives",
+ "Types",
+ },
+ "EnumDefinition": []string{
+ "Name",
+ "Directives",
+ "Values",
+ },
+ "EnumValueDefinition": []string{
+ "Name",
+ "Directives",
+ },
+ "InputObjectDefinition": []string{
+ "Name",
+ "Directives",
+ "Fields",
+ },
+
+ "TypeExtensionDefinition": []string{"Definition"},
+
+ "DirectiveDefinition": []string{"Name", "Arguments", "Locations"},
+}
+
+type stack struct {
+ Index int
+ Keys []interface{}
+ Edits []*edit
+ inSlice bool
+ Prev *stack
+}
+type edit struct {
+ Key interface{}
+ Value interface{}
+}
+
+type VisitFuncParams struct {
+ Node interface{}
+ Key interface{}
+ Parent ast.Node
+ Path []interface{}
+ Ancestors []ast.Node
+}
+
+type VisitFunc func(p VisitFuncParams) (string, interface{})
+
+type NamedVisitFuncs struct {
+ Kind VisitFunc // 1) Named visitors triggered when entering a node a specific kind.
+ Leave VisitFunc // 2) Named visitors that trigger upon entering and leaving a node of
+ Enter VisitFunc // 2) Named visitors that trigger upon entering and leaving a node of
+}
+
+type VisitorOptions struct {
+ KindFuncMap map[string]NamedVisitFuncs
+ Enter VisitFunc // 3) Generic visitors that trigger upon entering and leaving any node.
+ Leave VisitFunc // 3) Generic visitors that trigger upon entering and leaving any node.
+
+ EnterKindMap map[string]VisitFunc // 4) Parallel visitors for entering and leaving nodes of a specific kind
+ LeaveKindMap map[string]VisitFunc // 4) Parallel visitors for entering and leaving nodes of a specific kind
+}
+
+func Visit(root ast.Node, visitorOpts *VisitorOptions, keyMap KeyMap) interface{} {
+ visitorKeys := keyMap
+ if visitorKeys == nil {
+ visitorKeys = QueryDocumentKeys
+ }
+
+ var result interface{}
+ var newRoot = root
+ var sstack *stack
+ var parent interface{}
+ var parentSlice []interface{}
+ inSlice := false
+ prevInSlice := false
+ keys := []interface{}{newRoot}
+ index := -1
+ edits := []*edit{}
+ path := []interface{}{}
+ ancestors := []interface{}{}
+ ancestorsSlice := [][]interface{}{}
+Loop:
+ for {
+ index = index + 1
+
+ isLeaving := (len(keys) == index)
+ var key interface{} // string for structs or int for slices
+ var node interface{} // ast.Node or can be anything
+ var nodeSlice []interface{}
+ isEdited := (isLeaving && len(edits) != 0)
+
+ if isLeaving {
+ if !inSlice {
+ if len(ancestors) == 0 {
+ key = nil
+ } else {
+ key, path = pop(path)
+ }
+ } else {
+ if len(ancestorsSlice) == 0 {
+ key = nil
+ } else {
+ key, path = pop(path)
+ }
+ }
+
+ node = parent
+ parent, ancestors = pop(ancestors)
+ nodeSlice = parentSlice
+ parentSlice, ancestorsSlice = popNodeSlice(ancestorsSlice)
+
+ if isEdited {
+ prevInSlice = inSlice
+ editOffset := 0
+ for _, edit := range edits {
+ arrayEditKey := 0
+ if inSlice {
+ keyInt := edit.Key.(int)
+ edit.Key = keyInt - editOffset
+ arrayEditKey = edit.Key.(int)
+ }
+ if inSlice && isNilNode(edit.Value) {
+ nodeSlice = spliceNode(nodeSlice, arrayEditKey)
+ editOffset = editOffset + 1
+ } else {
+ if inSlice {
+ nodeSlice[arrayEditKey] = edit.Value
+ } else {
+ key, _ := edit.Key.(string)
+
+ var updatedNode interface{}
+ if !isSlice(edit.Value) {
+ if isStructNode(edit.Value) {
+ updatedNode = updateNodeField(node, key, edit.Value)
+ } else {
+ var todoNode map[string]interface{}
+ b, err := json.Marshal(node)
+ if err != nil {
+ panic(fmt.Sprintf("Invalid root AST Node: %v", root))
+ }
+ err = json.Unmarshal(b, &todoNode)
+ if err != nil {
+ panic(fmt.Sprintf("Invalid root AST Node (2): %v", root))
+ }
+ todoNode[key] = edit.Value
+ updatedNode = todoNode
+ }
+ } else {
+ isSliceOfNodes := true
+
+ // check if edit.value slice is ast.nodes
+ switch reflect.TypeOf(edit.Value).Kind() {
+ case reflect.Slice:
+ s := reflect.ValueOf(edit.Value)
+ for i := 0; i < s.Len(); i++ {
+ elem := s.Index(i)
+ if !isStructNode(elem.Interface()) {
+ isSliceOfNodes = false
+ }
+ }
+ }
+
+ // is a slice of real nodes
+ if isSliceOfNodes {
+ // the node we are writing to is an ast.Node
+ updatedNode = updateNodeField(node, key, edit.Value)
+ } else {
+ var todoNode map[string]interface{}
+ b, err := json.Marshal(node)
+ if err != nil {
+ panic(fmt.Sprintf("Invalid root AST Node: %v", root))
+ }
+ err = json.Unmarshal(b, &todoNode)
+ if err != nil {
+ panic(fmt.Sprintf("Invalid root AST Node (2): %v", root))
+ }
+ todoNode[key] = edit.Value
+ updatedNode = todoNode
+ }
+
+ }
+ node = updatedNode
+ }
+ }
+ }
+ }
+ index = sstack.Index
+ keys = sstack.Keys
+ edits = sstack.Edits
+ inSlice = sstack.inSlice
+ sstack = sstack.Prev
+ } else {
+ // get key
+ if !inSlice {
+ if !isNilNode(parent) {
+ key = getFieldValue(keys, index)
+ } else {
+ // initial conditions
+ key = nil
+ }
+ } else {
+ key = index
+ }
+ // get node
+ if !inSlice {
+ if !isNilNode(parent) {
+ fieldValue := getFieldValue(parent, key)
+ if isNode(fieldValue) {
+ node = fieldValue.(ast.Node)
+ }
+ if isSlice(fieldValue) {
+ nodeSlice = toSliceInterfaces(fieldValue)
+ }
+ } else {
+ // initial conditions
+ node = newRoot
+ }
+ } else {
+ if len(parentSlice) != 0 {
+ fieldValue := getFieldValue(parentSlice, key)
+ if isNode(fieldValue) {
+ node = fieldValue.(ast.Node)
+ }
+ if isSlice(fieldValue) {
+ nodeSlice = toSliceInterfaces(fieldValue)
+ }
+ } else {
+ // initial conditions
+ nodeSlice = []interface{}{}
+ }
+ }
+
+ if isNilNode(node) && len(nodeSlice) == 0 {
+ continue
+ }
+
+ if !inSlice {
+ if !isNilNode(parent) {
+ path = append(path, key)
+ }
+ } else {
+ if len(parentSlice) != 0 {
+ path = append(path, key)
+ }
+ }
+ }
+
+ // get result from visitFn for a node if set
+ var result interface{}
+ resultIsUndefined := true
+ if !isNilNode(node) {
+ if !isNode(node) { // is node-ish.
+ panic(fmt.Sprintf("Invalid AST Node (4): %v", node))
+ }
+
+ // Try to pass in current node as ast.Node
+ // Note that since user can potentially return a non-ast.Node from visit functions.
+ // In that case, we try to unmarshal map[string]interface{} into ast.Node
+ var nodeIn interface{}
+ if _, ok := node.(map[string]interface{}); ok {
+ b, err := json.Marshal(node)
+ if err != nil {
+ panic(fmt.Sprintf("Invalid root AST Node: %v", root))
+ }
+ err = json.Unmarshal(b, &nodeIn)
+ if err != nil {
+ panic(fmt.Sprintf("Invalid root AST Node (2a): %v", root))
+ }
+ } else {
+ nodeIn = node
+ }
+ parentConcrete, _ := parent.(ast.Node)
+ // ancestorsConcrete slice may contain nil values
+ ancestorsConcrete := []ast.Node{}
+ for _, ancestor := range ancestors {
+ if ancestorConcrete, ok := ancestor.(ast.Node); ok {
+ ancestorsConcrete = append(ancestorsConcrete, ancestorConcrete)
+ } else {
+ ancestorsConcrete = append(ancestorsConcrete, nil)
+ }
+ }
+
+ kind := ""
+ if node, ok := node.(map[string]interface{}); ok {
+ kind, _ = node["Kind"].(string)
+ }
+ if node, ok := node.(ast.Node); ok {
+ kind = node.GetKind()
+ }
+
+ visitFn := GetVisitFn(visitorOpts, kind, isLeaving)
+ if visitFn != nil {
+ p := VisitFuncParams{
+ Node: nodeIn,
+ Key: key,
+ Parent: parentConcrete,
+ Path: path,
+ Ancestors: ancestorsConcrete,
+ }
+ action := ActionUpdate
+ action, result = visitFn(p)
+ if action == ActionBreak {
+ break Loop
+ }
+ if action == ActionSkip {
+ if !isLeaving {
+ _, path = pop(path)
+ continue
+ }
+ }
+ if action != ActionNoChange {
+ resultIsUndefined = false
+ edits = append(edits, &edit{
+ Key: key,
+ Value: result,
+ })
+ if !isLeaving {
+ if isNode(result) {
+ node = result
+ } else {
+ _, path = pop(path)
+ continue
+ }
+ }
+ } else {
+ resultIsUndefined = true
+ }
+ }
+
+ }
+
+ // collect back edits on the way out
+ if resultIsUndefined && isEdited {
+ if !prevInSlice {
+ edits = append(edits, &edit{
+ Key: key,
+ Value: node,
+ })
+ } else {
+ edits = append(edits, &edit{
+ Key: key,
+ Value: nodeSlice,
+ })
+ }
+ }
+ if !isLeaving {
+
+ // add to stack
+ prevStack := sstack
+ sstack = &stack{
+ inSlice: inSlice,
+ Index: index,
+ Keys: keys,
+ Edits: edits,
+ Prev: prevStack,
+ }
+
+ // replace keys
+ inSlice = false
+ if len(nodeSlice) > 0 {
+ inSlice = true
+ }
+ keys = []interface{}{}
+
+ if inSlice {
+ // get keys
+ for _, m := range nodeSlice {
+ keys = append(keys, m)
+ }
+ } else {
+ if !isNilNode(node) {
+ if node, ok := node.(ast.Node); ok {
+ kind := node.GetKind()
+ if n, ok := visitorKeys[kind]; ok {
+ for _, m := range n {
+ keys = append(keys, m)
+ }
+ }
+ }
+
+ }
+
+ }
+ index = -1
+ edits = []*edit{}
+
+ ancestors = append(ancestors, parent)
+ parent = node
+ ancestorsSlice = append(ancestorsSlice, parentSlice)
+ parentSlice = nodeSlice
+
+ }
+
+ // loop guard
+ if sstack == nil {
+ break Loop
+ }
+ }
+ if len(edits) != 0 {
+ result = edits[len(edits)-1].Value
+ }
+ return result
+}
+
+func pop(a []interface{}) (x interface{}, aa []interface{}) {
+ if len(a) == 0 {
+ return x, aa
+ }
+ x, aa = a[len(a)-1], a[:len(a)-1]
+ return x, aa
+}
+func popNodeSlice(a [][]interface{}) (x []interface{}, aa [][]interface{}) {
+ if len(a) == 0 {
+ return x, aa
+ }
+ x, aa = a[len(a)-1], a[:len(a)-1]
+ return x, aa
+}
+func spliceNode(a interface{}, i int) (result []interface{}) {
+ if i < 0 {
+ return result
+ }
+ typeOf := reflect.TypeOf(a)
+ if typeOf == nil {
+ return result
+ }
+ switch typeOf.Kind() {
+ case reflect.Slice:
+ s := reflect.ValueOf(a)
+ for i := 0; i < s.Len(); i++ {
+ elem := s.Index(i)
+ elemInterface := elem.Interface()
+ result = append(result, elemInterface)
+ }
+ if i >= s.Len() {
+ return result
+ }
+ return append(result[:i], result[i+1:]...)
+ default:
+ return result
+ }
+}
+
+func getFieldValue(obj interface{}, key interface{}) interface{} {
+ val := reflect.ValueOf(obj)
+ if val.Type().Kind() == reflect.Ptr {
+ val = val.Elem()
+ }
+ if val.Type().Kind() == reflect.Struct {
+ key, ok := key.(string)
+ if !ok {
+ return nil
+ }
+ valField := val.FieldByName(key)
+ if valField.IsValid() {
+ return valField.Interface()
+ }
+ return nil
+ }
+ if val.Type().Kind() == reflect.Slice {
+ key, ok := key.(int)
+ if !ok {
+ return nil
+ }
+ if key >= val.Len() {
+ return nil
+ }
+ valField := val.Index(key)
+ if valField.IsValid() {
+ return valField.Interface()
+ }
+ return nil
+ }
+ if val.Type().Kind() == reflect.Map {
+ keyVal := reflect.ValueOf(key)
+ valField := val.MapIndex(keyVal)
+ if valField.IsValid() {
+ return valField.Interface()
+ }
+ return nil
+ }
+ return nil
+}
+
+func updateNodeField(value interface{}, fieldName string, fieldValue interface{}) (retVal interface{}) {
+ retVal = value
+ val := reflect.ValueOf(value)
+
+ isPtr := false
+ if val.IsValid() && val.Type().Kind() == reflect.Ptr {
+ val = val.Elem()
+ isPtr = true
+ }
+ if !val.IsValid() {
+ return retVal
+ }
+ if val.Type().Kind() == reflect.Struct {
+ for i := 0; i < val.NumField(); i++ {
+ valueField := val.Field(i)
+ typeField := val.Type().Field(i)
+
+ // try matching the field name
+ if typeField.Name == fieldName {
+ fieldValueVal := reflect.ValueOf(fieldValue)
+ if valueField.CanSet() {
+
+ if fieldValueVal.IsValid() {
+ if valueField.Type().Kind() == fieldValueVal.Type().Kind() {
+ if fieldValueVal.Type().Kind() == reflect.Slice {
+ newSliceValue := reflect.MakeSlice(reflect.TypeOf(valueField.Interface()), fieldValueVal.Len(), fieldValueVal.Len())
+ for i := 0; i < newSliceValue.Len(); i++ {
+ dst := newSliceValue.Index(i)
+ src := fieldValueVal.Index(i)
+ srcValue := reflect.ValueOf(src.Interface())
+ if dst.CanSet() {
+ dst.Set(srcValue)
+ }
+ }
+ valueField.Set(newSliceValue)
+
+ } else {
+ valueField.Set(fieldValueVal)
+ }
+ }
+ } else {
+ valueField.Set(reflect.New(valueField.Type()).Elem())
+ }
+ if isPtr == true {
+ retVal = val.Addr().Interface()
+ return retVal
+ }
+ retVal = val.Interface()
+ return retVal
+
+ }
+ }
+ }
+ }
+ return retVal
+}
+func toSliceInterfaces(slice interface{}) (result []interface{}) {
+ switch reflect.TypeOf(slice).Kind() {
+ case reflect.Slice:
+ s := reflect.ValueOf(slice)
+ for i := 0; i < s.Len(); i++ {
+ elem := s.Index(i)
+ elemInterface := elem.Interface()
+ if elem, ok := elemInterface.(ast.Node); ok {
+ result = append(result, elem)
+ }
+ }
+ return result
+ default:
+ return result
+ }
+}
+
+func isSlice(value interface{}) bool {
+ val := reflect.ValueOf(value)
+ if val.IsValid() && val.Type().Kind() == reflect.Slice {
+ return true
+ }
+ return false
+}
+func isNode(node interface{}) bool {
+ val := reflect.ValueOf(node)
+ if val.IsValid() && val.Type().Kind() == reflect.Ptr {
+ val = val.Elem()
+ }
+ if !val.IsValid() {
+ return false
+ }
+ if val.Type().Kind() == reflect.Map {
+ keyVal := reflect.ValueOf("Kind")
+ valField := val.MapIndex(keyVal)
+ return valField.IsValid()
+ }
+ if val.Type().Kind() == reflect.Struct {
+ valField := val.FieldByName("Kind")
+ return valField.IsValid()
+ }
+ return false
+}
+func isStructNode(node interface{}) bool {
+ val := reflect.ValueOf(node)
+ if val.IsValid() && val.Type().Kind() == reflect.Ptr {
+ val = val.Elem()
+ }
+ if !val.IsValid() {
+ return false
+ }
+ if val.Type().Kind() == reflect.Struct {
+ valField := val.FieldByName("Kind")
+ return valField.IsValid()
+ }
+ return false
+}
+
+func isNilNode(node interface{}) bool {
+ val := reflect.ValueOf(node)
+ if !val.IsValid() {
+ return true
+ }
+ if val.Type().Kind() == reflect.Ptr {
+ return val.IsNil()
+ }
+ if val.Type().Kind() == reflect.Slice {
+ return val.Len() == 0
+ }
+ if val.Type().Kind() == reflect.Map {
+ return val.Len() == 0
+ }
+ if val.Type().Kind() == reflect.Bool {
+ return val.Interface().(bool)
+ }
+ return val.Interface() == nil
+}
+
+// VisitInParallel Creates a new visitor instance which delegates to many visitors to run in
+// parallel. Each visitor will be visited for each node before moving on.
+//
+// If a prior visitor edits a node, no following visitors will see that node.
+func VisitInParallel(visitorOptsSlice ...*VisitorOptions) *VisitorOptions {
+ skipping := map[int]interface{}{}
+
+ return &VisitorOptions{
+ Enter: func(p VisitFuncParams) (string, interface{}) {
+ for i, visitorOpts := range visitorOptsSlice {
+ if _, ok := skipping[i]; !ok {
+ switch node := p.Node.(type) {
+ case ast.Node:
+ kind := node.GetKind()
+ fn := GetVisitFn(visitorOpts, kind, false)
+ if fn != nil {
+ action, result := fn(p)
+ if action == ActionSkip {
+ skipping[i] = node
+ } else if action == ActionBreak {
+ skipping[i] = ActionBreak
+ } else if action == ActionUpdate {
+ return ActionUpdate, result
+ }
+ }
+ }
+ }
+ }
+ return ActionNoChange, nil
+ },
+ Leave: func(p VisitFuncParams) (string, interface{}) {
+ for i, visitorOpts := range visitorOptsSlice {
+ skippedNode, ok := skipping[i]
+ if !ok {
+ switch node := p.Node.(type) {
+ case ast.Node:
+ kind := node.GetKind()
+ fn := GetVisitFn(visitorOpts, kind, true)
+ if fn != nil {
+ action, result := fn(p)
+ if action == ActionBreak {
+ skipping[i] = ActionBreak
+ } else if action == ActionUpdate {
+ return ActionUpdate, result
+ }
+ }
+ }
+ } else if skippedNode == p.Node {
+ delete(skipping, i)
+ }
+ }
+ return ActionNoChange, nil
+ },
+ }
+}
+
+// VisitWithTypeInfo Creates a new visitor instance which maintains a provided TypeInfo instance
+// along with visiting visitor.
+func VisitWithTypeInfo(ttypeInfo typeInfo.TypeInfoI, visitorOpts *VisitorOptions) *VisitorOptions {
+ return &VisitorOptions{
+ Enter: func(p VisitFuncParams) (string, interface{}) {
+ if node, ok := p.Node.(ast.Node); ok {
+ ttypeInfo.Enter(node)
+ fn := GetVisitFn(visitorOpts, node.GetKind(), false)
+ if fn != nil {
+ action, result := fn(p)
+ if action == ActionUpdate {
+ ttypeInfo.Leave(node)
+ if isNode(result) {
+ if result, ok := result.(ast.Node); ok {
+ ttypeInfo.Enter(result)
+ }
+ }
+ }
+ return action, result
+ }
+ }
+ return ActionNoChange, nil
+ },
+ Leave: func(p VisitFuncParams) (string, interface{}) {
+ action := ActionNoChange
+ var result interface{}
+ if node, ok := p.Node.(ast.Node); ok {
+ fn := GetVisitFn(visitorOpts, node.GetKind(), true)
+ if fn != nil {
+ action, result = fn(p)
+ }
+ ttypeInfo.Leave(node)
+ }
+ return action, result
+ },
+ }
+}
+
+// GetVisitFn Given a visitor instance, if it is leaving or not, and a node kind, return
+// the function the visitor runtime should call.
+func GetVisitFn(visitorOpts *VisitorOptions, kind string, isLeaving bool) VisitFunc {
+ if visitorOpts == nil {
+ return nil
+ }
+ kindVisitor, ok := visitorOpts.KindFuncMap[kind]
+ if ok {
+ if !isLeaving && kindVisitor.Kind != nil {
+ // { Kind() {} }
+ return kindVisitor.Kind
+ }
+ if isLeaving {
+ // { Kind: { leave() {} } }
+ return kindVisitor.Leave
+ }
+ // { Kind: { enter() {} } }
+ return kindVisitor.Enter
+
+ }
+ if isLeaving {
+ // { enter() {} }
+ specificVisitor := visitorOpts.Leave
+ if specificVisitor != nil {
+ return specificVisitor
+ }
+ if specificKindVisitor, ok := visitorOpts.LeaveKindMap[kind]; ok {
+ // { leave: { Kind() {} } }
+ return specificKindVisitor
+ }
+
+ }
+ // { leave() {} }
+ specificVisitor := visitorOpts.Enter
+ if specificVisitor != nil {
+ return specificVisitor
+ }
+ if specificKindVisitor, ok := visitorOpts.EnterKindMap[kind]; ok {
+ // { enter: { Kind() {} } }
+ return specificKindVisitor
+ }
+ return nil
+}