Skip to content

Commit f9a8d77

Browse files
author
Robert Mosolgo
authored
Merge pull request rmosolgo#248 from rmosolgo/document-ast-nodes
doc(GraphQL::Language) add documentation for ast node classes
2 parents e42a71b + 54f525c commit f9a8d77

File tree

3 files changed

+106
-8
lines changed

3 files changed

+106
-8
lines changed

lib/graphql/language/generation.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
module GraphQL
22
module Language
3+
# Exposes {.generate}, which turns AST nodes back into query strings.
34
module Generation
45
extend self
56

7+
# Turn an AST node back into a string.
8+
#
9+
# @example Turning a document into a query
10+
# document = GraphQL.parse(query_string)
11+
# GraphQL::Language::Generation.generate(document)
12+
# # => "{ ... }"
13+
#
14+
# @param node [GraphQL::Language::Nodes::AbstractNode] an AST node to recursively stringify
15+
# @param indent [String] Whitespace to add to each printed node
16+
# @return [String] Valid GraphQL for `node`
617
def generate(node, indent: "")
718
case node
819
when Nodes::Document

lib/graphql/language/nodes.rb

Lines changed: 91 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
module GraphQL
22
module Language
33
module Nodes
4-
# AbstractNode creates classes who:
5-
# - require their keyword arguments, throw ArgumentError if they don't match
6-
# - expose accessors for keyword arguments
4+
# {AbstractNode} is the base class for all nodes in a GraphQL AST.
5+
#
6+
# It provides some APIs for working with ASTs:
7+
# - `children` returns all AST nodes attached to this one. Used for tree traversal.
8+
# - `scalars` returns all scalar (Ruby) values attached to this one. Used for comparing nodes.
9+
# - `to_query_string` turns an AST node into a GraphQL string
710
class AbstractNode
811
attr_accessor :line, :col
912

10-
# @param options [Hash] Must contain all attributes defined by {required_attrs}, may also include `position_source`
13+
# Initialize a node by extracting its position,
14+
# then calling the class's `initialize_node` method.
15+
# @param options [Hash] Initial attributes for this node
1116
def initialize(options={})
1217
if options.key?(:position_source)
1318
position_source = options.delete(:position_source)
@@ -25,36 +30,43 @@ def initialize_node(options={})
2530
raise NotImplementedError
2631
end
2732

33+
# Value equality
34+
# @return [Boolean] True if `self` is equivalent to `other`
2835
def eql?(other)
2936
return true if equal?(other)
3037
other.is_a?(self.class) &&
3138
other.scalars.eql?(self.scalars) &&
3239
other.children.eql?(self.children)
3340
end
3441

35-
# @return [GraphQL::Language::Nodes::AbstractNode] all nodes in the tree below this one
42+
# @return [Array<GraphQL::Language::Nodes::AbstractNode>] all nodes in the tree below this one
3643
def children
3744
self.class.child_attributes
3845
.map { |attr_name| public_send(attr_name) }
3946
.flatten
4047
end
4148

49+
# @return [Array<Integer, Float, String, Boolean, Array>] Scalar values attached to this node
4250
def scalars
4351
self.class.scalar_attributes
4452
.map { |attr_name| public_send(attr_name) }
4553
end
4654

4755
class << self
56+
# A node subclass inherits `scalar_attributes`
57+
# and `child_attributes` from its parent
4858
def inherited(subclass)
4959
subclass.scalar_attributes(*@scalar_attributes)
5060
subclass.child_attributes(*@child_attributes)
5161
end
5262

63+
# define `attr_names` as places where scalars may be attached to this node
5364
def scalar_attributes(*attr_names)
5465
@scalar_attributes ||= []
5566
@scalar_attributes += attr_names
5667
end
5768

69+
# define `attr_names` as places where child nodes may be attached to this node
5870
def child_attributes(*attr_names)
5971
@child_attributes ||= []
6072
@child_attributes += attr_names
@@ -70,6 +82,7 @@ def to_query_string
7082
end
7183
end
7284

85+
# Base class for non-null type names and list type names
7386
class WrapperType < AbstractNode
7487
attr_accessor :of_type
7588
scalar_attributes :of_type
@@ -83,6 +96,7 @@ def children
8396
end
8497
end
8598

99+
# Base class for nodes whose only value is a name (no child nodes or other scalars)
86100
class NameOnlyNode < AbstractNode
87101
attr_accessor :name
88102
scalar_attributes :name
@@ -96,11 +110,17 @@ def children
96110
end
97111
end
98112

99-
113+
# A key-value pair for a field's inputs
100114
class Argument < AbstractNode
101115
attr_accessor :name, :value
102116
scalar_attributes :name, :value
103117

118+
# @!attribute name
119+
# @return [String] the key for this argument
120+
121+
# @!attribute value
122+
# @return [String, Float, Integer, Boolean, Array, InputObject] The value passed for this key
123+
104124
def initialize_node(name: nil, value: nil)
105125
@name = name
106126
@value = value
@@ -122,22 +142,38 @@ def initialize_node(name: nil, arguments: [])
122142
end
123143
end
124144

145+
# This is the AST root for normal queries
146+
#
147+
# @example Deriving a document by parsing a string
148+
# document = GraphQL.parse(query_string)
149+
#
150+
# @example Creating a string from a document
151+
# document.to_query_string
152+
# # { ... }
153+
#
125154
class Document < AbstractNode
126155
attr_accessor :definitions
127156
child_attributes :definitions
128157

158+
# @!attribute definitions
159+
# @return [Array<OperationDefinition, FragmentDefinition>] top-level GraphQL units: operations or fragments
129160
def initialize_node(definitions: [])
130161
@definitions = definitions
131162
end
132163
end
133164

165+
# An enum value. The string is available as {#name}.
134166
class Enum < NameOnlyNode; end
135167

168+
# A single selection in a GraphQL query.
136169
class Field < AbstractNode
137170
attr_accessor :name, :alias, :arguments, :directives, :selections
138171
scalar_attributes :name, :alias
139172
child_attributes :arguments, :directives, :selections
140173

174+
# @!attribute selections
175+
# @return [Array<Nodes::Field>] Selections on this object (or empty array if this is a scalar field)
176+
141177
def initialize_node(name: nil, arguments: [], directives: [], selections: [], **kwargs)
142178
@name = name
143179
# oops, alias is a keyword:
@@ -148,11 +184,17 @@ def initialize_node(name: nil, arguments: [], directives: [], selections: [], **
148184
end
149185
end
150186

187+
# A reusable fragment, defined at document-level.
151188
class FragmentDefinition < AbstractNode
152189
attr_accessor :name, :type, :directives, :selections
153190
scalar_attributes :name, :type
154191
child_attributes :directives, :selections
155192

193+
# @!attribute name
194+
# @return [String] the identifier for this fragment, which may be applied with `...#{name}`
195+
196+
# @!attribute type
197+
# @return [String] the type condition for this fragment (name of type which it may apply to)
156198
def initialize_node(name: nil, type: nil, directives: [], selections: [])
157199
@name = name
158200
@type = type
@@ -161,37 +203,50 @@ def initialize_node(name: nil, type: nil, directives: [], selections: [])
161203
end
162204
end
163205

206+
# Application of a named fragment in a selection
164207
class FragmentSpread < AbstractNode
165208
attr_accessor :name, :directives
166209
scalar_attributes :name
167210
child_attributes :directives
168211

212+
# @!attribute name
213+
# @return [String] The identifier of the fragment to apply, corresponds with {FragmentDefinition#name}
214+
169215
def initialize_node(name: nil, directives: [])
170216
@name = name
171217
@directives = directives
172218
end
173219
end
174220

221+
# An unnamed fragment, defined directly in the query with `... { }`
175222
class InlineFragment < AbstractNode
176223
attr_accessor :type, :directives, :selections
177224
scalar_attributes :type
178225
child_attributes :directives, :selections
179226

227+
# @!attribute type
228+
# @return [String, nil] Name of the type this fragment applies to, or `nil` if this fragment applies to any type
229+
180230
def initialize_node(type: nil, directives: [], selections: [])
181231
@type = type
182232
@directives = directives
183233
@selections = selections
184234
end
185235
end
186236

237+
# A collection of key-value inputs which may be a field argument
187238
class InputObject < AbstractNode
188239
attr_accessor :arguments
189240
child_attributes :arguments
190241

242+
# @!attribute arguments
243+
# @return [Array<Nodes::Argument>] A list of key-value pairs inside this input object
244+
191245
def initialize_node(arguments: [])
192246
@arguments = arguments
193247
end
194248

249+
# @return [Hash<String, Any>] Recursively turn this input object into a Ruby Hash
195250
def to_h(options={})
196251
arguments.inject({}) do |memo, pair|
197252
v = pair.value
@@ -202,15 +257,32 @@ def to_h(options={})
202257
end
203258

204259

205-
260+
# A list type definition, denoted with `[...]` (used for variable type definitions)
206261
class ListType < WrapperType; end
262+
263+
# A non-null type definition, denoted with `...!` (used for variable type definitions)
207264
class NonNullType < WrapperType; end
208265

266+
# A query, mutation or subscription.
267+
# May be anonymous or named.
268+
# May be explicitly typed (eg `mutation { ... }`) or implicitly a query (eg `{ ... }`).
209269
class OperationDefinition < AbstractNode
210270
attr_accessor :operation_type, :name, :variables, :directives, :selections
211271
scalar_attributes :operation_type, :name
212272
child_attributes :variables, :directives, :selections
213273

274+
# @!attribute variables
275+
# @return [Array<VariableDefinition>] Variable definitions for this operation
276+
277+
# @!attribute selections
278+
# @return [Array<Field>] Root-level fields on this operation
279+
280+
# @!attribute operation_type
281+
# @return [String, nil] The root type for this operation, or `nil` for implicit `"query"`
282+
283+
# @!attribute name
284+
# @return [String, nil] The name for this operation, or `nil` if unnamed
285+
214286
def initialize_node(operation_type: nil, name: nil, variables: [], directives: [], selections: [])
215287
@operation_type = operation_type
216288
@name = name
@@ -220,22 +292,33 @@ def initialize_node(operation_type: nil, name: nil, variables: [], directives: [
220292
end
221293
end
222294

295+
# A type name, used for variable definitions
223296
class TypeName < NameOnlyNode; end
224297

298+
# An operation-level query variable
225299
class VariableDefinition < AbstractNode
226300
attr_accessor :name, :type, :default_value
227301
scalar_attributes :name, :type, :default_value
228302

303+
# @!attribute default_value
304+
# @return [String, Integer, Float, Boolean, Array] A Ruby value to use if no other value is provided
305+
306+
# @!attribute type
307+
# @return [TypeName, NonNullType, ListType] The expected type of this value
308+
309+
# @!attribute name
310+
# @return [String] The identifier for this variable, _without_ `$`
311+
229312
def initialize_node(name: nil, type: nil, default_value: nil)
230313
@name = name
231314
@type = type
232315
@default_value = default_value
233316
end
234317
end
235318

319+
# Usage of a variable in a query. Name does _not_ include `$`.
236320
class VariableIdentifier < NameOnlyNode; end
237321

238-
239322
class SchemaDefinition < AbstractNode
240323
attr_accessor :query, :mutation, :subscription
241324
scalar_attributes :query, :mutation, :subscription

lib/graphql/language/token.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
module GraphQL
22
module Language
3+
# Emitted by the lexer and passed to the parser.
4+
# Contains type, value and position data.
35
class Token
6+
# @return [Symbol] The kind of token this is
47
attr_reader :name
8+
59
def initialize(value:, name:, line:, col:)
610
@name = name
711
@value = value

0 commit comments

Comments
 (0)