Skip to content

Commit 2f59322

Browse files
committed
Migrate rewrite to use class-based visitor
1 parent ed9e968 commit 2f59322

File tree

8 files changed

+200
-225
lines changed

8 files changed

+200
-225
lines changed

lib/graphql/internal_representation/rewrite.rb

Lines changed: 127 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -13,127 +13,29 @@ module InternalRepresentation
1313
#
1414
# The rewritten query tree serves as the basis for the `FieldsWillMerge` validation.
1515
#
16-
class Rewrite
16+
module Rewrite
1717
include GraphQL::Language
1818

1919
NO_DIRECTIVES = [].freeze
2020

2121
# @return InternalRepresentation::Document
22-
attr_reader :document
22+
attr_reader :rewrite_document
2323

24-
def initialize
25-
@document = InternalRepresentation::Document.new
26-
end
27-
28-
# @return [Hash<String, Node>] Roots of this query
29-
def operations
30-
warn "#{self.class}#operations is deprecated; use `document.operation_definitions` instead"
31-
document.operation_definitions
32-
end
33-
34-
def validate(context)
35-
visitor = context.visitor
36-
query = context.query
24+
def initialize(*)
25+
super
26+
@query = context.query
27+
@rewrite_document = InternalRepresentation::Document.new
3728
# Hash<Nodes::FragmentSpread => Set<InternalRepresentation::Node>>
3829
# A record of fragment spreads and the irep nodes that used them
39-
spread_parents = Hash.new { |h, k| h[k] = Set.new }
30+
@rewrite_spread_parents = Hash.new { |h, k| h[k] = Set.new }
4031
# Hash<Nodes::FragmentSpread => Scope>
41-
spread_scopes = {}
32+
@rewrite_spread_scopes = {}
4233
# Array<Set<InternalRepresentation::Node>>
4334
# The current point of the irep_tree during visitation
44-
nodes_stack = []
35+
@rewrite_nodes_stack = []
4536
# Array<Scope>
46-
scopes_stack = []
47-
48-
skip_nodes = Set.new
49-
50-
visit_op = VisitDefinition.new(context, @document.operation_definitions, nodes_stack, scopes_stack)
51-
visitor[Nodes::OperationDefinition].enter << visit_op.method(:enter)
52-
visitor[Nodes::OperationDefinition].leave << visit_op.method(:leave)
53-
54-
visit_frag = VisitDefinition.new(context, @document.fragment_definitions, nodes_stack, scopes_stack)
55-
visitor[Nodes::FragmentDefinition].enter << visit_frag.method(:enter)
56-
visitor[Nodes::FragmentDefinition].leave << visit_frag.method(:leave)
57-
58-
visitor[Nodes::InlineFragment].enter << ->(ast_node, ast_parent) {
59-
# Inline fragments provide two things to the rewritten tree:
60-
# - They _may_ narrow the scope by their type condition
61-
# - They _may_ apply their directives to their children
62-
if skip?(ast_node, query)
63-
skip_nodes.add(ast_node)
64-
end
65-
66-
if skip_nodes.none?
67-
scopes_stack.push(scopes_stack.last.enter(context.type_definition))
68-
end
69-
}
70-
71-
visitor[Nodes::InlineFragment].leave << ->(ast_node, ast_parent) {
72-
if skip_nodes.none?
73-
scopes_stack.pop
74-
end
75-
76-
if skip_nodes.include?(ast_node)
77-
skip_nodes.delete(ast_node)
78-
end
79-
}
80-
81-
visitor[Nodes::Field].enter << ->(ast_node, ast_parent) {
82-
if skip?(ast_node, query)
83-
skip_nodes.add(ast_node)
84-
end
85-
86-
if skip_nodes.none?
87-
node_name = ast_node.alias || ast_node.name
88-
parent_nodes = nodes_stack.last
89-
next_nodes = []
90-
91-
field_defn = context.field_definition
92-
if field_defn.nil?
93-
# It's a non-existent field
94-
new_scope = nil
95-
else
96-
field_return_type = field_defn.type
97-
scopes_stack.last.each do |scope_type|
98-
parent_nodes.each do |parent_node|
99-
node = parent_node.scoped_children[scope_type][node_name] ||= Node.new(
100-
parent: parent_node,
101-
name: node_name,
102-
owner_type: scope_type,
103-
query: query,
104-
return_type: field_return_type,
105-
)
106-
node.ast_nodes << ast_node
107-
node.definitions << field_defn
108-
next_nodes << node
109-
end
110-
end
111-
new_scope = Scope.new(query, field_return_type.unwrap)
112-
end
113-
114-
nodes_stack.push(next_nodes)
115-
scopes_stack.push(new_scope)
116-
end
117-
}
118-
119-
visitor[Nodes::Field].leave << ->(ast_node, ast_parent) {
120-
if skip_nodes.none?
121-
nodes_stack.pop
122-
scopes_stack.pop
123-
end
124-
125-
if skip_nodes.include?(ast_node)
126-
skip_nodes.delete(ast_node)
127-
end
128-
}
129-
130-
visitor[Nodes::FragmentSpread].enter << ->(ast_node, ast_parent) {
131-
if skip_nodes.none? && !skip?(ast_node, query)
132-
# Register the irep nodes that depend on this AST node:
133-
spread_parents[ast_node].merge(nodes_stack.last)
134-
spread_scopes[ast_node] = scopes_stack.last
135-
end
136-
}
37+
@rewrite_scopes_stack = []
38+
@rewrite_skip_nodes = Set.new
13739

13840
# Resolve fragment spreads.
13941
# Fragment definitions got their own irep trees during visitation.
@@ -142,12 +44,12 @@ def validate(context)
14244
# can be shared between its usages.
14345
context.on_dependency_resolve do |defn_ast_node, spread_ast_nodes, frag_ast_node|
14446
frag_name = frag_ast_node.name
145-
fragment_node = @document.fragment_definitions[frag_name]
47+
fragment_node = @rewrite_document.fragment_definitions[frag_name]
14648

14749
if fragment_node
14850
spread_ast_nodes.each do |spread_ast_node|
149-
parent_nodes = spread_parents[spread_ast_node]
150-
parent_scope = spread_scopes[spread_ast_node]
51+
parent_nodes = @rewrite_spread_parents[spread_ast_node]
52+
parent_scope = @rewrite_spread_scopes[spread_ast_node]
15153
parent_nodes.each do |parent_node|
15254
parent_node.deep_merge_node(fragment_node, scope: parent_scope, merge_self: false)
15355
end
@@ -156,43 +58,126 @@ def validate(context)
15658
end
15759
end
15860

159-
def skip?(ast_node, query)
160-
dir = ast_node.directives
161-
dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
61+
# @return [Hash<String, Node>] Roots of this query
62+
def operations
63+
warn "#{self.class}#operations is deprecated; use `document.operation_definitions` instead"
64+
@document.operation_definitions
65+
end
66+
67+
def on_operation_definition(ast_node, parent)
68+
push_root_node(ast_node, @rewrite_document.operation_definitions) { super }
69+
end
70+
71+
def on_fragment_definition(ast_node, parent)
72+
push_root_node(ast_node, @rewrite_document.fragment_definitions) { super }
73+
end
74+
75+
def push_root_node(ast_node, definitions)
76+
# Either QueryType or the fragment type condition
77+
owner_type = context.type_definition
78+
defn_name = ast_node.name
79+
80+
node = Node.new(
81+
parent: nil,
82+
name: defn_name,
83+
owner_type: owner_type,
84+
query: @query,
85+
ast_nodes: [ast_node],
86+
return_type: owner_type,
87+
)
88+
89+
definitions[defn_name] = node
90+
@rewrite_scopes_stack.push(Scope.new(@query, owner_type))
91+
@rewrite_nodes_stack.push([node])
92+
yield
93+
@rewrite_nodes_stack.pop
94+
@rewrite_scopes_stack.pop
95+
end
96+
97+
def on_inline_fragment(node, parent)
98+
# Inline fragments provide two things to the rewritten tree:
99+
# - They _may_ narrow the scope by their type condition
100+
# - They _may_ apply their directives to their children
101+
if skip?(node)
102+
@rewrite_skip_nodes.add(node)
103+
end
104+
105+
if @rewrite_skip_nodes.none?
106+
@rewrite_scopes_stack.push(@rewrite_scopes_stack.last.enter(context.type_definition))
107+
end
108+
109+
super
110+
111+
if @rewrite_skip_nodes.none?
112+
@rewrite_scopes_stack.pop
113+
end
114+
115+
if @rewrite_skip_nodes.include?(node)
116+
@rewrite_skip_nodes.delete(node)
117+
end
162118
end
163119

164-
class VisitDefinition
165-
def initialize(context, definitions, nodes_stack, scopes_stack)
166-
@context = context
167-
@query = context.query
168-
@definitions = definitions
169-
@nodes_stack = nodes_stack
170-
@scopes_stack = scopes_stack
120+
def on_field(ast_node, ast_parent)
121+
if skip?(ast_node)
122+
@rewrite_skip_nodes.add(ast_node)
123+
end
124+
125+
if @rewrite_skip_nodes.none?
126+
node_name = ast_node.alias || ast_node.name
127+
parent_nodes = @rewrite_nodes_stack.last
128+
next_nodes = []
129+
130+
field_defn = context.field_definition
131+
if field_defn.nil?
132+
# It's a non-existent field
133+
new_scope = nil
134+
else
135+
field_return_type = field_defn.type
136+
@rewrite_scopes_stack.last.each do |scope_type|
137+
parent_nodes.each do |parent_node|
138+
node = parent_node.scoped_children[scope_type][node_name] ||= Node.new(
139+
parent: parent_node,
140+
name: node_name,
141+
owner_type: scope_type,
142+
query: @query,
143+
return_type: field_return_type,
144+
)
145+
node.ast_nodes << ast_node
146+
node.definitions << field_defn
147+
next_nodes << node
148+
end
149+
end
150+
new_scope = Scope.new(@query, field_return_type.unwrap)
151+
end
152+
153+
@rewrite_nodes_stack.push(next_nodes)
154+
@rewrite_scopes_stack.push(new_scope)
155+
end
156+
157+
super
158+
159+
if @rewrite_skip_nodes.none?
160+
@rewrite_nodes_stack.pop
161+
@rewrite_scopes_stack.pop
171162
end
172163

173-
def enter(ast_node, ast_parent)
174-
# Either QueryType or the fragment type condition
175-
owner_type = @context.type_definition && @context.type_definition.unwrap
176-
defn_name = ast_node.name
177-
178-
node = Node.new(
179-
parent: nil,
180-
name: defn_name,
181-
owner_type: owner_type,
182-
query: @query,
183-
ast_nodes: [ast_node],
184-
return_type: @context.type_definition,
185-
)
186-
187-
@definitions[defn_name] = node
188-
@scopes_stack.push(Scope.new(@query, owner_type))
189-
@nodes_stack.push([node])
164+
if @rewrite_skip_nodes.include?(ast_node)
165+
@rewrite_skip_nodes.delete(ast_node)
190166
end
167+
end
191168

192-
def leave(ast_node, ast_parent)
193-
@nodes_stack.pop
194-
@scopes_stack.pop
169+
def on_fragment_spread(ast_node, ast_parent)
170+
if @rewrite_skip_nodes.none? && !skip?(ast_node)
171+
# Register the irep nodes that depend on this AST node:
172+
@rewrite_spread_parents[ast_node].merge(@rewrite_nodes_stack.last)
173+
@rewrite_spread_scopes[ast_node] = @rewrite_scopes_stack.last
195174
end
175+
super
176+
end
177+
178+
def skip?(ast_node)
179+
dir = ast_node.directives
180+
dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, @query)
196181
end
197182
end
198183
end

lib/graphql/static_validation/base_visitor.rb

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,11 @@ def on_inline_fragment(node, parent)
4949
end
5050

5151
def on_field(node, parent)
52-
parent_type = @object_types.last.unwrap
52+
parent_type = @object_types.last
5353
field_definition = @schema.get_field(parent_type, node.name)
5454
@field_definitions.push(field_definition)
5555
if !field_definition.nil?
56-
next_object_type = field_definition.type
56+
next_object_type = field_definition.type.unwrap
5757
@object_types.push(next_object_type)
5858
else
5959
@object_types.push(nil)
@@ -136,9 +136,6 @@ def on_fragment_with_type(node)
136136
else
137137
@object_types.last
138138
end
139-
if !object_type.nil?
140-
object_type = object_type.unwrap
141-
end
142139
@object_types.push(object_type)
143140
yield(node)
144141
@object_types.pop

lib/graphql/static_validation/default_visitor.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
module GraphQL
33
module StaticValidation
44
class DefaultVisitor < BaseVisitor
5+
include(GraphQL::StaticValidation::DefinitionDependencies)
6+
57
StaticValidation::ALL_RULES.reverse_each do |r|
68
include(r)
79
end
810

11+
include(GraphQL::InternalRepresentation::Rewrite)
912
prepend(ContextMethods)
1013
end
1114
end

0 commit comments

Comments
 (0)