Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion rules/aep0004/resource_name_components_alternate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"github.com/jhump/protoreflect/desc"
)

var identifierRegexp = regexp.MustCompile("^{[a-z][_a-z0-9]*[a-z0-9]}$")
var identifierRegexp = regexp.MustCompile("^{[a-z][-_a-z0-9]*[a-z0-9]}$")

var resourceNameComponentsAlternate = &lint.MessageRule{
Name: lint.NewRuleName(4, "resource-name-components-alternate"),
Expand Down
1 change: 1 addition & 0 deletions rules/aep0004/resource_name_components_alternate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func TestResourceNameComponentsAlternate(t *testing.T) {
problems testutils.Problems
}{
{"Valid", "author/{author}/books/{book}", testutils.Problems{}},
{"Valid", "publishers/{publisher}/books/{book}/editions/{book-edition}", testutils.Problems{}},
{"ValidSingleton", "user/{user}/config", testutils.Problems{}},
{"InvalidDoubleCollection", "author/books/{book}", testutils.Problems{{Message: "must alternate"}}},
{"InvalidDoubleIdentifier", "books/{author}/{book}", testutils.Problems{{Message: "must alternate"}}},
Expand Down
2 changes: 1 addition & 1 deletion rules/aep0004/resource_plural.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ var resourcePlural = &lint.MessageRule{
r := utils.GetResource(m)
l := locations.MessageResource(m)
p := r.GetPlural()
pLower := utils.ToLowerCamelCase(p)
pLower := utils.ToKebobCase(p)
if p == "" {
return []lint.Problem{{
Message: "Resources should declare plural.",
Expand Down
2 changes: 1 addition & 1 deletion rules/aep0004/resource_plural_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestResourcePlural(t *testing.T) {
}{
{
"Valid",
`plural: "bookShelves"`,
`plural: "book-shelves"`,
nil,
},
{
Expand Down
2 changes: 1 addition & 1 deletion rules/aep0004/resource_singular.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ var resourceSingular = &lint.MessageRule{
l := locations.MessageResource(m)
s := r.GetSingular()
_, typeName, ok := utils.SplitResourceTypeName(r.GetType())
lowerTypeName := utils.ToLowerCamelCase(typeName)
lowerTypeName := utils.ToKebobCase(typeName)
if s == "" {
return []lint.Problem{{
Message: fmt.Sprintf("Resources should declare singular: %q", lowerTypeName),
Expand Down
6 changes: 3 additions & 3 deletions rules/aep0004/resource_type_name.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ var resourceTypeName = &lint.MessageRule{
LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
resource := utils.GetResource(m)
_, typeName, ok := utils.SplitResourceTypeName(resource.GetType())
upperTypeName := utils.ToUpperCamelCase(typeName)
kebob_case := utils.ToKebobCase(typeName)
if !ok {
return []lint.Problem{{
Message: "Resource type names must be of the form {Service Name}/{Type}.",
Descriptor: m,
Location: locations.MessageResource(m),
}}
}
if upperTypeName != typeName {
if kebob_case != typeName {
return []lint.Problem{{
Message: fmt.Sprintf("Type must be UpperCamelCase with alphanumeric characters: %q", upperTypeName),
Message: fmt.Sprintf("Type must be kebob-case with alphanumeric characters: %q", kebob_case),
Descriptor: m,
Location: locations.MessageResource(m),
}}
Expand Down
15 changes: 8 additions & 7 deletions rules/aep0004/resource_type_name_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,16 @@ func TestResourceTypeName(t *testing.T) {
TypeName string
problems testutils.Problems
}{
{"Valid", "library.googleapis.com/Book", testutils.Problems{}},
{"Valid", "library.googleapis.com/book", testutils.Problems{}},
{"InvalidTooMany", "library.googleapis.com/shelf/Book", testutils.Problems{{Message: "{Service Name}/{Type}"}}},
{"InvalidNotEnough", "library.googleapis.com~Book", testutils.Problems{{Message: "{Service Name}/{Type}"}}},
{"InvalidWithUnicode", "library.googleapis.com/BoØkLibre", testutils.Problems{{Message: `Type must be UpperCamelCase`}}},
{"InvalidLowerCamelCase", "library.googleapis.com/bookLoan", testutils.Problems{{Message: `Type must be UpperCamelCase with alphanumeric characters: "BookLoan"`}}},
{"InvalidTypeNotAlphaNumeric", "library.googleapis.com/Book.:3", testutils.Problems{{Message: `Type must be UpperCamelCase with alphanumeric characters: "Book3"`}}},
{"InvalidTypeContainsEmoji", "library.googleapis.com/Book♥️", testutils.Problems{{Message: `Type must be UpperCamelCase with alphanumeric characters: "Book"`}}},
{"InvalidTypeContainsDashes", "library.googleapis.com/Book-Shelf️", testutils.Problems{{Message: `Type must be UpperCamelCase with alphanumeric characters: "BookShelf"`}}},
{"InvalidTypeContainsUnderscore", "library.googleapis.com/Book_Shelf️", testutils.Problems{{Message: `Type must be UpperCamelCase with alphanumeric characters: "BookShelf"`}}},
{"InvalidWithUnicode", "library.googleapis.com/BoØkLibre", testutils.Problems{{Message: `Type must be kebob-case`}}},
{"InvalidLowerCamelCase", "library.googleapis.com/bookLoan", testutils.Problems{{Message: `Type must be kebob-case with alphanumeric characters: "book-loan"`}}},
{"ValidLowerCamelCase", "library.googleapis.com/book-loan", testutils.Problems{}},
{"InvalidTypeNotAlphaNumeric", "library.googleapis.com/Book.:3", testutils.Problems{{Message: `Type must be kebob-case with alphanumeric characters: "book-:3"`}}},
{"InvalidTypeContainsEmoji", "library.googleapis.com/Book♥️", testutils.Problems{{Message: `Type must be kebob-case with alphanumeric characters: "book♥️"`}}},
{"InvalidTypeContainsDashes", "library.googleapis.com/Book-Shelf️", testutils.Problems{{Message: `Type must be kebob-case with alphanumeric characters: "book--she`}}},
{"InvalidTypeContainsUnderscore", "library.googleapis.com/Book_Shelf️", testutils.Problems{{Message: `Type must be kebob-case with alphanumeric characters: "book--she`}}},
} {
t.Run(test.name, func(t *testing.T) {
f := testutils.ParseProto3Tmpl(t, `
Expand Down
51 changes: 12 additions & 39 deletions rules/internal/utils/casing.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,54 +14,27 @@

package utils

// ToUpperCamelCase returns the UpperCamelCase of a string, including removing
// delimiters (_,-,., ) and using them to denote a new word.
func ToUpperCamelCase(s string) string {
return toCamelCase(s, true, false)
}

// ToLowerCamelCase returns the lowerCamelCase of a string, including removing
// delimiters (_,-,., ) and using them to denote a new word.
func ToLowerCamelCase(s string) string {
return toCamelCase(s, false, true)
}

func toCamelCase(s string, makeNextUpper bool, makeNextLower bool) string {
// ToKebobCase returns the kebob-case of a word (book-edition).
func ToKebobCase(s string) string {
asLower := make([]rune, 0, len(s))
for _, r := range s {
if isLower(r) {
if makeNextUpper {
r = r & '_' // make uppercase
}
asLower = append(asLower, r)
} else if isUpper(r) {
if makeNextLower {
r = r | ' ' // make lowercase
for i, r := range s {
if isUpper(r) {
r = r | ' ' // make lowercase

// Only insert hypen after first word.
if i != 0 {
asLower = append(asLower, '-')
}
asLower = append(asLower, r)
} else if isNumber(r) {
} else if r == '-' || r == '_' || r == ' ' || r == '.' {
asLower = append(asLower, '-')
} else {
asLower = append(asLower, r)
}
makeNextUpper = false
makeNextLower = false

if r == '-' || r == '_' || r == ' ' || r == '.' {
// handle snake case scenarios, which generally indicates
// a delimited word.
makeNextUpper = true
}
}
return string(asLower)
}

func isUpper(r rune) bool {
return ('A' <= r && r <= 'Z')
}

func isNumber(r rune) bool {
return ('0' <= r && r <= '9')
}

func isLower(r rune) bool {
return ('a' <= r && r <= 'z')
}
83 changes: 11 additions & 72 deletions rules/internal/utils/casing_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,67 +16,6 @@ package utils

import "testing"

func TestToLowerCamelCase(t *testing.T) {
for _, test := range []struct {
name string
input string
want string
}{
{
name: "OneWord",
input: "Foo",
want: "foo",
},
{
name: "OneWordNoop",
input: "foo",
want: "foo",
},
{
name: "TwoWords",
input: "bookShelf",
want: "bookShelf",
},
{
name: "WithDash",
input: "book-shelf",
want: "bookShelf",
},
{
name: "WithNumbers",
input: "universe42love",
want: "universe42love",
},
{
name: "WithUnderscore",
input: "book_shelf",
want: "bookShelf",
},
{
name: "WithUnderscore",
input: "book_shelf",
want: "bookShelf",
},
{
name: "WithSpaces",
input: "book shelf",
want: "bookShelf",
},
{
name: "WithPeriods",
input: "book.shelf",
want: "bookShelf",
},
} {
t.Run(test.name, func(t *testing.T) {
got := ToLowerCamelCase(test.input)
if got != test.want {
t.Errorf("ToLowerCamelCase(%q) = %q, got %q", test.input, test.want, got)
}
})
}
}

func TestToUpperCamelCase(t *testing.T) {
for _, test := range []struct {
name string
Expand All @@ -86,53 +25,53 @@ func TestToUpperCamelCase(t *testing.T) {
{
name: "OneWord",
input: "foo",
want: "Foo",
want: "foo",
},
{
name: "OneWordNoop",
input: "Foo",
want: "Foo",
want: "foo",
},
{
name: "TwoWords",
input: "bookShelf",
want: "BookShelf",
want: "book-shelf",
},
{
name: "WithDash",
input: "book-shelf",
want: "BookShelf",
want: "book-shelf",
},
{
name: "WithNumbers",
input: "universe42love",
want: "Universe42love",
want: "universe42love",
},
{
name: "WithUnderscore",
input: "Book_shelf",
want: "BookShelf",
want: "book-shelf",
},
{
name: "WithUnderscore",
input: "Book_shelf",
want: "BookShelf",
want: "book-shelf",
},
{
name: "WithSpaces",
input: "Book shelf",
want: "BookShelf",
want: "book-shelf",
},
{
name: "WithPeriods",
input: "book.shelf",
want: "BookShelf",
want: "book-shelf",
},
} {
t.Run(test.name, func(t *testing.T) {
got := ToUpperCamelCase(test.input)
got := ToKebobCase(test.input)
if got != test.want {
t.Errorf("ToLowerCamelCase(%q) = %q, got %q", test.input, test.want, got)
t.Errorf("ToKebobCase(%q) = %q, got %q", test.input, test.want, got)
}
})
}
Expand Down
Loading