Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take care about commonInitialisms in ToGo #579

Merged
merged 4 commits into from
Mar 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions codegen/generated!.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down Expand Up @@ -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 }}
Expand All @@ -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 }}
Expand Down
188 changes: 92 additions & 96 deletions codegen/templates/templates.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@ func Funcs() template.FuncMap {
"lcFirst": lcFirst,
"quote": strconv.Quote,
"rawQuote": rawQuote,
"toCamel": ToCamel,
"dump": Dump,
"ref": ref,
"ts": TypeIdentifier,
Expand Down Expand Up @@ -228,41 +227,106 @@ func Call(p *types.Func) string {
return pkg + p.Name()
}

func ToCamel(s string) string {
if s == "_" {
return "_"
}
buffer := make([]rune, 0, len(s))
upper := true
lastWasUpper := false

for _, c := range s {
if isDelimiter(c) {
upper = true
continue
}
if !lastWasUpper && unicode.IsUpper(c) {
upper = true
func ToGo(name string) string {
runes := make([]rune, 0, len(name))

wordWalker(name, func(info *wordInfo) {
word := info.Word
if info.MatchCommonInitial {
word = strings.ToUpper(word)
} else if !info.HasCommonInitial {
word = ucFirst(strings.ToLower(word))
}
runes = append(runes, []rune(word)...)
})

if upper {
buffer = append(buffer, unicode.ToUpper(c))
} else {
buffer = append(buffer, unicode.ToLower(c))
return string(runes)
}

func ToGoPrivate(name string) string {
runes := make([]rune, 0, len(name))

first := true
wordWalker(name, func(info *wordInfo) {
word := info.Word
if first {
word = strings.ToLower(info.Word)
first = false
} else if info.MatchCommonInitial {
word = strings.ToUpper(word)
} else if !info.HasCommonInitial {
word = ucFirst(strings.ToLower(word))
}
upper = false
lastWasUpper = unicode.IsUpper(c)
}
runes = append(runes, []rune(word)...)
})

return string(buffer)
return sanitizeKeywords(string(runes))
}

func ToGo(name string) string {
return lintName(ToCamel(name))
type wordInfo struct {
Word string
MatchCommonInitial bool
HasCommonInitial bool
}

func ToGoPrivate(name string) string {
return lintName(sanitizeKeywords(lcFirst(ToCamel(name))))
// This function is based on the following code.
// https://github.com/golang/lint/blob/06c8688daad7faa9da5a0c2f163a3d14aac986ca/lint.go#L679
func wordWalker(str string, f func(*wordInfo)) {
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 isDelimiter(runes[i+1]) {
// underscore; shift the remainder forward over any run of underscores
eow = true
n := 1
for i+n+1 < len(runes) && isDelimiter(runes[i+n+1]) {
n++
}

// 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--
}

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++

// [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
}

matchCommonInitial := false
if commonInitialisms[strings.ToUpper(word)] {
hasCommonInitial = true
matchCommonInitial = true
}

f(&wordInfo{
Word: word,
MatchCommonInitial: matchCommonInitial,
HasCommonInitial: hasCommonInitial,
})
hasCommonInitial = false
w = i
}
}

var keywords = []string{
Expand Down Expand Up @@ -304,74 +368,6 @@ func sanitizeKeywords(name string) string {
return name
}

// copy from https://github.com/golang/lint/blob/06c8688daad7faa9da5a0c2f163a3d14aac986ca/lint.go#L679
func lintName(name string) string {
// Fast path for simple cases: "_" and all lowercase.
if name == "_" {
return name
}
allLower := true
for _, r := range name {
if !unicode.IsLower(r) {
allLower = false
break
}
}
if allLower {
return name
}

// Split camelCase at any lower->upper transition, and split on underscores.
// Check each word for common initialisms.
runes := []rune(name)
w, i := 0, 0 // index of start of word, scan
for i+1 <= len(runes) {
eow := false // whether we hit the end of a word
if i+1 == len(runes) {
eow = true
} else if runes[i+1] == '_' {
// underscore; shift the remainder forward over any run of underscores
eow = true
n := 1
for i+n+1 < len(runes) && runes[i+n+1] == '_' {
n++
}

// 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--
}

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 !eow {
continue
}

// [w,i) is a word.
word := string(runes[w:i])
if u := strings.ToUpper(word); commonInitialisms[u] {
// Keep consistent case, which is lowercase only at the start.
if w == 0 && unicode.IsLower(runes[w]) {
u = strings.ToLower(u)
}
// All the common initialisms are ASCII,
// so we can replace the bytes exactly.
copy(runes[w:], []rune(u))
} else if w > 0 && strings.ToLower(word) == word {
// already all lowercase, and not the first word, so uppercase the first character.
runes[w] = unicode.ToUpper(runes[w])
}
w = i
}
return string(runes)
}

// commonInitialisms is a set of common initialisms.
// Only add entries that are highly unlikely to be non-initialisms.
// For instance, "ID" is fine (Freudian code is rare), but "AND" is not.
Expand Down
87 changes: 81 additions & 6 deletions codegen/templates/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,87 @@ import (
"github.com/stretchr/testify/require"
)

func TestToUpper(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"))
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, "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, "id", ToGoPrivate("id"))
require.Equal(t, "", ToGoPrivate(""))
}

func Test_wordWalker(t *testing.T) {

helper := func(str string) []*wordInfo {
resultList := []*wordInfo{}
wordWalker(str, func(info *wordInfo) {
resultList = append(resultList, info)
})
return resultList
}

require.Equal(t, []*wordInfo{{Word: "TO"}, {Word: "CAMEL"}}, helper("TO_CAMEL"))
require.Equal(t, []*wordInfo{{Word: "to"}, {Word: "camel"}}, helper("to_camel"))
require.Equal(t, []*wordInfo{{Word: "to"}, {Word: "Camel"}}, helper("toCamel"))
require.Equal(t, []*wordInfo{{Word: "To"}, {Word: "Camel"}}, helper("ToCamel"))
require.Equal(t, []*wordInfo{{Word: "to"}, {Word: "camel"}}, helper("to-camel"))

require.Equal(t, []*wordInfo{{Word: "Related"}, {Word: "URLs", HasCommonInitial: true}}, helper("RelatedURLs"))
require.Equal(t, []*wordInfo{{Word: "Image"}, {Word: "IDs", HasCommonInitial: true}}, helper("ImageIDs"))
require.Equal(t, []*wordInfo{{Word: "Foo"}, {Word: "ID", HasCommonInitial: true, MatchCommonInitial: true}}, helper("FooID"))
require.Equal(t, []*wordInfo{{Word: "ID", HasCommonInitial: true, MatchCommonInitial: true}, {Word: "Foo"}}, helper("IDFoo"))
require.Equal(t, []*wordInfo{{Word: "Foo"}, {Word: "ASCII", HasCommonInitial: true, MatchCommonInitial: true}}, helper("FooASCII"))
require.Equal(t, []*wordInfo{{Word: "ASCII", HasCommonInitial: true, MatchCommonInitial: true}, {Word: "Foo"}}, helper("ASCIIFoo"))
require.Equal(t, []*wordInfo{{Word: "Foo"}, {Word: "UTF8", HasCommonInitial: true, MatchCommonInitial: true}}, helper("FooUTF8"))
require.Equal(t, []*wordInfo{{Word: "UTF8", HasCommonInitial: true, MatchCommonInitial: true}, {Word: "Foo"}}, helper("UTF8Foo"))

require.Equal(t, []*wordInfo{{Word: "A"}}, helper("A"))
require.Equal(t, []*wordInfo{{Word: "ID", HasCommonInitial: true, MatchCommonInitial: true}}, helper("ID"))
require.Equal(t, []*wordInfo{{Word: "id", HasCommonInitial: true, MatchCommonInitial: true}}, helper("id"))
require.Equal(t, []*wordInfo{}, helper(""))

require.Equal(t, []*wordInfo{{Word: "Related"}, {Word: "Urls"}}, helper("RelatedUrls"))
}

func TestCenter(t *testing.T) {
Expand Down