Skip to content

Commit a08d5cf

Browse files
committed
Unify print_ast and print_schema output for block strings
Replicates graphql/graphql-js@ded3261
1 parent d20b831 commit a08d5cf

File tree

6 files changed

+121
-65
lines changed

6 files changed

+121
-65
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ a query language for APIs created by Facebook.
1313
[![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
1414

1515
The current version 1.0.2 of GraphQL-core-next is up-to-date with GraphQL.js version
16-
14.1.1. All parts of the API are covered by an extensive test suite of currently 1725
16+
14.1.1. All parts of the API are covered by an extensive test suite of currently 1737
1717
unit tests.
1818

1919

graphql/language/block_string_value.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__all__ = ["dedent_block_string_value"]
1+
__all__ = ["dedent_block_string_value", "print_block_string"]
22

33

44
def dedent_block_string_value(raw_string: str) -> str:
@@ -37,3 +37,31 @@ def leading_whitespace(s):
3737
while i < n and s[i] in " \t":
3838
i += 1
3939
return i
40+
41+
42+
def print_block_string(
43+
value: str, indentation: str = "", prefer_multiple_lines: bool = False
44+
) -> str:
45+
"""Print a block string in the indented block form.
46+
47+
Prints a block string in the indented block form by adding a leading and
48+
trailing blank line. However, if a block string starts with whitespace and
49+
is a single-line, adding a leading blank line would strip that whitespace.
50+
"""
51+
is_single_line = "\n" not in value
52+
has_leading_space = value.startswith(" ") or value.startswith("\t")
53+
has_trailing_quote = value.endswith('"')
54+
print_as_multiple_lines = (
55+
not is_single_line or has_trailing_quote or prefer_multiple_lines
56+
)
57+
58+
# Format a multi-line block quote to account for leading space.
59+
if print_as_multiple_lines and not (is_single_line and has_leading_space):
60+
result = "\n" + indentation
61+
else:
62+
result = ""
63+
result += value.replace("\n", "\n" + indentation) if indentation else value
64+
if print_as_multiple_lines:
65+
result += "\n"
66+
67+
return '"""' + result.replace('"""', '\\"""') + '"""'

graphql/language/printer.py

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from .ast import Node, OperationType
66
from .visitor import visit, Visitor
7+
from .block_string_value import print_block_string
78

89
__all__ = ["print_ast"]
910

@@ -113,7 +114,7 @@ def leave_float_value(self, node, *_args):
113114

114115
def leave_string_value(self, node, key, *_args):
115116
if node.block:
116-
return print_block_string(node.value, key == "description")
117+
return print_block_string(node.value, "" if key == "description" else " ")
117118
return dumps(node.value)
118119

119120
def leave_boolean_value(self, node, *_args):
@@ -301,24 +302,6 @@ def leave_input_object_type_extension(self, node, *_args):
301302
)
302303

303304

304-
def print_block_string(value: str, is_description: bool = False) -> str:
305-
"""Print a block string.
306-
307-
Prints a block string in the indented block form by adding a leading and trailing
308-
blank line. However, if a block string starts with whitespace and is a single-line,
309-
adding a leading blank line would strip that whitespace.
310-
"""
311-
escaped = value.replace('"""', '\\"""')
312-
if is_multiline(value) or not value.startswith((" ", "\t")):
313-
if not is_description:
314-
escaped = indent(escaped)
315-
return f'"""\n{escaped}\n"""'
316-
else:
317-
if escaped.endswith('"'):
318-
escaped += "\n"
319-
return f'"""{escaped}"""'
320-
321-
322305
def join(strings: Optional[Sequence[str]], separator: str = "") -> str:
323306
"""Join strings in a given sequence.
324307

graphql/utilities/schema_printer.py

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import Any, Callable, Dict, List, Optional, Union, cast
44

55
from ..language import print_ast
6+
from ..language.block_string_value import print_block_string
67
from ..pyutils import inspect
78
from ..type import (
89
DEFAULT_DEPRECATION_REASON,
@@ -254,34 +255,21 @@ def print_deprecated(field_or_enum_value: Union[GraphQLField, GraphQLEnumValue])
254255

255256

256257
def print_description(
257-
type_: Union[GraphQLArgument, GraphQLDirective, GraphQLEnumValue, GraphQLNamedType],
258+
def_: Union[GraphQLArgument, GraphQLDirective, GraphQLEnumValue, GraphQLNamedType],
258259
indentation="",
259260
first_in_block=True,
260261
) -> str:
261-
if not type_.description:
262+
if not def_.description:
262263
return ""
263-
lines = description_lines(type_.description, 120 - len(indentation))
264-
265-
description = []
266-
if indentation and not first_in_block:
267-
description.append("\n")
268-
description.extend([indentation, '"""'])
269-
270-
if len(lines) == 1 and len(lines[0]) < 70 and not lines[0].endswith('"'):
271-
# In some circumstances, a single line can be used for the description.
272-
description.extend([escape_quote(lines[0]), '"""\n'])
273-
else:
274-
# Format a multi-line block quote to account for leading space.
275-
has_leading_space = lines and lines[0].startswith((" ", "\t"))
276-
if not has_leading_space:
277-
description.append("\n")
278-
for i, line in enumerate(lines):
279-
if i or not has_leading_space:
280-
description.append(indentation)
281-
description.extend([escape_quote(line), "\n"])
282-
description.extend([indentation, '"""\n'])
283-
284-
return "".join(description)
264+
265+
lines = description_lines(def_.description, 120 - len(indentation))
266+
267+
text = '\n'.join(lines)
268+
prefer_multiple_lines = len(text) > 70
269+
block_string = print_block_string(text, '', prefer_multiple_lines)
270+
prefix = '\n' + indentation if indentation and not first_in_block else indentation
271+
272+
return prefix + block_string.replace('\n', '\n' + indentation) + '\n'
285273

286274

287275
def escape_quote(line: str) -> str:

tests/language/test_block_string_value.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
from graphql.language.block_string_value import dedent_block_string_value
1+
from graphql.language.block_string_value import (
2+
dedent_block_string_value,
3+
print_block_string,
4+
)
25

36

47
def join(*args):
58
return "\n".join(args)
69

710

8-
def describe_block_string_value():
11+
def describe_dedent_block_string_value():
912
def removes_uniform_indentation_from_a_string():
1013
raw_value = join(
1114
"", " Hello,", " World!", "", " Yours,", " GraphQL."
@@ -67,3 +70,69 @@ def does_not_alter_trailing_spaces():
6770
assert dedent_block_string_value(raw_value) == join(
6871
"Hello, ", " World! ", " ", "Yours, ", " GraphQL. "
6972
)
73+
74+
75+
def describe_print_block_string():
76+
def describe_single_line():
77+
def simple():
78+
assert print_block_string("single line") == '"""single line"""'
79+
80+
def with_leading_whitespace():
81+
assert print_block_string(" single line") == '""" single line"""'
82+
83+
def with_indentation():
84+
assert (
85+
print_block_string("single line", indentation=" ")
86+
== '"""single line"""'
87+
)
88+
89+
def with_indentation_and_leading_whitespace():
90+
assert (
91+
print_block_string(" single line", indentation=" ")
92+
== '""" single line"""'
93+
)
94+
95+
def with_trailing_quote():
96+
assert (
97+
print_block_string('single "line"')
98+
== '"""\nsingle "line"\n"""'
99+
)
100+
101+
def prefer_multiple_lines():
102+
assert (
103+
print_block_string("single line", prefer_multiple_lines=True)
104+
== '"""\nsingle line\n"""'
105+
)
106+
107+
def describe_multiple_lines():
108+
def simple():
109+
assert print_block_string("multiple\nlines") == '"""\nmultiple\nlines\n"""'
110+
111+
def with_leading_whitespace():
112+
assert (
113+
print_block_string(" multiple\nlines") == '"""\n multiple\nlines\n"""'
114+
)
115+
116+
def with_indentation():
117+
assert (
118+
print_block_string("multiple\nlines", indentation=" ")
119+
== '"""\n multiple\n lines\n"""'
120+
)
121+
122+
def with_indentation_and_leading_whitespace():
123+
assert (
124+
print_block_string(" multiple\nlines", indentation=" ")
125+
== '"""\n multiple\n lines\n"""'
126+
)
127+
128+
def with_trailing_quote():
129+
assert (
130+
print_block_string('multiple\n"line"')
131+
== '"""\nmultiple\n"line"\n"""'
132+
)
133+
134+
def do_not_prefer_multiple_lines():
135+
assert (
136+
print_block_string("multiple\nlines", prefer_multiple_lines=False)
137+
== '"""\nmultiple\nlines\n"""'
138+
)

tests/language/test_schema_printer.py

Lines changed: 6 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,12 @@ def prints_kitchen_sink(kitchen_sink_sdl): # noqa: F811
4848
type Foo implements Bar & Baz {
4949
"Description of the `one` field."
5050
one: Type
51-
"""
52-
This is a description of the `two` field.
53-
"""
51+
"""This is a description of the `two` field."""
5452
two(
55-
"""
56-
This is a description of the `argument` argument.
57-
"""
53+
"""This is a description of the `argument` argument."""
5854
argument: InputType!
5955
): Type
60-
"""
61-
This is a description of the `three` field.
62-
"""
56+
"""This is a description of the `three` field."""
6357
three(argument: InputType, other: String): Int
6458
four(argument: String = "string"): String
6559
five(argument: [String] = ["string", "string"]): String
@@ -115,13 +109,9 @@ def prints_kitchen_sink(kitchen_sink_sdl): # noqa: F811
115109
extend scalar CustomScalar @onScalar
116110
117111
enum Site {
118-
"""
119-
This is a description of the `DESKTOP` value
120-
"""
112+
"""This is a description of the `DESKTOP` value"""
121113
DESKTOP
122-
"""
123-
This is a description of the `MOBILE` value
124-
"""
114+
"""This is a description of the `MOBILE` value"""
125115
MOBILE
126116
"This is a description of the `WEB` value"
127117
WEB
@@ -157,9 +147,7 @@ def prints_kitchen_sink(kitchen_sink_sdl): # noqa: F811
157147
158148
extend input InputType @onInputObject
159149
160-
"""
161-
This is a description of the `@skip` directive
162-
"""
150+
"""This is a description of the `@skip` directive"""
163151
directive @skip(if: Boolean! @onArgumentDefinition) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
164152
165153
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT

0 commit comments

Comments
 (0)