From eb4536743c4dc507df32da1fe7a581052f7c438c Mon Sep 17 00:00:00 2001 From: vvakame Date: Wed, 6 Mar 2019 18:56:42 +0900 Subject: [PATCH] address comment --- codegen/generated!.gotpl | 6 +- codegen/templates/templates.go | 128 +++++++++++++++++----------- codegen/templates/templates_test.go | 102 +++++++++++++++++----- 3 files changed, 166 insertions(+), 70 deletions(-) diff --git a/codegen/generated!.gotpl b/codegen/generated!.gotpl index ca9e754929e..7c8afe436ea 100644 --- a/codegen/generated!.gotpl +++ b/codegen/generated!.gotpl @@ -45,7 +45,7 @@ type DirectiveRoot struct { type ComplexityRoot struct { {{ range $object := .Objects }} {{ if not $object.IsReserved -}} - {{ $object.Name|toCamel }} struct { + {{ $object.Name|go }} struct { {{ range $field := $object.Fields -}} {{ if not $field.IsReserved -}} {{ $field.GoFieldName }} {{ $field.ComplexitySignature }} @@ -87,7 +87,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in {{ range $field := $object.Fields }} {{ if not $field.IsReserved }} case "{{$object.Name}}.{{$field.GoFieldName}}": - if e.complexity.{{$object.Name|toCamel}}.{{$field.GoFieldName}} == nil { + if e.complexity.{{$object.Name|go}}.{{$field.GoFieldName}} == nil { break } {{ if $field.Args }} @@ -96,7 +96,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } {{ end }} - return e.complexity.{{$object.Name|toCamel}}.{{$field.GoFieldName}}(childComplexity{{if $field.Args}}, {{$field.ComplexityArgs}} {{end}}), true + return e.complexity.{{$object.Name|go}}.{{$field.GoFieldName}}(childComplexity{{if $field.Args}}, {{$field.ComplexityArgs}} {{end}}), true {{ end }} {{ end }} {{ end }} diff --git a/codegen/templates/templates.go b/codegen/templates/templates.go index 74085e0fd3f..fe51caaf5a4 100644 --- a/codegen/templates/templates.go +++ b/codegen/templates/templates.go @@ -129,7 +129,6 @@ func Funcs() template.FuncMap { "lcFirst": lcFirst, "quote": strconv.Quote, "rawQuote": rawQuote, - "toCamel": ToCamel, "dump": Dump, "ref": ref, "ts": TypeIdentifier, @@ -228,64 +227,97 @@ func Call(p *types.Func) string { return pkg + p.Name() } -func ToCamel(s string) string { - if s == "_" { - return "_" - } - buf := bytes.NewBuffer(make([]byte, 0, len(s))) - upper := true - lastWasUpper := false - var maxCommonInitialismsLen int - for word := range commonInitialisms { - if l := len(word); maxCommonInitialismsLen < l { - maxCommonInitialismsLen = l +func ToGo(name string) string { + runes := make([]rune, 0, len(name)) + + wordWalker(name, func(word string, hasCommonInitial bool) { + if !hasCommonInitial { + word = ucFirst(strings.ToLower(word)) } - } + runes = append(runes, []rune(word)...) + }) -outer: - for i, rs := 0, []rune(s); i < len(rs); i++ { - c := rs[i] - if isDelimiter(c) { - upper = true - continue + return string(runes) +} + +func ToGoPrivate(name string) string { + runes := make([]rune, 0, len(name)) + + first := true + wordWalker(name, func(word string, hasCommonInitial bool) { + if first { + word = strings.ToLower(word) + first = false + } else if !hasCommonInitial { + word = ucFirst(strings.ToLower(word)) } - if !lastWasUpper && unicode.IsUpper(c) { - tail := len(rs) - i - if maxCommonInitialismsLen < tail { - tail = maxCommonInitialismsLen + runes = append(runes, []rune(word)...) + }) + + return sanitizeKeywords(string(runes)) +} + +func wordWalker(str string, f func(word string, hasCommonInitial bool)) { + + skipRune := func(r rune) bool { + switch r { + case '-', '_': + return true + default: + return false + } + } + + runes := []rune(str) + w, i := 0, 0 // index of start of word, scan + hasCommonInitial := false + for i+1 <= len(runes) { + eow := false // whether we hit the end of a word + if i+1 == len(runes) { + eow = true + } else if skipRune(runes[i+1]) { + // underscore; shift the remainder forward over any run of underscores + eow = true + n := 1 + for i+n+1 < len(runes) && skipRune(runes[i+n+1]) { + n++ } - for j := tail; j != 0; j-- { - word := string(rs[i : i+j]) - if commonInitialisms[word] { - buf.WriteString(word) - i += j - 1 - upper = false - lastWasUpper = false // IDFoo will be IDFoo, not IDfoo - continue outer - } + + // Leave at most one underscore if the underscore is between two digits + if i+n+1 < len(runes) && unicode.IsDigit(runes[i]) && unicode.IsDigit(runes[i+n+1]) { + n-- } - upper = true + copy(runes[i+1:], runes[i+n+1:]) + runes = runes[:len(runes)-n] + } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { + // lower->non-lower + eow = true } + i++ - if upper { - buf.WriteRune(unicode.ToUpper(c)) - } else { - buf.WriteRune(unicode.ToLower(c)) + // [w,i) is a word. + word := string(runes[w:i]) + if !eow && commonInitialisms[word] && !unicode.IsLower(runes[i]) { + // through + // split IDFoo → ID, Foo + // but URLs → URLs + } else if !eow { + if commonInitialisms[word] { + hasCommonInitial = true + } + continue } - upper = false - lastWasUpper = unicode.IsUpper(c) - } - return buf.String() -} - -func ToGo(name string) string { - return lintName(ToCamel(name)) -} + if u := strings.ToUpper(word); commonInitialisms[u] { + hasCommonInitial = true + word = u + } -func ToGoPrivate(name string) string { - return lintName(sanitizeKeywords(lcFirst(ToCamel(name)))) + f(word, hasCommonInitial) + hasCommonInitial = false + w = i + } } var keywords = []string{ diff --git a/codegen/templates/templates_test.go b/codegen/templates/templates_test.go index 5debc545ab2..47700b4ef5c 100644 --- a/codegen/templates/templates_test.go +++ b/codegen/templates/templates_test.go @@ -6,25 +6,89 @@ import ( "github.com/stretchr/testify/require" ) -func TestToCamel(t *testing.T) { - require.Equal(t, "ToCamel", ToCamel("TO_CAMEL")) - require.Equal(t, "ToCamel", ToCamel("to_camel")) - require.Equal(t, "ToCamel", ToCamel("toCamel")) - require.Equal(t, "ToCamel", ToCamel("ToCamel")) - require.Equal(t, "ToCamel", ToCamel("to-camel")) - - require.Equal(t, "RelatedURLs", ToCamel("RelatedURLs")) - require.Equal(t, "ImageIDs", ToCamel("ImageIDs")) - require.Equal(t, "FooID", ToCamel("FooID")) - require.Equal(t, "IDFoo", ToCamel("IDFoo")) - require.Equal(t, "FooASCII", ToCamel("FooASCII")) - require.Equal(t, "ASCIIFoo", ToCamel("ASCIIFoo")) - require.Equal(t, "FooUTF8", ToCamel("FooUTF8")) - require.Equal(t, "UTF8Foo", ToCamel("UTF8Foo")) - - require.Equal(t, "A", ToCamel("A")) - require.Equal(t, "ID", ToCamel("ID")) - require.Equal(t, "", ToCamel("")) +func TestToGo(t *testing.T) { + require.Equal(t, "ToCamel", ToGo("TO_CAMEL")) + require.Equal(t, "ToCamel", ToGo("to_camel")) + require.Equal(t, "ToCamel", ToGo("toCamel")) + require.Equal(t, "ToCamel", ToGo("ToCamel")) + require.Equal(t, "ToCamel", ToGo("to-camel")) + + require.Equal(t, "RelatedURLs", ToGo("RelatedURLs")) + require.Equal(t, "ImageIDs", ToGo("ImageIDs")) + require.Equal(t, "FooID", ToGo("FooID")) + require.Equal(t, "IDFoo", ToGo("IDFoo")) + require.Equal(t, "FooASCII", ToGo("FooASCII")) + require.Equal(t, "ASCIIFoo", ToGo("ASCIIFoo")) + require.Equal(t, "FooUTF8", ToGo("FooUTF8")) + require.Equal(t, "UTF8Foo", ToGo("UTF8Foo")) + require.Equal(t, "JSONEncoding", ToGo("JSONEncoding")) + + require.Equal(t, "A", ToGo("A")) + require.Equal(t, "ID", ToGo("ID")) + require.Equal(t, "", ToGo("")) + + require.Equal(t, "RelatedUrls", ToGo("RelatedUrls")) +} + +func TestToGoPrivate(t *testing.T) { + require.Equal(t, "toCamel", ToGoPrivate("TO_CAMEL")) + require.Equal(t, "toCamel", ToGoPrivate("to_camel")) + require.Equal(t, "toCamel", ToGoPrivate("toCamel")) + require.Equal(t, "toCamel", ToGoPrivate("ToCamel")) + require.Equal(t, "toCamel", ToGoPrivate("to-camel")) + + require.Equal(t, "relatedURLs", ToGoPrivate("RelatedURLs")) + require.Equal(t, "imageIDs", ToGoPrivate("ImageIDs")) + require.Equal(t, "fooID", ToGoPrivate("FooID")) + require.Equal(t, "idFoo", ToGoPrivate("IDFoo")) + require.Equal(t, "fooASCII", ToGoPrivate("FooASCII")) + require.Equal(t, "asciiFoo", ToGoPrivate("ASCIIFoo")) + require.Equal(t, "fooUTF8", ToGoPrivate("FooUTF8")) + require.Equal(t, "utf8Foo", ToGoPrivate("UTF8Foo")) + require.Equal(t, "jsonEncoding", ToGoPrivate("JSONEncoding")) + + require.Equal(t, "rangeArg", ToGoPrivate("Range")) + + require.Equal(t, "a", ToGoPrivate("A")) + require.Equal(t, "id", ToGoPrivate("ID")) + require.Equal(t, "", ToGoPrivate("")) +} + +func Test_wordWalker(t *testing.T) { + + type Result struct { + Value string + HasCommonInitial bool + } + helper := func(str string) []*Result { + resultList := []*Result{} + wordWalker(str, func(word string, hasCommonInitial bool) { + resultList = append(resultList, &Result{word, hasCommonInitial}) + }) + return resultList + } + + require.Equal(t, []*Result{{Value: "TO"}, {Value: "CAMEL"}}, helper("TO_CAMEL")) + require.Equal(t, []*Result{{Value: "to"}, {Value: "camel"}}, helper("to_camel")) + require.Equal(t, []*Result{{Value: "to"}, {Value: "Camel"}}, helper("toCamel")) + require.Equal(t, []*Result{{Value: "To"}, {Value: "Camel"}}, helper("ToCamel")) + require.Equal(t, []*Result{{Value: "to"}, {Value: "camel"}}, helper("to-camel")) + + require.Equal(t, []*Result{{Value: "Related"}, {Value: "URLs", HasCommonInitial: true}}, helper("RelatedURLs")) + require.Equal(t, []*Result{{Value: "Image"}, {Value: "IDs", HasCommonInitial: true}}, helper("ImageIDs")) + require.Equal(t, []*Result{{Value: "Foo"}, {Value: "ID", HasCommonInitial: true}}, helper("FooID")) + require.Equal(t, []*Result{{Value: "ID", HasCommonInitial: true}, {Value: "Foo"}}, helper("IDFoo")) + require.Equal(t, []*Result{{Value: "Foo"}, {Value: "ASCII", HasCommonInitial: true}}, helper("FooASCII")) + require.Equal(t, []*Result{{Value: "ASCII", HasCommonInitial: true}, {Value: "Foo"}}, helper("ASCIIFoo")) + require.Equal(t, []*Result{{Value: "Foo"}, {Value: "UTF8", HasCommonInitial: true}}, helper("FooUTF8")) + require.Equal(t, []*Result{{Value: "UTF8", HasCommonInitial: true}, {Value: "Foo"}}, helper("UTF8Foo")) + + require.Equal(t, []*Result{{Value: "A"}}, helper("A")) + require.Equal(t, []*Result{{Value: "ID", HasCommonInitial: true}}, helper("ID")) + require.Equal(t, []*Result{{Value: "ID", HasCommonInitial: true}}, helper("id")) + require.Equal(t, []*Result{}, helper("")) + + require.Equal(t, []*Result{{Value: "Related"}, {Value: "Urls"}}, helper("RelatedUrls")) } func TestCenter(t *testing.T) {