| 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; |
| } |