mirror of
https://github.com/valitydev/thriftlint.git
synced 2024-11-06 00:05:20 +00:00
270 lines
5.6 KiB
Go
270 lines
5.6 KiB
Go
|
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]
|
||
|
}
|