@@ -4,25 +4,26 @@ module StaticValidation
44 class BaseVisitor < GraphQL ::Language ::StaticVisitor
55 def initialize ( document , context )
66 @path = [ ]
7- @object_types = [ ]
8- @directives = [ ]
9- @field_definitions = [ ]
10- @argument_definitions = [ ]
11- @directive_definitions = [ ]
7+ @path_depth = 0
8+ @current_object_type = nil
9+ @parent_object_type = nil
10+ @current_field_definition = nil
11+ @current_argument_definition = nil
12+ @parent_argument_definition = nil
13+ @current_directive_definition = nil
1214 @context = context
1315 @types = context . query . types
1416 @schema = context . schema
17+ @inline_fragment_paths = { }
18+ @field_unwrapped_types = { } . compare_by_identity
1519 super ( document )
1620 end
1721
1822 attr_reader :context
1923
20- # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
21- attr_reader :object_types
22-
2324 # @return [Array<String>] The nesting of the current position in the AST
2425 def path
25- @path . dup
26+ @path [ 0 , @path_depth ]
2627 end
2728
2829 # Build a class to visit the AST and perform validation,
@@ -55,135 +56,158 @@ def self.including_rules(rules)
5556 module ContextMethods
5657 def on_operation_definition ( node , parent )
5758 object_type = @schema . root_type_for_operation ( node . operation_type )
58- push_type ( object_type )
59- @path . push ( "#{ node . operation_type } #{ node . name ? " #{ node . name } " : "" } " )
59+ prev_parent_ot = @parent_object_type
60+ @parent_object_type = @current_object_type
61+ @current_object_type = object_type
62+ @path [ @path_depth ] = "#{ node . operation_type } #{ node . name ? " #{ node . name } " : "" } "
63+ @path_depth += 1
6064 super
61- @object_types . pop
62- @path . pop
65+ @current_object_type = @parent_object_type
66+ @parent_object_type = prev_parent_ot
67+ @path_depth -= 1
6368 end
6469
6570 def on_fragment_definition ( node , parent )
66- on_fragment_with_type ( node ) do
67- @path . push ( "fragment #{ node . name } " )
68- super
71+ object_type = if node . type
72+ @types . type ( node . type . name )
73+ else
74+ @current_object_type
6975 end
76+ prev_parent_ot = @parent_object_type
77+ @parent_object_type = @current_object_type
78+ @current_object_type = object_type
79+ @path [ @path_depth ] = "fragment #{ node . name } "
80+ @path_depth += 1
81+ super
82+ @current_object_type = @parent_object_type
83+ @parent_object_type = prev_parent_ot
84+ @path_depth -= 1
7085 end
7186
87+ INLINE_FRAGMENT_NO_TYPE = "..."
88+
7289 def on_inline_fragment ( node , parent )
73- on_fragment_with_type ( node ) do
74- @path . push ( "...#{ node . type ? " on #{ node . type . to_query_string } " : "" } " )
75- super
90+ if node . type
91+ object_type = @types . type ( node . type . name )
92+ @path [ @path_depth ] = @inline_fragment_paths [ node . type . name ] ||= -"... on #{ node . type . to_query_string } "
93+ @path_depth += 1
94+ else
95+ object_type = @current_object_type
96+ @path [ @path_depth ] = INLINE_FRAGMENT_NO_TYPE
97+ @path_depth += 1
7698 end
99+ prev_parent_ot = @parent_object_type
100+ @parent_object_type = @current_object_type
101+ @current_object_type = object_type
102+ super
103+ @current_object_type = @parent_object_type
104+ @parent_object_type = prev_parent_ot
105+ @path_depth -= 1
77106 end
78107
79108 def on_field ( node , parent )
80- parent_type = @object_types . last
109+ parent_type = @current_object_type
81110 field_definition = @types . field ( parent_type , node . name )
82- @field_definitions . push ( field_definition )
83- if !field_definition . nil?
84- next_object_type = field_definition . type . unwrap
85- push_type ( next_object_type )
111+ prev_field_definition = @current_field_definition
112+ @current_field_definition = field_definition
113+ prev_parent_ot = @parent_object_type
114+ @parent_object_type = @current_object_type
115+ if field_definition
116+ @current_object_type = @field_unwrapped_types [ field_definition ] ||= field_definition . type . unwrap
86117 else
87- push_type ( nil )
118+ @current_object_type = nil
88119 end
89- @path . push ( node . alias || node . name )
120+ @path [ @path_depth ] = node . alias || node . name
121+ @path_depth += 1
90122 super
91- @field_definitions . pop
92- @object_types . pop
93- @path . pop
123+ @current_field_definition = prev_field_definition
124+ @current_object_type = @parent_object_type
125+ @parent_object_type = prev_parent_ot
126+ @path_depth -= 1
94127 end
95128
96129 def on_directive ( node , parent )
97130 directive_defn = @context . schema_directives [ node . name ]
98- @directive_definitions . push ( directive_defn )
131+ prev_directive_definition = @current_directive_definition
132+ @current_directive_definition = directive_defn
99133 super
100- @directive_definitions . pop
134+ @current_directive_definition = prev_directive_definition
101135 end
102136
103137 def on_argument ( node , parent )
104- argument_defn = if ( arg = @argument_definitions . last )
138+ argument_defn = if ( arg = @current_argument_definition )
105139 arg_type = arg . type . unwrap
106140 if arg_type . kind . input_object?
107141 @types . argument ( arg_type , node . name )
108142 else
109143 nil
110144 end
111- elsif ( directive_defn = @directive_definitions . last )
145+ elsif ( directive_defn = @current_directive_definition )
112146 @types . argument ( directive_defn , node . name )
113- elsif ( field_defn = @field_definitions . last )
147+ elsif ( field_defn = @current_field_definition )
114148 @types . argument ( field_defn , node . name )
115149 else
116150 nil
117151 end
118152
119- @argument_definitions . push ( argument_defn )
120- @path . push ( node . name )
153+ prev_parent = @parent_argument_definition
154+ @parent_argument_definition = @current_argument_definition
155+ @current_argument_definition = argument_defn
156+ @path [ @path_depth ] = node . name
157+ @path_depth += 1
121158 super
122- @argument_definitions . pop
123- @path . pop
159+ @current_argument_definition = @parent_argument_definition
160+ @parent_argument_definition = prev_parent
161+ @path_depth -= 1
124162 end
125163
126164 def on_fragment_spread ( node , parent )
127- @path . push ( "... #{ node . name } " )
165+ @path [ @path_depth ] = "... #{ node . name } "
166+ @path_depth += 1
128167 super
129- @path . pop
168+ @path_depth -= 1
130169 end
131170
132171 def on_input_object ( node , parent )
133- arg_defn = @argument_definitions . last
172+ arg_defn = @current_argument_definition
134173 if arg_defn && arg_defn . type . list?
135- @path . push ( parent . children . index ( node ) )
174+ @path [ @path_depth ] = parent . children . index ( node )
175+ @path_depth += 1
136176 super
137- @path . pop
177+ @path_depth -= 1
138178 else
139179 super
140180 end
141181 end
142182
143183 # @return [GraphQL::BaseType] The current object type
144184 def type_definition
145- @object_types . last
185+ @current_object_type
146186 end
147187
148188 # @return [GraphQL::BaseType] The type which the current type came from
149189 def parent_type_definition
150- @object_types [ - 2 ]
190+ @parent_object_type
151191 end
152192
153193 # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
154194 def field_definition
155- @field_definitions . last
195+ @current_field_definition
156196 end
157197
158198 # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
159199 def directive_definition
160- @directive_definitions . last
200+ @current_directive_definition
161201 end
162202
163203 # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
164204 def argument_definition
165- # Don't get the _last_ one because that's the current one.
166- # Get the second-to-last one, which is the parent of the current one.
167- @argument_definitions [ -2 ]
205+ # Return the parent argument definition (not the current one).
206+ @parent_argument_definition
168207 end
169208
170209 private
171210
172- def on_fragment_with_type ( node )
173- object_type = if node . type
174- @types . type ( node . type . name )
175- else
176- @object_types . last
177- end
178- push_type ( object_type )
179- yield ( node )
180- @object_types . pop
181- @path . pop
182- end
183-
184- def push_type ( t )
185- @object_types . push ( t )
186- end
187211 end
188212
189213 private
@@ -192,7 +216,7 @@ def add_error(error, path: nil)
192216 if @context . too_many_errors?
193217 throw :too_many_validation_errors
194218 end
195- error . path ||= ( path || @path . dup )
219+ error . path ||= ( path || @path [ 0 , @path_depth ] )
196220 context . errors << error
197221 end
198222
0 commit comments