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) out += ' ' + nodes.SelectionSet(node.selectionSet); 89 return out; 90 }, 91 StringValue(node: StringValueNode): string { 92 if (node.block) { 93 return printBlockString(node.value).replace(/\n/g, LF); 94 } else { 95 return printString(node.value); 96 } 97 }, 98 BooleanValue(node: BooleanValueNode): string { 99 return '' + node.value; 100 }, 101 NullValue(_node: NullValueNode): string { 102 return 'null'; 103 }, 104 IntValue(node: IntValueNode): string { 105 return node.value; 106 }, 107 FloatValue(node: FloatValueNode): string { 108 return node.value; 109 }, 110 EnumValue(node: EnumValueNode): string { 111 return node.value; 112 }, 113 Name(node: NameNode): string { 114 return node.value; 115 }, 116 Variable(node: VariableNode): string { 117 return '$' + node.name.value; 118 }, 119 ListValue(node: ListValueNode): string { 120 return '[' + mapJoin(node.values, ', ', _print) + ']'; 121 }, 122 ObjectValue(node: ObjectValueNode): string { 123 return '{' + mapJoin(node.fields, ', ', nodes.ObjectField) + '}'; 124 }, 125 ObjectField(node: ObjectFieldNode): string { 126 return node.name.value + ': ' + _print(node.value); 127 }, 128 Document(node: DocumentNode): string { 129 if (!node.definitions || !node.definitions.length) return ''; 130 return mapJoin(node.definitions, '\n\n', _print); 131 }, 132 SelectionSet(node: SelectionSetNode): string { 133 return '{' + (LF += ' ') + mapJoin(node.selections, LF, _print) + (LF = LF.slice(0, -2)) + '}'; 134 }, 135 Argument(node: ArgumentNode): string { 136 return node.name.value + ': ' + _print(node.value); 137 }, 138 FragmentSpread(node: FragmentSpreadNode): string { 139 let out = '...' + node.name.value; 140 if (node.directives && node.directives.length) 141 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 142 return out; 143 }, 144 InlineFragment(node: InlineFragmentNode): string { 145 let out = '...'; 146 if (node.typeCondition) out += ' on ' + node.typeCondition.name.value; 147 if (node.directives && node.directives.length) 148 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 149 out += ' ' + nodes.SelectionSet(node.selectionSet); 150 return out; 151 }, 152 FragmentDefinition(node: FragmentDefinitionNode): string { 153 let out = 'fragment ' + node.name.value; 154 out += ' on ' + node.typeCondition.name.value; 155 if (node.directives && node.directives.length) 156 out += ' ' + mapJoin(node.directives, ' ', nodes.Directive); 157 return out + ' ' + nodes.SelectionSet(node.selectionSet); 158 }, 159 Directive(node: DirectiveNode): string { 160 let out = '@' + node.name.value; 161 if (node.arguments && node.arguments.length) 162 out += '(' + mapJoin(node.arguments, ', ', nodes.Argument) + ')'; 163 return out; 164 }, 165 NamedType(node: NamedTypeNode): string { 166 return node.name.value; 167 }, 168 ListType(node: ListTypeNode): string { 169 return '[' + _print(node.type) + ']'; 170 }, 171 NonNullType(node: NonNullTypeNode): string { 172 return _print(node.type) + '!'; 173 }, 174} as const; 175 176const _print = (node: ASTNode): string => nodes[node.kind](node); 177 178function print(node: ASTNode): string { 179 LF = '\n'; 180 return nodes[node.kind] ? nodes[node.kind](node) : ''; 181} 182 183export { print, printString, printBlockString };