1 package formatter
2
3 import (
4 "fmt"
5 "io"
6 "sort"
7 "strings"
8
9 "github.com/vektah/gqlparser/v2/ast"
10 )
11
12 type Formatter interface {
13 FormatSchema(schema *ast.Schema)
14 FormatSchemaDocument(doc *ast.SchemaDocument)
15 FormatQueryDocument(doc *ast.QueryDocument)
16 }
17
18
19 type FormatterOption func(*formatter)
20
21
22
23 func WithIndent(indent string) FormatterOption {
24 return func(f *formatter) {
25 f.indent = indent
26 }
27 }
28
29
30 func WithComments() FormatterOption {
31 return func(f *formatter) {
32 f.emitComments = true
33 }
34 }
35
36 func NewFormatter(w io.Writer, options ...FormatterOption) Formatter {
37 f := &formatter{
38 indent: "\t",
39 writer: w,
40 }
41 for _, opt := range options {
42 opt(f)
43 }
44 return f
45 }
46
47 type formatter struct {
48 writer io.Writer
49
50 indent string
51 indentSize int
52 emitBuiltin bool
53 emitComments bool
54
55 padNext bool
56 lineHead bool
57 }
58
59 func (f *formatter) writeString(s string) {
60 _, _ = f.writer.Write([]byte(s))
61 }
62
63 func (f *formatter) writeIndent() *formatter {
64 if f.lineHead {
65 f.writeString(strings.Repeat(f.indent, f.indentSize))
66 }
67 f.lineHead = false
68 f.padNext = false
69
70 return f
71 }
72
73 func (f *formatter) WriteNewline() *formatter {
74 f.writeString("\n")
75 f.lineHead = true
76 f.padNext = false
77
78 return f
79 }
80
81 func (f *formatter) WriteWord(word string) *formatter {
82 if f.lineHead {
83 f.writeIndent()
84 }
85 if f.padNext {
86 f.writeString(" ")
87 }
88 f.writeString(strings.TrimSpace(word))
89 f.padNext = true
90
91 return f
92 }
93
94 func (f *formatter) WriteString(s string) *formatter {
95 if f.lineHead {
96 f.writeIndent()
97 }
98 if f.padNext {
99 f.writeString(" ")
100 }
101 f.writeString(s)
102 f.padNext = false
103
104 return f
105 }
106
107 func (f *formatter) WriteDescription(s string) *formatter {
108 if s == "" {
109 return f
110 }
111
112 f.WriteString(`"""`)
113 ss := strings.Split(s, "\n")
114 f.WriteNewline()
115 for _, s := range ss {
116 f.WriteString(s).WriteNewline()
117 }
118
119 f.WriteString(`"""`).WriteNewline()
120
121 return f
122 }
123
124 func (f *formatter) IncrementIndent() {
125 f.indentSize++
126 }
127
128 func (f *formatter) DecrementIndent() {
129 f.indentSize--
130 }
131
132 func (f *formatter) NoPadding() *formatter {
133 f.padNext = false
134
135 return f
136 }
137
138 func (f *formatter) NeedPadding() *formatter {
139 f.padNext = true
140
141 return f
142 }
143
144 func (f *formatter) FormatSchema(schema *ast.Schema) {
145 if schema == nil {
146 return
147 }
148
149 f.FormatCommentGroup(schema.Comment)
150
151 var inSchema bool
152 startSchema := func() {
153 if !inSchema {
154 inSchema = true
155
156 f.WriteWord("schema").WriteString("{").WriteNewline()
157 f.IncrementIndent()
158 }
159 }
160 if schema.Query != nil && schema.Query.Name != "Query" {
161 startSchema()
162 f.WriteWord("query").NoPadding().WriteString(":").NeedPadding()
163 f.WriteWord(schema.Query.Name).WriteNewline()
164 }
165 if schema.Mutation != nil && schema.Mutation.Name != "Mutation" {
166 startSchema()
167 f.WriteWord("mutation").NoPadding().WriteString(":").NeedPadding()
168 f.WriteWord(schema.Mutation.Name).WriteNewline()
169 }
170 if schema.Subscription != nil && schema.Subscription.Name != "Subscription" {
171 startSchema()
172 f.WriteWord("subscription").NoPadding().WriteString(":").NeedPadding()
173 f.WriteWord(schema.Subscription.Name).WriteNewline()
174 }
175 if inSchema {
176 f.DecrementIndent()
177 f.WriteString("}").WriteNewline()
178 }
179
180 directiveNames := make([]string, 0, len(schema.Directives))
181 for name := range schema.Directives {
182 directiveNames = append(directiveNames, name)
183 }
184 sort.Strings(directiveNames)
185 for _, name := range directiveNames {
186 f.FormatDirectiveDefinition(schema.Directives[name])
187 }
188
189 typeNames := make([]string, 0, len(schema.Types))
190 for name := range schema.Types {
191 typeNames = append(typeNames, name)
192 }
193 sort.Strings(typeNames)
194 for _, name := range typeNames {
195 f.FormatDefinition(schema.Types[name], false)
196 }
197 }
198
199 func (f *formatter) FormatSchemaDocument(doc *ast.SchemaDocument) {
200
201
202 if doc == nil {
203 return
204 }
205
206 f.FormatSchemaDefinitionList(doc.Schema, false)
207 f.FormatSchemaDefinitionList(doc.SchemaExtension, true)
208
209 f.FormatDirectiveDefinitionList(doc.Directives)
210
211 f.FormatDefinitionList(doc.Definitions, false)
212 f.FormatDefinitionList(doc.Extensions, true)
213
214
215 f.FormatCommentGroup(doc.Comment)
216 }
217
218 func (f *formatter) FormatQueryDocument(doc *ast.QueryDocument) {
219
220
221 if doc == nil {
222 return
223 }
224
225 f.FormatCommentGroup(doc.Comment)
226
227 f.FormatOperationList(doc.Operations)
228 f.FormatFragmentDefinitionList(doc.Fragments)
229 }
230
231 func (f *formatter) FormatSchemaDefinitionList(lists ast.SchemaDefinitionList, extension bool) {
232 if len(lists) == 0 {
233 return
234 }
235
236 var (
237 beforeDescComment = new(ast.CommentGroup)
238 afterDescComment = new(ast.CommentGroup)
239 endOfDefinitionComment = new(ast.CommentGroup)
240 description string
241 )
242
243 for _, def := range lists {
244 if def.BeforeDescriptionComment != nil {
245 beforeDescComment.List = append(beforeDescComment.List, def.BeforeDescriptionComment.List...)
246 }
247 if def.AfterDescriptionComment != nil {
248 afterDescComment.List = append(afterDescComment.List, def.AfterDescriptionComment.List...)
249 }
250 if def.EndOfDefinitionComment != nil {
251 endOfDefinitionComment.List = append(endOfDefinitionComment.List, def.EndOfDefinitionComment.List...)
252 }
253 description += def.Description
254 }
255
256 f.FormatCommentGroup(beforeDescComment)
257 f.WriteDescription(description)
258 f.FormatCommentGroup(afterDescComment)
259
260 if extension {
261 f.WriteWord("extend")
262 }
263 f.WriteWord("schema").WriteString("{").WriteNewline()
264 f.IncrementIndent()
265
266 for _, def := range lists {
267 f.FormatSchemaDefinition(def)
268 }
269
270 f.FormatCommentGroup(endOfDefinitionComment)
271
272 f.DecrementIndent()
273 f.WriteString("}").WriteNewline()
274 }
275
276 func (f *formatter) FormatSchemaDefinition(def *ast.SchemaDefinition) {
277 f.FormatDirectiveList(def.Directives)
278
279 f.FormatOperationTypeDefinitionList(def.OperationTypes)
280 }
281
282 func (f *formatter) FormatOperationTypeDefinitionList(lists ast.OperationTypeDefinitionList) {
283 for _, def := range lists {
284 f.FormatOperationTypeDefinition(def)
285 }
286 }
287
288 func (f *formatter) FormatOperationTypeDefinition(def *ast.OperationTypeDefinition) {
289 f.FormatCommentGroup(def.Comment)
290 f.WriteWord(string(def.Operation)).NoPadding().WriteString(":").NeedPadding()
291 f.WriteWord(def.Type)
292 f.WriteNewline()
293 }
294
295 func (f *formatter) FormatFieldList(fieldList ast.FieldList, endOfDefComment *ast.CommentGroup) {
296 if len(fieldList) == 0 {
297 return
298 }
299
300 f.WriteString("{").WriteNewline()
301 f.IncrementIndent()
302
303 for _, field := range fieldList {
304 f.FormatFieldDefinition(field)
305 }
306
307 f.FormatCommentGroup(endOfDefComment)
308
309 f.DecrementIndent()
310 f.WriteString("}")
311 }
312
313 func (f *formatter) FormatFieldDefinition(field *ast.FieldDefinition) {
314 if !f.emitBuiltin && strings.HasPrefix(field.Name, "__") {
315 return
316 }
317
318 f.FormatCommentGroup(field.BeforeDescriptionComment)
319
320 f.WriteDescription(field.Description)
321
322 f.FormatCommentGroup(field.AfterDescriptionComment)
323
324 f.WriteWord(field.Name).NoPadding()
325 f.FormatArgumentDefinitionList(field.Arguments)
326 f.NoPadding().WriteString(":").NeedPadding()
327 f.FormatType(field.Type)
328
329 if field.DefaultValue != nil {
330 f.WriteWord("=")
331 f.FormatValue(field.DefaultValue)
332 }
333
334 f.FormatDirectiveList(field.Directives)
335
336 f.WriteNewline()
337 }
338
339 func (f *formatter) FormatArgumentDefinitionList(lists ast.ArgumentDefinitionList) {
340 if len(lists) == 0 {
341 return
342 }
343
344 f.WriteString("(")
345 for idx, arg := range lists {
346 f.FormatArgumentDefinition(arg)
347
348
349
350 if idx != len(lists)-1 && arg.Description == "" {
351 f.NoPadding().WriteWord(",")
352 }
353 }
354 f.NoPadding().WriteString(")").NeedPadding()
355 }
356
357 func (f *formatter) FormatArgumentDefinition(def *ast.ArgumentDefinition) {
358 f.FormatCommentGroup(def.BeforeDescriptionComment)
359
360 if def.Description != "" {
361 f.WriteNewline().IncrementIndent()
362 f.WriteDescription(def.Description)
363 }
364
365 f.FormatCommentGroup(def.AfterDescriptionComment)
366
367 f.WriteWord(def.Name).NoPadding().WriteString(":").NeedPadding()
368 f.FormatType(def.Type)
369
370 if def.DefaultValue != nil {
371 f.WriteWord("=")
372 f.FormatValue(def.DefaultValue)
373 }
374
375 f.NeedPadding().FormatDirectiveList(def.Directives)
376
377 if def.Description != "" {
378 f.DecrementIndent()
379 f.WriteNewline()
380 }
381 }
382
383 func (f *formatter) FormatDirectiveLocation(location ast.DirectiveLocation) {
384 f.WriteWord(string(location))
385 }
386
387 func (f *formatter) FormatDirectiveDefinitionList(lists ast.DirectiveDefinitionList) {
388 if len(lists) == 0 {
389 return
390 }
391
392 for _, dec := range lists {
393 f.FormatDirectiveDefinition(dec)
394 }
395 }
396
397 func (f *formatter) FormatDirectiveDefinition(def *ast.DirectiveDefinition) {
398 if !f.emitBuiltin {
399 if def.Position.Src.BuiltIn {
400 return
401 }
402 }
403
404 f.FormatCommentGroup(def.BeforeDescriptionComment)
405
406 f.WriteDescription(def.Description)
407
408 f.FormatCommentGroup(def.AfterDescriptionComment)
409
410 f.WriteWord("directive").WriteString("@").WriteWord(def.Name)
411
412 if len(def.Arguments) != 0 {
413 f.NoPadding()
414 f.FormatArgumentDefinitionList(def.Arguments)
415 }
416
417 if def.IsRepeatable {
418 f.WriteWord("repeatable")
419 }
420
421 if len(def.Locations) != 0 {
422 f.WriteWord("on")
423
424 for idx, dirLoc := range def.Locations {
425 f.FormatDirectiveLocation(dirLoc)
426
427 if idx != len(def.Locations)-1 {
428 f.WriteWord("|")
429 }
430 }
431 }
432
433 f.WriteNewline()
434 }
435
436 func (f *formatter) FormatDefinitionList(lists ast.DefinitionList, extend bool) {
437 if len(lists) == 0 {
438 return
439 }
440
441 for _, dec := range lists {
442 f.FormatDefinition(dec, extend)
443 }
444 }
445
446 func (f *formatter) FormatDefinition(def *ast.Definition, extend bool) {
447 if !f.emitBuiltin && def.BuiltIn {
448 return
449 }
450
451 f.FormatCommentGroup(def.BeforeDescriptionComment)
452
453 f.WriteDescription(def.Description)
454
455 f.FormatCommentGroup(def.AfterDescriptionComment)
456
457 if extend {
458 f.WriteWord("extend")
459 }
460
461 switch def.Kind {
462 case ast.Scalar:
463 f.WriteWord("scalar").WriteWord(def.Name)
464
465 case ast.Object:
466 f.WriteWord("type").WriteWord(def.Name)
467
468 case ast.Interface:
469 f.WriteWord("interface").WriteWord(def.Name)
470
471 case ast.Union:
472 f.WriteWord("union").WriteWord(def.Name)
473
474 case ast.Enum:
475 f.WriteWord("enum").WriteWord(def.Name)
476
477 case ast.InputObject:
478 f.WriteWord("input").WriteWord(def.Name)
479 }
480
481 if len(def.Interfaces) != 0 {
482 f.WriteWord("implements").WriteWord(strings.Join(def.Interfaces, " & "))
483 }
484
485 f.FormatDirectiveList(def.Directives)
486
487 if len(def.Types) != 0 {
488 f.WriteWord("=").WriteWord(strings.Join(def.Types, " | "))
489 }
490
491 f.FormatFieldList(def.Fields, def.EndOfDefinitionComment)
492
493 f.FormatEnumValueList(def.EnumValues, def.EndOfDefinitionComment)
494
495 f.WriteNewline()
496 }
497
498 func (f *formatter) FormatEnumValueList(lists ast.EnumValueList, endOfDefComment *ast.CommentGroup) {
499 if len(lists) == 0 {
500 return
501 }
502
503 f.WriteString("{").WriteNewline()
504 f.IncrementIndent()
505
506 for _, v := range lists {
507 f.FormatEnumValueDefinition(v)
508 }
509
510 f.FormatCommentGroup(endOfDefComment)
511
512 f.DecrementIndent()
513 f.WriteString("}")
514 }
515
516 func (f *formatter) FormatEnumValueDefinition(def *ast.EnumValueDefinition) {
517 f.FormatCommentGroup(def.BeforeDescriptionComment)
518
519 f.WriteDescription(def.Description)
520
521 f.FormatCommentGroup(def.AfterDescriptionComment)
522
523 f.WriteWord(def.Name)
524 f.FormatDirectiveList(def.Directives)
525
526 f.WriteNewline()
527 }
528
529 func (f *formatter) FormatOperationList(lists ast.OperationList) {
530 for _, def := range lists {
531 f.FormatOperationDefinition(def)
532 }
533 }
534
535 func (f *formatter) FormatOperationDefinition(def *ast.OperationDefinition) {
536 f.FormatCommentGroup(def.Comment)
537
538 f.WriteWord(string(def.Operation))
539 if def.Name != "" {
540 f.WriteWord(def.Name)
541 }
542 f.FormatVariableDefinitionList(def.VariableDefinitions)
543 f.FormatDirectiveList(def.Directives)
544
545 if len(def.SelectionSet) != 0 {
546 f.FormatSelectionSet(def.SelectionSet)
547 f.WriteNewline()
548 }
549 }
550
551 func (f *formatter) FormatDirectiveList(lists ast.DirectiveList) {
552 if len(lists) == 0 {
553 return
554 }
555
556 for _, dir := range lists {
557 f.FormatDirective(dir)
558 }
559 }
560
561 func (f *formatter) FormatDirective(dir *ast.Directive) {
562 f.WriteString("@").WriteWord(dir.Name)
563 f.FormatArgumentList(dir.Arguments)
564 }
565
566 func (f *formatter) FormatArgumentList(lists ast.ArgumentList) {
567 if len(lists) == 0 {
568 return
569 }
570 f.NoPadding().WriteString("(")
571 for idx, arg := range lists {
572 f.FormatArgument(arg)
573
574 if idx != len(lists)-1 {
575 f.NoPadding().WriteWord(",")
576 }
577 }
578 f.WriteString(")").NeedPadding()
579 }
580
581 func (f *formatter) FormatArgument(arg *ast.Argument) {
582 f.FormatCommentGroup(arg.Comment)
583
584 f.WriteWord(arg.Name).NoPadding().WriteString(":").NeedPadding()
585 f.WriteString(arg.Value.String())
586 }
587
588 func (f *formatter) FormatFragmentDefinitionList(lists ast.FragmentDefinitionList) {
589 for _, def := range lists {
590 f.FormatFragmentDefinition(def)
591 }
592 }
593
594 func (f *formatter) FormatFragmentDefinition(def *ast.FragmentDefinition) {
595 f.FormatCommentGroup(def.Comment)
596
597 f.WriteWord("fragment").WriteWord(def.Name)
598 f.FormatVariableDefinitionList(def.VariableDefinition)
599 f.WriteWord("on").WriteWord(def.TypeCondition)
600 f.FormatDirectiveList(def.Directives)
601
602 if len(def.SelectionSet) != 0 {
603 f.FormatSelectionSet(def.SelectionSet)
604 f.WriteNewline()
605 }
606 }
607
608 func (f *formatter) FormatVariableDefinitionList(lists ast.VariableDefinitionList) {
609 if len(lists) == 0 {
610 return
611 }
612
613 f.WriteString("(")
614 for idx, def := range lists {
615 f.FormatVariableDefinition(def)
616
617 if idx != len(lists)-1 {
618 f.NoPadding().WriteWord(",")
619 }
620 }
621 f.NoPadding().WriteString(")").NeedPadding()
622 }
623
624 func (f *formatter) FormatVariableDefinition(def *ast.VariableDefinition) {
625 f.FormatCommentGroup(def.Comment)
626
627 f.WriteString("$").WriteWord(def.Variable).NoPadding().WriteString(":").NeedPadding()
628 f.FormatType(def.Type)
629
630 if def.DefaultValue != nil {
631 f.WriteWord("=")
632 f.FormatValue(def.DefaultValue)
633 }
634
635
636
637 }
638
639 func (f *formatter) FormatSelectionSet(sets ast.SelectionSet) {
640 if len(sets) == 0 {
641 return
642 }
643
644 f.WriteString("{").WriteNewline()
645 f.IncrementIndent()
646
647 for _, sel := range sets {
648 f.FormatSelection(sel)
649 }
650
651 f.DecrementIndent()
652 f.WriteString("}")
653 }
654
655 func (f *formatter) FormatSelection(selection ast.Selection) {
656 switch v := selection.(type) {
657 case *ast.Field:
658 f.FormatField(v)
659
660 case *ast.FragmentSpread:
661 f.FormatFragmentSpread(v)
662
663 case *ast.InlineFragment:
664 f.FormatInlineFragment(v)
665
666 default:
667 panic(fmt.Errorf("unknown Selection type: %T", selection))
668 }
669
670 f.WriteNewline()
671 }
672
673 func (f *formatter) FormatField(field *ast.Field) {
674 f.FormatCommentGroup(field.Comment)
675
676 if field.Alias != "" && field.Alias != field.Name {
677 f.WriteWord(field.Alias).NoPadding().WriteString(":").NeedPadding()
678 }
679 f.WriteWord(field.Name)
680
681 if len(field.Arguments) != 0 {
682 f.NoPadding()
683 f.FormatArgumentList(field.Arguments)
684 f.NeedPadding()
685 }
686
687 f.FormatDirectiveList(field.Directives)
688
689 f.FormatSelectionSet(field.SelectionSet)
690 }
691
692 func (f *formatter) FormatFragmentSpread(spread *ast.FragmentSpread) {
693 f.FormatCommentGroup(spread.Comment)
694
695 f.WriteWord("...").WriteWord(spread.Name)
696
697 f.FormatDirectiveList(spread.Directives)
698 }
699
700 func (f *formatter) FormatInlineFragment(inline *ast.InlineFragment) {
701 f.FormatCommentGroup(inline.Comment)
702
703 f.WriteWord("...")
704 if inline.TypeCondition != "" {
705 f.WriteWord("on").WriteWord(inline.TypeCondition)
706 }
707
708 f.FormatDirectiveList(inline.Directives)
709
710 f.FormatSelectionSet(inline.SelectionSet)
711 }
712
713 func (f *formatter) FormatType(t *ast.Type) {
714 f.WriteWord(t.String())
715 }
716
717 func (f *formatter) FormatValue(value *ast.Value) {
718 f.FormatCommentGroup(value.Comment)
719
720 f.WriteString(value.String())
721 }
722
723 func (f *formatter) FormatCommentGroup(group *ast.CommentGroup) {
724 if !f.emitComments || group == nil {
725 return
726 }
727 for _, comment := range group.List {
728 f.FormatComment(comment)
729 }
730 }
731
732 func (f *formatter) FormatComment(comment *ast.Comment) {
733 if !f.emitComments || comment == nil {
734 return
735 }
736 f.WriteString("#").WriteString(comment.Text()).WriteNewline()
737 }
738
View as plain text