123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292 |
- // @flow strict
- import type { ASTNode } from './ast';
- import { visit } from './visitor';
- 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 });
- }
- const MAX_LINE_LENGTH = 80;
- // 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 }) => {
- const prefix = wrap('', alias, ': ') + name;
- let argsLine = prefix + wrap('(', join(args, ', '), ')');
- if (argsLine.length > MAX_LINE_LENGTH) {
- argsLine = prefix + wrap('(\n', indent(join(args, '\n')), '\n)');
- }
- return join([argsLine, 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: addDescription(({ 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, interfaces, directives, fields }) =>
- join(
- [
- 'interface',
- name,
- wrap('implements ', join(interfaces, ' & ')),
- 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, interfaces, directives, fields }) =>
- join(
- [
- 'extend interface',
- name,
- wrap('implements ', join(interfaces, ' & ')),
- 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: ?Array<string>, separator = ''): string {
- return maybeArray?.filter((x) => x).join(separator) ?? '';
- }
- /**
- * Given array, print each item on its own line, wrapped in an
- * indented "{ }" block.
- */
- function block(array: ?Array<string>): string {
- return wrap('{\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: string, maybeString: ?string, end: string = ''): string {
- return maybeString != null && maybeString !== ''
- ? start + maybeString + end
- : '';
- }
- function indent(str: string): string {
- return wrap(' ', str.replace(/\n/g, '\n '));
- }
- function isMultiline(str: string): boolean {
- return str.indexOf('\n') !== -1;
- }
- function hasMultilineItems(maybeArray: ?Array<string>): boolean {
- return maybeArray != null && maybeArray.some(isMultiline);
- }
|