thriftlint/symbol.go

270 lines
5.6 KiB
Go
Raw Permalink Normal View History

2017-01-11 04:36:58 +00:00
package thriftlint
import (
"bytes"
"fmt"
"go/doc"
"reflect"
"strings"
"unicode"
)
var (
// BuiltinThriftTypes is a map of the basic builtin Thrift types. Useful in templates.
BuiltinThriftTypes = map[string]bool{
"bool": true,
"byte": true,
"i16": true,
"i32": true,
"i64": true,
"double": true,
"string": true,
}
// BuiltinThriftCollections is the set of builtin collection types in Thrift.
BuiltinThriftCollections = map[string]bool{
"map": true,
"list": true,
"set": true,
"binary": true,
}
)
// Scanner over a sequence of runes.
type scanner struct {
runes []rune
cursor int
}
func (s *scanner) peek() rune {
if s.cursor >= len(s.runes) {
return -1
}
return s.runes[s.cursor]
}
func (s *scanner) next() rune {
r := s.peek()
if r != -1 {
s.cursor++
}
return r
}
func (s *scanner) reverse() {
s.cursor--
}
func consumeLower(scan *scanner) string {
out := ""
for unicode.IsLower(scan.peek()) || unicode.IsNumber(scan.peek()) {
out += string(scan.next())
}
return out
}
// ([A-Z]+)(?:[A-Z][a-z]|$)
func consumeMostUpper(scan *scanner) string {
out := ""
for unicode.IsUpper(scan.peek()) || unicode.IsNumber(scan.peek()) {
r := scan.next()
if unicode.IsLower(scan.peek()) && !commonInitialisms[out+string(r)] {
scan.reverse()
break
}
out += string(r)
}
return out
}
func title(s string) string {
return strings.ToUpper(s[0:1]) + strings.ToLower(s[1:])
}
// From https://github.com/golang/lint/blob/master/lint.go
var commonInitialisms = map[string]bool{
"API": true,
"ASCII": true,
"CPU": true,
"CSS": true,
"DB": true,
"DNS": true,
"EOF": true,
"GUID": true,
"HTML": true,
"HTTP": true,
"HTTPS": true,
"ID": true,
"IP": true,
"JSON": true,
"LHS": true,
"MD5": true,
"MLS": true,
"OK": true,
"QPS": true,
"RAM": true,
"RHS": true,
"RPC": true,
"SHA": true,
"SLA": true,
"SMTP": true,
"SQL": true,
"SSH": true,
"TCP": true,
"TLS": true,
"TTL": true,
"UDP": true,
"UI": true,
"UID": true,
"URI": true,
"URL": true,
"UTC": true,
"UTF8": true,
"UUID": true,
"VM": true,
"XML": true,
"XSRF": true,
"XSS": true,
}
func Comment(v interface{}) []string {
comment := reflect.Indirect(reflect.ValueOf(v)).FieldByName("Comment").Interface().(string)
if comment == "" {
return nil
}
w := bytes.NewBuffer(nil)
doc.ToText(w, comment, "", "", 80)
return strings.Split(strings.TrimSpace(w.String()), "\n")
}
func IsInitialism(s string) bool {
return commonInitialisms[strings.ToUpper(s)]
}
// UpperCamelCase converts a symbol to CamelCase
func UpperCamelCase(s string) string {
parts := []string{}
for _, part := range SplitSymbol(s) {
if part == "" {
parts = append(parts, "_")
continue
}
if part == "s" && len(parts) > 0 {
parts[len(parts)-1] += part
} else {
if commonInitialisms[strings.ToUpper(part)] {
part = strings.ToUpper(part)
} else {
part = title(part)
}
parts = append(parts, part)
}
}
return strings.Join(parts, "")
}
// LowerCamelCase converts a symbol to lowerCamelCase
func LowerCamelCase(s string) string {
first := true
parts := []string{}
for _, part := range SplitSymbol(s) {
if part == "" {
parts = append(parts, "_")
continue
}
if first {
parts = append(parts, strings.ToLower(part))
first = false
} else {
// Merge trailing s
if part == "s" && len(parts) > 0 {
parts[len(parts)-1] += part
} else {
if commonInitialisms[strings.ToUpper(part)] {
part = strings.ToUpper(part)
} else {
part = title(part)
}
parts = append(parts, part)
}
}
}
return strings.Join(parts, "")
}
// LowerSnakeCase converts a symbol to snake_case
func LowerSnakeCase(s string) string {
parts := []string{}
for _, part := range SplitSymbol(s) {
if part == "" {
parts = append(parts, "_")
continue
}
parts = append(parts, strings.ToLower(part))
}
return strings.Join(parts, "_")
}
// UpperSnakeCase converts a symbol to UPPER_SNAKE_CASE
func UpperSnakeCase(s string) string {
parts := []string{}
for _, part := range SplitSymbol(s) {
if part == "" {
parts = append(parts, "_")
continue
}
parts = append(parts, strings.ToUpper(part))
}
return strings.Join(parts, "_")
}
// SplitSymbol splits an arbitrary symbol into parts. It accepts symbols in snake case and camel
// case, and correctly supports all-caps substrings.
//
// eg. "some_snake_case_symbol" would become ["some", "snake", "case", "symbol"]
// and "someCamelCaseSymbol" would become ["some", "Camel", "Case", "Symbol"]
func SplitSymbol(s string) []string {
// This is painful. See TestSplitSymbol for examples of what this does.
out := []string{}
scan := &scanner{runes: []rune(s)}
for scan.peek() != -1 {
part := ""
r := scan.peek()
switch {
case unicode.IsLower(r):
part = consumeLower(scan)
case unicode.IsUpper(r):
scan.next()
// [A-Z][a-z]+
if unicode.IsLower(scan.peek()) {
part += string(r)
part += consumeLower(scan)
} else {
scan.reverse()
part += consumeMostUpper(scan)
}
case unicode.IsNumber(r):
for unicode.IsNumber(scan.peek()) {
part += string(scan.next())
}
case r == '_':
scan.next()
if len(out) == 0 {
break
}
continue
default:
panic(fmt.Sprintf("unsupported character %q in %q", r, s))
}
out = append(out, part)
}
return out
}
// Extract the suffix from a . separated string (ie. namespace).
// Useful for getting the package reference from a files namespace.
func DotSuffix(pkg string) string {
parts := strings.Split(pkg, ".")
return parts[len(parts)-1]
}