UNPKG

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