123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- // @flow strict
- import { visit } from './visitor';
- import { type ASTNode } from './ast';
- import { printBlockString } from './blockString';
- /**
- * Converts an AST into a string, using one set of reasonable
- * formatting rules.
- */
- export function print(ast: ASTNode): string {
- return visit(ast, { leave: printDocASTReducer });
- }
- // TODO: provide better type coverage in future
- const printDocASTReducer: any = {
- Name: node => node.value,
- Variable: node => '$' + node.name,
- // Document
- Document: node => join(node.definitions, '\n\n') + '\n',
- OperationDefinition(node) {
- const op = node.operation;
- const name = node.name;
- const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')');
- const directives = join(node.directives, ' ');
- const selectionSet = node.selectionSet;
- // Anonymous queries with no directives or variable definitions can use
- // the query short form.
- return !name && !directives && !varDefs && op === 'query'
- ? selectionSet
- : join([op, join([name, varDefs]), directives, selectionSet], ' ');
- },
- VariableDefinition: ({ variable, type, defaultValue, directives }) =>
- variable +
- ': ' +
- type +
- wrap(' = ', defaultValue) +
- wrap(' ', join(directives, ' ')),
- SelectionSet: ({ selections }) => block(selections),
- Field: ({ alias, name, arguments: args, directives, selectionSet }) =>
- join(
- [
- wrap('', alias, ': ') + name + wrap('(', join(args, ', '), ')'),
- join(directives, ' '),
- selectionSet,
- ],
- ' ',
- ),
- Argument: ({ name, value }) => name + ': ' + value,
- // Fragments
- FragmentSpread: ({ name, directives }) =>
- '...' + name + wrap(' ', join(directives, ' ')),
- InlineFragment: ({ typeCondition, directives, selectionSet }) =>
- join(
- ['...', wrap('on ', typeCondition), join(directives, ' '), selectionSet],
- ' ',
- ),
- FragmentDefinition: ({
- name,
- typeCondition,
- variableDefinitions,
- directives,
- selectionSet,
- }) =>
- // Note: fragment variable definitions are experimental and may be changed
- // or removed in the future.
- `fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` +
- `on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
- selectionSet,
- // Value
- IntValue: ({ value }) => value,
- FloatValue: ({ value }) => value,
- StringValue: ({ value, block: isBlockString }, key) =>
- isBlockString
- ? printBlockString(value, key === 'description' ? '' : ' ')
- : JSON.stringify(value),
- BooleanValue: ({ value }) => (value ? 'true' : 'false'),
- NullValue: () => 'null',
- EnumValue: ({ value }) => value,
- ListValue: ({ values }) => '[' + join(values, ', ') + ']',
- ObjectValue: ({ fields }) => '{' + join(fields, ', ') + '}',
- ObjectField: ({ name, value }) => name + ': ' + value,
- // Directive
- Directive: ({ name, arguments: args }) =>
- '@' + name + wrap('(', join(args, ', '), ')'),
- // Type
- NamedType: ({ name }) => name,
- ListType: ({ type }) => '[' + type + ']',
- NonNullType: ({ type }) => type + '!',
- // Type System Definitions
- SchemaDefinition: ({ directives, operationTypes }) =>
- join(['schema', join(directives, ' '), block(operationTypes)], ' '),
- OperationTypeDefinition: ({ operation, type }) => operation + ': ' + type,
- ScalarTypeDefinition: addDescription(({ name, directives }) =>
- join(['scalar', name, join(directives, ' ')], ' '),
- ),
- ObjectTypeDefinition: addDescription(
- ({ name, interfaces, directives, fields }) =>
- join(
- [
- 'type',
- name,
- wrap('implements ', join(interfaces, ' & ')),
- join(directives, ' '),
- block(fields),
- ],
- ' ',
- ),
- ),
- FieldDefinition: addDescription(
- ({ name, arguments: args, type, directives }) =>
- name +
- (hasMultilineItems(args)
- ? wrap('(\n', indent(join(args, '\n')), '\n)')
- : wrap('(', join(args, ', '), ')')) +
- ': ' +
- type +
- wrap(' ', join(directives, ' ')),
- ),
- InputValueDefinition: addDescription(
- ({ name, type, defaultValue, directives }) =>
- join(
- [name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')],
- ' ',
- ),
- ),
- InterfaceTypeDefinition: addDescription(({ name, directives, fields }) =>
- join(['interface', name, join(directives, ' '), block(fields)], ' '),
- ),
- UnionTypeDefinition: addDescription(({ name, directives, types }) =>
- join(
- [
- 'union',
- name,
- join(directives, ' '),
- types && types.length !== 0 ? '= ' + join(types, ' | ') : '',
- ],
- ' ',
- ),
- ),
- EnumTypeDefinition: addDescription(({ name, directives, values }) =>
- join(['enum', name, join(directives, ' '), block(values)], ' '),
- ),
- EnumValueDefinition: addDescription(({ name, directives }) =>
- join([name, join(directives, ' ')], ' '),
- ),
- InputObjectTypeDefinition: addDescription(({ name, directives, fields }) =>
- join(['input', name, join(directives, ' '), block(fields)], ' '),
- ),
- DirectiveDefinition: addDescription(
- ({ name, arguments: args, repeatable, locations }) =>
- 'directive @' +
- name +
- (hasMultilineItems(args)
- ? wrap('(\n', indent(join(args, '\n')), '\n)')
- : wrap('(', join(args, ', '), ')')) +
- (repeatable ? ' repeatable' : '') +
- ' on ' +
- join(locations, ' | '),
- ),
- SchemaExtension: ({ directives, operationTypes }) =>
- join(['extend schema', join(directives, ' '), block(operationTypes)], ' '),
- ScalarTypeExtension: ({ name, directives }) =>
- join(['extend scalar', name, join(directives, ' ')], ' '),
- ObjectTypeExtension: ({ name, interfaces, directives, fields }) =>
- join(
- [
- 'extend type',
- name,
- wrap('implements ', join(interfaces, ' & ')),
- join(directives, ' '),
- block(fields),
- ],
- ' ',
- ),
- InterfaceTypeExtension: ({ name, directives, fields }) =>
- join(['extend interface', name, join(directives, ' '), block(fields)], ' '),
- UnionTypeExtension: ({ name, directives, types }) =>
- join(
- [
- 'extend union',
- name,
- join(directives, ' '),
- types && types.length !== 0 ? '= ' + join(types, ' | ') : '',
- ],
- ' ',
- ),
- EnumTypeExtension: ({ name, directives, values }) =>
- join(['extend enum', name, join(directives, ' '), block(values)], ' '),
- InputObjectTypeExtension: ({ name, directives, fields }) =>
- join(['extend input', name, join(directives, ' '), block(fields)], ' '),
- };
- function addDescription(cb) {
- return node => join([node.description, cb(node)], '\n');
- }
- /**
- * Given maybeArray, print an empty string if it is null or empty, otherwise
- * print all items together separated by separator if provided
- */
- function join(maybeArray, separator) {
- return maybeArray ? maybeArray.filter(x => x).join(separator || '') : '';
- }
- /**
- * Given array, print each item on its own line, wrapped in an
- * indented "{ }" block.
- */
- function block(array) {
- return array && array.length !== 0
- ? '{\n' + indent(join(array, '\n')) + '\n}'
- : '';
- }
- /**
- * If maybeString is not null or empty, then wrap with start and end, otherwise
- * print an empty string.
- */
- function wrap(start, maybeString, end) {
- return maybeString ? start + maybeString + (end || '') : '';
- }
- function indent(maybeString) {
- return maybeString && ' ' + maybeString.replace(/\n/g, '\n ');
- }
- function isMultiline(string) {
- return string.indexOf('\n') !== -1;
- }
- function hasMultilineItems(maybeArray) {
- return maybeArray && maybeArray.some(isMultiline);
- }
|