UNPKG

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