import { printBlockString } from './blockString.mjs'; import { printString } from './printString.mjs'; import { visit } from './visitor.mjs'; /** * Converts an AST into a string, using one set of reasonable * formatting rules. */ export function print(ast) { return visit(ast, printDocASTReducer); } const MAX_LINE_LENGTH = 80; const printDocASTReducer = { Name: { leave: (node) => node.value, }, Variable: { leave: (node) => '$' + node.name, }, // Document Document: { leave: (node) => join(node.definitions, '\n\n'), }, OperationDefinition: { leave(node) { const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')'); const prefix = join( [ node.operation, join([node.name, varDefs]), join(node.directives, ' '), ], ' ', ); // Anonymous queries with no directives or variable definitions can use // the query short form. return (prefix === 'query' ? '' : prefix + ' ') + node.selectionSet; }, }, VariableDefinition: { leave: ({ variable, type, defaultValue, directives }) => variable + ': ' + type + wrap(' = ', defaultValue) + wrap(' ', join(directives, ' ')), }, SelectionSet: { leave: ({ selections }) => block(selections), }, Field: { leave({ 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: { leave: ({ name, value }) => name + ': ' + value, }, // Fragments FragmentSpread: { leave: ({ name, directives }) => '...' + name + wrap(' ', join(directives, ' ')), }, InlineFragment: { leave: ({ typeCondition, directives, selectionSet }) => join( [ '...', wrap('on ', typeCondition), join(directives, ' '), selectionSet, ], ' ', ), }, FragmentDefinition: { leave: ( { 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: { leave: ({ value }) => value, }, FloatValue: { leave: ({ value }) => value, }, StringValue: { leave: ({ value, block: isBlockString }) => isBlockString ? printBlockString(value) : printString(value), }, BooleanValue: { leave: ({ value }) => (value ? 'true' : 'false'), }, NullValue: { leave: () => 'null', }, EnumValue: { leave: ({ value }) => value, }, ListValue: { leave: ({ values }) => '[' + join(values, ', ') + ']', }, ObjectValue: { leave: ({ fields }) => '{' + join(fields, ', ') + '}', }, ObjectField: { leave: ({ name, value }) => name + ': ' + value, }, // Directive Directive: { leave: ({ name, arguments: args }) => '@' + name + wrap('(', join(args, ', '), ')'), }, // Type NamedType: { leave: ({ name }) => name, }, ListType: { leave: ({ type }) => '[' + type + ']', }, NonNullType: { leave: ({ type }) => type + '!', }, // Type System Definitions SchemaDefinition: { leave: ({ description, directives, operationTypes }) => wrap('', description, '\n') + join(['schema', join(directives, ' '), block(operationTypes)], ' '), }, OperationTypeDefinition: { leave: ({ operation, type }) => operation + ': ' + type, }, ScalarTypeDefinition: { leave: ({ description, name, directives }) => wrap('', description, '\n') + join(['scalar', name, join(directives, ' ')], ' '), }, ObjectTypeDefinition: { leave: ({ description, name, interfaces, directives, fields }) => wrap('', description, '\n') + join( [ 'type', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields), ], ' ', ), }, FieldDefinition: { leave: ({ description, name, arguments: args, type, directives }) => wrap('', description, '\n') + name + (hasMultilineItems(args) ? wrap('(\n', indent(join(args, '\n')), '\n)') : wrap('(', join(args, ', '), ')')) + ': ' + type + wrap(' ', join(directives, ' ')), }, InputValueDefinition: { leave: ({ description, name, type, defaultValue, directives }) => wrap('', description, '\n') + join( [name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')], ' ', ), }, InterfaceTypeDefinition: { leave: ({ description, name, interfaces, directives, fields }) => wrap('', description, '\n') + join( [ 'interface', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields), ], ' ', ), }, UnionTypeDefinition: { leave: ({ description, name, directives, types }) => wrap('', description, '\n') + join( ['union', name, join(directives, ' '), wrap('= ', join(types, ' | '))], ' ', ), }, EnumTypeDefinition: { leave: ({ description, name, directives, values }) => wrap('', description, '\n') + join(['enum', name, join(directives, ' '), block(values)], ' '), }, EnumValueDefinition: { leave: ({ description, name, directives }) => wrap('', description, '\n') + join([name, join(directives, ' ')], ' '), }, InputObjectTypeDefinition: { leave: ({ description, name, directives, fields }) => wrap('', description, '\n') + join(['input', name, join(directives, ' '), block(fields)], ' '), }, DirectiveDefinition: { leave: ({ description, name, arguments: args, repeatable, locations }) => wrap('', description, '\n') + 'directive @' + name + (hasMultilineItems(args) ? wrap('(\n', indent(join(args, '\n')), '\n)') : wrap('(', join(args, ', '), ')')) + (repeatable ? ' repeatable' : '') + ' on ' + join(locations, ' | '), }, SchemaExtension: { leave: ({ directives, operationTypes }) => join( ['extend schema', join(directives, ' '), block(operationTypes)], ' ', ), }, ScalarTypeExtension: { leave: ({ name, directives }) => join(['extend scalar', name, join(directives, ' ')], ' '), }, ObjectTypeExtension: { leave: ({ name, interfaces, directives, fields }) => join( [ 'extend type', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields), ], ' ', ), }, InterfaceTypeExtension: { leave: ({ name, interfaces, directives, fields }) => join( [ 'extend interface', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields), ], ' ', ), }, UnionTypeExtension: { leave: ({ name, directives, types }) => join( [ 'extend union', name, join(directives, ' '), wrap('= ', join(types, ' | ')), ], ' ', ), }, EnumTypeExtension: { leave: ({ name, directives, values }) => join(['extend enum', name, join(directives, ' '), block(values)], ' '), }, InputObjectTypeExtension: { leave: ({ name, directives, fields }) => join(['extend input', name, join(directives, ' '), block(fields)], ' '), }, }; /** * 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 = '') { var _maybeArray$filter$jo; return (_maybeArray$filter$jo = maybeArray === null || maybeArray === void 0 ? void 0 : maybeArray.filter((x) => x).join(separator)) !== null && _maybeArray$filter$jo !== void 0 ? _maybeArray$filter$jo : ''; } /** * Given array, print each item on its own line, wrapped in an indented `{ }` block. */ function block(array) { 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, maybeString, end = '') { return maybeString != null && maybeString !== '' ? start + maybeString + end : ''; } function indent(str) { return wrap(' ', str.replace(/\n/g, '\n ')); } function hasMultilineItems(maybeArray) { var _maybeArray$some; // FIXME: https://github.com/graphql/graphql-js/issues/2203 /* c8 ignore next */ return (_maybeArray$some = maybeArray === null || maybeArray === void 0 ? void 0 : maybeArray.some((str) => str.includes('\n'))) !== null && _maybeArray$some !== void 0 ? _maybeArray$some : false; }