diff --git a/language/printer/printer.go b/language/printer/printer.go index eb4d5ec1..eba872bb 100644 --- a/language/printer/printer.go +++ b/language/printer/printer.go @@ -70,6 +70,26 @@ func getMapValueString(m map[string]interface{}, key string) string { } return "" } +func getDescription(raw interface{}) string { + var desc string + + switch node := raw.(type) { + case ast.DescribableNode: + if sval := node.GetDescription(); sval != nil { + desc = sval.Value + } + case map[string]interface{}: + desc = getMapValueString(node, "Description.Value") + } + if desc != "" { + sep := "" + if strings.ContainsRune(desc, '\n') { + sep = "\n" + } + desc = join([]string{`"""`, desc, `"""`}, sep) + } + return desc +} func toSliceString(slice interface{}) []string { if slice == nil { @@ -506,6 +526,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ fmt.Sprintf("%v", node.Name), join(directives, " "), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -518,6 +541,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ name, join(directives, " "), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -539,6 +565,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), block(fields), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -555,6 +584,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), block(fields), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -569,7 +601,23 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ for _, directive := range node.Directives { directives = append(directives, fmt.Sprintf("%v", directive.Name)) } - str := name + wrap("(", join(args, ", "), ")") + ": " + ttype + wrap(" ", join(directives, " "), "") + hasArgDesc := false + for _, arg := range node.Arguments { + if arg.Description != nil && arg.Description.Value != "" { + hasArgDesc = true + break + } + } + var argsStr string + if hasArgDesc { + argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)") + } else { + argsStr = wrap("(", join(args, ", "), ")") + } + str := name + argsStr + ": " + ttype + wrap(" ", join(directives, " "), "") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("\n%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -579,7 +627,23 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ for _, directive := range getMapSliceValue(node, "Directives") { directives = append(directives, fmt.Sprintf("%v", directive)) } - str := name + wrap("(", join(args, ", "), ")") + ": " + ttype + wrap(" ", join(directives, " "), "") + hasArgDesc := false + for _, arg := range args { + if strings.HasPrefix(strings.TrimSpace(arg), `"""`) { + hasArgDesc = true + break + } + } + var argsStr string + if hasArgDesc { + argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)") + } else { + argsStr = wrap("(", join(args, ", "), ")") + } + str := name + argsStr + ": " + ttype + wrap(" ", join(directives, " "), "") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("\n%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -599,7 +663,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ wrap("= ", defaultValue, ""), join(directives, " "), }, " ") - + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("\n%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -614,6 +680,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ wrap("= ", defaultValue, ""), join(directives, " "), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("\n%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -633,6 +702,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), block(fields), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -647,6 +719,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), block(fields), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -666,6 +741,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), "= " + join(types, " | "), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -680,6 +758,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), "= " + join(types, " | "), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -699,6 +780,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), block(values), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -713,6 +797,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), block(values), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -729,6 +816,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ name, join(directives, " "), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("\n%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -740,6 +830,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ name, join(directives, " "), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("\n%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -759,6 +852,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), block(fields), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") @@ -773,6 +869,9 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ join(directives, " "), block(fields), }, " ") + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil @@ -793,15 +892,46 @@ var printDocASTReducer = map[string]visitor.VisitFunc{ "DirectiveDefinition": func(p visitor.VisitFuncParams) (string, interface{}) { switch node := p.Node.(type) { case *ast.DirectiveDefinition: - args := wrap("(", join(toSliceString(node.Arguments), ", "), ")") - str := fmt.Sprintf("directive @%v%v on %v", node.Name, args, join(toSliceString(node.Locations), " | ")) + args := toSliceString(node.Arguments) + hasArgDesc := false + for _, arg := range node.Arguments { + if arg.Description != nil && arg.Description.Value != "" { + hasArgDesc = true + break + } + } + var argsStr string + if hasArgDesc { + argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)") + } else { + argsStr = wrap("(", join(args, ", "), ")") + } + str := fmt.Sprintf("directive @%v%v on %v", node.Name, argsStr, join(toSliceString(node.Locations), " | ")) + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str case map[string]interface{}: name := getMapValueString(node, "Name") locations := toSliceString(getMapValue(node, "Locations")) args := toSliceString(getMapValue(node, "Arguments")) - argsStr := wrap("(", join(args, ", "), ")") + hasArgDesc := false + for _, arg := range args { + if strings.HasPrefix(strings.TrimSpace(arg), `"""`) { + hasArgDesc = true + break + } + } + var argsStr string + if hasArgDesc { + argsStr = wrap("(", indent("\n"+join(args, "\n")), "\n)") + } else { + argsStr = wrap("(", join(args, ", "), ")") + } str := fmt.Sprintf("directive @%v%v on %v", name, argsStr, join(locations, " | ")) + if desc := getDescription(node); desc != "" { + str = fmt.Sprintf("%s\n%s", desc, str) + } return visitor.ActionUpdate, str } return visitor.ActionNoChange, nil diff --git a/language/printer/schema_printer_test.go b/language/printer/schema_printer_test.go index df2b98ef..d080f551 100644 --- a/language/printer/schema_printer_test.go +++ b/language/printer/schema_printer_test.go @@ -124,3 +124,184 @@ directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) } } + +func TestSchemaPrinter_PrintsAllDescriptions(t *testing.T) { + b, err := ioutil.ReadFile("../../schema-all-descriptions.graphql") + if err != nil { + t.Fatalf("unable to load schema-all-descriptions.graphql") + } + + query := string(b) + astDoc := parse(t, query) + expected := `"""single line scalar description""" +scalar ScalarSingleLine + +""" +multi line + +scalar description +""" +scalar ScalarMultiLine + +"""single line object description""" +type ObjectSingleLine { + no_description: ID + + """single line field description""" + single_line(a: ID, b: ID, c: ID, d: ID): ID + + """ + multi line + + field description + """ + multi_line( + a: ID + + """single line argument description""" + b: ID + + """ + multi line + + field description + """ + c: ID + d: ID + ): ID +} + +""" +multi line + +object description +""" +type ObjectMultiLine { + foo: ID +} + +"""single line interface description""" +interface InterfaceSingleLine { + no_description: ID + + """single line field description""" + single_line(a: ID, b: ID, c: ID, d: ID): ID + + """ + multi line + + field description + """ + multi_line( + a: ID + + """single line argument description""" + b: ID + + """ + multi line + + argument description + """ + c: ID + d: ID + ): ID +} + +""" +multi line + +interface description +""" +interface InterfaceMultiLine { + foo: ID +} + +"""single line union description""" +union UnionSingleLine = String | Int | Float | ID + +""" +multi line + +union description +""" +union UnionSingleLine = String | Int | Float | ID + +"""single line enum description""" +enum EnumSingleLine { + no_description + + """single line enum description""" + single_line + + """ + multi line + + enum description + """ + multi_line + again_no_description +} + +""" +multi line + +enum description +""" +enum EnumMultiLine { + foo +} + +"""single line input description""" +input InputSingleLine { + a: ID + + """single line argument description""" + b: ID + + """ + multi line + + argument description + """ + c: ID + d: ID +} + +""" +multi line + +input description +""" +input InputMultiLine { + foo: ID +} + +"""single line directive description""" +directive @DirectiveSingleLine( + a: ID + + """single line argument description""" + b: ID + + """ + multi line + + argument description + """ + c: ID + d: ID +) on SCALAR + +""" +multi line + +directive description +""" +directive @DirectiveMultiLine on SCALAR +` + results := printer.Print(astDoc) + if !reflect.DeepEqual(expected, results) { + t.Fatalf("Unexpected result, Diff: %v", testutil.Diff(expected, results)) + } +} diff --git a/schema-all-descriptions.graphql b/schema-all-descriptions.graphql new file mode 100644 index 00000000..e30107f0 --- /dev/null +++ b/schema-all-descriptions.graphql @@ -0,0 +1,168 @@ +# File: schema-all-descriptions.graphql + +"""single line scalar description""" +scalar ScalarSingleLine + +""" +multi line + +scalar description +""" +scalar ScalarMultiLine + +"""single line object description""" +type ObjectSingleLine { + no_description: ID + + """single line field description""" + single_line(a: ID, b: ID, c: ID, d: ID): ID + + """ + multi line + + field description + """ + multi_line( + a: ID + + """single line argument description""" + b: ID + + """ + multi line + + field description + """ + c: ID + d: ID + ): ID +} + +""" +multi line + +object description +""" +type ObjectMultiLine { + foo: ID +} + +"""single line interface description""" +interface InterfaceSingleLine { + no_description: ID + + """single line field description""" + single_line(a: ID, b: ID, c: ID, d: ID): ID + + """ + multi line + + field description + """ + multi_line( + a: ID + + """single line argument description""" + b: ID + + """ + multi line + + argument description + """ + c: ID + d: ID + ): ID +} + +""" +multi line + +interface description +""" +interface InterfaceMultiLine { + foo: ID +} + +"""single line union description""" +union UnionSingleLine = String | Int | Float | ID + +""" +multi line + +union description +""" +union UnionSingleLine = String | Int | Float | ID + +"""single line enum description""" +enum EnumSingleLine { + no_description + + """single line enum description""" + single_line + + """ + multi line + + enum description + """ + multi_line + again_no_description +} + +""" +multi line + +enum description +""" +enum EnumMultiLine { + foo +} + +"""single line input description""" +input InputSingleLine { + a: ID + + """single line argument description""" + b: ID + + """ + multi line + + argument description + """ + c: ID + d: ID +} + +""" +multi line + +input description +""" +input InputMultiLine { + foo: ID +} + +"""single line directive description""" +directive @DirectiveSingleLine( + a: ID + + """single line argument description""" + b: ID + + """ + multi line + + argument description + """ + c: ID + d: ID +) on SCALAR + +""" +multi line + +directive description +""" +directive @DirectiveMultiLine on SCALAR