Skip to content

Commit bf6ce70

Browse files
committed
Implement changes for executable descriptions
Replicates graphql/graphql-js@364f17fd
1 parent b1c31c4 commit bf6ce70

9 files changed

Lines changed: 316 additions & 13 deletions

File tree

‎src/graphql/language/ast.py‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,19 @@ class OperationType(Enum):
256256
"name": (),
257257
"document": ("definitions",),
258258
"operation_definition": (
259+
"description",
259260
"name",
260261
"variable_definitions",
261262
"directives",
262263
"selection_set",
263264
),
264-
"variable_definition": ("variable", "type", "default_value", "directives"),
265+
"variable_definition": (
266+
"description",
267+
"variable",
268+
"type",
269+
"default_value",
270+
"directives",
271+
),
265272
"variable": ("name",),
266273
"selection_set": ("selections",),
267274
"field": (
@@ -283,6 +290,7 @@ class OperationType(Enum):
283290
"fragment_spread": ("name", "directives"),
284291
"inline_fragment": ("type_condition", "directives", "selection_set"),
285292
"fragment_definition": (
293+
"description",
286294
# Note: fragment variable definitions are deprecated and will be removed in v3.3
287295
"name",
288296
"variable_definitions",
@@ -418,6 +426,7 @@ class ExecutableDefinitionNode(DefinitionNode):
418426
"""Base class for executable definition nodes."""
419427

420428
selection_set: SelectionSetNode
429+
description: StringValueNode | None = None
421430
name: NameNode | None = None
422431
variable_definitions: tuple[VariableDefinitionNode, ...] = ()
423432
directives: tuple[DirectiveNode, ...] = ()
@@ -652,6 +661,7 @@ class SelectionSetNode(Node):
652661

653662
@node_class
654663
class VariableDefinitionNode(Node):
664+
description: StringValueNode | None = None
655665
variable: VariableNode
656666
type: TypeNode
657667
default_value: ConstValueNode | None = None

‎src/graphql/language/parser.py‎

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,9 +298,12 @@ def parse_document(self) -> DocumentNode:
298298
"directive": "directive_definition",
299299
}
300300

301-
_parse_other_definition_method_names: Mapping[str, str] = {
301+
_parse_executable_definition_method_names: Mapping[str, str] = {
302302
**dict.fromkeys(("query", "mutation", "subscription"), "operation_definition"),
303303
"fragment": "fragment_definition",
304+
}
305+
306+
_parse_other_definition_method_names: Mapping[str, str] = {
304307
"extend": "type_system_extension",
305308
}
306309

@@ -324,6 +327,14 @@ def parse_definition(self) -> DefinitionNode:
324327
self._lexer.lookahead() if has_description else self._lexer.token
325328
)
326329

330+
if has_description and keyword_token.kind is TokenKind.BRACE_L:
331+
raise GraphQLSyntaxError(
332+
self._lexer.source,
333+
self._lexer.token.start,
334+
"Unexpected description,"
335+
" descriptions are not supported on shorthand queries.",
336+
)
337+
327338
if keyword_token.kind is TokenKind.NAME:
328339
token_name = cast("str", keyword_token.value)
329340
method_name = self._parse_type_system_definition_method_names.get(
@@ -332,12 +343,16 @@ def parse_definition(self) -> DefinitionNode:
332343
if method_name:
333344
return getattr(self, f"parse_{method_name}")()
334345

346+
method_name = self._parse_executable_definition_method_names.get(token_name)
347+
if method_name:
348+
return getattr(self, f"parse_{method_name}")()
349+
335350
if has_description:
336351
raise GraphQLSyntaxError(
337352
self._lexer.source,
338353
self._lexer.token.start,
339354
"Unexpected description,"
340-
" descriptions are supported only on type definitions.",
355+
" only GraphQL definitions support descriptions.",
341356
)
342357

343358
method_name = self._parse_other_definition_method_names.get(token_name)
@@ -354,16 +369,19 @@ def parse_operation_definition(self) -> OperationDefinitionNode:
354369
if self.peek(TokenKind.BRACE_L):
355370
return OperationDefinitionNode(
356371
operation=OperationType.QUERY,
372+
description=None,
357373
name=None,
358374
variable_definitions=(),
359375
directives=(),
360376
selection_set=self.parse_selection_set(),
361377
loc=self.loc(start),
362378
)
379+
description = self.parse_description()
363380
operation = self.parse_operation_type()
364381
name = self.parse_name() if self.peek(TokenKind.NAME) else None
365382
return OperationDefinitionNode(
366383
operation=operation,
384+
description=description,
367385
name=name,
368386
variable_definitions=self.parse_variable_definitions(),
369387
directives=self.parse_directives(False),
@@ -388,10 +406,12 @@ def parse_variable_definitions(self) -> tuple[VariableDefinitionNode, ...]:
388406
def parse_variable_definition(self) -> VariableDefinitionNode:
389407
"""VariableDefinition: Variable: Type DefaultValue? Directives[Const]?"""
390408
start = self._lexer.token
409+
description = self.parse_description()
391410
variable = self.parse_variable()
392411
self.expect_token(TokenKind.COLON)
393412
type_ = self.parse_type_reference()
394413
return VariableDefinitionNode(
414+
description=description,
395415
variable=variable,
396416
type=type_,
397417
default_value=self.parse_const_value_literal()
@@ -528,11 +548,13 @@ def parse_fragment(self) -> FragmentSpreadNode | InlineFragmentNode:
528548
def parse_fragment_definition(self) -> FragmentDefinitionNode:
529549
"""FragmentDefinition"""
530550
start = self._lexer.token
551+
description = self.parse_description()
531552
self.expect_keyword("fragment")
532553
# Legacy support for defining variables within fragments changes
533554
# the grammar of FragmentDefinition
534555
if self._allow_legacy_fragment_variables:
535556
return FragmentDefinitionNode(
557+
description=description,
536558
name=self.parse_fragment_name(),
537559
variable_definitions=self.parse_variable_definitions(),
538560
type_condition=self.parse_type_condition(),
@@ -541,6 +563,7 @@ def parse_fragment_definition(self) -> FragmentDefinitionNode:
541563
loc=self.loc(start),
542564
)
543565
return FragmentDefinitionNode(
566+
description=description,
544567
name=self.parse_fragment_name(),
545568
variable_definitions=(),
546569
type_condition=self.parse_type_condition(),

‎src/graphql/language/printer.py‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,12 @@ def leave_document(node: PrintedNode, *_args: Any) -> str:
7676

7777
@staticmethod
7878
def leave_operation_definition(node: PrintedNode, *_args: Any) -> str:
79-
var_defs = wrap("(", join(node.variable_definitions, ", "), ")")
80-
prefix = join(
79+
var_defs = (
80+
wrap("(\n", join(node.variable_definitions, "\n"), "\n)")
81+
if has_multiline_items(node.variable_definitions)
82+
else wrap("(", join(node.variable_definitions, ", "), ")")
83+
)
84+
prefix = wrap("", node.description, "\n") + join(
8185
(
8286
node.operation.value,
8387
join((node.name, var_defs)),
@@ -92,7 +96,7 @@ def leave_operation_definition(node: PrintedNode, *_args: Any) -> str:
9296
@staticmethod
9397
def leave_variable_definition(node: PrintedNode, *_args: Any) -> str:
9498
return (
95-
f"{node.variable}: {node.type}"
99+
wrap("", node.description, "\n") + f"{node.variable}: {node.type}"
96100
f"{wrap(' = ', node.default_value)}"
97101
f"{wrap(' ', join(node.directives, ' '))}"
98102
)
@@ -160,7 +164,7 @@ def leave_inline_fragment(node: PrintedNode, *_args: Any) -> str:
160164
def leave_fragment_definition(node: PrintedNode, *_args: Any) -> str:
161165
# Note: fragment variable definitions are deprecated and will be removed in v3.3
162166
return (
163-
f"fragment {node.name}"
167+
wrap("", node.description, "\n") + f"fragment {node.name}"
164168
f"{wrap('(', join(node.variable_definitions, ', '), ')')}"
165169
f" on {node.type_condition}"
166170
f" {wrap('', join(node.directives, ' '), ' ')}"

‎tests/fixtures/kitchen_sink.graphql‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
query queryName($foo: ComplexType, $site: Site = MOBILE) @onQuery {
1+
"Query description"
2+
query queryName(
3+
"Very complex variable"
4+
$foo: ComplexType,
5+
$site: Site = MOBILE
6+
) @onQuery {
27
whoever123is: node(id: [123, 456]) {
38
id ,
49
... on User @onInlineFragment {
@@ -57,6 +62,9 @@ subscription StoryLikeSubscription(
5762
}
5863
}
5964

65+
"""
66+
Fragment description
67+
"""
6068
fragment frag on Friend @onFragmentDefinition {
6169
foo(size: $size, bar: $b, obj: {key: "value", block: """
6270

0 commit comments

Comments
 (0)