From dffc14f4141bfb68e3e4ae4346368e6e2b54b2d0 Mon Sep 17 00:00:00 2001 From: woonki Date: Tue, 2 Apr 2024 17:48:45 +0900 Subject: [PATCH] Fix parse and write the default values in the input value definition (#53) * add test * fix parse and write the default value in arg definitions * add test * fix handling default value in input object * rename Arg.TypeExt -> Arg.DefaultValue * fix handling multiple default values in argument definitions * add test * fix handling multiple default values in input object * printing directives in order by name * clean up --- lib/graphql.go | 35 +++++++++++---------- lib/lex.go | 35 +++++++++++++++++++++ lib/parse.go | 32 +++++++++++++++---- lib/schema.go | 26 +++++++++++++++ lib/write.go | 42 ++++++++++++++++++++++--- test/default_value/generated.graphql | 22 +++++++++++++ test/default_value/schema/a.graphql | 19 +++++++++++ test/object_extension/generated.graphql | 2 +- 8 files changed, 185 insertions(+), 28 deletions(-) create mode 100644 test/default_value/generated.graphql create mode 100644 test/default_value/schema/a.graphql diff --git a/lib/graphql.go b/lib/graphql.go index 4b09fbe..fbfd473 100644 --- a/lib/graphql.go +++ b/lib/graphql.go @@ -64,27 +64,28 @@ type Type struct { } type Arg struct { - Name string - Type string - TypeExt *string // in case of enum e.g. admin(role: Role = ADMIN): Admin! - Null bool - IsList bool - IsListNull bool - Directives []*Directive - Descriptions *[]string + Name string + Type string + DefaultValues *[]string // in case of default values e.g. admin(role: Role = ADMIN): Admin! + Null bool + IsList bool + IsListNull bool + Directives []*Directive + Descriptions *[]string } type Field struct { BaseFileInfo - Name string - Args []*Arg - Type string - Null bool - IsList bool - IsListNull bool - Directives []*Directive - Descriptions *[]string - Comments *[]string + Name string + Args []*Arg + Type string + Null bool + IsList bool + IsListNull bool + DefaultValues *[]string + Directives []*Directive + Descriptions *[]string + Comments *[]string } type Scalar struct { diff --git a/lib/lex.go b/lib/lex.go index 054290e..6374313 100644 --- a/lib/lex.go +++ b/lib/lex.go @@ -489,3 +489,38 @@ func (l *lexer) consumeIdent(includings ...tokenType) (*token, *[]string) { return tok, &comments } } + +/* +The `consumeIdent` function treats tokString as a comment. +If you want to scan the tokString for ident, you can use the consumeIdentInclString function. + +e.g. scan "woonki" for ident here + +test(name: String = "woonki"): Bool +*/ +func (l *lexer) consumeIdentInclString(includings ...tokenType) (*token, *[]string) { + comments := []string{} + includings = append(includings, tokString) + + for { + tok := l.next() + if tok.typ == tokSingleLineComment || tok.typ == tokBlockString { + comments = append(comments, tok.String()) + continue + } + + isIncluded := false + for _, incl := range includings { + if tok.typ == incl { + isIncluded = true + break + } + } + + if tok.typ != tokIdent && !isIncluded { + errorf(`%s:%d:%d: unexpected "%s"`, l.filename, l.line, l.col, tok.String()) + } + l.skipSpace() + return tok, &comments + } +} diff --git a/lib/parse.go b/lib/parse.go index 7731913..f840ea5 100644 --- a/lib/parse.go +++ b/lib/parse.go @@ -64,16 +64,27 @@ func (p *Parser) parseArgs() []*Arg { } else { arg.IsListNull = true } - } else { - typ, _ := p.lex.consumeIdent() - arg.Type = typ.String() if p.lex.peek() == '=' { p.lex.consumeToken(tokEqual) - tex, _ := p.lex.consumeIdent() - te := tex.String() - arg.TypeExt = &te + defaultValues := []string{} + if p.lex.peek() == '[' { + p.lex.consumeIdent(tokLBracket) + for p.lex.peek() != ']' { + tex, _ := p.lex.consumeIdentInclString(tokNumber) + te := tex.String() + defaultValues = append(defaultValues, te) + if p.lex.peek() == ',' { + p.lex.consumeToken(tokComma) + } + } + p.lex.consumeIdent(tokRBracket) + arg.DefaultValues = &defaultValues + } } + } else { + typ, _ := p.lex.consumeIdent() + arg.Type = typ.String() if p.lex.peek() == '!' { arg.Null = false @@ -81,6 +92,15 @@ func (p *Parser) parseArgs() []*Arg { } else { arg.Null = true } + + if p.lex.peek() == '=' { + p.lex.consumeToken(tokEqual) + tex, _ := p.lex.consumeIdentInclString(tokNumber) + te := tex.String() + defaultValues := []string{} + defaultValues = append(defaultValues, te) + arg.DefaultValues = &defaultValues + } } arg.Directives = p.parseDirectives() diff --git a/lib/schema.go b/lib/schema.go index 4619c5e..47d929a 100644 --- a/lib/schema.go +++ b/lib/schema.go @@ -319,6 +319,23 @@ func (s *Schema) Parse(p *Parser) { } else { fd.IsListNull = true } + if p.lex.peek() == '=' { + p.lex.consumeToken(tokEqual) + defaultValues := []string{} + if p.lex.peek() == '[' { + p.lex.consumeIdent(tokLBracket) + for p.lex.peek() != ']' { + tex, _ := p.lex.consumeIdentInclString(tokNumber) + te := tex.String() + defaultValues = append(defaultValues, te) + if p.lex.peek() == ',' { + p.lex.consumeToken(tokComma) + } + } + p.lex.consumeIdent(tokRBracket) + fd.DefaultValues = &defaultValues + } + } } else { fd.IsList = false fd.IsListNull = false @@ -330,6 +347,15 @@ func (s *Schema) Parse(p *Parser) { } else { fd.Null = true } + + if p.lex.peek() == '=' { + p.lex.consumeToken(tokEqual) + tex, _ := p.lex.consumeIdentInclString(tokNumber) + te := tex.String() + defaultValues := []string{} + defaultValues = append(defaultValues, te) + fd.DefaultValues = &defaultValues + } } fd.Directives = p.parseDirectives() diff --git a/lib/write.go b/lib/write.go index e95b86b..b81c2da 100644 --- a/lib/write.go +++ b/lib/write.go @@ -1,6 +1,7 @@ package lib import ( + "sort" "strings" ) @@ -237,7 +238,17 @@ func (ms *MergedSchema) WriteSchema(s *Schema) string { if p.IsList && !p.IsListNull { ms.buf.WriteString("!") } - + if p.DefaultValues != nil { + if p.IsList { + ms.buf.WriteString(" = ") + ms.buf.WriteString("[") + ms.stitchDefaultValues(p.DefaultValues) + ms.buf.WriteString("]") + } else { + ms.buf.WriteString(" = ") + ms.stitchDefaultValues(p.DefaultValues) + } + } ms.stitchDirectives(p.Directives) ms.buf.WriteString("\n") @@ -278,14 +289,23 @@ func (ms *MergedSchema) stitchArgument(a *Arg, l int, i int) { if !a.IsListNull { ms.buf.WriteString("!") } + if a.DefaultValues != nil { + ms.buf.WriteString(" = ") + ms.buf.WriteString("[") + ms.stitchDefaultValues(a.DefaultValues) + ms.buf.WriteString("]") + } + ms.stitchDirectives(a.Directives) } else { ms.buf.WriteString(a.Type) - if a.TypeExt != nil { - ms.buf.WriteString(" = " + *a.TypeExt) - } if !a.Null { ms.buf.WriteString("!") } + if a.DefaultValues != nil { + ms.buf.WriteString(" = ") + ms.stitchDefaultValues(a.DefaultValues) + } + ms.stitchDirectives(a.Directives) } if l <= 2 && i != l-1 { @@ -297,6 +317,9 @@ func (ms *MergedSchema) stitchArgument(a *Arg, l int, i int) { } func (ms *MergedSchema) stitchDirectives(a []*Directive) { + sort.SliceStable(a, func(i, j int) bool { + return a[i].Name > a[j].Name + }) if l := len(a); l > 0 { for _, a := range a { ms.buf.WriteString(" @" + a.Name) @@ -311,6 +334,17 @@ func (ms *MergedSchema) stitchDirectives(a []*Directive) { } } +func (ms *MergedSchema) stitchDefaultValues(a *[]string) { + if l := len(*a); l > 0 { + for i, v := range *a { + ms.buf.WriteString(v) + if i < l-1 { + ms.buf.WriteString(", ") + } + } + } +} + func (ms *MergedSchema) stitchDirectiveArgument(a *DirectiveArg, l int, i int) { if l > 2 { ms.addIndent(2) diff --git a/test/default_value/generated.graphql b/test/default_value/generated.graphql new file mode 100644 index 0000000..e947b58 --- /dev/null +++ b/test/default_value/generated.graphql @@ -0,0 +1,22 @@ +type Query { + test1(X: Int = 20): Int + test2(X: Int! = 20): Int + test3(X: User = ADMIN): Int + test4(X: String! = "user"): Int + test5(X: String = "user" @deprecated): Int + test6(X: String = "user" @deprecated, Y: String! = "operator" @unique): Int + test7(X: [Int] = [20]): Int + test8(X: [Int] = [20, 30]): Int + test9(X: [User!]! = [ADMIN]): Int + test10(X: [String!] = ["user"]): Int + test11(X: [String] = ["user", "user1"] @deprecated): Int + test12(X: [String] = ["user", "user1"] @deprecated, Y: String! = "operator" @unique): Int +} + + + + +input User { + name: String! = "woonki" + nicknames: [String!]! = ["mununki", "arnold"] +} diff --git a/test/default_value/schema/a.graphql b/test/default_value/schema/a.graphql new file mode 100644 index 0000000..8bb862a --- /dev/null +++ b/test/default_value/schema/a.graphql @@ -0,0 +1,19 @@ +type Query { + test1(X: Int = 20): Int + test2(X: Int! = 20): Int + test3(X: User = ADMIN): Int + test4(X: String! = "user"): Int + test5(X: String = "user" @deprecated): Int + test6(X: String = "user" @deprecated, Y: String! = "operator" @unique): Int + test7(X: [Int] = [20]): Int + test8(X: [Int] = [20, 30]): Int + test9(X: [User!]! = [ADMIN]): Int + test10(X: [String!] = ["user"]): Int + test11(X: [String] = ["user", "user1"] @deprecated): Int + test12(X: [String] = ["user", "user1"] @deprecated, Y: String! = "operator" @unique): Int +} + +input User { + name: String! = "woonki" + nicknames: [String!]! = ["mununki", "arnold"] +} diff --git a/test/object_extension/generated.graphql b/test/object_extension/generated.graphql index df1238f..5a57a73 100644 --- a/test/object_extension/generated.graphql +++ b/test/object_extension/generated.graphql @@ -1,4 +1,4 @@ -type Person implements Node @talkable @walkable { +type Person implements Node @walkable @talkable { id: ID! createTime: Time! updateTime: Time!