printer.mjs 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. import { printBlockString } from './blockString.mjs';
  2. import { printString } from './printString.mjs';
  3. import { visit } from './visitor.mjs';
  4. /**
  5. * Converts an AST into a string, using one set of reasonable
  6. * formatting rules.
  7. */
  8. export function print(ast) {
  9. return visit(ast, printDocASTReducer);
  10. }
  11. const MAX_LINE_LENGTH = 80;
  12. const printDocASTReducer = {
  13. Name: {
  14. leave: (node) => node.value,
  15. },
  16. Variable: {
  17. leave: (node) => '$' + node.name,
  18. },
  19. // Document
  20. Document: {
  21. leave: (node) => join(node.definitions, '\n\n'),
  22. },
  23. OperationDefinition: {
  24. leave(node) {
  25. const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')');
  26. const prefix = join(
  27. [
  28. node.operation,
  29. join([node.name, varDefs]),
  30. join(node.directives, ' '),
  31. ],
  32. ' ',
  33. ); // Anonymous queries with no directives or variable definitions can use
  34. // the query short form.
  35. return (prefix === 'query' ? '' : prefix + ' ') + node.selectionSet;
  36. },
  37. },
  38. VariableDefinition: {
  39. leave: ({ variable, type, defaultValue, directives }) =>
  40. variable +
  41. ': ' +
  42. type +
  43. wrap(' = ', defaultValue) +
  44. wrap(' ', join(directives, ' ')),
  45. },
  46. SelectionSet: {
  47. leave: ({ selections }) => block(selections),
  48. },
  49. Field: {
  50. leave({ alias, name, arguments: args, directives, selectionSet }) {
  51. const prefix = wrap('', alias, ': ') + name;
  52. let argsLine = prefix + wrap('(', join(args, ', '), ')');
  53. if (argsLine.length > MAX_LINE_LENGTH) {
  54. argsLine = prefix + wrap('(\n', indent(join(args, '\n')), '\n)');
  55. }
  56. return join([argsLine, join(directives, ' '), selectionSet], ' ');
  57. },
  58. },
  59. Argument: {
  60. leave: ({ name, value }) => name + ': ' + value,
  61. },
  62. // Fragments
  63. FragmentSpread: {
  64. leave: ({ name, directives }) =>
  65. '...' + name + wrap(' ', join(directives, ' ')),
  66. },
  67. InlineFragment: {
  68. leave: ({ typeCondition, directives, selectionSet }) =>
  69. join(
  70. [
  71. '...',
  72. wrap('on ', typeCondition),
  73. join(directives, ' '),
  74. selectionSet,
  75. ],
  76. ' ',
  77. ),
  78. },
  79. FragmentDefinition: {
  80. leave: (
  81. { name, typeCondition, variableDefinitions, directives, selectionSet }, // Note: fragment variable definitions are experimental and may be changed
  82. ) =>
  83. // or removed in the future.
  84. `fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` +
  85. `on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
  86. selectionSet,
  87. },
  88. // Value
  89. IntValue: {
  90. leave: ({ value }) => value,
  91. },
  92. FloatValue: {
  93. leave: ({ value }) => value,
  94. },
  95. StringValue: {
  96. leave: ({ value, block: isBlockString }) =>
  97. isBlockString ? printBlockString(value) : printString(value),
  98. },
  99. BooleanValue: {
  100. leave: ({ value }) => (value ? 'true' : 'false'),
  101. },
  102. NullValue: {
  103. leave: () => 'null',
  104. },
  105. EnumValue: {
  106. leave: ({ value }) => value,
  107. },
  108. ListValue: {
  109. leave: ({ values }) => '[' + join(values, ', ') + ']',
  110. },
  111. ObjectValue: {
  112. leave: ({ fields }) => '{' + join(fields, ', ') + '}',
  113. },
  114. ObjectField: {
  115. leave: ({ name, value }) => name + ': ' + value,
  116. },
  117. // Directive
  118. Directive: {
  119. leave: ({ name, arguments: args }) =>
  120. '@' + name + wrap('(', join(args, ', '), ')'),
  121. },
  122. // Type
  123. NamedType: {
  124. leave: ({ name }) => name,
  125. },
  126. ListType: {
  127. leave: ({ type }) => '[' + type + ']',
  128. },
  129. NonNullType: {
  130. leave: ({ type }) => type + '!',
  131. },
  132. // Type System Definitions
  133. SchemaDefinition: {
  134. leave: ({ description, directives, operationTypes }) =>
  135. wrap('', description, '\n') +
  136. join(['schema', join(directives, ' '), block(operationTypes)], ' '),
  137. },
  138. OperationTypeDefinition: {
  139. leave: ({ operation, type }) => operation + ': ' + type,
  140. },
  141. ScalarTypeDefinition: {
  142. leave: ({ description, name, directives }) =>
  143. wrap('', description, '\n') +
  144. join(['scalar', name, join(directives, ' ')], ' '),
  145. },
  146. ObjectTypeDefinition: {
  147. leave: ({ description, name, interfaces, directives, fields }) =>
  148. wrap('', description, '\n') +
  149. join(
  150. [
  151. 'type',
  152. name,
  153. wrap('implements ', join(interfaces, ' & ')),
  154. join(directives, ' '),
  155. block(fields),
  156. ],
  157. ' ',
  158. ),
  159. },
  160. FieldDefinition: {
  161. leave: ({ description, name, arguments: args, type, directives }) =>
  162. wrap('', description, '\n') +
  163. name +
  164. (hasMultilineItems(args)
  165. ? wrap('(\n', indent(join(args, '\n')), '\n)')
  166. : wrap('(', join(args, ', '), ')')) +
  167. ': ' +
  168. type +
  169. wrap(' ', join(directives, ' ')),
  170. },
  171. InputValueDefinition: {
  172. leave: ({ description, name, type, defaultValue, directives }) =>
  173. wrap('', description, '\n') +
  174. join(
  175. [name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')],
  176. ' ',
  177. ),
  178. },
  179. InterfaceTypeDefinition: {
  180. leave: ({ description, name, interfaces, directives, fields }) =>
  181. wrap('', description, '\n') +
  182. join(
  183. [
  184. 'interface',
  185. name,
  186. wrap('implements ', join(interfaces, ' & ')),
  187. join(directives, ' '),
  188. block(fields),
  189. ],
  190. ' ',
  191. ),
  192. },
  193. UnionTypeDefinition: {
  194. leave: ({ description, name, directives, types }) =>
  195. wrap('', description, '\n') +
  196. join(
  197. ['union', name, join(directives, ' '), wrap('= ', join(types, ' | '))],
  198. ' ',
  199. ),
  200. },
  201. EnumTypeDefinition: {
  202. leave: ({ description, name, directives, values }) =>
  203. wrap('', description, '\n') +
  204. join(['enum', name, join(directives, ' '), block(values)], ' '),
  205. },
  206. EnumValueDefinition: {
  207. leave: ({ description, name, directives }) =>
  208. wrap('', description, '\n') + join([name, join(directives, ' ')], ' '),
  209. },
  210. InputObjectTypeDefinition: {
  211. leave: ({ description, name, directives, fields }) =>
  212. wrap('', description, '\n') +
  213. join(['input', name, join(directives, ' '), block(fields)], ' '),
  214. },
  215. DirectiveDefinition: {
  216. leave: ({ description, name, arguments: args, repeatable, locations }) =>
  217. wrap('', description, '\n') +
  218. 'directive @' +
  219. name +
  220. (hasMultilineItems(args)
  221. ? wrap('(\n', indent(join(args, '\n')), '\n)')
  222. : wrap('(', join(args, ', '), ')')) +
  223. (repeatable ? ' repeatable' : '') +
  224. ' on ' +
  225. join(locations, ' | '),
  226. },
  227. SchemaExtension: {
  228. leave: ({ directives, operationTypes }) =>
  229. join(
  230. ['extend schema', join(directives, ' '), block(operationTypes)],
  231. ' ',
  232. ),
  233. },
  234. ScalarTypeExtension: {
  235. leave: ({ name, directives }) =>
  236. join(['extend scalar', name, join(directives, ' ')], ' '),
  237. },
  238. ObjectTypeExtension: {
  239. leave: ({ name, interfaces, directives, fields }) =>
  240. join(
  241. [
  242. 'extend type',
  243. name,
  244. wrap('implements ', join(interfaces, ' & ')),
  245. join(directives, ' '),
  246. block(fields),
  247. ],
  248. ' ',
  249. ),
  250. },
  251. InterfaceTypeExtension: {
  252. leave: ({ name, interfaces, directives, fields }) =>
  253. join(
  254. [
  255. 'extend interface',
  256. name,
  257. wrap('implements ', join(interfaces, ' & ')),
  258. join(directives, ' '),
  259. block(fields),
  260. ],
  261. ' ',
  262. ),
  263. },
  264. UnionTypeExtension: {
  265. leave: ({ name, directives, types }) =>
  266. join(
  267. [
  268. 'extend union',
  269. name,
  270. join(directives, ' '),
  271. wrap('= ', join(types, ' | ')),
  272. ],
  273. ' ',
  274. ),
  275. },
  276. EnumTypeExtension: {
  277. leave: ({ name, directives, values }) =>
  278. join(['extend enum', name, join(directives, ' '), block(values)], ' '),
  279. },
  280. InputObjectTypeExtension: {
  281. leave: ({ name, directives, fields }) =>
  282. join(['extend input', name, join(directives, ' '), block(fields)], ' '),
  283. },
  284. };
  285. /**
  286. * Given maybeArray, print an empty string if it is null or empty, otherwise
  287. * print all items together separated by separator if provided
  288. */
  289. function join(maybeArray, separator = '') {
  290. var _maybeArray$filter$jo;
  291. return (_maybeArray$filter$jo =
  292. maybeArray === null || maybeArray === void 0
  293. ? void 0
  294. : maybeArray.filter((x) => x).join(separator)) !== null &&
  295. _maybeArray$filter$jo !== void 0
  296. ? _maybeArray$filter$jo
  297. : '';
  298. }
  299. /**
  300. * Given array, print each item on its own line, wrapped in an indented `{ }` block.
  301. */
  302. function block(array) {
  303. return wrap('{\n', indent(join(array, '\n')), '\n}');
  304. }
  305. /**
  306. * If maybeString is not null or empty, then wrap with start and end, otherwise print an empty string.
  307. */
  308. function wrap(start, maybeString, end = '') {
  309. return maybeString != null && maybeString !== ''
  310. ? start + maybeString + end
  311. : '';
  312. }
  313. function indent(str) {
  314. return wrap(' ', str.replace(/\n/g, '\n '));
  315. }
  316. function hasMultilineItems(maybeArray) {
  317. var _maybeArray$some;
  318. // FIXME: https://github.com/graphql/graphql-js/issues/2203
  319. /* c8 ignore next */
  320. return (_maybeArray$some =
  321. maybeArray === null || maybeArray === void 0
  322. ? void 0
  323. : maybeArray.some((str) => str.includes('\n'))) !== null &&
  324. _maybeArray$some !== void 0
  325. ? _maybeArray$some
  326. : false;
  327. }