Mirror: The spec-compliant minimum of client-side GraphQL.
1import type { 2 ASTNode, 3 NameNode, 4 DocumentNode, 5 VariableNode, 6 SelectionSetNode, 7 FieldNode, 8 ArgumentNode, 9 FragmentSpreadNode, 10 InlineFragmentNode, 11 VariableDefinitionNode, 12 OperationDefinitionNode, 13 FragmentDefinitionNode, 14 IntValueNode, 15 FloatValueNode, 16 StringValueNode, 17 BooleanValueNode, 18 NullValueNode, 19 EnumValueNode, 20 ListValueNode, 21 ObjectValueNode, 22 ObjectFieldNode, 23 DirectiveNode, 24 NamedTypeNode, 25 ListTypeNode, 26 NonNullTypeNode, 27} from './ast'; 28 29function mapJoin<T>(value: readonly T[], joiner: string, mapper: (value: T) => string): string { 30 let out = ''; 31 for (let index = 0; index < value.length; index++) { 32 if (index) out += joiner; 33 out += mapper(value[index]); 34 } 35 return out; 36} 37 38function printString(string: string): string { 39 return JSON.stringify(string); 40} 41 42function printBlockString(string: string): string { 43 return '"""\n' + string.replace(/"""/g, '\\"""') + '\n"""'; 44} 45 46const MAX_LINE_LENGTH = 80; 47 48let LF = '\n'; 49 50const nodes = { 51 OperationDefinition(node: OperationDefinitionNode): string { 52 let out: string = node.operation; 53 if (node.name) out += ' ' + node.name.value; 54 if (node.variableDefinitions && node.variableDefinitions.length) { 55 if (!node.name) out += ' '; 56 out += '(' + mapJoin(node.variableDefinitions, ', ', nodes.VariableDefinition) + ')'; 57 } 58 if (node.directives && node.directives.length) 59 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 60 return out !== 'query' 61 ? out + ' ' + nodes.SelectionSet(node.selectionSet) 62 : nodes.SelectionSet(node.selectionSet); 63 }, 64 VariableDefinition(node: VariableDefinitionNode): string { 65 let out = nodes.Variable!(node.variable) + ': ' + _print(node.type); 66 if (node.defaultValue) out += ' = ' + _print(node.defaultValue); 67 if (node.directives && node.directives.length) 68 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 69 return out; 70 }, 71 Field(node: FieldNode): string { 72 let out = node.alias ? node.alias.value + ': ' + node.name.value : node.name.value; 73 if (node.arguments && node.arguments.length) { 74 const args = mapJoin(node.arguments, ', ', nodes.Argument); 75 if (out.length + args.length + 2 > MAX_LINE_LENGTH) { 76 out += 77 '(' + 78 (LF += ' ') + 79 mapJoin(node.arguments, LF, nodes.Argument) + 80 (LF = LF.slice(0, -2)) + 81 ')'; 82 } else { 83 out += '(' + args + ')'; 84 } 85 } 86 if (node.directives && node.directives.length) 87 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 88 if (node.selectionSet && node.selectionSet.selections.length) { 89 out += ' ' + nodes.SelectionSet(node.selectionSet); 90 } 91 return out; 92 }, 93 StringValue(node: StringValueNode): string { 94 if (node.block) { 95 return printBlockString(node.value).replace(/\n/g, LF); 96 } else { 97 return printString(node.value); 98 } 99 }, 100 BooleanValue(node: BooleanValueNode): string { 101 return '' + node.value; 102 }, 103 NullValue(_node: NullValueNode): string { 104 return 'null'; 105 }, 106 IntValue(node: IntValueNode): string { 107 return node.value; 108 }, 109 FloatValue(node: FloatValueNode): string { 110 return node.value; 111 }, 112 EnumValue(node: EnumValueNode): string { 113 return node.value; 114 }, 115 Name(node: NameNode): string { 116 return node.value; 117 }, 118 Variable(node: VariableNode): string { 119 return '$' + node.name.value; 120 }, 121 ListValue(node: ListValueNode): string { 122 return '[' + mapJoin(node.values, ', ', _print) + ']'; 123 }, 124 ObjectValue(node: ObjectValueNode): string { 125 return '{' + mapJoin(node.fields, ', ', nodes.ObjectField) + '}'; 126 }, 127 ObjectField(node: ObjectFieldNode): string { 128 return node.name.value + ': ' + _print(node.value); 129 }, 130 Document(node: DocumentNode): string { 131 if (!node.definitions || !node.definitions.length) return ''; 132 return mapJoin(node.definitions, '\n\n', _print); 133 }, 134 SelectionSet(node: SelectionSetNode): string { 135 return '{' + (LF += ' ') + mapJoin(node.selections, LF, _print) + (LF = LF.slice(0, -2)) + '}'; 136 }, 137 Argument(node: ArgumentNode): string { 138 return node.name.value + ': ' + _print(node.value); 139 }, 140 FragmentSpread(node: FragmentSpreadNode): string { 141 let out = '...' + node.name.value; 142 if (node.directives && node.directives.length) 143 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 144 return out; 145 }, 146 InlineFragment(node: InlineFragmentNode): string { 147 let out = '...'; 148 if (node.typeCondition) out += ' on ' + node.typeCondition.name.value; 149 if (node.directives && node.directives.length) 150 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 151 out += ' ' + nodes.SelectionSet(node.selectionSet); 152 return out; 153 }, 154 FragmentDefinition(node: FragmentDefinitionNode): string { 155 let out = 'fragment ' + node.name.value; 156 out += ' on ' + node.typeCondition.name.value; 157 if (node.directives && node.directives.length) 158 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 159 return out + ' ' + nodes.SelectionSet(node.selectionSet); 160 }, 161 Directive(node: DirectiveNode): string { 162 let out = '@' + node.name.value; 163 if (node.arguments && node.arguments.length) 164 out += '(' + mapJoin(node.arguments, ', ', nodes.Argument) + ')'; 165 return out; 166 }, 167 NamedType(node: NamedTypeNode): string { 168 return node.name.value; 169 }, 170 ListType(node: ListTypeNode): string { 171 return '[' + _print(node.type) + ']'; 172 }, 173 NonNullType(node: NonNullTypeNode): string { 174 return _print(node.type) + '!'; 175 }, 176} as const; 177 178const _print = (node: ASTNode): string => nodes[node.kind](node); 179 180function print(node: ASTNode): string { 181 LF = '\n'; 182 return nodes[node.kind] ? nodes[node.kind](node) : ''; 183} 184 185export { print, printString, printBlockString };