From a40730aff99d1e0b288005bdc95d2346da0d41cc Mon Sep 17 00:00:00 2001
From: Phil Aquilina <philaquilina@gmail.com>
Date: Fri, 7 Dec 2018 15:35:25 -0800
Subject: [PATCH 1/2] Fix indent function

The indent function wasn't actually indenting. Add the indent to
the function and fix up the one call site.
---
 graphql/language/printer.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/graphql/language/printer.py b/graphql/language/printer.py
index d2f27ef8..676af0a8 100644
--- a/graphql/language/printer.py
+++ b/graphql/language/printer.py
@@ -301,7 +301,7 @@ def block(_list):
     # type: (List[str]) -> str
     """Given a list, print each item on its own line, wrapped in an indented "{ }" block."""
     if _list:
-        return indent("{\n" + join(_list, "\n")) + "\n}"
+        return "{\n" + indent(join(_list, "\n")) + "\n}"
     return "{}"
 
 
@@ -315,5 +315,5 @@ def wrap(start, maybe_str, end=""):
 def indent(maybe_str):
     # type: (Optional[str]) -> str
     if maybe_str:
-        return maybe_str.replace("\n", "\n  ")
+        return '  ' + maybe_str.replace("\n", "\n  ")
     return ""

From 3338aaae8dfa5bb96f6dd8d1368c2cdf3d7ddce7 Mon Sep 17 00:00:00 2001
From: Phil Aquilina <philaquilina@gmail.com>
Date: Thu, 29 Nov 2018 15:52:36 -0800
Subject: [PATCH 2/2] Add description to schema parser and printer

Descriptions are now part of the [graphql spec](https://facebook.github.io/graphql/June2018/#sec-Descriptions).
This commit brings graphql-core inline with the spec.
---
 graphql/language/ast.py                       |  48 ++++--
 graphql/language/lexer.py                     |  96 +++++++++++-
 graphql/language/parser.py                    |  53 ++++++-
 graphql/language/printer.py                   |  73 ++++++---
 graphql/language/tests/fixtures.py            |  31 +++-
 .../language/tests/test_block_string_value.py | 108 +++++++++++++
 graphql/language/tests/test_schema_printer.py |  33 +++-
 graphql/language/visitor_meta.py              |  20 +--
 graphql/type/directives.py                    |   2 +-
 graphql/utils/build_ast_schema.py             |  22 ++-
 graphql/utils/schema_printer.py               | 142 +++++++++++++++---
 graphql/utils/tests/test_build_ast_schema.py  |  31 ++++
 graphql/utils/tests/test_schema_printer.py    | 141 ++++++++++++++++-
 13 files changed, 715 insertions(+), 85 deletions(-)
 create mode 100644 graphql/language/tests/test_block_string_value.py

diff --git a/graphql/language/ast.py b/graphql/language/ast.py
index f7f407ea..cd616313 100644
--- a/graphql/language/ast.py
+++ b/graphql/language/ast.py
@@ -543,13 +543,14 @@ def __hash__(self):
 
 
 class StringValue(Value):
-    __slots__ = ("loc", "value")
+    __slots__ = ("loc", "value", "is_block_string")
     _fields = ("value",)
 
-    def __init__(self, value, loc=None):
+    def __init__(self, value, loc=None, is_block_string=False):
         # type: (str, Optional[Loc]) -> None
         self.loc = loc
         self.value = value
+        self.is_block_string = is_block_string
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -989,7 +990,7 @@ def __hash__(self):
 
 
 class ObjectTypeDefinition(TypeDefinition):
-    __slots__ = ("loc", "name", "interfaces", "directives", "fields")
+    __slots__ = ("loc", "name", "interfaces", "directives", "fields", "description")
     _fields = ("name", "interfaces", "fields")
 
     def __init__(
@@ -999,6 +1000,7 @@ def __init__(
         interfaces=None,  # type: Optional[List[NamedType]]
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List[Directive]]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
@@ -1006,12 +1008,12 @@ def __init__(
         self.interfaces = interfaces
         self.fields = fields
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
         return self is other or (
-            isinstance(other, ObjectTypeDefinition)
-            and
+            isinstance(other, ObjectTypeDefinition) and
             # self.loc == other.loc and
             self.name == other.name
             and self.interfaces == other.interfaces
@@ -1042,7 +1044,7 @@ def __hash__(self):
 
 
 class FieldDefinition(Node):
-    __slots__ = ("loc", "name", "arguments", "type", "directives")
+    __slots__ = ("loc", "name", "arguments", "type", "directives", "description")
     _fields = ("name", "arguments", "type")
 
     def __init__(
@@ -1052,6 +1054,7 @@ def __init__(
         type,  # type: Union[NamedType, NonNullType, ListType]
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
@@ -1059,6 +1062,7 @@ def __init__(
         self.arguments = arguments
         self.type = type
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -1094,7 +1098,7 @@ def __hash__(self):
 
 
 class InputValueDefinition(Node):
-    __slots__ = ("loc", "name", "type", "default_value", "directives")
+    __slots__ = ("loc", "name", "type", "default_value", "directives", "description")
     _fields = ("name", "type", "default_value")
 
     def __init__(
@@ -1104,6 +1108,7 @@ def __init__(
         default_value=None,  # type: Any
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
@@ -1111,6 +1116,7 @@ def __init__(
         self.type = type
         self.default_value = default_value
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -1147,7 +1153,7 @@ def __hash__(self):
 
 
 class InterfaceTypeDefinition(TypeDefinition):
-    __slots__ = ("loc", "name", "fields", "directives")
+    __slots__ = ("loc", "name", "fields", "directives", "description")
     _fields = ("name", "fields")
 
     def __init__(
@@ -1156,12 +1162,14 @@ def __init__(
         fields,  # type: List[FieldDefinition]
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List[Directive]]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
         self.name = name
         self.fields = fields
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -1194,7 +1202,7 @@ def __hash__(self):
 
 
 class UnionTypeDefinition(TypeDefinition):
-    __slots__ = ("loc", "name", "types", "directives")
+    __slots__ = ("loc", "name", "types", "directives", "description")
     _fields = ("name", "types")
 
     def __init__(
@@ -1203,12 +1211,14 @@ def __init__(
         types,  # type: List[NamedType]
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List[Directive]]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
         self.name = name
         self.types = types
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -1241,7 +1251,7 @@ def __hash__(self):
 
 
 class ScalarTypeDefinition(TypeDefinition):
-    __slots__ = ("loc", "name", "directives")
+    __slots__ = ("loc", "name", "directives", "description")
     _fields = ("name",)
 
     def __init__(
@@ -1249,11 +1259,13 @@ def __init__(
         name,  # type: Name
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List[Directive]]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
         self.name = name
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -1284,7 +1296,7 @@ def __hash__(self):
 
 
 class EnumTypeDefinition(TypeDefinition):
-    __slots__ = ("loc", "name", "values", "directives")
+    __slots__ = ("loc", "name", "values", "directives", "description")
     _fields = ("name", "values")
 
     def __init__(
@@ -1293,12 +1305,14 @@ def __init__(
         values,  # type: List[EnumValueDefinition]
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List[Directive]]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
         self.name = name
         self.values = values
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -1331,7 +1345,7 @@ def __hash__(self):
 
 
 class EnumValueDefinition(Node):
-    __slots__ = ("loc", "name", "directives")
+    __slots__ = ("loc", "name", "directives", "description")
     _fields = ("name",)
 
     def __init__(
@@ -1339,11 +1353,13 @@ def __init__(
         name,  # type: Name
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List[Directive]]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
         self.name = name
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -1374,7 +1390,7 @@ def __hash__(self):
 
 
 class InputObjectTypeDefinition(TypeDefinition):
-    __slots__ = ("loc", "name", "fields", "directives")
+    __slots__ = ("loc", "name", "fields", "directives", "description")
     _fields = ("name", "fields")
 
     def __init__(
@@ -1383,12 +1399,14 @@ def __init__(
         fields,  # type: List[InputValueDefinition]
         loc=None,  # type: Optional[Loc]
         directives=None,  # type: Optional[List[Directive]]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.loc = loc
         self.name = name
         self.fields = fields
         self.directives = directives
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
@@ -1454,7 +1472,7 @@ def __hash__(self):
 
 
 class DirectiveDefinition(TypeSystemDefinition):
-    __slots__ = ("loc", "name", "arguments", "locations")
+    __slots__ = ("loc", "name", "arguments", "locations", "description")
     _fields = ("name", "locations")
 
     def __init__(
@@ -1463,12 +1481,14 @@ def __init__(
         locations,  # type: List[Name]
         arguments=None,  # type: Optional[List[InputValueDefinition]]
         loc=None,  # type: Optional[Loc]
+        description=None,  # type: Optional[String]
     ):
         # type: (...) -> None
         self.name = name
         self.locations = locations
         self.loc = loc
         self.arguments = arguments
+        self.description = description
 
     def __eq__(self, other):
         # type: (Any) -> bool
diff --git a/graphql/language/lexer.py b/graphql/language/lexer.py
index a60bc6e2..b1451dbc 100644
--- a/graphql/language/lexer.py
+++ b/graphql/language/lexer.py
@@ -1,3 +1,4 @@
+import re
 import json
 
 from six import unichr
@@ -55,6 +56,10 @@ def next_token(self, reset_position=None):
         self.prev_position = token.end
         return token
 
+    def look_ahead(self):
+        skip_token = read_token(self.source, self.prev_position)
+        return read_token(self.source, skip_token.start)
+
 
 class TokenKind(object):
     EOF = 1
@@ -76,6 +81,7 @@ class TokenKind(object):
     INT = 17
     FLOAT = 18
     STRING = 19
+    BLOCK_STRING = 20
 
 
 def get_token_desc(token):
@@ -111,6 +117,7 @@ def get_token_kind_desc(kind):
     TokenKind.INT: "Int",
     TokenKind.FLOAT: "Float",
     TokenKind.STRING: "String",
+    TokenKind.BLOCK_STRING: "Block string",
 }
 
 
@@ -155,7 +162,7 @@ def read_token(source, from_position):
 
     This skips over whitespace and comments until it finds the next lexable
     token, then lexes punctuators immediately or calls the appropriate
-    helper fucntion for more complicated tokens."""
+    helper function for more complicated tokens."""
     body = source.body
     body_length = len(body)
 
@@ -191,6 +198,11 @@ def read_token(source, from_position):
             return read_number(source, position, code)
 
         elif code == 34:  # "
+            if (
+                char_code_at(body, position + 1) == 34 and
+                char_code_at(body, position + 2) == 34
+            ):
+                return read_block_string(source, position)
             return read_string(source, position)
 
     raise GraphQLSyntaxError(
@@ -417,6 +429,55 @@ def read_string(source, start):
     return Token(TokenKind.STRING, start, position + 1, u"".join(value))
 
 
+def read_block_string(source, from_position):
+    body = source.body
+    position = from_position + 3
+
+    chunk_start = position
+    code = 0    # type: Optional[int]
+    value = []  # type: List[str]
+
+    while position < len(body) and code is not None:
+        code = char_code_at(body, position)
+
+        # Closing triple quote
+        if (
+            code == 34 and
+            char_code_at(body, position + 1) == 34 and
+            char_code_at(body, position + 2) == 34
+        ):
+            value.append(body[chunk_start:position])
+            return Token(
+                TokenKind.BLOCK_STRING,
+                from_position,
+                position + 3,
+                block_string_value(u"".join(value)),
+            )
+
+        if code < 0x0020 and code not in (0x0009, 0x000a, 0x000d):
+            raise GraphQLSyntaxError(
+                source,
+                position,
+                "Invalid character within str: %s." % print_char_code(code),
+            )
+
+        # Escaped triple quote (\""")
+        if (
+            code == 92 and
+            char_code_at(body, position + 1) == 34 and
+            char_code_at(body, position + 2) == 34 and
+            char_code_at(body, position + 3) == 34
+        ):
+            value.append(body[chunk_start, position] + '"""')
+            position += 4
+            chunk_start = position
+        else:
+            position += 1
+
+    raise GraphQLSyntaxError(source, position, "Unterminated string")
+
+
+
 def uni_char_code(a, b, c, d):
     # type: (int, int, int, int) -> int
     """Converts four hexidecimal chars to the integer that the
@@ -473,3 +534,36 @@ def read_name(source, position):
         end += 1
 
     return Token(TokenKind.NAME, position, end, body[position:end])
+
+
+
+SPLIT_RE = re.compile("\r\n|[\n\r]")
+WHITESPACE_RE = re.compile("(^[ |\t]*)")
+EMPTY_LINE_RE = re.compile("^\s*$")
+
+def block_string_value(value):
+    lines = SPLIT_RE.split(value)
+
+    common_indent = None
+    for line in lines[1:]:
+        match = WHITESPACE_RE.match(line)
+        indent = len(match.groups()[0])
+
+        if indent < len(line) and (common_indent is None or indent < common_indent):
+            common_indent = indent
+            if common_indent == 0:
+                break
+
+    if common_indent:
+        new_lines = [lines[0]]
+        for line in lines[1:]:
+            new_lines.append(line[common_indent:])
+        lines = new_lines
+
+    while len(lines) and EMPTY_LINE_RE.match(lines[0]):
+        lines = lines[1:]
+
+    while len(lines) and EMPTY_LINE_RE.match(lines[-1]):
+        lines = lines[:-1]
+
+    return '\n'.join(lines)
diff --git a/graphql/language/parser.py b/graphql/language/parser.py
index 8b658e50..406abef5 100644
--- a/graphql/language/parser.py
+++ b/graphql/language/parser.py
@@ -140,6 +140,10 @@ def peek(parser, kind):
     return parser.token.kind == kind
 
 
+def peek_description(parser):
+    return peek(parser, TokenKind.STRING) or peek(parser, TokenKind.BLOCK_STRING)
+
+
 def skip(parser, kind):
     # type: (Parser, int) -> bool
     """If the next token is of the given kind, return true after advancing
@@ -254,6 +258,9 @@ def parse_definition(parser):
     if peek(parser, TokenKind.BRACE_L):
         return parse_operation_definition(parser)
 
+    if peek_description(parser):
+        return parse_type_system_definition(parser)
+
     if peek(parser, TokenKind.NAME):
         name = parser.token.value
 
@@ -277,6 +284,11 @@ def parse_definition(parser):
     raise unexpected(parser)
 
 
+def parse_description(parser):
+    if peek_description(parser):
+        return parse_value_literal(parser, False)
+
+
 # Implements the parsing rules in the Operations section.
 def parse_operation_definition(parser):
     # type: (Parser) -> OperationDefinition
@@ -494,6 +506,14 @@ def parse_value_literal(parser, is_const):
             value=token.value, loc=loc(parser, token.start)
         )
 
+    elif token.kind == TokenKind.BLOCK_STRING:
+        advance(parser)
+        return ast.StringValue(  # type: ignore
+            value=token.value,
+            loc=loc(parser, token.start),
+            is_block_string=True,
+        )
+
     elif token.kind == TokenKind.NAME:
         if token.value in ("true", "false"):
             advance(parser)
@@ -624,10 +644,10 @@ def parse_type_system_definition(parser):
       - EnumTypeDefinition
       - InputObjectTypeDefinition
     """
-    if not peek(parser, TokenKind.NAME):
-        raise unexpected(parser)
-
-    name = parser.token.value
+    if peek_description(parser):
+        name = parser.lexer.look_ahead().value
+    else:
+        name = parser.token.value
 
     if name == "schema":
         return parse_schema_definition(parser)
@@ -688,9 +708,11 @@ def parse_scalar_type_definition(parser):
     # type: (Parser) -> ScalarTypeDefinition
     start = parser.token.start
     expect_keyword(parser, "scalar")
+    description = parse_description(parser)
 
     return ast.ScalarTypeDefinition(
         name=parse_name(parser),
+        description=description,
         directives=parse_directives(parser),
         loc=loc(parser, start),
     )
@@ -699,9 +721,11 @@ def parse_scalar_type_definition(parser):
 def parse_object_type_definition(parser):
     # type: (Parser) -> ObjectTypeDefinition
     start = parser.token.start
+    description = parse_description(parser)
     expect_keyword(parser, "type")
     return ast.ObjectTypeDefinition(
         name=parse_name(parser),
+        description=description,
         interfaces=parse_implements_interfaces(parser),
         directives=parse_directives(parser),
         fields=any(
@@ -729,9 +753,11 @@ def parse_implements_interfaces(parser):
 def parse_field_definition(parser):
     # type: (Parser) -> FieldDefinition
     start = parser.token.start
+    description = parse_description(parser)
 
     return ast.FieldDefinition(  # type: ignore
         name=parse_name(parser),
+        description=description,
         arguments=parse_argument_defs(parser),
         type=expect(parser, TokenKind.COLON) and parse_type(parser),
         directives=parse_directives(parser),
@@ -750,9 +776,11 @@ def parse_argument_defs(parser):
 def parse_input_value_def(parser):
     # type: (Parser) -> InputValueDefinition
     start = parser.token.start
+    description = parse_description(parser)
 
     return ast.InputValueDefinition(  # type: ignore
         name=parse_name(parser),
+        description=description,
         type=expect(parser, TokenKind.COLON) and parse_type(parser),
         default_value=parse_const_value(parser)
         if skip(parser, TokenKind.EQUALS)
@@ -765,10 +793,12 @@ def parse_input_value_def(parser):
 def parse_interface_type_definition(parser):
     # type: (Parser) -> InterfaceTypeDefinition
     start = parser.token.start
+    description = parse_description(parser)
     expect_keyword(parser, "interface")
 
     return ast.InterfaceTypeDefinition(
         name=parse_name(parser),
+        description=description,
         directives=parse_directives(parser),
         fields=any(
             parser, TokenKind.BRACE_L, parse_field_definition, TokenKind.BRACE_R
@@ -780,10 +810,12 @@ def parse_interface_type_definition(parser):
 def parse_union_type_definition(parser):
     # type: (Parser) -> UnionTypeDefinition
     start = parser.token.start
+    description = parse_description(parser)
     expect_keyword(parser, "union")
 
     return ast.UnionTypeDefinition(  # type: ignore
         name=parse_name(parser),
+        description=description,
         directives=parse_directives(parser),
         types=expect(parser, TokenKind.EQUALS) and parse_union_members(parser),
         loc=loc(parser, start),
@@ -806,10 +838,12 @@ def parse_union_members(parser):
 def parse_enum_type_definition(parser):
     # type: (Parser) -> EnumTypeDefinition
     start = parser.token.start
+    description = parse_description(parser)
     expect_keyword(parser, "enum")
 
     return ast.EnumTypeDefinition(
         name=parse_name(parser),
+        description=description,
         directives=parse_directives(parser),
         values=many(
             parser, TokenKind.BRACE_L, parse_enum_value_definition, TokenKind.BRACE_R
@@ -821,9 +855,11 @@ def parse_enum_type_definition(parser):
 def parse_enum_value_definition(parser):
     # type: (Parser) -> EnumValueDefinition
     start = parser.token.start
+    description = parse_description(parser)
 
     return ast.EnumValueDefinition(
         name=parse_name(parser),
+        description=description,
         directives=parse_directives(parser),
         loc=loc(parser, start),
     )
@@ -832,10 +868,12 @@ def parse_enum_value_definition(parser):
 def parse_input_object_type_definition(parser):
     # type: (Parser) -> InputObjectTypeDefinition
     start = parser.token.start
+    description = parse_description(parser)
     expect_keyword(parser, "input")
 
     return ast.InputObjectTypeDefinition(
         name=parse_name(parser),
+        description=description,
         directives=parse_directives(parser),
         fields=any(parser, TokenKind.BRACE_L, parse_input_value_def, TokenKind.BRACE_R),
         loc=loc(parser, start),
@@ -855,6 +893,7 @@ def parse_type_extension_definition(parser):
 def parse_directive_definition(parser):
     # type: (Parser) -> DirectiveDefinition
     start = parser.token.start
+    description = parse_description(parser)
     expect_keyword(parser, "directive")
     expect(parser, TokenKind.AT)
 
@@ -864,7 +903,11 @@ def parse_directive_definition(parser):
 
     locations = parse_directive_locations(parser)
     return ast.DirectiveDefinition(
-        name=name, locations=locations, arguments=args, loc=loc(parser, start)
+        name=name,
+        description=description,
+        locations=locations,
+        arguments=args,
+        loc=loc(parser, start),
     )
 
 
diff --git a/graphql/language/printer.py b/graphql/language/printer.py
index 676af0a8..67cde409 100644
--- a/graphql/language/printer.py
+++ b/graphql/language/printer.py
@@ -140,8 +140,10 @@ def leave_IntValue(self, node, *args):
     def leave_FloatValue(self, node, *args):
         return node.value
 
-    def leave_StringValue(self, node, *args):
+    def leave_StringValue(self, node, key, *args):
         # type: (Any, *Any) -> str
+        if node.is_block_string:
+            return print_block_string(node.value, key == 'description')
         return json.dumps(node.value)
 
     def leave_BooleanValue(self, node, *args):
@@ -198,84 +200,102 @@ def leave_OperationTypeDefinition(self, node, *args):
 
     def leave_ScalarTypeDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return "scalar " + node.name + wrap(" ", join(node.directives, " "))
+        return join([
+            node.description,
+            join(["scalar", node.name, join(node.directives, " ")], " "),
+        ], "\n")
 
     def leave_ObjectTypeDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return join(
-            [
+        return join([
+            node.description,
+            join([
                 "type",
                 node.name,
                 wrap("implements ", join(node.interfaces, ", ")),
                 join(node.directives, " "),
                 block(node.fields),
-            ],
-            " ",
-        )
+            ], " "),
+        ], "\n")
 
     def leave_FieldDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return (
+        has_multiline_item = any("\n" in arg for arg in node.arguments)
+        if has_multiline_item:
+            arguments_str = wrap("(\n", indent(join(node.arguments, "\n")), "\n)")
+        else:
+            arguments_str = wrap("(", join(node.arguments, ", "), ")")
+
+        definition_str = (
             node.name
-            + wrap("(", join(node.arguments, ", "), ")")
+            + arguments_str
             + ": "
             + node.type
             + wrap(" ", join(node.directives, " "))
         )
+        return join([node.description, definition_str], "\n")
 
     def leave_InputValueDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return (
+        definition_str = (
             node.name
             + ": "
             + node.type
             + wrap(" = ", node.default_value)
             + wrap(" ", join(node.directives, " "))
         )
+        return join([node.description, definition_str], "\n")
 
     def leave_InterfaceTypeDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return (
+        definition_str = (
             "interface "
             + node.name
             + wrap(" ", join(node.directives, " "))
             + " "
             + block(node.fields)
         )
+        return join([node.description, definition_str], "\n")
 
     def leave_UnionTypeDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return (
+        definition_str = (
             "union "
             + node.name
             + wrap(" ", join(node.directives, " "))
             + " = "
             + join(node.types, " | ")
         )
+        return join([node.description, definition_str], "\n")
 
     def leave_EnumTypeDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return (
+        definition_str = (
             "enum "
             + node.name
             + wrap(" ", join(node.directives, " "))
             + " "
             + block(node.values)
         )
+        return join([node.description, definition_str], "\n")
 
     def leave_EnumValueDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return node.name + wrap(" ", join(node.directives, " "))
+        return join([
+            node.description,
+            join([node.name, join(node.directives, " ")], " "),
+        ], "\n")
 
     def leave_InputObjectTypeDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return (
+        definition_str = (
             "input "
             + node.name
             + wrap(" ", join(node.directives, " "))
             + " "
             + block(node.fields)
         )
+        return join([node.description, definition_str], "\n")
 
     def leave_TypeExtensionDefinition(self, node, *args):
         # type: (Any, *Any) -> str
@@ -283,11 +303,14 @@ def leave_TypeExtensionDefinition(self, node, *args):
 
     def leave_DirectiveDefinition(self, node, *args):
         # type: (Any, *Any) -> str
-        return "directive @{}{} on {}".format(
-            node.name,
-            wrap("(", join(node.arguments, ", "), ")"),
-            " | ".join(node.locations),
-        )
+        return join([
+            node.description,
+            "directive @{}{} on {}".format(
+                node.name,
+                wrap("(", join(node.arguments, ", "), ")"),
+                " | ".join(node.locations),
+            )
+        ], "\n")
 
 
 def join(maybe_list, separator=""):
@@ -317,3 +340,13 @@ def indent(maybe_str):
     if maybe_str:
         return '  ' + maybe_str.replace("\n", "\n  ")
     return ""
+
+
+def print_block_string(value, is_description):
+    escaped = value.replace('"""', '\\"""')
+    if "\n" in value or (value[0] != " " and value[0] != "\t"):
+        if is_description:
+            return '"""\n' + escaped + '\n"""'
+        else:
+            return '"""\n' + indent(escaped) + '\n"""'
+    return '"""' + escaped.replace(r'"$', '"\n') + '"""'
diff --git a/graphql/language/tests/fixtures.py b/graphql/language/tests/fixtures.py
index b16653c4..290dc98d 100644
--- a/graphql/language/tests/fixtures.py
+++ b/graphql/language/tests/fixtures.py
@@ -58,7 +58,7 @@
 }
 """
 
-SCHEMA_KITCHEN_SINK = """
+SCHEMA_KITCHEN_SINK = '''
 
 # Copyright (c) 2015, Facebook, Inc.
 # All rights reserved.
@@ -72,9 +72,23 @@
   mutation: MutationType
 }
 
+"""
+This is a description
+of the `Foo` type.
+"""
 type Foo implements Bar {
+  "Description of the `one` field."
   one: Type
-  two(argument: InputType!): Type
+  """
+  This is a description of the `two` field.
+  """
+  two(
+    """
+    This is a description of the `argument` argument.
+    """
+    argument: InputType!
+  ): Type
+  """This is a description of the `three` field."""
   three(argument: InputType, other: String): Int
   four(argument: String = "string"): String
   five(argument: [String] = ["string", "string"]): String
@@ -103,8 +117,16 @@
 scalar AnnotatedScalar @onScalar
 
 enum Site {
+  """
+  This is a description of the `DESKTOP` value
+  """
+
   DESKTOP
+  """This is a description of the `MOBILE` value"""
   MOBILE
+
+  "This is a description of the `WEB` value"
+  WEB
 }
 
 enum AnnotatedEnum @onEnum {
@@ -129,7 +151,10 @@
 
 type NoFields {}
 
+"""
+This is a description of the `@skip` directive
+"""
 directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
 
 directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
-"""
+'''
diff --git a/graphql/language/tests/test_block_string_value.py b/graphql/language/tests/test_block_string_value.py
new file mode 100644
index 00000000..cbbaacf6
--- /dev/null
+++ b/graphql/language/tests/test_block_string_value.py
@@ -0,0 +1,108 @@
+from graphql.language.lexer import block_string_value
+
+
+def test_uniform_indentation():
+    _input = [
+        '',
+        '    Hello,',
+        '      World!',
+        '',
+        '    Yours,',
+        '      GraphQL.',
+    ]
+    expectation = [
+        'Hello,',
+        '  World!',
+        '',
+        'Yours,',
+        '  GraphQL.',
+    ]
+    _test_harness(_input, expectation)
+
+
+def test_empty_leading_and_trailing_lines():
+    _input = [
+        '',
+        '',
+        '    Hello,',
+        '      World!',
+        '',
+        '    Yours,',
+        '      GraphQL.',
+        '',
+        '',
+    ]
+    expectation = [
+        'Hello,',
+        '  World!',
+        '',
+        'Yours,',
+        '  GraphQL.',
+    ]
+    _test_harness(_input, expectation)
+
+
+def remove_blank_and_leading_lines():
+    _input = [
+        '  ',
+        '        ',
+        '    Hello,',
+        '      World!',
+        '',
+        '    Yours,',
+        '      GraphQL.',
+        '        ',
+        '  ',
+    ]
+    expectation = [
+        'Hello,',
+        '  World!',
+        '',
+        'Yours,',
+        '  GraphQL.',
+    ]
+    _test_harness(_input, expectation)
+
+
+def test_retain_indentation_from_first_line():
+    _input = [
+        '    Hello,',
+        '      World!',
+        '',
+        '    Yours,',
+        '      GraphQL.',
+    ]
+    expectation = [
+        '    Hello,',
+        '  World!',
+        '',
+        'Yours,',
+        '  GraphQL.',
+    ]
+    _test_harness(_input, expectation)
+
+
+def test_does_not_alter_trailing_spaces():
+    _input = [
+        '               ',
+        '    Hello,     ',
+        '      World!   ',
+        '               ',
+        '    Yours,     ',
+        '      GraphQL. ',
+        '               ',
+    ]
+    expectation = [
+        'Hello,     ',
+        '  World!   ',
+        '           ',
+        'Yours,     ',
+        '  GraphQL. ',
+    ]
+    _test_harness(_input, expectation)
+
+
+def _test_harness(_input, expectation):
+    _input = "\n".join(_input)
+    expectation = "\n".join(expectation)
+    assert block_string_value(_input) == expectation
diff --git a/graphql/language/tests/test_schema_printer.py b/graphql/language/tests/test_schema_printer.py
index afa979cb..310fef55 100644
--- a/graphql/language/tests/test_schema_printer.py
+++ b/graphql/language/tests/test_schema_printer.py
@@ -38,14 +38,30 @@ def test_prints_kitchen_sink():
     ast = parse(SCHEMA_KITCHEN_SINK)
     printed = print_ast(ast)
 
-    expected = """schema {
+    expected = '''schema {
   query: QueryType
   mutation: MutationType
 }
 
+"""
+This is a description
+of the `Foo` type.
+"""
 type Foo implements Bar {
+  "Description of the `one` field."
   one: Type
-  two(argument: InputType!): Type
+  """
+  This is a description of the `two` field.
+  """
+  two(
+    """
+    This is a description of the `argument` argument.
+    """
+    argument: InputType!
+  ): Type
+  """
+  This is a description of the `three` field.
+  """
   three(argument: InputType, other: String): Int
   four(argument: String = "string"): String
   five(argument: [String] = ["string", "string"]): String
@@ -74,8 +90,16 @@ def test_prints_kitchen_sink():
 scalar AnnotatedScalar @onScalar
 
 enum Site {
+  """
+  This is a description of the `DESKTOP` value
+  """
   DESKTOP
+  """
+  This is a description of the `MOBILE` value
+  """
   MOBILE
+  "This is a description of the `WEB` value"
+  WEB
 }
 
 enum AnnotatedEnum @onEnum {
@@ -100,9 +124,12 @@ def test_prints_kitchen_sink():
 
 type NoFields {}
 
+"""
+This is a description of the `@skip` directive
+"""
 directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
 
 directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
-"""
+'''
 
     assert printed == expected
diff --git a/graphql/language/visitor_meta.py b/graphql/language/visitor_meta.py
index 37372c48..87f5ad37 100644
--- a/graphql/language/visitor_meta.py
+++ b/graphql/language/visitor_meta.py
@@ -31,17 +31,17 @@
     ast.NonNullType: ("type",),
     ast.SchemaDefinition: ("directives", "operation_types"),
     ast.OperationTypeDefinition: ("type",),
-    ast.ScalarTypeDefinition: ("name", "directives"),
-    ast.ObjectTypeDefinition: ("name", "interfaces", "directives", "fields"),
-    ast.FieldDefinition: ("name", "arguments", "directives", "type"),
-    ast.InputValueDefinition: ("name", "type", "directives", "default_value"),
-    ast.InterfaceTypeDefinition: ("name", "directives", "fields"),
-    ast.UnionTypeDefinition: ("name", "directives", "types"),
-    ast.EnumTypeDefinition: ("name", "directives", "values"),
-    ast.EnumValueDefinition: ("name", "directives"),
-    ast.InputObjectTypeDefinition: ("name", "directives", "fields"),
+    ast.ScalarTypeDefinition: ("description", "name", "directives"),
+    ast.ObjectTypeDefinition: ("description", "name", "interfaces", "directives", "fields"),
+    ast.FieldDefinition: ("description", "name", "arguments", "directives", "type"),
+    ast.InputValueDefinition: ("description", "name", "type", "directives", "default_value"),
+    ast.InterfaceTypeDefinition: ("description", "name", "directives", "fields"),
+    ast.UnionTypeDefinition: ("description", "name", "directives", "types"),
+    ast.EnumTypeDefinition: ("description", "name", "directives", "values"),
+    ast.EnumValueDefinition: ("description", "name", "directives"),
+    ast.InputObjectTypeDefinition: ("description", "name", "directives", "fields"),
     ast.TypeExtensionDefinition: ("definition",),
-    ast.DirectiveDefinition: ("name", "arguments", "locations"),
+    ast.DirectiveDefinition: ("description", "name", "arguments", "locations"),
 }
 
 AST_KIND_TO_TYPE = {c.__name__: c for c in QUERY_DOCUMENT_KEYS.keys()}
diff --git a/graphql/type/directives.py b/graphql/type/directives.py
index ef7417c4..1a7bf701 100644
--- a/graphql/type/directives.py
+++ b/graphql/type/directives.py
@@ -107,7 +107,7 @@ def __init__(self, name, description=None, args=None, locations=None):
         "reason": GraphQLArgument(
             type=GraphQLString,
             description=(
-                "Explains why this element was deprecated, usually also including a suggestion for how to"
+                "Explains why this element was deprecated, usually also including a suggestion for how to "
                 "access supported similar data. Formatted in [Markdown]"
                 "(https://daringfireball.net/projects/markdown/)."
             ),
diff --git a/graphql/utils/build_ast_schema.py b/graphql/utils/build_ast_schema.py
index 8d28f4be..541be45b 100644
--- a/graphql/utils/build_ast_schema.py
+++ b/graphql/utils/build_ast_schema.py
@@ -167,6 +167,7 @@ def get_directive(directive_ast):
             name=directive_ast.name.value,
             locations=[node.value for node in directive_ast.locations],
             args=make_input_values(directive_ast.arguments, GraphQLArgument),
+            description=get_description(directive_ast),
         )
 
     def get_object_type(type_ast):
@@ -210,6 +211,7 @@ def make_type_def(definition):
             name=definition.name.value,
             fields=lambda: make_field_def_map(definition),
             interfaces=make_implemented_interfaces(definition),
+            description=get_description(definition),
         )
 
     def make_field_def_map(definition):
@@ -220,6 +222,7 @@ def make_field_def_map(definition):
                     type=produce_type_def(f.type),
                     args=make_input_values(f.arguments, GraphQLArgument),
                     deprecation_reason=get_deprecation_reason(f.directives),
+                    description=get_description(f),
                 ),
             )
             for f in definition.fields
@@ -237,6 +240,7 @@ def make_input_values(values, cls):
                     default_value=value_from_ast(
                         value.default_value, produce_type_def(value.type)
                     ),
+                    description=get_description(value),
                 ),
             )
             for value in values
@@ -247,6 +251,7 @@ def make_interface_def(definition):
             name=definition.name.value,
             resolve_type=_none,
             fields=lambda: make_field_def_map(definition),
+            description=get_description(definition),
         )
 
     def make_enum_def(definition):
@@ -254,18 +259,24 @@ def make_enum_def(definition):
             (
                 v.name.value,
                 GraphQLEnumValue(
-                    deprecation_reason=get_deprecation_reason(v.directives)
+                    deprecation_reason=get_deprecation_reason(v.directives),
+                    description=get_description(v),
                 ),
             )
             for v in definition.values
         )
-        return GraphQLEnumType(name=definition.name.value, values=values)
+        return GraphQLEnumType(
+            name=definition.name.value,
+            values=values,
+            description=get_description(definition),
+        )
 
     def make_union_def(definition):
         return GraphQLUnionType(
             name=definition.name.value,
             resolve_type=_none,
             types=[produce_type_def(t) for t in definition.types],
+            description=get_description(definition),
         )
 
     def make_scalar_def(definition):
@@ -277,6 +288,7 @@ def make_scalar_def(definition):
             # will cause them to pass.
             parse_literal=_false,
             parse_value=_false,
+            description=get_description(definition),
         )
 
     def make_input_object_def(definition):
@@ -285,6 +297,7 @@ def make_input_object_def(definition):
             fields=lambda: make_input_values(
                 definition.fields, GraphQLInputObjectField
             ),
+            description=get_description(definition),
         )
 
     _schema_def_handlers = {
@@ -352,3 +365,8 @@ def get_deprecation_reason(directives):
         return args["reason"]
     else:
         return None
+
+
+def get_description(node):
+    if node.description:
+        return node.description.value
diff --git a/graphql/utils/schema_printer.py b/graphql/utils/schema_printer.py
index 30a28abd..18356a75 100644
--- a/graphql/utils/schema_printer.py
+++ b/graphql/utils/schema_printer.py
@@ -1,3 +1,5 @@
+import re
+
 from ..language.printer import print_ast
 from ..type.definition import (
     GraphQLEnumType,
@@ -25,6 +27,9 @@
     from typing import Any, Union, Callable
 
 
+MAX_DESC_LEN = 120
+
+
 def print_schema(schema):
     # type: (GraphQLSchema) -> str
     return _print_filtered_schema(
@@ -122,7 +127,7 @@ def _print_type(type):
 
 def _print_scalar(type):
     # type: (GraphQLScalarType) -> str
-    return "scalar {}".format(type.name)
+    return _print_description(type) + "scalar {}".format(type.name)
 
 
 def _print_object(type):
@@ -134,44 +139,69 @@ def _print_object(type):
         else ""
     )
 
-    return ("type {}{} {{\n" "{}\n" "}}").format(
-        type.name, implemented_interfaces, _print_fields(type)
+    return ("{}type {}{} {{\n" "{}\n" "}}").format(
+        _print_description(type),
+        type.name,
+        implemented_interfaces,
+        _print_fields(type),
     )
 
 
 def _print_interface(type):
     # type: (GraphQLInterfaceType) -> str
-    return ("interface {} {{\n" "{}\n" "}}").format(type.name, _print_fields(type))
+    return ("{}interface {} {{\n" "{}\n" "}}").format(
+        _print_description(type),
+        type.name,
+        _print_fields(type),
+    )
 
 
 def _print_union(type):
     # type: (GraphQLUnionType) -> str
-    return "union {} = {}".format(type.name, " | ".join(str(t) for t in type.types))
+    return "{}union {} = {}".format(
+        _print_description(type),
+        type.name,
+        " | ".join(str(t) for t in type.types),
+    )
 
 
 def _print_enum(type):
     # type: (GraphQLEnumType) -> str
-    return ("enum {} {{\n" "{}\n" "}}").format(
-        type.name, "\n".join("  " + v.name + _print_deprecated(v) for v in type.values)
+    enum_values_str = "\n".join(
+        _print_description(v, '  ', not idx) + '  ' + v.name + _print_deprecated(v)
+        for idx, v in enumerate(type.values)
+    )
+    return ("{}enum {} {{\n" "{}\n" "}}").format(
+        _print_description(type),
+        type.name,
+        enum_values_str,
     )
 
 
 def _print_input_object(type):
     # type: (GraphQLInputObjectType) -> str
-    return ("input {} {{\n" "{}\n" "}}").format(
+    fields_str = "\n".join(
+        _print_description(f, "  ", not idx) + "  " +  _print_input_value(name, f)
+        for idx, (name, f) in enumerate(type.fields.items())
+    )
+    return ("{}input {} {{\n" "{}\n" "}}").format(
+        _print_description(type),
         type.name,
-        "\n".join(
-            "  " + _print_input_value(name, field)
-            for name, field in type.fields.items()
-        ),
+        fields_str,
     )
 
 
 def _print_fields(type):
     # type: (Union[GraphQLObjectType, GraphQLInterfaceType]) -> str
     return "\n".join(
-        "  {}{}: {}{}".format(f_name, _print_args(f), f.type, _print_deprecated(f))
-        for f_name, f in type.fields.items()
+        "{}  {}{}: {}{}".format(
+            _print_description(f, '  ', not idx),
+            f_name,
+            _print_args(f),
+            f.type,
+            _print_deprecated(f),
+        )
+        for idx, (f_name, f) in enumerate(type.fields.items())
     )
 
 
@@ -189,15 +219,24 @@ def _print_deprecated(field_or_enum_value):
 
 def _print_args(field_or_directives):
     # type: (Union[GraphQLField, GraphQLDirective]) -> str
-    if not field_or_directives.args:
+    args = field_or_directives.args
+
+    if not args:
         return ""
 
-    return "({})".format(
-        ", ".join(
-            _print_input_value(arg_name, arg)
-            for arg_name, arg in field_or_directives.args.items()
+    if all(not arg.description for arg in args.values()):
+        return "({})".format(
+            ", ".join(
+                _print_input_value(arg_name, arg)
+                for arg_name, arg in args.items()
+            )
         )
+
+    args_description = "\n".join(
+        _print_description(arg, '  ', not idx) + "  " + _print_input_value(arg_name, arg)
+        for idx, (arg_name, arg) in enumerate(args.items())
     )
+    return "(\n" + args_description + "\n)"
 
 
 def _print_input_value(name, arg):
@@ -212,9 +251,70 @@ def _print_input_value(name, arg):
 
 def _print_directive(directive):
     # type: (GraphQLDirective) -> str
-    return "directive @{}{} on {}".format(
-        directive.name, _print_args(directive), " | ".join(directive.locations)
+    return "{}directive @{}{} on {}".format(
+        _print_description(directive),
+        directive.name,
+        _print_args(directive),
+        " | ".join(directive.locations),
     )
 
 
+def _print_description(definition, indentation="", first_in_block=True):
+    if not definition.description:
+        return ""
+
+    lines = _description_lines(definition.description, MAX_DESC_LEN - len(indentation))
+    if indentation and not first_in_block:
+        description = "\n" + indentation + '"""'
+    else:
+        description = indentation + '"""'
+
+    if len(lines) == 1 and len(lines[0]) < 70 and lines[0][-1] != '"':
+        return description + _escape_quote(lines[0]) + '"""\n'
+
+    has_leading_space = lines[0][0] == " " or lines[0][0] == "\t";
+    if not has_leading_space:
+        description += "\n";
+
+    for idx, line in enumerate(lines):
+        if idx != 0 or not has_leading_space:
+            description += indentation;
+
+        description += _escape_quote(line) + "\n";
+
+    return description + indentation + '"""\n';
+
+
+def _description_lines(description, max_len):
+    lines = []
+    raw_lines = description.split("\n")
+    for line in raw_lines:
+        if line == "":
+            lines.append(line)
+        else:
+            lines = lines + _break_lines(line, max_len)
+    return lines
+
+
+def _break_lines(line, max_len):
+    if len(line) < max_len + 5:
+        return [line]
+
+    line_split_re = r"((?: |^).{15,%s}(?= |$))" % str(max_len - 40)
+    parts = re.split(line_split_re, line)
+
+    if len(parts) < 4:
+        return [line]
+
+    sublines = [parts[0] + parts[1] + parts[2]]
+    for idx in range(3, len(parts), 2):
+        sublines.append(parts[idx][1:] + parts[idx + 1])
+
+    return sublines
+
+
+def _escape_quote(line):
+    return line.replace('"""', '\\"""')
+
+
 __all__ = ["print_schema", "print_introspection_schema"]
diff --git a/graphql/utils/tests/test_build_ast_schema.py b/graphql/utils/tests/test_build_ast_schema.py
index 6f84aa64..bf9bcf0f 100644
--- a/graphql/utils/tests/test_build_ast_schema.py
+++ b/graphql/utils/tests/test_build_ast_schema.py
@@ -54,6 +54,37 @@ def test_with_directives():
     assert output == body
 
 
+def test_supports_descriptions():
+    body = '''
+schema {
+  query: Query
+}
+
+"""This is a directive"""
+directive @foo(
+  """It has an argument"""
+  arg: Int
+) on FIELD
+
+"""With an enum"""
+enum Color {
+  RED
+
+  """Not a creative color"""
+  GREEN
+  BLUE
+}
+
+"""What a great type"""
+type Query {
+  """And a field to boot"""
+  str: String
+}
+'''
+    output = cycle_output(body)
+    assert output == body
+
+
 def test_maintains_skip_and_include_directives():
     body = """
     schema {
diff --git a/graphql/utils/tests/test_schema_printer.py b/graphql/utils/tests/test_schema_printer.py
index 0d61facf..fbaf3a5d 100644
--- a/graphql/utils/tests/test_schema_printer.py
+++ b/graphql/utils/tests/test_schema_printer.py
@@ -569,17 +569,45 @@ def test_print_introspection_schema():
 
     assert (
         output
-        == """
+        == '''
 schema {
   query: Root
 }
 
-directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+"""
+Directs the executor to include this field or fragment only when the `if` argument is true.
+"""
+directive @include(
+  """Included when true."""
+  if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
 
-directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+"""
+Directs the executor to skip this field or fragment when the `if` argument is true.
+"""
+directive @skip(
+  """Skipped when true."""
+  if: Boolean!
+) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
+
+"""Marks an element of a GraphQL schema as no longer supported."""
+directive @deprecated(
+  """
+  Explains why this element was deprecated, usually also including a suggestion
+  for how to access supported similar data. Formatted in
+  [Markdown](https://daringfireball.net/projects/markdown/).
+  """
+  reason: String = "No longer supported"
+) on FIELD_DEFINITION | ENUM_VALUE
 
-directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ENUM_VALUE
+"""
+A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.
 
+In some cases, you need to provide options to alter GraphQL's execution behavior
+in ways field arguments will not suffice, such as conditionally including or
+skipping a field. Directives provide this by describing additional information
+to the executor.
+"""
 type __Directive {
   name: String!
   description: String
@@ -590,27 +618,71 @@ def test_print_introspection_schema():
   onField: Boolean! @deprecated(reason: "Use `locations`.")
 }
 
+"""
+A Directive can be adjacent to many parts of the GraphQL language, a
+__DirectiveLocation describes one such possible adjacencies.
+"""
 enum __DirectiveLocation {
+  """Location adjacent to a query operation."""
   QUERY
+
+  """Location adjacent to a mutation operation."""
   MUTATION
+
+  """Location adjacent to a subscription operation."""
   SUBSCRIPTION
+
+  """Location adjacent to a field."""
   FIELD
+
+  """Location adjacent to a fragment definition."""
   FRAGMENT_DEFINITION
+
+  """Location adjacent to a fragment spread."""
   FRAGMENT_SPREAD
+
+  """Location adjacent to an inline fragment."""
   INLINE_FRAGMENT
+
+  """Location adjacent to a schema definition."""
   SCHEMA
+
+  """Location adjacent to a scalar definition."""
   SCALAR
+
+  """Location adjacent to an object definition."""
   OBJECT
+
+  """Location adjacent to a field definition."""
   FIELD_DEFINITION
+
+  """Location adjacent to an argument definition."""
   ARGUMENT_DEFINITION
+
+  """Location adjacent to an interface definition."""
   INTERFACE
+
+  """Location adjacent to a union definition."""
   UNION
+
+  """Location adjacent to an enum definition."""
   ENUM
+
+  """Location adjacent to an enum value definition."""
   ENUM_VALUE
+
+  """Location adjacent to an input object definition."""
   INPUT_OBJECT
+
+  """Location adjacent to an input object field definition."""
   INPUT_FIELD_DEFINITION
 }
 
+"""
+One possible value for a given Enum. Enum values are unique values, not a
+placeholder for a string or numeric value. However an Enum value is returned in
+a JSON response as a string.
+"""
 type __EnumValue {
   name: String!
   description: String
@@ -618,6 +690,10 @@ def test_print_introspection_schema():
   deprecationReason: String
 }
 
+"""
+Object and Interface types are described by a list of Fields, each of which has
+a name, potentially a list of arguments, and a return type.
+"""
 type __Field {
   name: String!
   description: String
@@ -627,6 +703,11 @@ def test_print_introspection_schema():
   deprecationReason: String
 }
 
+"""
+Arguments provided to Fields or Directives and the input fields of an
+InputObject are represented as Input Values which describe their type and
+optionally a default value.
+"""
 type __InputValue {
   name: String!
   description: String
@@ -634,14 +715,42 @@ def test_print_introspection_schema():
   defaultValue: String
 }
 
+"""
+A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all
+available types and directives on the server, as well as the entry points for
+query, mutation and subscription operations.
+"""
 type __Schema {
+  """A list of all types supported by this server."""
   types: [__Type!]!
+
+  """The type that query operations will be rooted at."""
   queryType: __Type!
+
+  """
+  If this server supports mutation, the type that mutation operations will be rooted at.
+  """
   mutationType: __Type
+
+  """
+  If this server support subscription, the type that subscription operations will be rooted at.
+  """
   subscriptionType: __Type
+
+  """A list of all directives supported by this server."""
   directives: [__Directive!]!
 }
 
+"""
+The fundamental unit of any GraphQL Schema is the type. There are many kinds of
+types in GraphQL as represented by the `__TypeKind` enum.
+
+Depending on the kind of a type, certain fields describe information about that
+type. Scalar types provide no information beyond a name and description, while
+Enum types provide their values. Object and Interface types provide the fields
+they describe. Abstract types, Union and Interface, provide the Object types
+possible at runtime. List and NonNull types compose other types.
+"""
 type __Type {
   kind: __TypeKind!
   name: String
@@ -654,15 +763,37 @@ def test_print_introspection_schema():
   ofType: __Type
 }
 
+"""An enum describing what kind of type a given `__Type` is"""
 enum __TypeKind {
+  """Indicates this type is a scalar."""
   SCALAR
+
+  """
+  Indicates this type is an object. `fields` and `interfaces` are valid fields.
+  """
   OBJECT
+
+  """
+  Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.
+  """
   INTERFACE
+
+  """Indicates this type is a union. `possibleTypes` is a valid field."""
   UNION
+
+  """Indicates this type is an enum. `enumValues` is a valid field."""
   ENUM
+
+  """
+  Indicates this type is an input object. `inputFields` is a valid field.
+  """
   INPUT_OBJECT
+
+  """Indicates this type is a list. `ofType` is a valid field."""
   LIST
+
+  """Indicates this type is a non-null. `ofType` is a valid field."""
   NON_NULL
 }
-"""
+'''
     )