340 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			340 lines
		
	
	
		
			9.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 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;
 | |
| }
 |