comments.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.getBlockStringIndentation = exports.dedentBlockStringValue = exports.getLeadingCommentBlock = exports.getComment = exports.getDescription = exports.printWithComments = exports.printComment = exports.pushComment = exports.collectComment = exports.resetComments = void 0;
  4. const graphql_1 = require("graphql");
  5. const MAX_LINE_LENGTH = 80;
  6. let commentsRegistry = {};
  7. function resetComments() {
  8. commentsRegistry = {};
  9. }
  10. exports.resetComments = resetComments;
  11. function collectComment(node) {
  12. var _a;
  13. const entityName = (_a = node.name) === null || _a === void 0 ? void 0 : _a.value;
  14. if (entityName == null) {
  15. return;
  16. }
  17. pushComment(node, entityName);
  18. switch (node.kind) {
  19. case 'EnumTypeDefinition':
  20. if (node.values) {
  21. for (const value of node.values) {
  22. pushComment(value, entityName, value.name.value);
  23. }
  24. }
  25. break;
  26. case 'ObjectTypeDefinition':
  27. case 'InputObjectTypeDefinition':
  28. case 'InterfaceTypeDefinition':
  29. if (node.fields) {
  30. for (const field of node.fields) {
  31. pushComment(field, entityName, field.name.value);
  32. if (isFieldDefinitionNode(field) && field.arguments) {
  33. for (const arg of field.arguments) {
  34. pushComment(arg, entityName, field.name.value, arg.name.value);
  35. }
  36. }
  37. }
  38. }
  39. break;
  40. }
  41. }
  42. exports.collectComment = collectComment;
  43. function pushComment(node, entity, field, argument) {
  44. const comment = getComment(node);
  45. if (typeof comment !== 'string' || comment.length === 0) {
  46. return;
  47. }
  48. const keys = [entity];
  49. if (field) {
  50. keys.push(field);
  51. if (argument) {
  52. keys.push(argument);
  53. }
  54. }
  55. const path = keys.join('.');
  56. if (!commentsRegistry[path]) {
  57. commentsRegistry[path] = [];
  58. }
  59. commentsRegistry[path].push(comment);
  60. }
  61. exports.pushComment = pushComment;
  62. function printComment(comment) {
  63. return '\n# ' + comment.replace(/\n/g, '\n# ');
  64. }
  65. exports.printComment = printComment;
  66. /**
  67. * Copyright (c) 2015-present, Facebook, Inc.
  68. *
  69. * This source code is licensed under the MIT license found in the
  70. * LICENSE file in the root directory of this source tree.
  71. */
  72. /**
  73. * NOTE: ==> This file has been modified just to add comments to the printed AST
  74. * This is a temp measure, we will move to using the original non modified printer.js ASAP.
  75. */
  76. /**
  77. * Given maybeArray, print an empty string if it is null or empty, otherwise
  78. * print all items together separated by separator if provided
  79. */
  80. function join(maybeArray, separator) {
  81. return maybeArray ? maybeArray.filter(x => x).join(separator || '') : '';
  82. }
  83. function hasMultilineItems(maybeArray) {
  84. var _a;
  85. return (_a = maybeArray === null || maybeArray === void 0 ? void 0 : maybeArray.some(str => str.includes('\n'))) !== null && _a !== void 0 ? _a : false;
  86. }
  87. function addDescription(cb) {
  88. return (node, _key, _parent, path, ancestors) => {
  89. var _a;
  90. const keys = [];
  91. const parent = path.reduce((prev, key) => {
  92. if (['fields', 'arguments', 'values'].includes(key) && prev.name) {
  93. keys.push(prev.name.value);
  94. }
  95. return prev[key];
  96. }, ancestors[0]);
  97. const key = [...keys, (_a = parent === null || parent === void 0 ? void 0 : parent.name) === null || _a === void 0 ? void 0 : _a.value].filter(Boolean).join('.');
  98. const items = [];
  99. if (node.kind.includes('Definition') && commentsRegistry[key]) {
  100. items.push(...commentsRegistry[key]);
  101. }
  102. return join([...items.map(printComment), node.description, cb(node, _key, _parent, path, ancestors)], '\n');
  103. };
  104. }
  105. function indent(maybeString) {
  106. return maybeString && ` ${maybeString.replace(/\n/g, '\n ')}`;
  107. }
  108. /**
  109. * Given array, print each item on its own line, wrapped in an
  110. * indented "{ }" block.
  111. */
  112. function block(array) {
  113. return array && array.length !== 0 ? `{\n${indent(join(array, '\n'))}\n}` : '';
  114. }
  115. /**
  116. * If maybeString is not null or empty, then wrap with start and end, otherwise
  117. * print an empty string.
  118. */
  119. function wrap(start, maybeString, end) {
  120. return maybeString ? start + maybeString + (end || '') : '';
  121. }
  122. /**
  123. * Print a block string in the indented block form by adding a leading and
  124. * trailing blank line. However, if a block string starts with whitespace and is
  125. * a single-line, adding a leading blank line would strip that whitespace.
  126. */
  127. function printBlockString(value, isDescription = false) {
  128. const escaped = value.replace(/"""/g, '\\"""');
  129. return (value[0] === ' ' || value[0] === '\t') && value.indexOf('\n') === -1
  130. ? `"""${escaped.replace(/"$/, '"\n')}"""`
  131. : `"""\n${isDescription ? escaped : indent(escaped)}\n"""`;
  132. }
  133. const printDocASTReducer = {
  134. Name: { leave: node => node.value },
  135. Variable: { leave: node => '$' + node.name },
  136. // Document
  137. Document: {
  138. leave: node => join(node.definitions, '\n\n'),
  139. },
  140. OperationDefinition: {
  141. leave: node => {
  142. const varDefs = wrap('(', join(node.variableDefinitions, ', '), ')');
  143. const prefix = join([node.operation, join([node.name, varDefs]), join(node.directives, ' ')], ' ');
  144. // the query short form.
  145. return prefix + ' ' + node.selectionSet;
  146. },
  147. },
  148. VariableDefinition: {
  149. leave: ({ variable, type, defaultValue, directives }) => variable + ': ' + type + wrap(' = ', defaultValue) + wrap(' ', join(directives, ' ')),
  150. },
  151. SelectionSet: { leave: ({ selections }) => block(selections) },
  152. Field: {
  153. leave({ alias, name, arguments: args, directives, selectionSet }) {
  154. const prefix = wrap('', alias, ': ') + name;
  155. let argsLine = prefix + wrap('(', join(args, ', '), ')');
  156. if (argsLine.length > MAX_LINE_LENGTH) {
  157. argsLine = prefix + wrap('(\n', indent(join(args, '\n')), '\n)');
  158. }
  159. return join([argsLine, join(directives, ' '), selectionSet], ' ');
  160. },
  161. },
  162. Argument: { leave: ({ name, value }) => name + ': ' + value },
  163. // Fragments
  164. FragmentSpread: {
  165. leave: ({ name, directives }) => '...' + name + wrap(' ', join(directives, ' ')),
  166. },
  167. InlineFragment: {
  168. leave: ({ typeCondition, directives, selectionSet }) => join(['...', wrap('on ', typeCondition), join(directives, ' '), selectionSet], ' '),
  169. },
  170. FragmentDefinition: {
  171. leave: ({ name, typeCondition, variableDefinitions, directives, selectionSet }) =>
  172. // Note: fragment variable definitions are experimental and may be changed
  173. // or removed in the future.
  174. `fragment ${name}${wrap('(', join(variableDefinitions, ', '), ')')} ` +
  175. `on ${typeCondition} ${wrap('', join(directives, ' '), ' ')}` +
  176. selectionSet,
  177. },
  178. // Value
  179. IntValue: { leave: ({ value }) => value },
  180. FloatValue: { leave: ({ value }) => value },
  181. StringValue: {
  182. leave: ({ value, block: isBlockString }) => {
  183. if (isBlockString) {
  184. return printBlockString(value);
  185. }
  186. return JSON.stringify(value);
  187. },
  188. },
  189. BooleanValue: { leave: ({ value }) => (value ? 'true' : 'false') },
  190. NullValue: { leave: () => 'null' },
  191. EnumValue: { leave: ({ value }) => value },
  192. ListValue: { leave: ({ values }) => '[' + join(values, ', ') + ']' },
  193. ObjectValue: { leave: ({ fields }) => '{' + join(fields, ', ') + '}' },
  194. ObjectField: { leave: ({ name, value }) => name + ': ' + value },
  195. // Directive
  196. Directive: {
  197. leave: ({ name, arguments: args }) => '@' + name + wrap('(', join(args, ', '), ')'),
  198. },
  199. // Type
  200. NamedType: { leave: ({ name }) => name },
  201. ListType: { leave: ({ type }) => '[' + type + ']' },
  202. NonNullType: { leave: ({ type }) => type + '!' },
  203. // Type System Definitions
  204. SchemaDefinition: {
  205. leave: ({ directives, operationTypes }) => join(['schema', join(directives, ' '), block(operationTypes)], ' '),
  206. },
  207. OperationTypeDefinition: {
  208. leave: ({ operation, type }) => operation + ': ' + type,
  209. },
  210. ScalarTypeDefinition: {
  211. leave: ({ name, directives }) => join(['scalar', name, join(directives, ' ')], ' '),
  212. },
  213. ObjectTypeDefinition: {
  214. leave: ({ name, interfaces, directives, fields }) => join(['type', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], ' '),
  215. },
  216. FieldDefinition: {
  217. leave: ({ name, arguments: args, type, directives }) => name +
  218. (hasMultilineItems(args)
  219. ? wrap('(\n', indent(join(args, '\n')), '\n)')
  220. : wrap('(', join(args, ', '), ')')) +
  221. ': ' +
  222. type +
  223. wrap(' ', join(directives, ' ')),
  224. },
  225. InputValueDefinition: {
  226. leave: ({ name, type, defaultValue, directives }) => join([name + ': ' + type, wrap('= ', defaultValue), join(directives, ' ')], ' '),
  227. },
  228. InterfaceTypeDefinition: {
  229. leave: ({ name, interfaces, directives, fields }) => join(['interface', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], ' '),
  230. },
  231. UnionTypeDefinition: {
  232. leave: ({ name, directives, types }) => join(['union', name, join(directives, ' '), wrap('= ', join(types, ' | '))], ' '),
  233. },
  234. EnumTypeDefinition: {
  235. leave: ({ name, directives, values }) => join(['enum', name, join(directives, ' '), block(values)], ' '),
  236. },
  237. EnumValueDefinition: {
  238. leave: ({ name, directives }) => join([name, join(directives, ' ')], ' '),
  239. },
  240. InputObjectTypeDefinition: {
  241. leave: ({ name, directives, fields }) => join(['input', name, join(directives, ' '), block(fields)], ' '),
  242. },
  243. DirectiveDefinition: {
  244. leave: ({ name, arguments: args, repeatable, locations }) => 'directive @' +
  245. name +
  246. (hasMultilineItems(args)
  247. ? wrap('(\n', indent(join(args, '\n')), '\n)')
  248. : wrap('(', join(args, ', '), ')')) +
  249. (repeatable ? ' repeatable' : '') +
  250. ' on ' +
  251. join(locations, ' | '),
  252. },
  253. SchemaExtension: {
  254. leave: ({ directives, operationTypes }) => join(['extend schema', join(directives, ' '), block(operationTypes)], ' '),
  255. },
  256. ScalarTypeExtension: {
  257. leave: ({ name, directives }) => join(['extend scalar', name, join(directives, ' ')], ' '),
  258. },
  259. ObjectTypeExtension: {
  260. leave: ({ name, interfaces, directives, fields }) => join(['extend type', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], ' '),
  261. },
  262. InterfaceTypeExtension: {
  263. leave: ({ name, interfaces, directives, fields }) => join(['extend interface', name, wrap('implements ', join(interfaces, ' & ')), join(directives, ' '), block(fields)], ' '),
  264. },
  265. UnionTypeExtension: {
  266. leave: ({ name, directives, types }) => join(['extend union', name, join(directives, ' '), wrap('= ', join(types, ' | '))], ' '),
  267. },
  268. EnumTypeExtension: {
  269. leave: ({ name, directives, values }) => join(['extend enum', name, join(directives, ' '), block(values)], ' '),
  270. },
  271. InputObjectTypeExtension: {
  272. leave: ({ name, directives, fields }) => join(['extend input', name, join(directives, ' '), block(fields)], ' '),
  273. },
  274. };
  275. const printDocASTReducerWithComments = Object.keys(printDocASTReducer).reduce((prev, key) => ({
  276. ...prev,
  277. [key]: {
  278. leave: addDescription(printDocASTReducer[key].leave),
  279. },
  280. }), {});
  281. /**
  282. * Converts an AST into a string, using one set of reasonable
  283. * formatting rules.
  284. */
  285. function printWithComments(ast) {
  286. return (0, graphql_1.visit)(ast, printDocASTReducerWithComments);
  287. }
  288. exports.printWithComments = printWithComments;
  289. function isFieldDefinitionNode(node) {
  290. return node.kind === 'FieldDefinition';
  291. }
  292. // graphql < v13 and > v15 does not export getDescription
  293. function getDescription(node, options) {
  294. if (node.description != null) {
  295. return node.description.value;
  296. }
  297. if (options === null || options === void 0 ? void 0 : options.commentDescriptions) {
  298. return getComment(node);
  299. }
  300. }
  301. exports.getDescription = getDescription;
  302. function getComment(node) {
  303. const rawValue = getLeadingCommentBlock(node);
  304. if (rawValue !== undefined) {
  305. return dedentBlockStringValue(`\n${rawValue}`);
  306. }
  307. }
  308. exports.getComment = getComment;
  309. function getLeadingCommentBlock(node) {
  310. const loc = node.loc;
  311. if (!loc) {
  312. return;
  313. }
  314. const comments = [];
  315. let token = loc.startToken.prev;
  316. while (token != null &&
  317. token.kind === graphql_1.TokenKind.COMMENT &&
  318. token.next != null &&
  319. token.prev != null &&
  320. token.line + 1 === token.next.line &&
  321. token.line !== token.prev.line) {
  322. const value = String(token.value);
  323. comments.push(value);
  324. token = token.prev;
  325. }
  326. return comments.length > 0 ? comments.reverse().join('\n') : undefined;
  327. }
  328. exports.getLeadingCommentBlock = getLeadingCommentBlock;
  329. function dedentBlockStringValue(rawString) {
  330. // Expand a block string's raw value into independent lines.
  331. const lines = rawString.split(/\r\n|[\n\r]/g);
  332. // Remove common indentation from all lines but first.
  333. const commonIndent = getBlockStringIndentation(lines);
  334. if (commonIndent !== 0) {
  335. for (let i = 1; i < lines.length; i++) {
  336. lines[i] = lines[i].slice(commonIndent);
  337. }
  338. }
  339. // Remove leading and trailing blank lines.
  340. while (lines.length > 0 && isBlank(lines[0])) {
  341. lines.shift();
  342. }
  343. while (lines.length > 0 && isBlank(lines[lines.length - 1])) {
  344. lines.pop();
  345. }
  346. // Return a string of the lines joined with U+000A.
  347. return lines.join('\n');
  348. }
  349. exports.dedentBlockStringValue = dedentBlockStringValue;
  350. /**
  351. * @internal
  352. */
  353. function getBlockStringIndentation(lines) {
  354. let commonIndent = null;
  355. for (let i = 1; i < lines.length; i++) {
  356. const line = lines[i];
  357. const indent = leadingWhitespace(line);
  358. if (indent === line.length) {
  359. continue; // skip empty lines
  360. }
  361. if (commonIndent === null || indent < commonIndent) {
  362. commonIndent = indent;
  363. if (commonIndent === 0) {
  364. break;
  365. }
  366. }
  367. }
  368. return commonIndent === null ? 0 : commonIndent;
  369. }
  370. exports.getBlockStringIndentation = getBlockStringIndentation;
  371. function leadingWhitespace(str) {
  372. let i = 0;
  373. while (i < str.length && (str[i] === ' ' || str[i] === '\t')) {
  374. i++;
  375. }
  376. return i;
  377. }
  378. function isBlank(str) {
  379. return leadingWhitespace(str) === str.length;
  380. }