@@ -3,26 +3,29 @@ module GraphQL
33 module StaticValidation
44 class BaseVisitor < GraphQL ::Language ::StaticVisitor
55 def initialize ( document , context )
6- @path = [ ]
7- @object_types = [ ]
8- @directives = [ ]
9- @field_definitions = [ ]
10- @argument_definitions = [ ]
11- @directive_definitions = [ ]
6+ @path = Array . new ( 32 ) # pre-allocated, indexed by @path_depth
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
24+ attr_reader :current_object_type , :parent_object_type
2225
2326 # @return [Array<String>] The nesting of the current position in the AST
2427 def path
25- @path . dup
28+ @path [ 0 , @path_depth ]
2629 end
2730
2831 # Build a class to visit the AST and perform validation,
@@ -55,135 +58,150 @@ def self.including_rules(rules)
5558 module ContextMethods
5659 def on_operation_definition ( node , parent )
5760 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 } " : "" } " )
61+ prev_parent_ot = @parent_object_type
62+ @parent_object_type = @current_object_type
63+ @current_object_type = object_type
64+ @path [ @path_depth ] = "#{ node . operation_type } #{ node . name ? " #{ node . name } " : "" } " ; @path_depth += 1
6065 super
61- @object_types . pop
62- @path . pop
66+ @current_object_type = @parent_object_type
67+ @parent_object_type = prev_parent_ot
68+ @path_depth -= 1
6369 end
6470
6571 def on_fragment_definition ( node , parent )
66- on_fragment_with_type ( node ) do
67- @path . push ( "fragment #{ node . name } " )
68- super
72+ object_type = if node . type
73+ @types . type ( node . type . name )
74+ else
75+ @current_object_type
6976 end
77+ prev_parent_ot = @parent_object_type
78+ @parent_object_type = @current_object_type
79+ @current_object_type = object_type
80+ @path [ @path_depth ] = "fragment #{ node . name } " ; @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 } " ; @path_depth += 1
93+ else
94+ object_type = @current_object_type
95+ @path [ @path_depth ] = INLINE_FRAGMENT_NO_TYPE ; @path_depth += 1
7696 end
97+ prev_parent_ot = @parent_object_type
98+ @parent_object_type = @current_object_type
99+ @current_object_type = object_type
100+ super
101+ @current_object_type = @parent_object_type
102+ @parent_object_type = prev_parent_ot
103+ @path_depth -= 1
77104 end
78105
79106 def on_field ( node , parent )
80- parent_type = @object_types . last
107+ parent_type = @current_object_type
81108 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 )
109+ prev_field_definition = @current_field_definition
110+ @current_field_definition = field_definition
111+ prev_parent_ot = @parent_object_type
112+ @parent_object_type = @current_object_type
113+ if field_definition
114+ @current_object_type = @field_unwrapped_types [ field_definition ] ||= field_definition . type . unwrap
86115 else
87- push_type ( nil )
116+ @current_object_type = nil
88117 end
89- @path . push ( node . alias || node . name )
118+ @path [ @path_depth ] = node . alias || node . name ; @path_depth += 1
90119 super
91- @field_definitions . pop
92- @object_types . pop
93- @path . pop
120+ @current_field_definition = prev_field_definition
121+ @current_object_type = @parent_object_type
122+ @parent_object_type = prev_parent_ot
123+ @path_depth -= 1
94124 end
95125
96126 def on_directive ( node , parent )
97127 directive_defn = @context . schema_directives [ node . name ]
98- @directive_definitions . push ( directive_defn )
128+ prev_directive_definition = @current_directive_definition
129+ @current_directive_definition = directive_defn
99130 super
100- @directive_definitions . pop
131+ @current_directive_definition = prev_directive_definition
101132 end
102133
103134 def on_argument ( node , parent )
104- argument_defn = if ( arg = @argument_definitions . last )
135+ argument_defn = if ( arg = @current_argument_definition )
105136 arg_type = arg . type . unwrap
106137 if arg_type . kind . input_object?
107138 @types . argument ( arg_type , node . name )
108139 else
109140 nil
110141 end
111- elsif ( directive_defn = @directive_definitions . last )
142+ elsif ( directive_defn = @current_directive_definition )
112143 @types . argument ( directive_defn , node . name )
113- elsif ( field_defn = @field_definitions . last )
144+ elsif ( field_defn = @current_field_definition )
114145 @types . argument ( field_defn , node . name )
115146 else
116147 nil
117148 end
118149
119- @argument_definitions . push ( argument_defn )
120- @path . push ( node . name )
150+ prev_parent = @parent_argument_definition
151+ @parent_argument_definition = @current_argument_definition
152+ @current_argument_definition = argument_defn
153+ @path [ @path_depth ] = node . name ; @path_depth += 1
121154 super
122- @argument_definitions . pop
123- @path . pop
155+ @current_argument_definition = @parent_argument_definition
156+ @parent_argument_definition = prev_parent
157+ @path_depth -= 1
124158 end
125159
126160 def on_fragment_spread ( node , parent )
127- @path . push ( "... #{ node . name } " )
161+ @path [ @path_depth ] = "... #{ node . name } " ; @path_depth += 1
128162 super
129- @path . pop
163+ @path_depth -= 1
130164 end
131165
132166 def on_input_object ( node , parent )
133- arg_defn = @argument_definitions . last
167+ arg_defn = @current_argument_definition
134168 if arg_defn && arg_defn . type . list?
135- @path . push ( parent . children . index ( node ) )
169+ @path [ @path_depth ] = parent . children . index ( node ) ; @path_depth += 1
136170 super
137- @path . pop
171+ @path_depth -= 1
138172 else
139173 super
140174 end
141175 end
142176
143177 # @return [GraphQL::BaseType] The current object type
144178 def type_definition
145- @object_types . last
179+ @current_object_type
146180 end
147181
148182 # @return [GraphQL::BaseType] The type which the current type came from
149183 def parent_type_definition
150- @object_types [ - 2 ]
184+ @parent_object_type
151185 end
152186
153187 # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
154188 def field_definition
155- @field_definitions . last
189+ @current_field_definition
156190 end
157191
158192 # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
159193 def directive_definition
160- @directive_definitions . last
194+ @current_directive_definition
161195 end
162196
163197 # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
164198 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 ]
199+ # Return the parent argument definition (not the current one).
200+ @parent_argument_definition
168201 end
169202
170203 private
171204
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
187205 end
188206
189207 private
@@ -192,7 +210,7 @@ def add_error(error, path: nil)
192210 if @context . too_many_errors?
193211 throw :too_many_validation_errors
194212 end
195- error . path ||= ( path || @path . dup )
213+ error . path ||= ( path || @path [ 0 , @path_depth ] )
196214 context . errors << error
197215 end
198216
0 commit comments