Class: GraphQL::Analysis::AST::Visitor

Inherits:
Language::Visitor show all
Defined in:
lib/graphql/analysis/ast/visitor.rb

Overview

Depth first traversal through a query AST, calling AST analyzers along the way.

The visitor is a special case of GraphQL::Language::Visitor, visiting only the selected operation, providing helpers for common use cases such as skipped fields and visiting fragment spreads.

See Also:

  • AST Analyzers for queries

Constant Summary

Constants inherited from Language::Visitor

Language::Visitor::DELETE_NODE, Language::Visitor::SKIP

Instance Attribute Summary collapse

Attributes inherited from Language::Visitor

#result

Instance Method Summary collapse

Methods inherited from Language::Visitor

#[], make_visit_method, #visit_node

Constructor Details

#initialize(query:, analyzers:) ⇒ Visitor

Returns a new instance of Visitor



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/graphql/analysis/ast/visitor.rb', line 14

def initialize(query:, analyzers:)
  @analyzers = analyzers
  @path = []
  @object_types = []
  @directives = []
  @field_definitions = []
  @argument_definitions = []
  @directive_definitions = []
  @query = query
  @schema = query.schema
  @response_path = []
  @skip_stack = [false]
  super(query.selected_operation)
end

Instance Attribute Details

#object_typesArray<GraphQL::ObjectType> (readonly)

Returns Types whose scope we’ve entered

Returns:



33
34
35
# File 'lib/graphql/analysis/ast/visitor.rb', line 33

def object_types
  @object_types
end

#queryGraphQL::Query (readonly)

Returns the query being visited

Returns:



30
31
32
# File 'lib/graphql/analysis/ast/visitor.rb', line 30

def query
  @query
end

Instance Method Details

#argument_definitionGraphQL::Argument?

Returns The most-recently-entered GraphQL::Argument, if currently inside one

Returns:

  • (GraphQL::Argument, nil)

    The most-recently-entered GraphQL::Argument, if currently inside one



222
223
224
225
226
# File 'lib/graphql/analysis/ast/visitor.rb', line 222

def argument_definition
  # Don't get the _last_ one because that's the current one.
  # Get the second-to-last one, which is the parent of the current one.
  @argument_definitions[-2]
end

#arguments_for(ast_node, field_definition) ⇒ GraphQL::Query::Arguments

Returns Arguments for this node, merging default values, literal values and query variables

Returns:

See Also:

  • GraphQL::Analysis::AST::Visitor.{GraphQL{GraphQL::Query{GraphQL::Query#arguments_for}


44
45
46
# File 'lib/graphql/analysis/ast/visitor.rb', line 44

def arguments_for(ast_node, field_definition)
  @query.arguments_for(ast_node, field_definition)
end

#directive_definitionGraphQL::Directive?

Returns The most-recently-entered GraphQL::Directive, if currently inside one

Returns:

  • (GraphQL::Directive, nil)

    The most-recently-entered GraphQL::Directive, if currently inside one



217
218
219
# File 'lib/graphql/analysis/ast/visitor.rb', line 217

def directive_definition
  @directive_definitions.last
end

#enter_fragment_spread_inline(fragment_spread) ⇒ Object

Visit a fragment spread inline instead of visiting the definition by itself.



174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/graphql/analysis/ast/visitor.rb', line 174

def enter_fragment_spread_inline(fragment_spread)
  fragment_def = query.fragments[fragment_spread.name]

  object_type = if fragment_def.type
    query.schema.types.fetch(fragment_def.type.name, nil)
  else
    object_types.last
  end

  object_types << object_type

  fragment_def.selections.each do |selection|
    visit_node(selection, fragment_def)
  end
end

#field_definitionGraphQL::Field?

Returns The most-recently-entered GraphQL::Field, if currently inside one

Returns:

  • (GraphQL::Field, nil)

    The most-recently-entered GraphQL::Field, if currently inside one



207
208
209
# File 'lib/graphql/analysis/ast/visitor.rb', line 207

def field_definition
  @field_definitions.last
end

#leave_fragment_spread_inline(_fragment_spread) ⇒ Object

Visit a fragment spread inline instead of visiting the definition by itself.



192
193
194
# File 'lib/graphql/analysis/ast/visitor.rb', line 192

def leave_fragment_spread_inline(_fragment_spread)
  object_types.pop
end

#on_abstract_node(node, parent) ⇒ Object



166
167
168
169
170
# File 'lib/graphql/analysis/ast/visitor.rb', line 166

def on_abstract_node(node, parent)
  call_analyzers(:on_enter_abstract_node, node, parent)
  super
  call_analyzers(:on_leave_abstract_node, node, parent)
end

#on_argument(node, parent) ⇒ Object



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/graphql/analysis/ast/visitor.rb', line 133

def on_argument(node, parent)
  argument_defn = if (arg = @argument_definitions.last)
    arg_type = arg.type.unwrap
    if arg_type.kind.input_object?
      arg_type.input_fields[node.name]
    else
      nil
    end
  elsif (directive_defn = @directive_definitions.last)
    directive_defn.arguments[node.name]
  elsif (field_defn = @field_definitions.last)
    field_defn.arguments[node.name]
  else
    nil
  end

  @argument_definitions.push(argument_defn)
  @path.push(node.name)
  call_analyzers(:on_enter_argument, node, parent)
  super
  call_analyzers(:on_leave_argument, node, parent)
  @argument_definitions.pop
  @path.pop
end

#on_directive(node, parent) ⇒ Object



124
125
126
127
128
129
130
131
# File 'lib/graphql/analysis/ast/visitor.rb', line 124

def on_directive(node, parent)
  directive_defn = @schema.directives[node.name]
  @directive_definitions.push(directive_defn)
  call_analyzers(:on_enter_directive, node, parent)
  super
  call_analyzers(:on_leave_directive, node, parent)
  @directive_definitions.pop
end

#on_field(node, parent) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/graphql/analysis/ast/visitor.rb', line 96

def on_field(node, parent)
  @response_path.push(node.alias || node.name)
  parent_type = @object_types.last
  field_definition = @schema.get_field(parent_type, node.name)
  @field_definitions.push(field_definition)
  if !field_definition.nil?
    next_object_type = field_definition.type.unwrap
    @object_types.push(next_object_type)
  else
    @object_types.push(nil)
  end
  @path.push(node.alias || node.name)

  @skipping = @skip_stack.last || skip?(node)
  @skip_stack << @skipping

  call_analyzers(:on_enter_field, node, parent)
  super

  @skipping = @skip_stack.pop

  call_analyzers(:on_leave_field, node, parent)
  @response_path.pop
  @field_definitions.pop
  @object_types.pop
  @path.pop
end

#on_fragment_definition(node, parent) ⇒ Object



76
77
78
79
80
81
82
83
84
85
# File 'lib/graphql/analysis/ast/visitor.rb', line 76

def on_fragment_definition(node, parent)
  on_fragment_with_type(node) do
    @path.push("fragment #{node.name}")
    @in_fragment_def = false
    call_analyzers(:on_enter_fragment_definition, node, parent)
    super
    @in_fragment_def = false
    call_analyzers(:on_leave_fragment_definition, node, parent)
  end
end

#on_fragment_spread(node, parent) ⇒ Object



158
159
160
161
162
163
164
# File 'lib/graphql/analysis/ast/visitor.rb', line 158

def on_fragment_spread(node, parent)
  @path.push("... #{node.name}")
  call_analyzers(:on_enter_fragment_spread, node, parent)
  super
  call_analyzers(:on_leave_fragment_spread, node, parent)
  @path.pop
end

#on_inline_fragment(node, parent) ⇒ Object



87
88
89
90
91
92
93
94
# File 'lib/graphql/analysis/ast/visitor.rb', line 87

def on_inline_fragment(node, parent)
  on_fragment_with_type(node) do
    @path.push("...#{node.type ? " on #{node.type.name}" : ""}")
    call_analyzers(:on_enter_inline_fragment, node, parent)
    super
    call_analyzers(:on_leave_inline_fragment, node, parent)
  end
end

#on_operation_definition(node, parent) ⇒ Object

Visitor Hooks



65
66
67
68
69
70
71
72
73
74
# File 'lib/graphql/analysis/ast/visitor.rb', line 65

def on_operation_definition(node, parent)
  object_type = @schema.root_type_for_operation(node.operation_type)
  @object_types.push(object_type)
  @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
  call_analyzers(:on_enter_operation_definition, node, parent)
  super
  call_analyzers(:on_leave_operation_definition, node, parent)
  @object_types.pop
  @path.pop
end

#parent_type_definitionGraphQL::BaseType

Returns The type which the current type came from

Returns:



202
203
204
# File 'lib/graphql/analysis/ast/visitor.rb', line 202

def parent_type_definition
  @object_types[-2]
end

#previous_field_definitionGraphQL::Field?

Returns The GraphQL field which returned the object that the current field belongs to

Returns:

  • (GraphQL::Field, nil)

    The GraphQL field which returned the object that the current field belongs to



212
213
214
# File 'lib/graphql/analysis/ast/visitor.rb', line 212

def previous_field_definition
  @field_definitions[-2]
end

#response_pathArray<String>

Returns The path to the response key for the current field

Returns:

  • (Array<String>)

    The path to the response key for the current field



59
60
61
# File 'lib/graphql/analysis/ast/visitor.rb', line 59

def response_path
  @response_path.dup
end

#skipping?Boolean

Returns If the current node should be skipped because of a skip or include directive

Returns:

  • (Boolean)

    If the current node should be skipped because of a skip or include directive



54
55
56
# File 'lib/graphql/analysis/ast/visitor.rb', line 54

def skipping?
  @skipping
end

#type_definitionGraphQL::BaseType

Returns The current object type

Returns:



197
198
199
# File 'lib/graphql/analysis/ast/visitor.rb', line 197

def type_definition
  @object_types.last
end

#visitObject



35
36
37
38
# File 'lib/graphql/analysis/ast/visitor.rb', line 35

def visit
  return unless @document
  super
end

#visiting_fragment_definition?Boolean

Returns If the visitor is currently inside a fragment definition

Returns:

  • (Boolean)

    If the visitor is currently inside a fragment definition



49
50
51
# File 'lib/graphql/analysis/ast/visitor.rb', line 49

def visiting_fragment_definition?
  @in_fragment_def
end