Initial commit

This commit is contained in:
Donny
2019-04-22 20:46:32 +08:00
commit 49ab8aadd1
25441 changed files with 4055000 additions and 0 deletions

View File

@@ -0,0 +1 @@
# gengo

View File

@@ -0,0 +1,9 @@
# Owner
maojian
zhoujiahui
# Author
zhoujiahui
# Reviewer
zhoujiahui

2
app/tool/gengo/Makefile Normal file
View File

@@ -0,0 +1,2 @@
install:
go install ./cmd/deepcopy-gen/

11
app/tool/gengo/OWNERS Normal file
View File

@@ -0,0 +1,11 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- maojian
- zhoujiahui
labels:
- tool
options:
no_parent_owners: true
reviewers:
- zhoujiahui

3
app/tool/gengo/README.md Normal file
View File

@@ -0,0 +1,3 @@
# gengo
## deepcopy-gen

47
app/tool/gengo/args/BUILD Normal file
View File

@@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["args_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//app/tool/gengo/types:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"args.go",
"var.go",
],
importpath = "go-common/app/tool/gengo/args",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/gengo/generator:go_default_library",
"//app/tool/gengo/namer:go_default_library",
"//app/tool/gengo/parser:go_default_library",
"//app/tool/gengo/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

182
app/tool/gengo/args/args.go Normal file
View File

@@ -0,0 +1,182 @@
// Package args has common command-line flags for generation programs.
package args
import (
"bytes"
"flag"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"strconv"
"strings"
"time"
"go-common/app/tool/gengo/generator"
"go-common/app/tool/gengo/namer"
"go-common/app/tool/gengo/parser"
"go-common/app/tool/gengo/types"
)
// Default returns a defaulted GeneratorArgs. You may change the defaults
// before calling AddFlags.
func Default() *GeneratorArgs {
return &GeneratorArgs{
OutputBase: DefaultSourceTree(),
GoHeaderFilePath: filepath.Join(DefaultSourceTree(), "go-common/app/tool/gengo/boilerplate/boilerplate.go.txt"),
GeneratedBuildTag: "ignore_autogenerated",
GeneratedByCommentTemplate: "// Code generated by GENERATOR_NAME. DO NOT EDIT.",
defaultCommandLineFlags: true,
}
}
// GeneratorArgs has arguments that are passed to generators.
type GeneratorArgs struct {
// Which directories to parse.
InputDirs StringSliceVar
// Source tree to write results to.
OutputBase string
// Package path within the source tree.
OutputPackagePath string
// Output file name.
OutputFileBaseName string
// Where to get copyright header text.
GoHeaderFilePath string
// If GeneratedByCommentTemplate is set, generate a "Code generated by" comment
// below the bloilerplate, of the format defined by this string.
// Any instances of "GENERATOR_NAME" will be replaced with the name of the code generator.
GeneratedByCommentTemplate string
// If true, only verify, don't write anything.
VerifyOnly bool
// GeneratedBuildTag is the tag used to identify code generated by execution
// of this type. Each generator should use a different tag, and different
// groups of generators (external API that depends on Kube generations) should
// keep tags distinct as well.
GeneratedBuildTag string
// Any custom arguments go here
CustomArgs interface{}
// Whether to use default command line flags
defaultCommandLineFlags bool
}
// WithoutDefaultFlagParsing disables implicit addition of command line flags and parsing.
func (g *GeneratorArgs) WithoutDefaultFlagParsing() *GeneratorArgs {
g.defaultCommandLineFlags = false
return g
}
// AddFlags is
func (g *GeneratorArgs) AddFlags(fs *flag.FlagSet) {
fs.Var(&g.InputDirs, "input-dirs", "Comma-separated list of import paths to get input types from.")
fs.StringVar(&g.OutputBase, "output-base", g.OutputBase, "Output base; defaults to $GOPATH/src/ or ./ if $GOPATH is not set.")
fs.StringVar(&g.OutputPackagePath, "output-package", g.OutputPackagePath, "Base package path.")
fs.StringVar(&g.OutputFileBaseName, "output-file-base", g.OutputFileBaseName, "Base name (without .go suffix) for output files.")
fs.StringVar(&g.GoHeaderFilePath, "go-header-file", g.GoHeaderFilePath, "File containing boilerplate header text. The string YEAR will be replaced with the current 4-digit year.")
fs.BoolVar(&g.VerifyOnly, "verify-only", g.VerifyOnly, "If true, only verify existing output, do not write anything.")
fs.StringVar(&g.GeneratedBuildTag, "build-tag", g.GeneratedBuildTag, "A Go build tag to use to identify files generated by this command. Should be unique.")
}
// LoadGoBoilerplate loads the boilerplate file passed to --go-header-file.
func (g *GeneratorArgs) LoadGoBoilerplate() ([]byte, error) {
b, err := ioutil.ReadFile(g.GoHeaderFilePath)
if err != nil {
return nil, err
}
b = bytes.Replace(b, []byte("YEAR"), []byte(strconv.Itoa(time.Now().Year())), -1)
if g.GeneratedByCommentTemplate != "" {
if len(b) != 0 {
b = append(b, byte('\n'))
}
generatorName := path.Base(os.Args[0])
generatedByComment := strings.Replace(g.GeneratedByCommentTemplate, "GENERATOR_NAME", generatorName, -1)
s := fmt.Sprintf("%s\n\n", generatedByComment)
b = append(b, []byte(s)...)
}
return b, nil
}
// NewBuilder makes a new parser.Builder and populates it with the input
// directories.
func (g *GeneratorArgs) NewBuilder() (*parser.Builder, error) {
b := parser.New()
// Ignore all auto-generated files.
b.AddBuildTags(g.GeneratedBuildTag)
for _, d := range g.InputDirs {
var err error
if strings.HasSuffix(d, "/...") {
err = b.AddDirRecursive(strings.TrimSuffix(d, "/..."))
} else {
err = b.AddDir(d)
}
if err != nil {
return nil, fmt.Errorf("unable to add directory %q: %v", d, err)
}
}
return b, nil
}
// InputIncludes returns true if the given package is a (sub) package of one of
// the InputDirs.
func (g *GeneratorArgs) InputIncludes(p *types.Package) bool {
for _, dir := range g.InputDirs {
d := dir
if strings.HasSuffix(d, "...") {
d = strings.TrimSuffix(d, "...")
}
if strings.HasPrefix(p.Path, d) {
return true
}
}
return false
}
// DefaultSourceTree returns the /src directory of the first entry in $GOPATH.
// If $GOPATH is empty, it returns "./". Useful as a default output location.
func DefaultSourceTree() string {
paths := strings.Split(os.Getenv("GOPATH"), string(filepath.ListSeparator))
if len(paths) > 0 && len(paths[0]) > 0 {
return filepath.Join(paths[0], "src")
}
return "./"
}
// Execute implements main().
// If you don't need any non-default behavior, use as:
// args.Default().Execute(...)
func (g *GeneratorArgs) Execute(nameSystems namer.NameSystems, defaultSystem string, pkgs func(*generator.Context, *GeneratorArgs) generator.Packages) error {
if g.defaultCommandLineFlags {
g.AddFlags(flag.CommandLine)
// flag.CommandLine.AddGoFlagSet(goflag.CommandLine)
flag.Parse()
}
b, err := g.NewBuilder()
if err != nil {
return fmt.Errorf("Failed making a parser: %v", err)
}
c, err := generator.NewContext(b, nameSystems, defaultSystem)
if err != nil {
return fmt.Errorf("Failed making a context: %v", err)
}
c.Verify = g.VerifyOnly
packages := pkgs(c, g)
if err := c.ExecutePackages(g.OutputBase, packages); err != nil {
return fmt.Errorf("Failed executing generator: %v", err)
}
return nil
}

View File

@@ -0,0 +1,19 @@
package args
import (
"testing"
"go-common/app/tool/gengo/types"
)
func TestInputIncludes(t *testing.T) {
a := &GeneratorArgs{
InputDirs: []string{"a/b/..."},
}
if !a.InputIncludes(&types.Package{Path: "a/b/c"}) {
t.Errorf("Expected /... syntax to work")
}
if a.InputIncludes(&types.Package{Path: "a/c/b"}) {
t.Errorf("Expected correctness")
}
}

View File

@@ -0,0 +1,20 @@
package args
import (
"strings"
)
// StringSliceVar is
type StringSliceVar []string
func (v StringSliceVar) String() string {
return strings.Join(v, ",")
}
// Set is
func (v *StringSliceVar) Set(in string) error {
for _, a := range strings.Split(in, ",") {
*v = append(*v, a)
}
return nil
}

View File

@@ -0,0 +1 @@
deepcopy-gen

View File

@@ -0,0 +1,47 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "deepcopy-gen",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "go-common/app/tool/gengo/cmd/deepcopy-gen",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/gengo/args:go_default_library",
"//app/tool/gengo/cmd/deepcopy-gen/generators:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/tool/gengo/cmd/deepcopy-gen/examples/api/v1:all-srcs",
"//app/tool/gengo/cmd/deepcopy-gen/examples/model:all-srcs",
"//app/tool/gengo/cmd/deepcopy-gen/examples/tests:all-srcs",
"//app/tool/gengo/cmd/deepcopy-gen/generators:all-srcs",
"//app/tool/gengo/cmd/deepcopy-gen/sets:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,4 @@
example:
go install
deepcopy-gen -logtostderr -v=6 -input-dirs ./examples/api/v1/,./examples/model/ -output-file-base copy
go test -v ./examples/tests

View File

@@ -0,0 +1,62 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v1_proto",
srcs = ["api.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_library(
name = "go_default_library",
srcs = [
"copy.go",
"doc.go",
],
embed = [":v1_go_proto"],
importpath = "go-common/app/tool/gengo/cmd/deepcopy-gen/examples/api/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/gengo/cmd/deepcopy-gen/examples/model:go_default_library",
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_proto"],
importpath = "go-common/app/tool/gengo/cmd/deepcopy-gen/examples/api/v1",
proto = ":v1_proto",
tags = ["automanaged"],
deps = [
"//library/time:go_default_library",
"@com_github_gogo_protobuf//gogoproto:go_default_library",
],
)

View File

@@ -0,0 +1,961 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: api.proto
/*
Package v1 is a generated protocol buffer package.
It is generated from these files:
api.proto
It has these top-level messages:
BaseInfoReply
NamesReply
MidsReply
*/
package v1
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
import go_common_library_time "go-common/library/time"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// +bili:deepcopy-gen:structs=go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model.MemberBase
type BaseInfoReply struct {
Mid int64 `protobuf:"varint,1,opt,name=mid,proto3" json:"mid"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name"`
Sex int64 `protobuf:"varint,3,opt,name=sex,proto3" json:"sex"`
Face string `protobuf:"bytes,4,opt,name=face,proto3" json:"face"`
Sign string `protobuf:"bytes,5,opt,name=sign,proto3" json:"sign"`
Rank int64 `protobuf:"varint,6,opt,name=rank,proto3" json:"rank"`
Birthday go_common_library_time.Time `protobuf:"varint,7,opt,name=birthday,proto3,casttype=go-common/library/time.Time" json:"birthday"`
}
func (m *BaseInfoReply) Reset() { *m = BaseInfoReply{} }
func (m *BaseInfoReply) String() string { return proto.CompactTextString(m) }
func (*BaseInfoReply) ProtoMessage() {}
func (*BaseInfoReply) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{0} }
func (m *BaseInfoReply) GetMid() int64 {
if m != nil {
return m.Mid
}
return 0
}
func (m *BaseInfoReply) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *BaseInfoReply) GetSex() int64 {
if m != nil {
return m.Sex
}
return 0
}
func (m *BaseInfoReply) GetFace() string {
if m != nil {
return m.Face
}
return ""
}
func (m *BaseInfoReply) GetSign() string {
if m != nil {
return m.Sign
}
return ""
}
func (m *BaseInfoReply) GetRank() int64 {
if m != nil {
return m.Rank
}
return 0
}
func (m *BaseInfoReply) GetBirthday() go_common_library_time.Time {
if m != nil {
return m.Birthday
}
return 0
}
// +bili:deepcopy-gen:structs=go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model.Names
type NamesReply struct {
Names map[int64]string `protobuf:"bytes,1,rep,name=names" json:"names,omitempty" protobuf_key:"varint,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (m *NamesReply) Reset() { *m = NamesReply{} }
func (m *NamesReply) String() string { return proto.CompactTextString(m) }
func (*NamesReply) ProtoMessage() {}
func (*NamesReply) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{1} }
func (m *NamesReply) GetNames() map[int64]string {
if m != nil {
return m.Names
}
return nil
}
// +bili:deepcopy-gen:structs=go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model.Mids
type MidsReply struct {
Mids []int64 `protobuf:"varint,1,rep,packed,name=mids" json:"mids,omitempty"`
}
func (m *MidsReply) Reset() { *m = MidsReply{} }
func (m *MidsReply) String() string { return proto.CompactTextString(m) }
func (*MidsReply) ProtoMessage() {}
func (*MidsReply) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2} }
func (m *MidsReply) GetMids() []int64 {
if m != nil {
return m.Mids
}
return nil
}
func init() {
proto.RegisterType((*BaseInfoReply)(nil), "account.service.member.v1.BaseInfoReply")
proto.RegisterType((*NamesReply)(nil), "account.service.member.v1.NamesReply")
proto.RegisterType((*MidsReply)(nil), "account.service.member.v1.MidsReply")
}
func (m *BaseInfoReply) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *BaseInfoReply) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if m.Mid != 0 {
dAtA[i] = 0x8
i++
i = encodeVarintApi(dAtA, i, uint64(m.Mid))
}
if len(m.Name) > 0 {
dAtA[i] = 0x12
i++
i = encodeVarintApi(dAtA, i, uint64(len(m.Name)))
i += copy(dAtA[i:], m.Name)
}
if m.Sex != 0 {
dAtA[i] = 0x18
i++
i = encodeVarintApi(dAtA, i, uint64(m.Sex))
}
if len(m.Face) > 0 {
dAtA[i] = 0x22
i++
i = encodeVarintApi(dAtA, i, uint64(len(m.Face)))
i += copy(dAtA[i:], m.Face)
}
if len(m.Sign) > 0 {
dAtA[i] = 0x2a
i++
i = encodeVarintApi(dAtA, i, uint64(len(m.Sign)))
i += copy(dAtA[i:], m.Sign)
}
if m.Rank != 0 {
dAtA[i] = 0x30
i++
i = encodeVarintApi(dAtA, i, uint64(m.Rank))
}
if m.Birthday != 0 {
dAtA[i] = 0x38
i++
i = encodeVarintApi(dAtA, i, uint64(m.Birthday))
}
return i, nil
}
func (m *NamesReply) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *NamesReply) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Names) > 0 {
for k, _ := range m.Names {
dAtA[i] = 0xa
i++
v := m.Names[k]
mapSize := 1 + sovApi(uint64(k)) + 1 + len(v) + sovApi(uint64(len(v)))
i = encodeVarintApi(dAtA, i, uint64(mapSize))
dAtA[i] = 0x8
i++
i = encodeVarintApi(dAtA, i, uint64(k))
dAtA[i] = 0x12
i++
i = encodeVarintApi(dAtA, i, uint64(len(v)))
i += copy(dAtA[i:], v)
}
}
return i, nil
}
func (m *MidsReply) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *MidsReply) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Mids) > 0 {
dAtA2 := make([]byte, len(m.Mids)*10)
var j1 int
for _, num1 := range m.Mids {
num := uint64(num1)
for num >= 1<<7 {
dAtA2[j1] = uint8(uint64(num)&0x7f | 0x80)
num >>= 7
j1++
}
dAtA2[j1] = uint8(num)
j1++
}
dAtA[i] = 0xa
i++
i = encodeVarintApi(dAtA, i, uint64(j1))
i += copy(dAtA[i:], dAtA2[:j1])
}
return i, nil
}
func encodeVarintApi(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *BaseInfoReply) Size() (n int) {
var l int
_ = l
if m.Mid != 0 {
n += 1 + sovApi(uint64(m.Mid))
}
l = len(m.Name)
if l > 0 {
n += 1 + l + sovApi(uint64(l))
}
if m.Sex != 0 {
n += 1 + sovApi(uint64(m.Sex))
}
l = len(m.Face)
if l > 0 {
n += 1 + l + sovApi(uint64(l))
}
l = len(m.Sign)
if l > 0 {
n += 1 + l + sovApi(uint64(l))
}
if m.Rank != 0 {
n += 1 + sovApi(uint64(m.Rank))
}
if m.Birthday != 0 {
n += 1 + sovApi(uint64(m.Birthday))
}
return n
}
func (m *NamesReply) Size() (n int) {
var l int
_ = l
if len(m.Names) > 0 {
for k, v := range m.Names {
_ = k
_ = v
mapEntrySize := 1 + sovApi(uint64(k)) + 1 + len(v) + sovApi(uint64(len(v)))
n += mapEntrySize + 1 + sovApi(uint64(mapEntrySize))
}
}
return n
}
func (m *MidsReply) Size() (n int) {
var l int
_ = l
if len(m.Mids) > 0 {
l = 0
for _, e := range m.Mids {
l += sovApi(uint64(e))
}
n += 1 + sovApi(uint64(l)) + l
}
return n
}
func sovApi(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozApi(x uint64) (n int) {
return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *BaseInfoReply) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: BaseInfoReply: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: BaseInfoReply: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Mid", wireType)
}
m.Mid = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Mid |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 3:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Sex", wireType)
}
m.Sex = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Sex |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 4:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Face", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Face = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 5:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Sign", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Sign = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 6:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Rank", wireType)
}
m.Rank = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Rank |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
case 7:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Birthday", wireType)
}
m.Birthday = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Birthday |= (go_common_library_time.Time(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipApi(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthApi
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *NamesReply) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: NamesReply: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: NamesReply: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Names", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
if m.Names == nil {
m.Names = make(map[int64]string)
}
var mapkey int64
var mapvalue string
for iNdEx < postIndex {
entryPreIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
if fieldNum == 1 {
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
mapkey |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
} else if fieldNum == 2 {
var stringLenmapvalue uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLenmapvalue |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLenmapvalue := int(stringLenmapvalue)
if intStringLenmapvalue < 0 {
return ErrInvalidLengthApi
}
postStringIndexmapvalue := iNdEx + intStringLenmapvalue
if postStringIndexmapvalue > l {
return io.ErrUnexpectedEOF
}
mapvalue = string(dAtA[iNdEx:postStringIndexmapvalue])
iNdEx = postStringIndexmapvalue
} else {
iNdEx = entryPreIndex
skippy, err := skipApi(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthApi
}
if (iNdEx + skippy) > postIndex {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
m.Names[mapkey] = mapvalue
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipApi(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthApi
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *MidsReply) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: MidsReply: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: MidsReply: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType == 0 {
var v int64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.Mids = append(m.Mids, v)
} else if wireType == 2 {
var packedLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
packedLen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if packedLen < 0 {
return ErrInvalidLengthApi
}
postIndex := iNdEx + packedLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
for iNdEx < postIndex {
var v int64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowApi
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.Mids = append(m.Mids, v)
}
} else {
return fmt.Errorf("proto: wrong wireType = %d for field Mids", wireType)
}
default:
iNdEx = preIndex
skippy, err := skipApi(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthApi
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipApi(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthApi
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowApi
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipApi(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthApi = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowApi = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("api.proto", fileDescriptorApi) }
var fileDescriptorApi = []byte{
// 383 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x74, 0x91, 0xbb, 0x6e, 0xdb, 0x30,
0x18, 0x85, 0x4b, 0xc9, 0x57, 0x16, 0x05, 0x0a, 0xc1, 0x03, 0xed, 0x16, 0x92, 0xe1, 0xc9, 0x40,
0x61, 0xaa, 0x6e, 0x17, 0xa3, 0xa3, 0x80, 0xb6, 0xc8, 0x90, 0x0c, 0x44, 0xa6, 0x6c, 0x94, 0x44,
0xcb, 0x84, 0x4d, 0xd1, 0xa0, 0x2e, 0xb0, 0x5e, 0x20, 0x63, 0xe6, 0x3c, 0x52, 0xc6, 0x3c, 0x81,
0x10, 0x38, 0x9b, 0x1f, 0x21, 0x53, 0x40, 0xca, 0xb0, 0xa6, 0x2c, 0x07, 0xff, 0xc7, 0xc3, 0xf3,
0x83, 0x3a, 0x82, 0x43, 0xba, 0xe7, 0x78, 0xaf, 0x64, 0x2e, 0x9d, 0x31, 0x8d, 0x22, 0x59, 0xa4,
0x39, 0xce, 0x98, 0x2a, 0x79, 0xc4, 0xb0, 0x60, 0x22, 0x64, 0x0a, 0x97, 0xcb, 0xc9, 0x22, 0xe1,
0xf9, 0xa6, 0x08, 0x71, 0x24, 0x85, 0x9f, 0xc8, 0x44, 0xfa, 0x26, 0x11, 0x16, 0x6b, 0x43, 0x06,
0xcc, 0xd4, 0x6c, 0x9a, 0xdd, 0x5b, 0xf0, 0x4b, 0x40, 0x33, 0x76, 0x95, 0xae, 0x25, 0x61, 0xfb,
0x5d, 0xe5, 0x8c, 0xa1, 0x2d, 0x78, 0x8c, 0xc0, 0x14, 0xcc, 0xed, 0xa0, 0x7f, 0xaa, 0x3d, 0x8d,
0x44, 0x8b, 0xf3, 0x1d, 0x76, 0x52, 0x2a, 0x18, 0xb2, 0xa6, 0x60, 0x3e, 0x0c, 0x06, 0xa7, 0xda,
0x33, 0x4c, 0x8c, 0xea, 0x60, 0xc6, 0x0e, 0xc8, 0x6e, 0x83, 0x19, 0x3b, 0x10, 0x2d, 0x3a, 0xb8,
0xa6, 0x11, 0x43, 0x9d, 0x36, 0xa8, 0x99, 0x18, 0xd5, 0x6e, 0xc6, 0x93, 0x14, 0x75, 0x5b, 0x57,
0x33, 0x31, 0xaa, 0x5d, 0x45, 0xd3, 0x2d, 0xea, 0x99, 0xbd, 0xc6, 0xd5, 0x4c, 0x8c, 0x3a, 0xff,
0xe1, 0x20, 0xe4, 0x2a, 0xdf, 0xc4, 0xb4, 0x42, 0x7d, 0x73, 0xe3, 0xc7, 0xa9, 0xf6, 0x2e, 0x67,
0x6f, 0xb5, 0xf7, 0x2d, 0x91, 0x8b, 0x48, 0x0a, 0x21, 0x53, 0x7f, 0xc7, 0x43, 0x45, 0x55, 0xe5,
0xe7, 0x5c, 0x30, 0x7c, 0xcb, 0x05, 0x23, 0x97, 0x8b, 0xb3, 0x07, 0x00, 0xe1, 0x0d, 0x15, 0x2c,
0x6b, 0x5a, 0xf8, 0x07, 0xbb, 0xfa, 0xa3, 0x32, 0x04, 0xa6, 0xf6, 0xfc, 0xf3, 0xaf, 0x9f, 0xf8,
0xc3, 0xc6, 0x71, 0x9b, 0x6a, 0xc6, 0xbf, 0x69, 0xae, 0x2a, 0xd2, 0xc4, 0x27, 0xab, 0xf3, 0x56,
0x73, 0xe8, 0x7c, 0x85, 0xf6, 0x96, 0x55, 0x4d, 0xb7, 0x44, 0x8f, 0xce, 0x08, 0x76, 0x4b, 0xba,
0x2b, 0xce, 0x9d, 0x92, 0x06, 0xfe, 0x58, 0x2b, 0x30, 0xf3, 0xe0, 0xf0, 0x9a, 0xc7, 0xe7, 0xe7,
0x38, 0xb0, 0x23, 0x78, 0xdc, 0xbc, 0xc6, 0x26, 0x66, 0x0e, 0x46, 0x4f, 0x47, 0x17, 0x3c, 0x1f,
0x5d, 0xf0, 0x72, 0x74, 0xc1, 0xe3, 0xab, 0xfb, 0xe9, 0xce, 0x2a, 0x97, 0x61, 0xcf, 0xfc, 0xd7,
0xdf, 0xef, 0x01, 0x00, 0x00, 0xff, 0xff, 0x85, 0x29, 0x80, 0x4b, 0x2e, 0x02, 0x00, 0x00,
}

View File

@@ -0,0 +1,25 @@
syntax = "proto3";
package account.service.member.v1;
option go_package = "v1";
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
// +bili:deepcopy-gen:structs=go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model.MemberBase
message BaseInfoReply {
int64 mid = 1 [ (gogoproto.jsontag) = "mid" ];
string name = 2 [ (gogoproto.jsontag) = "name" ];
int64 sex = 3 [ (gogoproto.jsontag) = "sex" ];
string face = 4 [ (gogoproto.jsontag) = "face" ];
string sign = 5 [ (gogoproto.jsontag) = "sign" ];
int64 rank = 6 [ (gogoproto.jsontag) = "rank" ];
int64 birthday = 7 [
(gogoproto.jsontag) = "birthday",
(gogoproto.casttype) = "go-common/library/time.Time"
];
}
// +bili:deepcopy-gen:structs=go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model.Names
message NamesReply { map<int64, string> names = 1; }
// +bili:deepcopy-gen:structs=go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model.Mids
message MidsReply { repeated int64 mids = 1; }

View File

@@ -0,0 +1,167 @@
// +build !ignore_autogenerated
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
import (
model "go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BaseInfoReply) DeepCopyInto(out *BaseInfoReply) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BaseInfoReply.
func (in *BaseInfoReply) DeepCopy() *BaseInfoReply {
if in == nil {
return nil
}
out := new(BaseInfoReply)
in.DeepCopyInto(out)
return out
}
// DeepCopyAsIntoMemberBase is an autogenerated deepcopy function, copying the receiver, writing into model.MemberBase.
func (in *BaseInfoReply) DeepCopyAsIntoMemberBase(out *model.MemberBase) {
out.Mid = in.Mid
out.Name = in.Name
out.Sex = in.Sex
out.Face = in.Face
out.Sign = in.Sign
out.Rank = in.Rank
out.Birthday = in.Birthday
return
}
// DeepCopyFromMemberBase is an autogenerated deepcopy function, copying the receiver, writing into model.MemberBase.
func (out *BaseInfoReply) DeepCopyFromMemberBase(in *model.MemberBase) {
out.Mid = in.Mid
out.Name = in.Name
out.Sex = in.Sex
out.Face = in.Face
out.Sign = in.Sign
out.Rank = in.Rank
out.Birthday = in.Birthday
return
}
// DeepCopyAsMemberBase is an autogenerated deepcopy function, copying the receiver, creating a new model.MemberBase.
func (in *BaseInfoReply) DeepCopyAsMemberBase() *model.MemberBase {
if in == nil {
return nil
}
out := new(model.MemberBase)
in.DeepCopyAsIntoMemberBase(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MidsReply) DeepCopyInto(out *MidsReply) {
*out = *in
if in.Mids != nil {
in, out := &in.Mids, &out.Mids
*out = make([]int64, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MidsReply.
func (in *MidsReply) DeepCopy() *MidsReply {
if in == nil {
return nil
}
out := new(MidsReply)
in.DeepCopyInto(out)
return out
}
// DeepCopyAsIntoMids is an autogenerated deepcopy function, copying the receiver, writing into model.Mids.
func (in *MidsReply) DeepCopyAsIntoMids(out *model.Mids) {
if in.Mids != nil {
in, out := &in.Mids, &out.Mids
*out = make([]int64, len(*in))
copy(*out, *in)
}
return
}
// DeepCopyFromMids is an autogenerated deepcopy function, copying the receiver, writing into model.Mids.
func (out *MidsReply) DeepCopyFromMids(in *model.Mids) {
if in.Mids != nil {
in, out := &in.Mids, &out.Mids
*out = make([]int64, len(*in))
copy(*out, *in)
}
return
}
// DeepCopyAsMids is an autogenerated deepcopy function, copying the receiver, creating a new model.Mids.
func (in *MidsReply) DeepCopyAsMids() *model.Mids {
if in == nil {
return nil
}
out := new(model.Mids)
in.DeepCopyAsIntoMids(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NamesReply) DeepCopyInto(out *NamesReply) {
*out = *in
if in.Names != nil {
in, out := &in.Names, &out.Names
*out = make(map[int64]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamesReply.
func (in *NamesReply) DeepCopy() *NamesReply {
if in == nil {
return nil
}
out := new(NamesReply)
in.DeepCopyInto(out)
return out
}
// DeepCopyAsIntoNames is an autogenerated deepcopy function, copying the receiver, writing into model.Names.
func (in *NamesReply) DeepCopyAsIntoNames(out *model.Names) {
if in.Names != nil {
in, out := &in.Names, &out.Names
*out = make(map[int64]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopyFromNames is an autogenerated deepcopy function, copying the receiver, writing into model.Names.
func (out *NamesReply) DeepCopyFromNames(in *model.Names) {
if in.Names != nil {
in, out := &in.Names, &out.Names
*out = make(map[int64]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopyAsNames is an autogenerated deepcopy function, copying the receiver, creating a new model.Names.
func (in *NamesReply) DeepCopyAsNames() *model.Names {
if in == nil {
return nil
}
out := new(model.Names)
in.DeepCopyAsIntoNames(out)
return out
}

View File

@@ -0,0 +1,2 @@
// +bili:deepcopy-gen=package
package v1

View File

@@ -0,0 +1,32 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"model.go",
],
importpath = "go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/time:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1 @@
package model

View File

@@ -0,0 +1,26 @@
package model
import (
"go-common/library/time"
)
// MemberBase is
type MemberBase struct {
Mid int64
Name string
Sex int64
Face string
Sign string
Rank int64
Birthday time.Time
}
// Names is
type Names struct {
Names map[int64]string
}
// Mids is
type Mids struct {
Mids []int64
}

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["example_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//app/tool/gengo/cmd/deepcopy-gen/examples/api/v1:go_default_library",
"//app/tool/gengo/cmd/deepcopy-gen/examples/model:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["example.go"],
importpath = "go-common/app/tool/gengo/cmd/deepcopy-gen/examples/tests",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1 @@
package tests

View File

@@ -0,0 +1,77 @@
package tests
import (
"testing"
"time"
"unsafe"
"github.com/stretchr/testify/assert"
"go-common/app/tool/gengo/cmd/deepcopy-gen/examples/api/v1"
"go-common/app/tool/gengo/cmd/deepcopy-gen/examples/model"
xtime "go-common/library/time"
)
func TestCopyFromAllBuitin(t *testing.T) {
base := &model.MemberBase{
Mid: 1,
Name: "name",
Sex: 1,
Face: "aaa.jpg",
Sign: "sign",
Rank: 10000,
Birthday: xtime.Time(time.Now().Unix()),
}
reply := new(v1.BaseInfoReply)
reply.DeepCopyFromMemberBase(base)
assert.Equal(t, base.Mid, reply.Mid)
assert.Equal(t, base.Name, reply.Name)
assert.Equal(t, base.Sex, reply.Sex)
assert.Equal(t, base.Face, reply.Face)
assert.Equal(t, base.Sign, reply.Sign)
assert.Equal(t, base.Rank, reply.Rank)
assert.Equal(t, base.Birthday, reply.Birthday)
copied := reply.DeepCopyAsMemberBase()
assert.Equal(t, base, copied)
}
func TestCopyFromMap(t *testing.T) {
names := &model.Names{
Names: map[int64]string{
1: "1",
2: "2",
},
}
reply := new(v1.NamesReply)
reply.DeepCopyFromNames(names)
assert.Equal(t, names.Names[1], reply.Names[1])
assert.Equal(t, names.Names[2], reply.Names[2])
assert.Equal(t, unsafe.Pointer(&names.Names), unsafe.Pointer(&names.Names))
assert.Equal(t, unsafe.Pointer(&reply.Names), unsafe.Pointer(&reply.Names))
assert.NotEqual(t, unsafe.Pointer(&names.Names), unsafe.Pointer(&reply.Names))
names.Names[3] = "3"
assert.Contains(t, names.Names, int64(3))
assert.NotContains(t, reply.Names, int64(3))
}
func TestCopyFromSlice(t *testing.T) {
mids := &model.Mids{
Mids: []int64{1, 2},
}
reply := new(v1.MidsReply)
reply.DeepCopyFromMids(mids)
assert.Equal(t, mids.Mids[0], reply.Mids[0])
assert.Equal(t, mids.Mids[1], reply.Mids[1])
assert.Equal(t, unsafe.Pointer(&mids.Mids), unsafe.Pointer(&mids.Mids))
assert.Equal(t, unsafe.Pointer(&reply.Mids), unsafe.Pointer(&reply.Mids))
assert.NotEqual(t, unsafe.Pointer(&mids.Mids), unsafe.Pointer(&reply.Mids))
mids.Mids = append(mids.Mids, 3)
assert.Equal(t, mids.Mids[2], int64(3))
assert.NotContains(t, reply.Mids, int64(3))
}

View File

@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["deepcopy_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//app/tool/gengo/types:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["deepcopy.go"],
importpath = "go-common/app/tool/gengo/cmd/deepcopy-gen/generators",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/gengo/args:go_default_library",
"//app/tool/gengo/cmd/deepcopy-gen/sets:go_default_library",
"//app/tool/gengo/generator:go_default_library",
"//app/tool/gengo/namer:go_default_library",
"//app/tool/gengo/types:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,697 @@
package generators
import (
"reflect"
"testing"
"go-common/app/tool/gengo/types"
)
func Test_isRootedUnder(t *testing.T) {
testCases := []struct {
path string
roots []string
expect bool
}{
{
path: "/foo/bar",
roots: nil,
expect: false,
},
{
path: "/foo/bar",
roots: []string{},
expect: false,
},
{
path: "/foo/bar",
roots: []string{
"/bad",
},
expect: false,
},
{
path: "/foo/bar",
roots: []string{
"/foo",
},
expect: true,
},
{
path: "/foo/bar",
roots: []string{
"/bad",
"/foo",
},
expect: true,
},
{
path: "/foo/bar/qux/zorb",
roots: []string{
"/foo/bar/qux",
},
expect: true,
},
{
path: "/foo/bar",
roots: []string{
"/foo/bar",
},
expect: true,
},
{
path: "/foo/barn",
roots: []string{
"/foo/bar",
},
expect: false,
},
{
path: "/foo/bar",
roots: []string{
"/foo/barn",
},
expect: false,
},
{
path: "/foo/bar",
roots: []string{
"",
},
expect: true,
},
}
for i, tc := range testCases {
r := isRootedUnder(tc.path, tc.roots)
if r != tc.expect {
t.Errorf("case[%d]: expected %t, got %t for %q in %q", i, tc.expect, r, tc.path, tc.roots)
}
}
}
func Test_deepCopyMethod(t *testing.T) {
testCases := []struct {
typ types.Type
expect bool
error bool
}{
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
// No DeepCopy method.
Methods: map[string]*types.Type{},
},
expect: false,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// No DeepCopy method.
"method": {
Name: types.Name{Package: "pkgname", Name: "func()"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{},
Results: []*types.Type{},
},
},
},
},
expect: false,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature (no result).
"DeepCopy": {
Name: types.Name{Package: "pkgname", Name: "func()"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{},
Results: []*types.Type{},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature (wrong result).
"DeepCopy": {
Name: types.Name{Package: "pkgname", Name: "func() int"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{},
Results: []*types.Type{
{
Name: types.Name{Name: "int"},
Kind: types.Builtin,
},
},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature with pointer receiver, but non-pointer result.
"DeepCopy": {
Name: types.Name{Package: "pkgname", Name: "func() pkgname.typename"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{},
Results: []*types.Type{
{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
},
},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature with non-pointer receiver, but pointer result.
"DeepCopy": {
Name: types.Name{Package: "pkgname", Name: "func() pkgname.typename"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
Parameters: []*types.Type{},
Results: []*types.Type{
{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Correct signature with non-pointer receiver.
"DeepCopy": {
Name: types.Name{Package: "pkgname", Name: "func() pkgname.typename"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
Parameters: []*types.Type{},
Results: []*types.Type{
{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
},
},
},
},
},
},
expect: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Correct signature with pointer receiver.
"DeepCopy": {
Name: types.Name{Package: "pkgname", Name: "func() pkgname.typename"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{},
Results: []*types.Type{
{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
},
},
},
},
},
expect: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature (has params).
"DeepCopy": {
Name: types.Name{Package: "pkgname", Name: "func(int) pkgname.typename"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{
{
Name: types.Name{Name: "int"},
Kind: types.Builtin,
},
},
Results: []*types.Type{
{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
},
},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature (extra results).
"DeepCopy": {
Name: types.Name{Package: "pkgname", Name: "func() (pkgname.typename, int)"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{},
Results: []*types.Type{
{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
},
{
Name: types.Name{Name: "int"},
Kind: types.Builtin,
},
},
},
},
},
},
expect: false,
error: true,
},
}
for i, tc := range testCases {
r, err := deepCopyMethod(&tc.typ)
if tc.error && err == nil {
t.Errorf("case[%d]: expected an error, got none", i)
} else if !tc.error && err != nil {
t.Errorf("case[%d]: expected no error, got: %v", i, err)
} else if !tc.error && (r != nil) != tc.expect {
t.Errorf("case[%d]: expected result %v, got: %v", i, tc.expect, r)
}
}
}
func Test_deepCopyIntoMethod(t *testing.T) {
testCases := []struct {
typ types.Type
expect bool
error bool
}{
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
// No DeepCopyInto method.
Methods: map[string]*types.Type{},
},
expect: false,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// No DeepCopyInto method.
"method": {
Name: types.Name{Package: "pkgname", Name: "func()"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{},
Results: []*types.Type{},
},
},
},
},
expect: false,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature (no parameter).
"DeepCopyInto": {
Name: types.Name{Package: "pkgname", Name: "func()"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{},
Results: []*types.Type{},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature (unexpected result).
"DeepCopyInto": {
Name: types.Name{Package: "pkgname", Name: "func(*pkgname.typename) int"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{
{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
},
Results: []*types.Type{
{
Name: types.Name{Name: "int"},
Kind: types.Builtin,
},
},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature (non-pointer parameter, pointer receiver).
"DeepCopyInto": {
Name: types.Name{Package: "pkgname", Name: "func(pkgname.typename)"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{
{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Results: []*types.Type{},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Wrong signature (non-pointer parameter, non-pointer receiver).
"DeepCopyInto": {
Name: types.Name{Package: "pkgname", Name: "func(pkgname.typename)"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
Parameters: []*types.Type{
{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Results: []*types.Type{},
},
},
},
},
expect: false,
error: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Correct signature with non-pointer receiver.
"DeepCopyInto": {
Name: types.Name{Package: "pkgname", Name: "func(*pkgname.typename)"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
Parameters: []*types.Type{
{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
},
Results: []*types.Type{},
},
},
},
},
expect: true,
},
{
typ: types.Type{
Name: types.Name{Package: "pkgname", Name: "typename"},
Kind: types.Builtin,
Methods: map[string]*types.Type{
// Correct signature with pointer receiver.
"DeepCopyInto": {
Name: types.Name{Package: "pkgname", Name: "func(*pkgname.typename)"},
Kind: types.Func,
Signature: &types.Signature{
Receiver: &types.Type{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
Parameters: []*types.Type{
{
Kind: types.Pointer,
Elem: &types.Type{Kind: types.Struct, Name: types.Name{Package: "pkgname", Name: "typename"}},
},
},
Results: []*types.Type{},
},
},
},
},
expect: true,
},
}
for i, tc := range testCases {
r, err := deepCopyIntoMethod(&tc.typ)
if tc.error && err == nil {
t.Errorf("case[%d]: expected an error, got none", i)
} else if !tc.error && err != nil {
t.Errorf("case[%d]: expected no error, got: %v", i, err)
} else if !tc.error && (r != nil) != tc.expect {
t.Errorf("case[%d]: expected result %v, got: %v", i, tc.expect, r)
}
}
}
func Test_extractTagParams(t *testing.T) {
testCases := []struct {
comments []string
expect *tagValue
}{
{
comments: []string{
"Human comment",
},
expect: nil,
},
{
comments: []string{
"Human comment",
"+bili:deepcopy-gen",
},
expect: &tagValue{
value: "",
register: false,
},
},
{
comments: []string{
"Human comment",
"+bili:deepcopy-gen=package",
},
expect: &tagValue{
value: "package",
register: false,
},
},
{
comments: []string{
"Human comment",
"+bili:deepcopy-gen=package,register",
},
expect: &tagValue{
value: "package",
register: true,
},
},
{
comments: []string{
"Human comment",
"+bili:deepcopy-gen=package,register=true",
},
expect: &tagValue{
value: "package",
register: true,
},
},
{
comments: []string{
"Human comment",
"+bili:deepcopy-gen=package,register=false",
},
expect: &tagValue{
value: "package",
register: false,
},
},
}
for i, tc := range testCases {
r := extractTag(tc.comments)
if r == nil && tc.expect != nil {
t.Errorf("case[%d]: expected non-nil", i)
}
if r != nil && tc.expect == nil {
t.Errorf("case[%d]: expected nil, got %v", i, *r)
}
if r != nil && *r != *tc.expect {
t.Errorf("case[%d]: expected %v, got %v", i, *tc.expect, *r)
}
}
}
func Test_extractInterfacesTag(t *testing.T) {
testCases := []struct {
comments []string
expect []string
}{
{
comments: []string{},
expect: nil,
},
{
comments: []string{
"+bili:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object",
},
expect: []string{
"k8s.io/kubernetes/runtime.Object",
},
},
{
comments: []string{
"+bili:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object",
"+bili:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.List",
},
expect: []string{
"k8s.io/kubernetes/runtime.Object",
"k8s.io/kubernetes/runtime.List",
},
},
{
comments: []string{
"+bili:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object",
"+bili:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object",
},
expect: []string{
"k8s.io/kubernetes/runtime.Object",
"k8s.io/kubernetes/runtime.Object",
},
},
}
for i, tc := range testCases {
r := extractInterfacesTag(tc.comments)
if r == nil && tc.expect != nil {
t.Errorf("case[%d]: expected non-nil", i)
}
if r != nil && tc.expect == nil {
t.Errorf("case[%d]: expected nil, got %v", i, r)
}
if r != nil && !reflect.DeepEqual(r, tc.expect) {
t.Errorf("case[%d]: expected %v, got %v", i, tc.expect, r)
}
}
}

View File

@@ -0,0 +1,72 @@
// deepcopy-gen is a tool for auto-generating DeepCopy functions.
//
// Given a list of input directories, it will generate DeepCopy and DeepCopyInto
// methods that efficiently perform a full deep-copy of each type. If these
// already exist (are predefined by the developer), they are used instead of
// generating new ones.
//
// If interfaces are referenced in types, it is expected that corresponding
// DeepCopyInterfaceName methods exist, e.g. DeepCopyObject for runtime.Object.
// These can be predefined by the developer or generated through tags, see below.
// They must be added to the interfaces themselves manually, e.g.
// type Object interface {
// ...
// DeepCopyObject() Object
// }
//
// All generation is governed by comment tags in the source. Any package may
// request DeepCopy generation by including a comment in the file-comments of
// one file, of the form:
// // +bili:deepcopy-gen=package
//
// DeepCopy functions can be generated for individual types, rather than the
// entire package by specifying a comment on the type definion of the form:
// // +bili:deepcopy-gen=true
//
// When generating for a whole package, individual types may opt out of
// DeepCopy generation by specifying a comment on the type definition of the form:
// // +bili:deepcopy-gen=false
//
// Additional DeepCopyInterfaceName methods can be generated by sepcifying a
// comment on the type definition of the form:
// // +bili:deepcopy-gen:interfaces=k8s.io/kubernetes/runtime.Object,k8s.io/kubernetes/runtime.List
// This leads to the generation of DeepCopyObject and DeepCopyList with the given
// interfaces as return types. We say that the tagged type implements deepcopy for the
// interfaces.
//
// The deepcopy funcs for interfaces using "+bili:deepcopy-gen:interfaces" use the pointer
// of the type as receiver. For those special cases where the non-pointer object should
// implement the interface, this can be done with:
// // +bili:deepcopy-gen:nonpointer-interfaces=true
package main
import (
"flag"
"go-common/app/tool/gengo/args"
"go-common/app/tool/gengo/cmd/deepcopy-gen/generators"
"github.com/golang/glog"
)
func main() {
arguments := args.Default()
// Override defaults.
arguments.OutputFileBaseName = "deepcopy_generated"
// Custom args.
customArgs := &generators.CustomArgs{}
flag.CommandLine.Var(&customArgs.BoundingDirs, "bounding-dirs", "Comma-separated list of import paths which bound the types for which deep-copies will be generated.")
arguments.CustomArgs = customArgs
// Run it.
if err := arguments.Execute(
generators.NameSystems(),
generators.DefaultNameSystem(),
generators.Packages,
); err != nil {
glog.Fatalf("Error: %v", err)
}
glog.V(2).Info("Completed successfully.")
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["string.go"],
importpath = "go-common/app/tool/gengo/cmd/deepcopy-gen/sets",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,187 @@
package sets
import (
"reflect"
"sort"
)
type Empty struct{}
// sets.String is a set of strings, implemented via map[string]struct{} for minimal memory consumption.
type String map[string]Empty
// NewString creates a String from a list of values.
func NewString(items ...string) String {
ss := String{}
ss.Insert(items...)
return ss
}
// StringKeySet creates a String from a keys of a map[string](? extends interface{}).
// If the value passed in is not actually a map, this will panic.
func StringKeySet(theMap interface{}) String {
v := reflect.ValueOf(theMap)
ret := String{}
for _, keyValue := range v.MapKeys() {
ret.Insert(keyValue.Interface().(string))
}
return ret
}
// Insert adds items to the set.
func (s String) Insert(items ...string) {
for _, item := range items {
s[item] = Empty{}
}
}
// Delete removes all items from the set.
func (s String) Delete(items ...string) {
for _, item := range items {
delete(s, item)
}
}
// Has returns true if and only if item is contained in the set.
func (s String) Has(item string) bool {
_, contained := s[item]
return contained
}
// HasAll returns true if and only if all items are contained in the set.
func (s String) HasAll(items ...string) bool {
for _, item := range items {
if !s.Has(item) {
return false
}
}
return true
}
// HasAny returns true if any items are contained in the set.
func (s String) HasAny(items ...string) bool {
for _, item := range items {
if s.Has(item) {
return true
}
}
return false
}
// Difference returns a set of objects that are not in s2
// For example:
// s1 = {a1, a2, a3}
// s2 = {a1, a2, a4, a5}
// s1.Difference(s2) = {a3}
// s2.Difference(s1) = {a4, a5}
func (s String) Difference(s2 String) String {
result := NewString()
for key := range s {
if !s2.Has(key) {
result.Insert(key)
}
}
return result
}
// Union returns a new set which includes items in either s1 or s2.
// For example:
// s1 = {a1, a2}
// s2 = {a3, a4}
// s1.Union(s2) = {a1, a2, a3, a4}
// s2.Union(s1) = {a1, a2, a3, a4}
func (s1 String) Union(s2 String) String {
result := NewString()
for key := range s1 {
result.Insert(key)
}
for key := range s2 {
result.Insert(key)
}
return result
}
// Intersection returns a new set which includes the item in BOTH s1 and s2
// For example:
// s1 = {a1, a2}
// s2 = {a2, a3}
// s1.Intersection(s2) = {a2}
func (s1 String) Intersection(s2 String) String {
var walk, other String
result := NewString()
if s1.Len() < s2.Len() {
walk = s1
other = s2
} else {
walk = s2
other = s1
}
for key := range walk {
if other.Has(key) {
result.Insert(key)
}
}
return result
}
// IsSuperset returns true if and only if s1 is a superset of s2.
func (s1 String) IsSuperset(s2 String) bool {
for item := range s2 {
if !s1.Has(item) {
return false
}
}
return true
}
// Equal returns true if and only if s1 is equal (as a set) to s2.
// Two sets are equal if their membership is identical.
// (In practice, this means same elements, order doesn't matter)
func (s1 String) Equal(s2 String) bool {
return len(s1) == len(s2) && s1.IsSuperset(s2)
}
type sortableSliceOfString []string
func (s sortableSliceOfString) Len() int { return len(s) }
func (s sortableSliceOfString) Less(i, j int) bool { return lessString(s[i], s[j]) }
func (s sortableSliceOfString) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// List returns the contents as a sorted string slice.
func (s String) List() []string {
res := make(sortableSliceOfString, 0, len(s))
for key := range s {
res = append(res, key)
}
sort.Sort(res)
return []string(res)
}
// UnsortedList returns the slice with contents in random order.
func (s String) UnsortedList() []string {
res := make([]string, 0, len(s))
for key := range s {
res = append(res, key)
}
return res
}
// Returns a single element from the set.
func (s String) PopAny() (string, bool) {
for key := range s {
s.Delete(key)
return key, true
}
var zeroValue string
return zeroValue, false
}
// Len returns the size of the set.
func (s String) Len() int {
return len(s)
}
func lessString(lhs, rhs string) bool {
return lhs < rhs
}

View File

@@ -0,0 +1,56 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"default_generator.go",
"default_package.go",
"doc.go",
"error_tracker.go",
"execute.go",
"generator.go",
"import_tracker.go",
"snippet_writer.go",
],
importpath = "go-common/app/tool/gengo/generator",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/gengo/namer:go_default_library",
"//app/tool/gengo/parser:go_default_library",
"//app/tool/gengo/types:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/golang.org/x/tools/imports:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["snippet_writer_test.go"],
tags = ["automanaged"],
deps = [
"//app/tool/gengo/generator:go_default_library",
"//app/tool/gengo/namer:go_default_library",
"//app/tool/gengo/parser:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,47 @@
package generator
import (
"io"
"go-common/app/tool/gengo/namer"
"go-common/app/tool/gengo/types"
)
// consts
const (
GolangFileType = "golang"
)
// DefaultGen implements a do-nothing Generator.
//
// It can be used to implement static content files.
type DefaultGen struct {
// OptionalName, if present, will be used for the generator's name, and
// the filename (with ".go" appended).
OptionalName string
// OptionalBody, if present, will be used as the return from the "Init"
// method. This causes it to be static content for the entire file if
// no other generator touches the file.
OptionalBody []byte
}
func (d DefaultGen) Name() string { return d.OptionalName }
func (d DefaultGen) Filter(*Context, *types.Type) bool { return true }
func (d DefaultGen) Namers(*Context) namer.NameSystems { return nil }
func (d DefaultGen) Imports(*Context) []string { return []string{} }
func (d DefaultGen) PackageVars(*Context) []string { return []string{} }
func (d DefaultGen) PackageConsts(*Context) []string { return []string{} }
func (d DefaultGen) GenerateType(*Context, *types.Type, io.Writer) error { return nil }
func (d DefaultGen) Filename() string { return d.OptionalName + ".go" }
func (d DefaultGen) FileType() string { return GolangFileType }
func (d DefaultGen) Finalize(*Context, io.Writer) error { return nil }
func (d DefaultGen) Init(c *Context, w io.Writer) error {
_, err := w.Write(d.OptionalBody)
return err
}
var (
_ = Generator(DefaultGen{})
)

View File

@@ -0,0 +1,56 @@
package generator
import (
"go-common/app/tool/gengo/types"
)
// DefaultPackage contains a default implementation of Package.
type DefaultPackage struct {
// Short name of package, used in the "package xxxx" line.
PackageName string
// Import path of the package, and the location on disk of the package.
PackagePath string
// Emitted at the top of every file.
HeaderText []byte
// Emitted only for a "doc.go" file; appended to the HeaderText for
// that file.
PackageDocumentation []byte
// If non-nil, will be called on "Generators"; otherwise, the static
// list will be used. So you should set only one of these two fields.
GeneratorFunc func(*Context) []Generator
GeneratorList []Generator
// Optional; filters the types exposed to the generators.
FilterFunc func(*Context, *types.Type) bool
}
func (d *DefaultPackage) Name() string { return d.PackageName }
func (d *DefaultPackage) Path() string { return d.PackagePath }
func (d *DefaultPackage) Filter(c *Context, t *types.Type) bool {
if d.FilterFunc != nil {
return d.FilterFunc(c, t)
}
return true
}
func (d *DefaultPackage) Generators(c *Context) []Generator {
if d.GeneratorFunc != nil {
return d.GeneratorFunc(c)
}
return d.GeneratorList
}
func (d *DefaultPackage) Header(filename string) []byte {
if filename == "doc.go" {
return append(d.HeaderText, d.PackageDocumentation...)
}
return d.HeaderText
}
var (
_ = Package(&DefaultPackage{})
)

View File

@@ -0,0 +1,15 @@
// Package generator defines an interface for code generators to implement.
//
// To use this package, you'll implement the "Package" and "Generator"
// interfaces; you'll call NewContext to load up the types you want to work
// with, and then you'll call one or more of the Execute methods. See the
// interface definitions for explanations. All output will have gofmt called on
// it automatically, so you do not need to worry about generating correct
// indentation.
//
// This package also exposes SnippetWriter. SnippetWriter reduces to a minimum
// the boilerplate involved in setting up a template from go's text/template
// package. Additionally, all naming systems in the Context will be added as
// functions to the parsed template, so that they can be called directly from
// your templates!
package generator // import "go-common/app/tool/gengo/generator"

View File

@@ -0,0 +1,34 @@
package generator
import (
"io"
)
// ErrorTracker tracks errors to the underlying writer, so that you can ignore
// them until you're ready to return.
type ErrorTracker struct {
io.Writer
err error
}
// NewErrorTracker makes a new error tracker; note that it implements io.Writer.
func NewErrorTracker(w io.Writer) *ErrorTracker {
return &ErrorTracker{Writer: w}
}
// Write intercepts calls to Write.
func (et *ErrorTracker) Write(p []byte) (n int, err error) {
if et.err != nil {
return 0, et.err
}
n, err = et.Writer.Write(p)
if err != nil {
et.err = err
}
return n, err
}
// Error returns nil if no error has occurred, otherwise it returns the error.
func (et *ErrorTracker) Error() error {
return et.err
}

View File

@@ -0,0 +1,301 @@
package generator
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"go-common/app/tool/gengo/namer"
"go-common/app/tool/gengo/types"
"golang.org/x/tools/imports"
"github.com/golang/glog"
)
func errs2strings(errors []error) []string {
strs := make([]string, len(errors))
for i := range errors {
strs[i] = errors[i].Error()
}
return strs
}
// ExecutePackages runs the generators for every package in 'packages'. 'outDir'
// is the base directory in which to place all the generated packages; it
// should be a physical path on disk, not an import path. e.g.:
// /path/to/home/path/to/gopath/src/
// Each package has its import path already, this will be appended to 'outDir'.
func (c *Context) ExecutePackages(outDir string, packages Packages) error {
var errors []error
for _, p := range packages {
if err := c.ExecutePackage(outDir, p); err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
return fmt.Errorf("some packages had errors:\n%v\n", strings.Join(errs2strings(errors), "\n"))
}
return nil
}
// DefaultFileType is
type DefaultFileType struct {
Format func([]byte) ([]byte, error)
Assemble func(io.Writer, *File)
}
// AssembleFile is
func (ft DefaultFileType) AssembleFile(f *File, pathname string) error {
glog.V(2).Infof("Assembling file %q", pathname)
destFile, err := os.Create(pathname)
if err != nil {
return err
}
defer destFile.Close()
b := &bytes.Buffer{}
et := NewErrorTracker(b)
ft.Assemble(et, f)
if et.Error() != nil {
return et.Error()
}
if formatted, err := ft.Format(b.Bytes()); err != nil {
err = fmt.Errorf("unable to format file %q (%v)", pathname, err)
// Write the file anyway, so they can see what's going wrong and fix the generator.
if _, err2 := destFile.Write(b.Bytes()); err2 != nil {
return err2
}
return err
} else {
_, err = destFile.Write(formatted)
return err
}
}
// VerifyFile is
func (ft DefaultFileType) VerifyFile(f *File, pathname string) error {
glog.V(2).Infof("Verifying file %q", pathname)
friendlyName := filepath.Join(f.PackageName, f.Name)
b := &bytes.Buffer{}
et := NewErrorTracker(b)
ft.Assemble(et, f)
if et.Error() != nil {
return et.Error()
}
formatted, err := ft.Format(b.Bytes())
if err != nil {
return fmt.Errorf("unable to format the output for %q: %v", friendlyName, err)
}
existing, err := ioutil.ReadFile(pathname)
if err != nil {
return fmt.Errorf("unable to read file %q for comparison: %v", friendlyName, err)
}
if bytes.Compare(formatted, existing) == 0 {
return nil
}
// Be nice and find the first place where they differ
i := 0
for i < len(formatted) && i < len(existing) && formatted[i] == existing[i] {
i++
}
eDiff, fDiff := existing[i:], formatted[i:]
if len(eDiff) > 100 {
eDiff = eDiff[:100]
}
if len(fDiff) > 100 {
fDiff = fDiff[:100]
}
return fmt.Errorf("output for %q differs; first existing/expected diff: \n %q\n %q", friendlyName, string(eDiff), string(fDiff))
}
func assembleGolangFile(w io.Writer, f *File) {
w.Write(f.Header)
fmt.Fprintf(w, "package %v\n\n", f.PackageName)
if len(f.Imports) > 0 {
fmt.Fprint(w, "import (\n")
for i := range f.Imports {
if strings.Contains(i, "\"") {
// they included quotes, or are using the
// `name "path/to/pkg"` format.
fmt.Fprintf(w, "\t%s\n", i)
} else {
fmt.Fprintf(w, "\t%q\n", i)
}
}
fmt.Fprint(w, ")\n\n")
}
if f.Vars.Len() > 0 {
fmt.Fprint(w, "var (\n")
w.Write(f.Vars.Bytes())
fmt.Fprint(w, ")\n\n")
}
if f.Consts.Len() > 0 {
fmt.Fprint(w, "const (\n")
w.Write(f.Consts.Bytes())
fmt.Fprint(w, ")\n\n")
}
w.Write(f.Body.Bytes())
}
func importsWrapper(src []byte) ([]byte, error) {
return imports.Process("", src, nil)
}
// NewGolangFile is
func NewGolangFile() *DefaultFileType {
return &DefaultFileType{
Format: importsWrapper,
Assemble: assembleGolangFile,
}
}
// format should be one line only, and not end with \n.
func addIndentHeaderComment(b *bytes.Buffer, format string, args ...interface{}) {
if b.Len() > 0 {
fmt.Fprintf(b, "\n// "+format+"\n", args...)
} else {
fmt.Fprintf(b, "// "+format+"\n", args...)
}
}
func (c *Context) filteredBy(f func(*Context, *types.Type) bool) *Context {
c2 := *c
c2.Order = []*types.Type{}
for _, t := range c.Order {
if f(c, t) {
c2.Order = append(c2.Order, t)
}
}
return &c2
}
// make a new context; inheret c.Namers, but add on 'namers'. In case of a name
// collision, the namer in 'namers' wins.
func (c *Context) addNameSystems(namers namer.NameSystems) *Context {
if namers == nil {
return c
}
c2 := *c
// Copy the existing name systems so we don't corrupt a parent context
c2.Namers = namer.NameSystems{}
for k, v := range c.Namers {
c2.Namers[k] = v
}
for name, namer := range namers {
c2.Namers[name] = namer
}
return &c2
}
// ExecutePackage executes a single package. 'outDir' is the base directory in
// which to place the package; it should be a physical path on disk, not an
// import path. e.g.: '/path/to/home/path/to/gopath/src/' The package knows its
// import path already, this will be appended to 'outDir'.
func (c *Context) ExecutePackage(outDir string, p Package) error {
path := filepath.Join(outDir, p.Path())
glog.V(2).Infof("Processing package %q, disk location %q", p.Name(), path)
// Filter out any types the *package* doesn't care about.
packageContext := c.filteredBy(p.Filter)
os.MkdirAll(path, 0755)
files := map[string]*File{}
for _, g := range p.Generators(packageContext) {
// Filter out types the *generator* doesn't care about.
genContext := packageContext.filteredBy(g.Filter)
// Now add any extra name systems defined by this generator
genContext = genContext.addNameSystems(g.Namers(genContext))
fileType := g.FileType()
if len(fileType) == 0 {
return fmt.Errorf("generator %q must specify a file type", g.Name())
}
f := files[g.Filename()]
if f == nil {
// This is the first generator to reference this file, so start it.
f = &File{
Name: g.Filename(),
FileType: fileType,
PackageName: p.Name(),
Header: p.Header(g.Filename()),
Imports: map[string]struct{}{},
}
files[f.Name] = f
} else {
if f.FileType != g.FileType() {
return fmt.Errorf("file %q already has type %q, but generator %q wants to use type %q", f.Name, f.FileType, g.Name(), g.FileType())
}
}
if vars := g.PackageVars(genContext); len(vars) > 0 {
addIndentHeaderComment(&f.Vars, "Package-wide variables from generator %q.", g.Name())
for _, v := range vars {
if _, err := fmt.Fprintf(&f.Vars, "%s\n", v); err != nil {
return err
}
}
}
if consts := g.PackageConsts(genContext); len(consts) > 0 {
addIndentHeaderComment(&f.Consts, "Package-wide consts from generator %q.", g.Name())
for _, v := range consts {
if _, err := fmt.Fprintf(&f.Consts, "%s\n", v); err != nil {
return err
}
}
}
if err := genContext.executeBody(&f.Body, g); err != nil {
return err
}
if imports := g.Imports(genContext); len(imports) > 0 {
for _, i := range imports {
f.Imports[i] = struct{}{}
}
}
}
var errors []error
for _, f := range files {
finalPath := filepath.Join(path, f.Name)
assembler, ok := c.FileTypes[f.FileType]
if !ok {
return fmt.Errorf("the file type %q registered for file %q does not exist in the context", f.FileType, f.Name)
}
var err error
if c.Verify {
err = assembler.VerifyFile(f, finalPath)
} else {
err = assembler.AssembleFile(f, finalPath)
}
if err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
return fmt.Errorf("errors in package %q:\n%v", p.Path(), strings.Join(errs2strings(errors), "\n"))
}
return nil
}
func (c *Context) executeBody(w io.Writer, generator Generator) error {
et := NewErrorTracker(w)
if err := generator.Init(c, et); err != nil {
return err
}
for _, t := range c.Order {
if err := generator.GenerateType(c, t, et); err != nil {
return err
}
}
if err := generator.Finalize(c, et); err != nil {
return err
}
return et.Error()
}

View File

@@ -0,0 +1,205 @@
package generator
import (
"bytes"
"io"
"go-common/app/tool/gengo/namer"
"go-common/app/tool/gengo/parser"
"go-common/app/tool/gengo/types"
)
// Package contains the contract for generating a package.
type Package interface {
// Name returns the package short name.
Name() string
// Path returns the package import path.
Path() string
// Filter should return true if this package cares about this type.
// Otherwise, this type will be omitted from the type ordering for
// this package.
Filter(*Context, *types.Type) bool
// Header should return a header for the file, including comment markers.
// Useful for copyright notices and doc strings. Include an
// autogeneration notice! Do not include the "package x" line.
Header(filename string) []byte
// Generators returns the list of generators for this package. It is
// allowed for more than one generator to write to the same file.
// A Context is passed in case the list of generators depends on the
// input types.
Generators(*Context) []Generator
}
// File is
type File struct {
Name string
FileType string
PackageName string
Header []byte
Imports map[string]struct{}
Vars bytes.Buffer
Consts bytes.Buffer
Body bytes.Buffer
}
// FileType is
type FileType interface {
AssembleFile(f *File, path string) error
VerifyFile(f *File, path string) error
}
// Packages is a list of packages to generate.
type Packages []Package
// Generator is the contract for anything that wants to do auto-generation.
// It's expected that the io.Writers passed to the below functions will be
// ErrorTrackers; this allows implementations to not check for io errors,
// making more readable code.
//
// The call order for the functions that take a Context is:
// 1. Filter() // Subsequent calls see only types that pass this.
// 2. Namers() // Subsequent calls see the namers provided by this.
// 3. PackageVars()
// 4. PackageConsts()
// 5. Init()
// 6. GenerateType() // Called N times, once per type in the context's Order.
// 7. Imports()
//
// You may have multiple generators for the same file.
type Generator interface {
// The name of this generator. Will be included in generated comments.
Name() string
// Filter should return true if this generator cares about this type.
// (otherwise, GenerateType will not be called.)
//
// Filter is called before any of the generator's other functions;
// subsequent calls will get a context with only the types that passed
// this filter.
Filter(*Context, *types.Type) bool
// If this generator needs special namers, return them here. These will
// override the original namers in the context if there is a collision.
// You may return nil if you don't need special names. These names will
// be available in the context passed to the rest of the generator's
// functions.
//
// A use case for this is to return a namer that tracks imports.
Namers(*Context) namer.NameSystems
// Init should write an init function, and any other content that's not
// generated per-type. (It's not intended for generator specific
// initialization! Do that when your Package constructs the
// Generators.)
Init(*Context, io.Writer) error
// Finalize should write finish up functions, and any other content that's not
// generated per-type.
Finalize(*Context, io.Writer) error
// PackageVars should emit an array of variable lines. They will be
// placed in a var ( ... ) block. There's no need to include a leading
// \t or trailing \n.
PackageVars(*Context) []string
// PackageConsts should emit an array of constant lines. They will be
// placed in a const ( ... ) block. There's no need to include a leading
// \t or trailing \n.
PackageConsts(*Context) []string
// GenerateType should emit the code for a particular type.
GenerateType(*Context, *types.Type, io.Writer) error
// Imports should return a list of necessary imports. They will be
// formatted correctly. You do not need to include quotation marks,
// return only the package name; alternatively, you can also return
// imports in the format `name "path/to/pkg"`. Imports will be called
// after Init, PackageVars, PackageConsts, and GenerateType, to allow
// you to keep track of what imports you actually need.
Imports(*Context) []string
// Preferred file name of this generator, not including a path. It is
// allowed for multiple generators to use the same filename, but it's
// up to you to make sure they don't have colliding import names.
// TODO: provide per-file import tracking, removing the requirement
// that generators coordinate..
Filename() string
// A registered file type in the context to generate this file with. If
// the FileType is not found in the context, execution will stop.
FileType() string
}
// Context is global context for individual generators to consume.
type Context struct {
// A map from the naming system to the names for that system. E.g., you
// might have public names and several private naming systems.
Namers namer.NameSystems
// All the types, in case you want to look up something.
Universe types.Universe
// All the user-specified packages. This is after recursive expansion.
Inputs []string
// The canonical ordering of the types (will be filtered by both the
// Package's and Generator's Filter methods).
Order []*types.Type
// A set of types this context can process. If this is empty or nil,
// the default "golang" filetype will be provided.
FileTypes map[string]FileType
// If true, Execute* calls will just verify that the existing output is
// correct. (You may set this after calling NewContext.)
Verify bool
// Allows generators to add packages at runtime.
builder *parser.Builder
}
// NewContext generates a context from the given builder, naming systems, and
// the naming system you wish to construct the canonical ordering from.
func NewContext(b *parser.Builder, nameSystems namer.NameSystems, canonicalOrderName string) (*Context, error) {
universe, err := b.FindTypes()
if err != nil {
return nil, err
}
c := &Context{
Namers: namer.NameSystems{},
Universe: universe,
Inputs: b.FindPackages(),
FileTypes: map[string]FileType{
GolangFileType: NewGolangFile(),
},
builder: b,
}
for name, systemNamer := range nameSystems {
c.Namers[name] = systemNamer
if name == canonicalOrderName {
orderer := namer.Orderer{Namer: systemNamer}
c.Order = orderer.OrderUniverse(universe)
}
}
return c, nil
}
// AddDir adds a Go package to the context. The specified path must be a single
// go package import path. GOPATH, GOROOT, and the location of your go binary
// (`which go`) will all be searched, in the normal Go fashion.
// Deprecated. Please use AddDirectory.
func (ctxt *Context) AddDir(path string) error {
return ctxt.builder.AddDirTo(path, &ctxt.Universe)
}
// AddDirectory adds a Go package to the context. The specified path must be a
// single go package import path. GOPATH, GOROOT, and the location of your go
// binary (`which go`) will all be searched, in the normal Go fashion.
func (ctxt *Context) AddDirectory(path string) (*types.Package, error) {
return ctxt.builder.AddDirectoryTo(path, &ctxt.Universe)
}

View File

@@ -0,0 +1,49 @@
package generator
import (
"strings"
"github.com/golang/glog"
"go-common/app/tool/gengo/namer"
"go-common/app/tool/gengo/types"
)
// NewImportTracker is
func NewImportTracker(typesToAdd ...*types.Type) namer.ImportTracker {
tracker := namer.NewDefaultImportTracker(types.Name{})
tracker.IsInvalidType = func(*types.Type) bool { return false }
tracker.LocalName = func(name types.Name) string { return golangTrackerLocalName(&tracker, name) }
tracker.PrintImport = func(path, name string) string { return name + " \"" + path + "\"" }
tracker.AddTypes(typesToAdd...)
return &tracker
}
func golangTrackerLocalName(tracker namer.ImportTracker, t types.Name) string {
path := t.Package
// Using backslashes in package names causes gengo to produce Go code which
// will not compile with the gc compiler. See the comment on GoSeperator.
if strings.ContainsRune(path, '\\') {
glog.Warningf("Warning: backslash used in import path '%v', this is unsupported.\n", path)
}
dirs := strings.Split(path, namer.GoSeperator)
for n := len(dirs) - 1; n >= 0; n-- {
// follow kube convention of not having anything between directory names
name := strings.Join(dirs[n:], "")
name = strings.Replace(name, "_", "", -1)
// These characters commonly appear in import paths for go
// packages, but aren't legal go names. So we'll sanitize.
name = strings.Replace(name, ".", "", -1)
name = strings.Replace(name, "-", "", -1)
if _, found := tracker.PathOf(name); found {
// This name collides with some other package
continue
}
return name
}
panic("can't find import for " + path)
}

View File

@@ -0,0 +1,140 @@
package generator
import (
"fmt"
"io"
"runtime"
"text/template"
)
// SnippetWriter is an attempt to make the template library usable.
// Methods are chainable, and you don't have to check Error() until you're all
// done.
type SnippetWriter struct {
w io.Writer
context *Context
// Left & right delimiters. text/template defaults to "{{" and "}}"
// which is totally unusable for go code based templates.
left, right string
funcMap template.FuncMap
err error
}
// NewSnippetWriter is
// w is the destination; left and right are the delimiters; @ and $ are both
// reasonable choices.
//
// c is used to make a function for every naming system, to which you can pass
// a type and get the corresponding name.
func NewSnippetWriter(w io.Writer, c *Context, left, right string) *SnippetWriter {
sw := &SnippetWriter{
w: w,
context: c,
left: left,
right: right,
funcMap: template.FuncMap{},
}
for name, namer := range c.Namers {
sw.funcMap[name] = namer.Name
}
return sw
}
// Do parses format and runs args through it. You can have arbitrary logic in
// the format (see the text/template documentation), but consider running many
// short templaces, with ordinary go logic in between--this may be more
// readable. Do is chainable. Any error causes every other call to do to be
// ignored, and the error will be returned by Error(). So you can check it just
// once, at the end of your function.
//
// 'args' can be quite literally anything; read the text/template documentation
// for details. Maps and structs work particularly nicely. Conveniently, the
// types package is designed to have structs that are easily referencable from
// the template language.
//
// Example:
//
// sw := generator.NewSnippetWriter(outBuffer, context, "$", "$")
// sw.Do(`The public type name is: $.type|public$`, map[string]interface{}{"type": t})
// return sw.Error()
//
// Where:
// * "$" starts a template directive
// * "." references the entire thing passed as args
// * "type" therefore sees a map and looks up the key "type"
// * "|" means "pass the thing on the left to the thing on the right"
// * "public" is the name of a naming system, so the SnippetWriter has given
// the template a function called "public" that takes a *types.Type and
// returns the naming system's name. E.g., if the type is "string" this might
// return "String".
// * the second "$" ends the template directive.
//
// The map is actually not necessary. The below does the same thing:
//
// sw.Do(`The public type name is: $.|public$`, t)
//
// You may or may not find it more readable to use the map with a descriptive
// key, but if you want to pass more than one arg, the map or a custom struct
// becomes a requirement. You can do arbitrary logic inside these templates,
// but you should consider doing the logic in go and stitching them together
// for the sake of your readers.
//
// TODO: Change Do() to optionally take a list of pairs of parameters (key, value)
// and have it construct a combined map with that and args.
func (s *SnippetWriter) Do(format string, args interface{}) *SnippetWriter {
if s.err != nil {
return s
}
// Name the template by source file:line so it can be found when
// there's an error.
_, file, line, _ := runtime.Caller(1)
tmpl, err := template.
New(fmt.Sprintf("%s:%d", file, line)).
Delims(s.left, s.right).
Funcs(s.funcMap).
Parse(format)
if err != nil {
s.err = err
return s
}
err = tmpl.Execute(s.w, args)
if err != nil {
s.err = err
}
return s
}
// Args exists to make it convenient to construct arguments for
// SnippetWriter.Do.
type Args map[interface{}]interface{}
// With makes a copy of a and adds the given key, value pair.
func (a Args) With(key, value interface{}) Args {
a2 := Args{key: value}
for k, v := range a {
a2[k] = v
}
return a2
}
// WithArgs makes a copy of a and adds the given arguments.
func (a Args) WithArgs(rhs Args) Args {
a2 := Args{}
for k, v := range rhs {
a2[k] = v
}
for k, v := range a {
a2[k] = v
}
return a2
}
// Out is
func (s *SnippetWriter) Out() io.Writer {
return s.w
}
// Error returns any encountered error.
func (s *SnippetWriter) Error() error {
return s.err
}

View File

@@ -0,0 +1,72 @@
package generator_test
import (
"bytes"
"strings"
"testing"
"go-common/app/tool/gengo/generator"
"go-common/app/tool/gengo/namer"
"go-common/app/tool/gengo/parser"
)
func construct(t *testing.T, files map[string]string) *generator.Context {
b := parser.New()
for name, src := range files {
if err := b.AddFileForTest("/tmp/"+name, name, []byte(src)); err != nil {
t.Fatal(err)
}
}
c, err := generator.NewContext(b, namer.NameSystems{
"public": namer.NewPublicNamer(0),
"private": namer.NewPrivateNamer(0),
}, "public")
if err != nil {
t.Fatal(err)
}
return c
}
func TestSnippetWriter(t *testing.T) {
var structTest = map[string]string{
"base/foo/proto/foo.go": `
package foo
// Blah is a test.
// A test, I tell you.
type Blah struct {
// A is the first field.
A int64 ` + "`" + `json:"a"` + "`" + `
// B is the second field.
// Multiline comments work.
B string ` + "`" + `json:"b"` + "`" + `
}
`,
}
c := construct(t, structTest)
b := &bytes.Buffer{}
err := generator.NewSnippetWriter(b, c, "$", "$").
Do("$.|public$$.|private$", c.Order[0]).
Error()
if err != nil {
t.Errorf("Unexpected error %v", err)
}
if e, a := "Blahblah", b.String(); e != a {
t.Errorf("Expected %q, got %q", e, a)
}
err = generator.NewSnippetWriter(b, c, "$", "$").
Do("$.|public", c.Order[0]).
Error()
if err == nil {
t.Errorf("expected error on invalid template")
} else {
// Dear reader, I apologize for making the worst change
// detection test in the history of ever.
if e, a := "snippet_writer_test.go", err.Error(); !strings.Contains(a, e) {
t.Errorf("Expected %q but didn't find it in %q", e, a)
}
}
}

View File

@@ -0,0 +1,48 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"namer_test.go",
"plural_namer_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = ["//app/tool/gengo/types:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"import_tracker.go",
"namer.go",
"order.go",
"plural_namer.go",
],
importpath = "go-common/app/tool/gengo/namer",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//app/tool/gengo/types:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,15 @@
// Package namer has support for making different type naming systems.
//
// This is because sometimes you want to refer to the literal type, sometimes
// you want to make a name for the thing you're generating, and you want to
// make the name based on the type. For example, if you have `type foo string`,
// you want to be able to generate something like `func FooPrinter(f *foo) {
// Print(string(*f)) }`; that is, you want to refer to a public name, a literal
// name, and the underlying literal name.
//
// This package supports the idea of a "Namer" and a set of "NameSystems" to
// support these use cases.
//
// Additionally, a "RawNamer" can optionally keep track of what needs to be
// imported.
package namer // import "go-common/app/tool/gengo/namer"

View File

@@ -0,0 +1,102 @@
package namer
import (
"sort"
"go-common/app/tool/gengo/types"
)
// DefaultImportTracker is
// ImportTracker may be passed to a namer.RawNamer, to track the imports needed
// for the types it names.
//
// TODO: pay attention to the package name (instead of renaming every package).
type DefaultImportTracker struct {
pathToName map[string]string
// forbidden names are in here. (e.g. "go" is a directory in which
// there is code, but "go" is not a legal name for a package, so we put
// it here to prevent us from naming any package "go")
nameToPath map[string]string
local types.Name
// Returns true if a given types is an invalid type and should be ignored.
IsInvalidType func(*types.Type) bool
// Returns the final local name for the given name
LocalName func(types.Name) string
// Returns the "import" line for a given (path, name).
PrintImport func(string, string) string
}
// NewDefaultImportTracker is
func NewDefaultImportTracker(local types.Name) DefaultImportTracker {
return DefaultImportTracker{
pathToName: map[string]string{},
nameToPath: map[string]string{},
local: local,
}
}
// AddTypes is
func (tracker *DefaultImportTracker) AddTypes(types ...*types.Type) {
for _, t := range types {
tracker.AddType(t)
}
}
// AddType is
func (tracker *DefaultImportTracker) AddType(t *types.Type) {
if tracker.local.Package == t.Name.Package {
return
}
if tracker.IsInvalidType(t) {
if t.Kind == types.Builtin {
return
}
if _, ok := tracker.nameToPath[t.Name.Package]; !ok {
tracker.nameToPath[t.Name.Package] = ""
}
return
}
if len(t.Name.Package) == 0 {
return
}
path := t.Name.Path
if len(path) == 0 {
path = t.Name.Package
}
if _, ok := tracker.pathToName[path]; ok {
return
}
name := tracker.LocalName(t.Name)
tracker.nameToPath[name] = path
tracker.pathToName[path] = name
}
// ImportLines is
func (tracker *DefaultImportTracker) ImportLines() []string {
importPaths := []string{}
for path := range tracker.pathToName {
importPaths = append(importPaths, path)
}
sort.Sort(sort.StringSlice(importPaths))
out := []string{}
for _, path := range importPaths {
out = append(out, tracker.PrintImport(path, tracker.pathToName[path]))
}
return out
}
// LocalNameOf returns the name you would use to refer to the package at the
// specified path within the body of a file.
func (tracker *DefaultImportTracker) LocalNameOf(path string) string {
return tracker.pathToName[path]
}
// PathOf returns the path that a given localName is referring to within the
// body of a file.
func (tracker *DefaultImportTracker) PathOf(localName string) (string, bool) {
name, ok := tracker.nameToPath[localName]
return name, ok
}

View File

@@ -0,0 +1,367 @@
package namer
import (
"path/filepath"
"strings"
"go-common/app/tool/gengo/types"
)
const (
// GoSeperator is used to split go import paths.
// Forward slash is used instead of filepath.Seperator because it is the
// only universally-accepted path delimiter and the only delimiter not
// potentially forbidden by Go compilers. (In particular gc does not allow
// the use of backslashes in import paths.)
// See https://golang.org/ref/spec#Import_declarations.
// See also https://github.com/kubernetes/gengo/issues/83#issuecomment-367040772.
GoSeperator = "/"
)
// IsPrivateGoName returns whether a name is a private Go name.
func IsPrivateGoName(name string) bool {
return len(name) == 0 || strings.ToLower(name[:1]) == name[:1]
}
// NewPublicNamer is a helper function that returns a namer that makes
// CamelCase names. See the NameStrategy struct for an explanation of the
// arguments to this constructor.
func NewPublicNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy {
n := &NameStrategy{
Join: Joiner(IC, IC),
IgnoreWords: map[string]bool{},
PrependPackageNames: prependPackageNames,
}
for _, w := range ignoreWords {
n.IgnoreWords[w] = true
}
return n
}
// NewPrivateNamer is a helper function that returns a namer that makes
// camelCase names. See the NameStrategy struct for an explanation of the
// arguments to this constructor.
func NewPrivateNamer(prependPackageNames int, ignoreWords ...string) *NameStrategy {
n := &NameStrategy{
Join: Joiner(IL, IC),
IgnoreWords: map[string]bool{},
PrependPackageNames: prependPackageNames,
}
for _, w := range ignoreWords {
n.IgnoreWords[w] = true
}
return n
}
// NewRawNamer will return a Namer that makes a name by which you would
// directly refer to a type, optionally keeping track of the import paths
// necessary to reference the names it provides. Tracker may be nil.
// The 'pkg' is the full package name, in which the Namer is used - all
// types from that package will be referenced by just type name without
// referencing the package.
//
// For example, if the type is map[string]int, a raw namer will literally
// return "map[string]int".
//
// Or if the type, in package foo, is "type Bar struct { ... }", then the raw
// namer will return "foo.Bar" as the name of the type, and if 'tracker' was
// not nil, will record that package foo needs to be imported.
func NewRawNamer(pkg string, tracker ImportTracker) *rawNamer {
return &rawNamer{pkg: pkg, tracker: tracker}
}
// Names is a map from Type to name, as defined by some Namer.
type Names map[*types.Type]string
// Namer takes a type, and assigns a name.
//
// The purpose of this complexity is so that you can assign coherent
// side-by-side systems of names for the types. For example, you might want a
// public interface, a private implementation struct, and also to reference
// literally the type name.
//
// Note that it is safe to call your own Name() function recursively to find
// the names of keys, elements, etc. This is because anonymous types can't have
// cycles in their names, and named types don't require the sort of recursion
// that would be problematic.
type Namer interface {
Name(*types.Type) string
}
// NameSystems is a map of a system name to a namer for that system.
type NameSystems map[string]Namer
// NameStrategy is a general Namer. The easiest way to use it is to copy the
// Public/PrivateNamer variables, and modify the members you wish to change.
//
// The Name method produces a name for the given type, of the forms:
// Anonymous types: <Prefix><Type description><Suffix>
// Named types: <Prefix><Optional Prepended Package name(s)><Original name><Suffix>
//
// In all cases, every part of the name is run through the capitalization
// functions.
//
// The IgnoreWords map can be set if you have directory names that are
// semantically meaningless for naming purposes, e.g. "proto".
//
// Prefix and Suffix can be used to disambiguate parallel systems of type
// names. For example, if you want to generate an interface and an
// implementation, you might want to suffix one with "Interface" and the other
// with "Implementation". Another common use-- if you want to generate private
// types, and one of your source types could be "string", you can't use the
// default lowercase private namer. You'll have to add a suffix or prefix.
type NameStrategy struct {
Prefix, Suffix string
Join func(pre string, parts []string, post string) string
// Add non-meaningful package directory names here (e.g. "proto") and
// they will be ignored.
IgnoreWords map[string]bool
// If > 0, prepend exactly that many package directory names (or as
// many as there are). Package names listed in "IgnoreWords" will be
// ignored.
//
// For example, if Ignore words lists "proto" and type Foo is in
// pkg/server/frobbing/proto, then a value of 1 will give a type name
// of FrobbingFoo, 2 gives ServerFrobbingFoo, etc.
PrependPackageNames int
// A cache of names thus far assigned by this namer.
Names
}
// IC ensures the first character is uppercase.
func IC(in string) string {
if in == "" {
return in
}
return strings.ToUpper(in[:1]) + in[1:]
}
// IL ensures the first character is lowercase.
func IL(in string) string {
if in == "" {
return in
}
return strings.ToLower(in[:1]) + in[1:]
}
// Joiner lets you specify functions that preprocess the various components of
// a name before joining them. You can construct e.g. camelCase or CamelCase or
// any other way of joining words. (See the IC and IL convenience functions.)
func Joiner(first, others func(string) string) func(pre string, in []string, post string) string {
return func(pre string, in []string, post string) string {
tmp := []string{others(pre)}
for i := range in {
tmp = append(tmp, others(in[i]))
}
tmp = append(tmp, others(post))
return first(strings.Join(tmp, ""))
}
}
func (ns *NameStrategy) removePrefixAndSuffix(s string) string {
// The join function may have changed capitalization.
lowerIn := strings.ToLower(s)
lowerP := strings.ToLower(ns.Prefix)
lowerS := strings.ToLower(ns.Suffix)
b, e := 0, len(s)
if strings.HasPrefix(lowerIn, lowerP) {
b = len(ns.Prefix)
}
if strings.HasSuffix(lowerIn, lowerS) {
e -= len(ns.Suffix)
}
return s[b:e]
}
var (
importPathNameSanitizer = strings.NewReplacer("-", "_", ".", "")
)
// filters out unwanted directory names and sanitizes remaining names.
func (ns *NameStrategy) filterDirs(path string) []string {
allDirs := strings.Split(path, GoSeperator)
dirs := make([]string, 0, len(allDirs))
for _, p := range allDirs {
if ns.IgnoreWords == nil || !ns.IgnoreWords[p] {
dirs = append(dirs, importPathNameSanitizer.Replace(p))
}
}
return dirs
}
// Name See the comment on NameStrategy.
func (ns *NameStrategy) Name(t *types.Type) string {
if ns.Names == nil {
ns.Names = Names{}
}
if s, ok := ns.Names[t]; ok {
return s
}
if t.Name.Package != "" {
dirs := append(ns.filterDirs(t.Name.Package), t.Name.Name)
i := ns.PrependPackageNames + 1
dn := len(dirs)
if i > dn {
i = dn
}
name := ns.Join(ns.Prefix, dirs[dn-i:], ns.Suffix)
ns.Names[t] = name
return name
}
// Only anonymous types remain.
var name string
switch t.Kind {
case types.Builtin:
name = ns.Join(ns.Prefix, []string{t.Name.Name}, ns.Suffix)
case types.Map:
name = ns.Join(ns.Prefix, []string{
"Map",
ns.removePrefixAndSuffix(ns.Name(t.Key)),
"To",
ns.removePrefixAndSuffix(ns.Name(t.Elem)),
}, ns.Suffix)
case types.Slice:
name = ns.Join(ns.Prefix, []string{
"Slice",
ns.removePrefixAndSuffix(ns.Name(t.Elem)),
}, ns.Suffix)
case types.Pointer:
name = ns.Join(ns.Prefix, []string{
"Pointer",
ns.removePrefixAndSuffix(ns.Name(t.Elem)),
}, ns.Suffix)
case types.Struct:
names := []string{"Struct"}
for _, m := range t.Members {
names = append(names, ns.removePrefixAndSuffix(ns.Name(m.Type)))
}
name = ns.Join(ns.Prefix, names, ns.Suffix)
case types.Chan:
name = ns.Join(ns.Prefix, []string{
"Chan",
ns.removePrefixAndSuffix(ns.Name(t.Elem)),
}, ns.Suffix)
case types.Interface:
// TODO: add to name test
names := []string{"Interface"}
for _, m := range t.Methods {
// TODO: include function signature
names = append(names, m.Name.Name)
}
name = ns.Join(ns.Prefix, names, ns.Suffix)
case types.Func:
// TODO: add to name test
parts := []string{"Func"}
for _, pt := range t.Signature.Parameters {
parts = append(parts, ns.removePrefixAndSuffix(ns.Name(pt)))
}
parts = append(parts, "Returns")
for _, rt := range t.Signature.Results {
parts = append(parts, ns.removePrefixAndSuffix(ns.Name(rt)))
}
name = ns.Join(ns.Prefix, parts, ns.Suffix)
default:
name = "unnameable_" + string(t.Kind)
}
ns.Names[t] = name
return name
}
// ImportTracker allows a raw namer to keep track of the packages needed for
// import. You can implement yourself or use the one in the generation package.
type ImportTracker interface {
AddType(*types.Type)
LocalNameOf(packagePath string) string
PathOf(localName string) (string, bool)
ImportLines() []string
}
type rawNamer struct {
pkg string
tracker ImportTracker
Names
}
// Name makes a name the way you'd write it to literally refer to type t,
// making ordinary assumptions about how you've imported t's package (or using
// r.tracker to specifically track the package imports).
func (r *rawNamer) Name(t *types.Type) string {
if r.Names == nil {
r.Names = Names{}
}
if name, ok := r.Names[t]; ok {
return name
}
if t.Name.Package != "" {
var name string
if r.tracker != nil {
r.tracker.AddType(t)
if t.Name.Package == r.pkg {
name = t.Name.Name
} else {
name = r.tracker.LocalNameOf(t.Name.Package) + "." + t.Name.Name
}
} else {
if t.Name.Package == r.pkg {
name = t.Name.Name
} else {
name = filepath.Base(t.Name.Package) + "." + t.Name.Name
}
}
r.Names[t] = name
return name
}
var name string
switch t.Kind {
case types.Builtin:
name = t.Name.Name
case types.Map:
name = "map[" + r.Name(t.Key) + "]" + r.Name(t.Elem)
case types.Slice:
name = "[]" + r.Name(t.Elem)
case types.Pointer:
name = "*" + r.Name(t.Elem)
case types.Struct:
elems := []string{}
for _, m := range t.Members {
elems = append(elems, m.Name+" "+r.Name(m.Type))
}
name = "struct{" + strings.Join(elems, "; ") + "}"
case types.Chan:
// TODO: include directionality
name = "chan " + r.Name(t.Elem)
case types.Interface:
// TODO: add to name test
elems := []string{}
for _, m := range t.Methods {
// TODO: include function signature
elems = append(elems, m.Name.Name)
}
name = "interface{" + strings.Join(elems, "; ") + "}"
case types.Func:
// TODO: add to name test
params := []string{}
for _, pt := range t.Signature.Parameters {
params = append(params, r.Name(pt))
}
results := []string{}
for _, rt := range t.Signature.Results {
results = append(results, r.Name(rt))
}
name = "func(" + strings.Join(params, ",") + ")"
if len(results) == 1 {
name += " " + results[0]
} else if len(results) > 1 {
name += " (" + strings.Join(results, ",") + ")"
}
default:
name = "unnameable_" + string(t.Kind)
}
r.Names[t] = name
return name
}

View File

@@ -0,0 +1,84 @@
package namer
import (
"reflect"
"testing"
"go-common/app/tool/gengo/types"
)
func TestNameStrategy(t *testing.T) {
u := types.Universe{}
// Add some types.
base := u.Type(types.Name{Package: "foo/bar", Name: "Baz"})
base.Kind = types.Struct
tmp := u.Type(types.Name{Package: "", Name: "[]bar.Baz"})
tmp.Kind = types.Slice
tmp.Elem = base
tmp = u.Type(types.Name{Package: "", Name: "map[string]bar.Baz"})
tmp.Kind = types.Map
tmp.Key = types.String
tmp.Elem = base
tmp = u.Type(types.Name{Package: "foo/other", Name: "Baz"})
tmp.Kind = types.Struct
tmp.Members = []types.Member{{
Embedded: true,
Type: base,
}}
tmp = u.Type(types.Name{Package: "", Name: "chan Baz"})
tmp.Kind = types.Chan
tmp.Elem = base
u.Type(types.Name{Package: "", Name: "string"})
o := Orderer{NewPublicNamer(0)}
order := o.OrderUniverse(u)
orderedNames := make([]string, len(order))
for i, t := range order {
orderedNames[i] = o.Name(t)
}
expect := []string{"Baz", "Baz", "ChanBaz", "MapStringToBaz", "SliceBaz", "String"}
if e, a := expect, orderedNames; !reflect.DeepEqual(e, a) {
t.Errorf("Wanted %#v, got %#v", e, a)
}
o = Orderer{NewRawNamer("my/package", nil)}
order = o.OrderUniverse(u)
orderedNames = make([]string, len(order))
for i, t := range order {
orderedNames[i] = o.Name(t)
}
expect = []string{"[]bar.Baz", "bar.Baz", "chan bar.Baz", "map[string]bar.Baz", "other.Baz", "string"}
if e, a := expect, orderedNames; !reflect.DeepEqual(e, a) {
t.Errorf("Wanted %#v, got %#v", e, a)
}
o = Orderer{NewRawNamer("foo/bar", nil)}
order = o.OrderUniverse(u)
orderedNames = make([]string, len(order))
for i, t := range order {
orderedNames[i] = o.Name(t)
}
expect = []string{"Baz", "[]Baz", "chan Baz", "map[string]Baz", "other.Baz", "string"}
if e, a := expect, orderedNames; !reflect.DeepEqual(e, a) {
t.Errorf("Wanted %#v, got %#v", e, a)
}
o = Orderer{NewPublicNamer(1)}
order = o.OrderUniverse(u)
orderedNames = make([]string, len(order))
for i, t := range order {
orderedNames[i] = o.Name(t)
}
expect = []string{"BarBaz", "ChanBarBaz", "MapStringToBarBaz", "OtherBaz", "SliceBarBaz", "String"}
if e, a := expect, orderedNames; !reflect.DeepEqual(e, a) {
t.Errorf("Wanted %#v, got %#v", e, a)
}
}

View File

@@ -0,0 +1,53 @@
package namer
import (
"sort"
"go-common/app/tool/gengo/types"
)
// Orderer produces an ordering of types given a Namer.
type Orderer struct {
Namer
}
// OrderUniverse assigns a name to every type in the Universe, including Types,
// Functions and Variables, and returns a list sorted by those names.
func (o *Orderer) OrderUniverse(u types.Universe) []*types.Type {
list := tList{
namer: o.Namer,
}
for _, p := range u {
for _, t := range p.Types {
list.types = append(list.types, t)
}
for _, f := range p.Functions {
list.types = append(list.types, f)
}
for _, v := range p.Variables {
list.types = append(list.types, v)
}
}
sort.Sort(list)
return list.types
}
// OrderTypes assigns a name to every type, and returns a list sorted by those
// names.
func (o *Orderer) OrderTypes(typeList []*types.Type) []*types.Type {
list := tList{
namer: o.Namer,
types: typeList,
}
sort.Sort(list)
return list.types
}
type tList struct {
namer Namer
types []*types.Type
}
func (t tList) Len() int { return len(t.types) }
func (t tList) Less(i, j int) bool { return t.namer.Name(t.types[i]) < t.namer.Name(t.types[j]) }
func (t tList) Swap(i, j int) { t.types[i], t.types[j] = t.types[j], t.types[i] }

View File

@@ -0,0 +1,104 @@
package namer
import (
"strings"
"go-common/app/tool/gengo/types"
)
var consonants = "bcdfghjklmnpqrsttvwxyz"
type pluralNamer struct {
// key is the case-sensitive type name, value is the case-insensitive
// intended output.
exceptions map[string]string
finalize func(string) string
}
// NewPublicPluralNamer returns a namer that returns the plural form of the input
// type's name, starting with a uppercase letter.
func NewPublicPluralNamer(exceptions map[string]string) *pluralNamer {
return &pluralNamer{exceptions, IC}
}
// NewPrivatePluralNamer returns a namer that returns the plural form of the input
// type's name, starting with a lowercase letter.
func NewPrivatePluralNamer(exceptions map[string]string) *pluralNamer {
return &pluralNamer{exceptions, IL}
}
// NewAllLowercasePluralNamer returns a namer that returns the plural form of the input
// type's name, with all letters in lowercase.
func NewAllLowercasePluralNamer(exceptions map[string]string) *pluralNamer {
return &pluralNamer{exceptions, strings.ToLower}
}
// Name returns the plural form of the type's name. If the type's name is found
// in the exceptions map, the map value is returned.
func (r *pluralNamer) Name(t *types.Type) string {
singular := t.Name.Name
var plural string
var ok bool
if plural, ok = r.exceptions[singular]; ok {
return r.finalize(plural)
}
if len(singular) < 2 {
return r.finalize(singular)
}
switch rune(singular[len(singular)-1]) {
case 's', 'x', 'z':
plural = esPlural(singular)
case 'y':
sl := rune(singular[len(singular)-2])
if isConsonant(sl) {
plural = iesPlural(singular)
} else {
plural = sPlural(singular)
}
case 'h':
sl := rune(singular[len(singular)-2])
if sl == 'c' || sl == 's' {
plural = esPlural(singular)
} else {
plural = sPlural(singular)
}
case 'e':
sl := rune(singular[len(singular)-2])
if sl == 'f' {
plural = vesPlural(singular[:len(singular)-1])
} else {
plural = sPlural(singular)
}
case 'f':
plural = vesPlural(singular)
default:
plural = sPlural(singular)
}
return r.finalize(plural)
}
func iesPlural(singular string) string {
return singular[:len(singular)-1] + "ies"
}
func vesPlural(singular string) string {
return singular[:len(singular)-1] + "ves"
}
func esPlural(singular string) string {
return singular + "es"
}
func sPlural(singular string) string {
return singular + "s"
}
func isConsonant(char rune) bool {
for _, c := range consonants {
if char == c {
return true
}
}
return false
}

View File

@@ -0,0 +1,107 @@
package namer
import (
"testing"
"go-common/app/tool/gengo/types"
)
func TestPluralNamer(t *testing.T) {
exceptions := map[string]string{
// The type name is already in the plural form
"Endpoints": "endpoints",
}
public := NewPublicPluralNamer(exceptions)
private := NewPrivatePluralNamer(exceptions)
cases := []struct {
typeName string
expectedPrivate string
expectedPublic string
}{
{
"I",
"i",
"I",
},
{
"Pod",
"pods",
"Pods",
},
{
"Entry",
"entries",
"Entries",
},
{
"Endpoints",
"endpoints",
"Endpoints",
},
{
"Bus",
"buses",
"Buses",
},
{
"Fizz",
"fizzes",
"Fizzes",
},
{
"Search",
"searches",
"Searches",
},
{
"Autograph",
"autographs",
"Autographs",
},
{
"Dispatch",
"dispatches",
"Dispatches",
},
{
"Earth",
"earths",
"Earths",
},
{
"City",
"cities",
"Cities",
},
{
"Ray",
"rays",
"Rays",
},
{
"Fountain",
"fountains",
"Fountains",
},
{
"Life",
"lives",
"Lives",
},
{
"Leaf",
"leaves",
"Leaves",
},
}
for _, c := range cases {
testType := &types.Type{Name: types.Name{Name: c.typeName}}
if e, a := c.expectedPrivate, private.Name(testType); e != a {
t.Errorf("Unexpected result from private plural namer. Expected: %s, Got: %s", e, a)
}
if e, a := c.expectedPublic, public.Name(testType); e != a {
t.Errorf("Unexpected result from public plural namer. Expected: %s, Got: %s", e, a)
}
}
}

View File

@@ -0,0 +1,56 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["local_parse_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"parse.go",
],
importpath = "go-common/app/tool/gengo/parser",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//app/tool/gengo/types:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["parse_test.go"],
tags = ["automanaged"],
deps = [
"//app/tool/gengo/args:go_default_library",
"//app/tool/gengo/namer:go_default_library",
"//app/tool/gengo/parser:go_default_library",
"//app/tool/gengo/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,3 @@
// Package parser provides code to parse go files, type-check them, extract the
// types.
package parser // import "go-common/app/tool/gengo/parser"

View File

@@ -0,0 +1,52 @@
package parser
import (
"testing"
)
func TestImportBuildPackage(t *testing.T) {
b := New()
if _, err := b.importBuildPackage("go-common/app/tool/gengo/testdata/fake/dep"); err != nil {
t.Fatal(err)
}
if _, ok := b.buildPackages["go-common/app/tool/gengo/testdata/fake/dep"]; !ok {
t.Errorf("missing expected, but got %v", b.buildPackages)
}
if len(b.buildPackages) > 1 {
// this would happen if the canonicalization failed to normalize the path
// you'd get a go-common/app/tool/gengo/testdata/fake/dep key too
t.Errorf("missing one, but got %v", b.buildPackages)
}
}
func TestCanonicalizeImportPath(t *testing.T) {
tcs := []struct {
name string
input string
output string
}{
{
name: "passthrough",
input: "github.com/foo/bar",
output: "github.com/foo/bar",
},
{
name: "simple",
input: "github.com/foo/vendor/k8s.io/kubernetes/pkg/api",
output: "k8s.io/kubernetes/pkg/api",
},
{
name: "deeper",
input: "github.com/foo/bar/vendor/k8s.io/kubernetes/pkg/api",
output: "k8s.io/kubernetes/pkg/api",
},
}
for _, tc := range tcs {
actual := canonicalizeImportPath(tc.input)
if string(actual) != tc.output {
t.Errorf("%v: expected %q got %q", tc.name, tc.output, actual)
}
}
}

View File

@@ -0,0 +1,798 @@
package parser
import (
"fmt"
"go/ast"
"go/build"
"go/parser"
"go/token"
tc "go/types"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"strings"
"go-common/app/tool/gengo/types"
"github.com/golang/glog"
)
// This clarifies when a pkg path has been canonicalized.
type importPathString string
// Builder lets you add all the go files in all the packages that you care
// about, then constructs the type source data.
type Builder struct {
context *build.Context
// Map of package names to more canonical information about the package.
// This might hold the same value for multiple names, e.g. if someone
// referenced ./pkg/name or in the case of vendoring, which canonicalizes
// differently that what humans would type.
buildPackages map[string]*build.Package
fset *token.FileSet
// map of package path to list of parsed files
parsed map[importPathString][]parsedFile
// map of package path to absolute path (to prevent overlap)
absPaths map[importPathString]string
// Set by typeCheckPackage(), used by importPackage() and friends.
typeCheckedPackages map[importPathString]*tc.Package
// Map of package path to whether the user requested it or it was from
// an import.
userRequested map[importPathString]bool
// All comments from everywhere in every parsed file.
endLineToCommentGroup map[fileLine]*ast.CommentGroup
// map of package to list of packages it imports.
importGraph map[importPathString]map[string]struct{}
}
// parsedFile is for tracking files with name
type parsedFile struct {
name string
file *ast.File
}
// key type for finding comments.
type fileLine struct {
file string
line int
}
// New constructs a new builder.
func New() *Builder {
c := build.Default
if c.GOROOT == "" {
if p, err := exec.Command("which", "go").CombinedOutput(); err == nil {
// The returned string will have some/path/bin/go, so remove the last two elements.
c.GOROOT = filepath.Dir(filepath.Dir(strings.Trim(string(p), "\n")))
} else {
glog.Warningf("Warning: $GOROOT not set, and unable to run `which go` to find it: %v\n", err)
}
}
// Force this to off, since we don't properly parse CGo. All symbols must
// have non-CGo equivalents.
c.CgoEnabled = false
return &Builder{
context: &c,
buildPackages: map[string]*build.Package{},
typeCheckedPackages: map[importPathString]*tc.Package{},
fset: token.NewFileSet(),
parsed: map[importPathString][]parsedFile{},
absPaths: map[importPathString]string{},
userRequested: map[importPathString]bool{},
endLineToCommentGroup: map[fileLine]*ast.CommentGroup{},
importGraph: map[importPathString]map[string]struct{}{},
}
}
// AddBuildTags adds the specified build tags to the parse context.
func (b *Builder) AddBuildTags(tags ...string) {
b.context.BuildTags = append(b.context.BuildTags, tags...)
}
// Get package information from the go/build package. Automatically excludes
// e.g. test files and files for other platforms-- there is quite a bit of
// logic of that nature in the build package.
func (b *Builder) importBuildPackage(dir string) (*build.Package, error) {
if buildPkg, ok := b.buildPackages[dir]; ok {
return buildPkg, nil
}
// This validates the `package foo // github.com/bar/foo` comments.
buildPkg, err := b.importWithMode(dir, build.ImportComment)
if err != nil {
if _, ok := err.(*build.NoGoError); !ok {
return nil, fmt.Errorf("unable to import %q: %v", dir, err)
}
}
if buildPkg == nil {
// Might be an empty directory. Try to just find the dir.
buildPkg, err = b.importWithMode(dir, build.FindOnly)
if err != nil {
return nil, err
}
}
// Remember it under the user-provided name.
glog.V(5).Infof("saving buildPackage %s", dir)
b.buildPackages[dir] = buildPkg
canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath)
if dir != string(canonicalPackage) {
// Since `dir` is not the canonical name, see if we knew it under another name.
if buildPkg, ok := b.buildPackages[string(canonicalPackage)]; ok {
return buildPkg, nil
}
// Must be new, save it under the canonical name, too.
glog.V(5).Infof("saving buildPackage %s", canonicalPackage)
b.buildPackages[string(canonicalPackage)] = buildPkg
}
return buildPkg, nil
}
// AddFileForTest adds a file to the set, without verifying that the provided
// pkg actually exists on disk. The pkg must be of the form "canonical/pkg/path"
// and the path must be the absolute path to the file. Because this bypasses
// the normal recursive finding of package dependencies (on disk), test should
// sort their test files topologically first, so all deps are resolved by the
// time we need them.
func (b *Builder) AddFileForTest(pkg string, path string, src []byte) error {
if err := b.addFile(importPathString(pkg), path, src, true); err != nil {
return err
}
if _, err := b.typeCheckPackage(importPathString(pkg)); err != nil {
return err
}
return nil
}
// addFile adds a file to the set. The pkgPath must be of the form
// "canonical/pkg/path" and the path must be the absolute path to the file. A
// flag indicates whether this file was user-requested or just from following
// the import graph.
func (b *Builder) addFile(pkgPath importPathString, path string, src []byte, userRequested bool) error {
for _, p := range b.parsed[pkgPath] {
if path == p.name {
glog.V(5).Infof("addFile %s %s already parsed, skipping", pkgPath, path)
return nil
}
}
glog.V(6).Infof("addFile %s %s", pkgPath, path)
p, err := parser.ParseFile(b.fset, path, src, parser.DeclarationErrors|parser.ParseComments)
if err != nil {
return err
}
// This is redundant with addDir, but some tests call AddFileForTest, which
// call into here without calling addDir.
b.userRequested[pkgPath] = userRequested || b.userRequested[pkgPath]
b.parsed[pkgPath] = append(b.parsed[pkgPath], parsedFile{path, p})
for _, c := range p.Comments {
position := b.fset.Position(c.End())
b.endLineToCommentGroup[fileLine{position.Filename, position.Line}] = c
}
// We have to get the packages from this specific file, in case the
// user added individual files instead of entire directories.
if b.importGraph[pkgPath] == nil {
b.importGraph[pkgPath] = map[string]struct{}{}
}
for _, im := range p.Imports {
importedPath := strings.Trim(im.Path.Value, `"`)
b.importGraph[pkgPath][importedPath] = struct{}{}
}
return nil
}
// AddDir adds an entire directory, scanning it for go files. 'dir' should have
// a single go package in it. GOPATH, GOROOT, and the location of your go
// binary (`which go`) will all be searched if dir doesn't literally resolve.
func (b *Builder) AddDir(dir string) error {
_, err := b.importPackage(dir, true)
return err
}
// AddDirRecursive is just like AddDir, but it also recursively adds
// subdirectories; it returns an error only if the path couldn't be resolved;
// any directories recursed into without go source are ignored.
func (b *Builder) AddDirRecursive(dir string) error {
// Add the root.
if _, err := b.importPackage(dir, true); err != nil {
glog.Warningf("Ignoring directory %v: %v", dir, err)
}
// filepath.Walk includes the root dir, but we already did that, so we'll
// remove that prefix and rebuild a package import path.
prefix := b.buildPackages[dir].Dir
fn := func(filePath string, info os.FileInfo, err error) error {
if info != nil && info.IsDir() {
rel := filepath.ToSlash(strings.TrimPrefix(filePath, prefix))
if rel != "" {
// Make a pkg path.
pkg := path.Join(string(canonicalizeImportPath(b.buildPackages[dir].ImportPath)), rel)
// Add it.
if _, err := b.importPackage(pkg, true); err != nil {
glog.Warningf("Ignoring child directory %v: %v", pkg, err)
}
}
}
return nil
}
if err := filepath.Walk(b.buildPackages[dir].Dir, fn); err != nil {
return err
}
return nil
}
// AddDirTo adds an entire directory to a given Universe. Unlike AddDir, this
// processes the package immediately, which makes it safe to use from within a
// generator (rather than just at init time. 'dir' must be a single go package.
// GOPATH, GOROOT, and the location of your go binary (`which go`) will all be
// searched if dir doesn't literally resolve.
// Deprecated. Please use AddDirectoryTo.
func (b *Builder) AddDirTo(dir string, u *types.Universe) error {
// We want all types from this package, as if they were directly added
// by the user. They WERE added by the user, in effect.
if _, err := b.importPackage(dir, true); err != nil {
return err
}
return b.findTypesIn(canonicalizeImportPath(b.buildPackages[dir].ImportPath), u)
}
// AddDirectoryTo adds an entire directory to a given Universe. Unlike AddDir,
// this processes the package immediately, which makes it safe to use from
// within a generator (rather than just at init time. 'dir' must be a single go
// package. GOPATH, GOROOT, and the location of your go binary (`which go`)
// will all be searched if dir doesn't literally resolve.
func (b *Builder) AddDirectoryTo(dir string, u *types.Universe) (*types.Package, error) {
// We want all types from this package, as if they were directly added
// by the user. They WERE added by the user, in effect.
if _, err := b.importPackage(dir, true); err != nil {
return nil, err
}
path := canonicalizeImportPath(b.buildPackages[dir].ImportPath)
if err := b.findTypesIn(path, u); err != nil {
return nil, err
}
return u.Package(string(path)), nil
}
// The implementation of AddDir. A flag indicates whether this directory was
// user-requested or just from following the import graph.
func (b *Builder) addDir(dir string, userRequested bool) error {
glog.V(5).Infof("addDir %s", dir)
buildPkg, err := b.importBuildPackage(dir)
if err != nil {
return err
}
canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath)
pkgPath := canonicalPackage
if dir != string(canonicalPackage) {
glog.V(5).Infof("addDir %s, canonical path is %s", dir, pkgPath)
}
// Sanity check the pkg dir has not changed.
if prev, found := b.absPaths[pkgPath]; found {
if buildPkg.Dir != prev {
return fmt.Errorf("package %q (%s) previously resolved to %s", pkgPath, buildPkg.Dir, prev)
}
} else {
b.absPaths[pkgPath] = buildPkg.Dir
}
for _, n := range buildPkg.GoFiles {
if !strings.HasSuffix(n, ".go") {
continue
}
absPath := filepath.Join(buildPkg.Dir, n)
data, err := ioutil.ReadFile(absPath)
if err != nil {
return fmt.Errorf("while loading %q: %v", absPath, err)
}
err = b.addFile(pkgPath, absPath, data, userRequested)
if err != nil {
return fmt.Errorf("while parsing %q: %v", absPath, err)
}
}
return nil
}
// importPackage is a function that will be called by the type check package when it
// needs to import a go package. 'path' is the import path.
func (b *Builder) importPackage(dir string, userRequested bool) (*tc.Package, error) {
glog.V(5).Infof("importPackage %s", dir)
var pkgPath = importPathString(dir)
// Get the canonical path if we can.
if buildPkg := b.buildPackages[dir]; buildPkg != nil {
canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath)
glog.V(5).Infof("importPackage %s, canonical path is %s", dir, canonicalPackage)
pkgPath = canonicalPackage
}
// If we have not seen this before, process it now.
ignoreError := false
if _, found := b.parsed[pkgPath]; !found {
// Ignore errors in paths that we're importing solely because
// they're referenced by other packages.
ignoreError = true
// Add it.
if err := b.addDir(dir, userRequested); err != nil {
return nil, err
}
// Get the canonical path now that it has been added.
if buildPkg := b.buildPackages[dir]; buildPkg != nil {
canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath)
glog.V(5).Infof("importPackage %s, canonical path is %s", dir, canonicalPackage)
pkgPath = canonicalPackage
}
}
// If it was previously known, just check that the user-requestedness hasn't
// changed.
b.userRequested[pkgPath] = userRequested || b.userRequested[pkgPath]
// Run the type checker. We may end up doing this to pkgs that are already
// done, or are in the queue to be done later, but it will short-circuit,
// and we can't miss pkgs that are only depended on.
pkg, err := b.typeCheckPackage(pkgPath)
if err != nil {
switch {
case ignoreError && pkg != nil:
glog.V(2).Infof("type checking encountered some issues in %q, but ignoring.\n", pkgPath)
case !ignoreError && pkg != nil:
glog.V(2).Infof("type checking encountered some errors in %q\n", pkgPath)
return nil, err
default:
return nil, err
}
}
return pkg, nil
}
type importAdapter struct {
b *Builder
}
func (a importAdapter) Import(path string) (*tc.Package, error) {
return a.b.importPackage(path, false)
}
// typeCheckPackage will attempt to return the package even if there are some
// errors, so you may check whether the package is nil or not even if you get
// an error.
func (b *Builder) typeCheckPackage(pkgPath importPathString) (*tc.Package, error) {
glog.V(5).Infof("typeCheckPackage %s", pkgPath)
if pkg, ok := b.typeCheckedPackages[pkgPath]; ok {
if pkg != nil {
glog.V(6).Infof("typeCheckPackage %s already done", pkgPath)
return pkg, nil
}
// We store a nil right before starting work on a package. So
// if we get here and it's present and nil, that means there's
// another invocation of this function on the call stack
// already processing this package.
return nil, fmt.Errorf("circular dependency for %q", pkgPath)
}
parsedFiles, ok := b.parsed[pkgPath]
if !ok {
return nil, fmt.Errorf("No files for pkg %q: %#v", pkgPath, b.parsed)
}
files := make([]*ast.File, len(parsedFiles))
for i := range parsedFiles {
files[i] = parsedFiles[i].file
}
b.typeCheckedPackages[pkgPath] = nil
c := tc.Config{
IgnoreFuncBodies: true,
// Note that importAdapter can call b.importPackage which calls this
// method. So there can't be cycles in the import graph.
Importer: importAdapter{b},
Error: func(err error) {
glog.V(2).Infof("type checker: %v\n", err)
},
}
pkg, err := c.Check(string(pkgPath), b.fset, files, nil)
b.typeCheckedPackages[pkgPath] = pkg // record the result whether or not there was an error
return pkg, err
}
// FindPackages fetches a list of the user-imported packages.
// Note that you need to call b.FindTypes() first.
func (b *Builder) FindPackages() []string {
// Iterate packages in a predictable order.
pkgPaths := []string{}
for k := range b.typeCheckedPackages {
pkgPaths = append(pkgPaths, string(k))
}
sort.Strings(pkgPaths)
result := []string{}
for _, pkgPath := range pkgPaths {
if b.userRequested[importPathString(pkgPath)] {
// Since walkType is recursive, all types that are in packages that
// were directly mentioned will be included. We don't need to
// include all types in all transitive packages, though.
result = append(result, pkgPath)
}
}
return result
}
// FindTypes finalizes the package imports, and searches through all the
// packages for types.
func (b *Builder) FindTypes() (types.Universe, error) {
// Take a snapshot of pkgs to iterate, since this will recursively mutate
// b.parsed. Iterate in a predictable order.
pkgPaths := []string{}
for pkgPath := range b.parsed {
pkgPaths = append(pkgPaths, string(pkgPath))
}
sort.Strings(pkgPaths)
u := types.Universe{}
for _, pkgPath := range pkgPaths {
if err := b.findTypesIn(importPathString(pkgPath), &u); err != nil {
return nil, err
}
}
return u, nil
}
// findTypesIn finalizes the package import and searches through the package
// for types.
func (b *Builder) findTypesIn(pkgPath importPathString, u *types.Universe) error {
glog.V(5).Infof("findTypesIn %s", pkgPath)
pkg := b.typeCheckedPackages[pkgPath]
if pkg == nil {
return fmt.Errorf("findTypesIn(%s): package is not known", pkgPath)
}
if !b.userRequested[pkgPath] {
// Since walkType is recursive, all types that the
// packages they asked for depend on will be included.
// But we don't need to include all types in all
// *packages* they depend on.
glog.V(5).Infof("findTypesIn %s: package is not user requested", pkgPath)
return nil
}
// We're keeping this package. This call will create the record.
u.Package(string(pkgPath)).Name = pkg.Name()
u.Package(string(pkgPath)).Path = pkg.Path()
u.Package(string(pkgPath)).SourcePath = b.absPaths[pkgPath]
for _, f := range b.parsed[pkgPath] {
if _, fileName := filepath.Split(f.name); fileName == "doc.go" {
tp := u.Package(string(pkgPath))
// findTypesIn might be called multiple times. Clean up tp.Comments
// to avoid repeatedly fill same comments to it.
tp.Comments = []string{}
for i := range f.file.Comments {
tp.Comments = append(tp.Comments, splitLines(f.file.Comments[i].Text())...)
}
if f.file.Doc != nil {
tp.DocComments = splitLines(f.file.Doc.Text())
}
}
}
s := pkg.Scope()
for _, n := range s.Names() {
obj := s.Lookup(n)
tn, ok := obj.(*tc.TypeName)
if ok {
t := b.walkType(*u, nil, tn.Type())
c1 := b.priorCommentLines(obj.Pos(), 1)
// c1.Text() is safe if c1 is nil
t.CommentLines = splitLines(c1.Text())
if c1 == nil {
t.SecondClosestCommentLines = splitLines(b.priorCommentLines(obj.Pos(), 2).Text())
} else {
t.SecondClosestCommentLines = splitLines(b.priorCommentLines(c1.List[0].Slash, 2).Text())
}
}
tf, ok := obj.(*tc.Func)
// We only care about functions, not concrete/abstract methods.
if ok && tf.Type() != nil && tf.Type().(*tc.Signature).Recv() == nil {
t := b.addFunction(*u, nil, tf)
c1 := b.priorCommentLines(obj.Pos(), 1)
// c1.Text() is safe if c1 is nil
t.CommentLines = splitLines(c1.Text())
if c1 == nil {
t.SecondClosestCommentLines = splitLines(b.priorCommentLines(obj.Pos(), 2).Text())
} else {
t.SecondClosestCommentLines = splitLines(b.priorCommentLines(c1.List[0].Slash, 2).Text())
}
}
tv, ok := obj.(*tc.Var)
if ok && !tv.IsField() {
b.addVariable(*u, nil, tv)
}
}
importedPkgs := []string{}
for k := range b.importGraph[pkgPath] {
importedPkgs = append(importedPkgs, string(k))
}
sort.Strings(importedPkgs)
for _, p := range importedPkgs {
u.AddImports(string(pkgPath), p)
}
return nil
}
func (b *Builder) importWithMode(dir string, mode build.ImportMode) (*build.Package, error) {
// This is a bit of a hack. The srcDir argument to Import() should
// properly be the dir of the file which depends on the package to be
// imported, so that vendoring can work properly and local paths can
// resolve. We assume that there is only one level of vendoring, and that
// the CWD is inside the GOPATH, so this should be safe. Nobody should be
// using local (relative) paths except on the CLI, so CWD is also
// sufficient.
cwd, err := os.Getwd()
if err != nil {
return nil, fmt.Errorf("unable to get current directory: %v", err)
}
buildPkg, err := b.context.Import(dir, cwd, mode)
if err != nil {
return nil, err
}
return buildPkg, nil
}
// if there's a comment on the line `lines` before pos, return its text, otherwise "".
func (b *Builder) priorCommentLines(pos token.Pos, lines int) *ast.CommentGroup {
position := b.fset.Position(pos)
key := fileLine{position.Filename, position.Line - lines}
return b.endLineToCommentGroup[key]
}
func splitLines(str string) []string {
return strings.Split(strings.TrimRight(str, "\n"), "\n")
}
func tcFuncNameToName(in string) types.Name {
name := strings.TrimPrefix(in, "func ")
nameParts := strings.Split(name, "(")
return tcNameToName(nameParts[0])
}
func tcVarNameToName(in string) types.Name {
nameParts := strings.Split(in, " ")
// nameParts[0] is "var".
// nameParts[2:] is the type of the variable, we ignore it for now.
return tcNameToName(nameParts[1])
}
func tcNameToName(in string) types.Name {
// Detect anonymous type names. (These may have '.' characters because
// embedded types may have packages, so we detect them specially.)
if strings.HasPrefix(in, "struct{") ||
strings.HasPrefix(in, "<-chan") ||
strings.HasPrefix(in, "chan<-") ||
strings.HasPrefix(in, "chan ") ||
strings.HasPrefix(in, "func(") ||
strings.HasPrefix(in, "*") ||
strings.HasPrefix(in, "map[") ||
strings.HasPrefix(in, "[") {
return types.Name{Name: in}
}
// Otherwise, if there are '.' characters present, the name has a
// package path in front.
nameParts := strings.Split(in, ".")
name := types.Name{Name: in}
if n := len(nameParts); n >= 2 {
// The final "." is the name of the type--previous ones must
// have been in the package path.
name.Package, name.Name = strings.Join(nameParts[:n-1], "."), nameParts[n-1]
}
return name
}
func (b *Builder) convertSignature(u types.Universe, t *tc.Signature) *types.Signature {
signature := &types.Signature{}
for i := 0; i < t.Params().Len(); i++ {
signature.Parameters = append(signature.Parameters, b.walkType(u, nil, t.Params().At(i).Type()))
}
for i := 0; i < t.Results().Len(); i++ {
signature.Results = append(signature.Results, b.walkType(u, nil, t.Results().At(i).Type()))
}
if r := t.Recv(); r != nil {
signature.Receiver = b.walkType(u, nil, r.Type())
}
signature.Variadic = t.Variadic()
return signature
}
// walkType adds the type, and any necessary child types.
func (b *Builder) walkType(u types.Universe, useName *types.Name, in tc.Type) *types.Type {
// Most of the cases are underlying types of the named type.
name := tcNameToName(in.String())
if useName != nil {
name = *useName
}
switch t := in.(type) {
case *tc.Struct:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Struct
for i := 0; i < t.NumFields(); i++ {
f := t.Field(i)
m := types.Member{
Name: f.Name(),
Embedded: f.Anonymous(),
Tags: t.Tag(i),
Type: b.walkType(u, nil, f.Type()),
CommentLines: splitLines(b.priorCommentLines(f.Pos(), 1).Text()),
}
out.Members = append(out.Members, m)
}
return out
case *tc.Map:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Map
out.Elem = b.walkType(u, nil, t.Elem())
out.Key = b.walkType(u, nil, t.Key())
return out
case *tc.Pointer:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Pointer
out.Elem = b.walkType(u, nil, t.Elem())
return out
case *tc.Slice:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Slice
out.Elem = b.walkType(u, nil, t.Elem())
return out
case *tc.Array:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Array
out.Elem = b.walkType(u, nil, t.Elem())
// TODO: need to store array length, otherwise raw type name
// cannot be properly written.
return out
case *tc.Chan:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Chan
out.Elem = b.walkType(u, nil, t.Elem())
// TODO: need to store direction, otherwise raw type name
// cannot be properly written.
return out
case *tc.Basic:
out := u.Type(types.Name{
Package: "",
Name: t.Name(),
})
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Unsupported
return out
case *tc.Signature:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Func
out.Signature = b.convertSignature(u, t)
return out
case *tc.Interface:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Interface
t.Complete()
for i := 0; i < t.NumMethods(); i++ {
if out.Methods == nil {
out.Methods = map[string]*types.Type{}
}
out.Methods[t.Method(i).Name()] = b.walkType(u, nil, t.Method(i).Type())
}
return out
case *tc.Named:
var out *types.Type
switch t.Underlying().(type) {
case *tc.Named, *tc.Basic, *tc.Map, *tc.Slice:
name := tcNameToName(t.String())
out = u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Alias
out.Underlying = b.walkType(u, nil, t.Underlying())
default:
// tc package makes everything "named" with an
// underlying anonymous type--we remove that annoying
// "feature" for users. This flattens those types
// together.
name := tcNameToName(t.String())
if out := u.Type(name); out.Kind != types.Unknown {
return out // short circuit if we've already made this.
}
out = b.walkType(u, &name, t.Underlying())
}
// If the underlying type didn't already add methods, add them.
// (Interface types will have already added methods.)
if len(out.Methods) == 0 {
for i := 0; i < t.NumMethods(); i++ {
if out.Methods == nil {
out.Methods = map[string]*types.Type{}
}
out.Methods[t.Method(i).Name()] = b.walkType(u, nil, t.Method(i).Type())
}
}
return out
default:
out := u.Type(name)
if out.Kind != types.Unknown {
return out
}
out.Kind = types.Unsupported
glog.Warningf("Making unsupported type entry %q for: %#v\n", out, t)
return out
}
}
func (b *Builder) addFunction(u types.Universe, useName *types.Name, in *tc.Func) *types.Type {
name := tcFuncNameToName(in.String())
if useName != nil {
name = *useName
}
out := u.Function(name)
out.Kind = types.DeclarationOf
out.Underlying = b.walkType(u, nil, in.Type())
return out
}
func (b *Builder) addVariable(u types.Universe, useName *types.Name, in *tc.Var) *types.Type {
name := tcVarNameToName(in.String())
if useName != nil {
name = *useName
}
out := u.Variable(name)
out.Kind = types.DeclarationOf
out.Underlying = b.walkType(u, nil, in.Type())
return out
}
// canonicalizeImportPath takes an import path and returns the actual package.
// It doesn't support nested vendoring.
func canonicalizeImportPath(importPath string) importPathString {
if !strings.Contains(importPath, "/vendor/") {
return importPathString(importPath)
}
return importPathString(importPath[strings.Index(importPath, "/vendor/")+len("/vendor/"):])
}

View File

@@ -0,0 +1,443 @@
package parser_test
import (
"bytes"
"path"
"path/filepath"
"reflect"
"testing"
"text/template"
"go-common/app/tool/gengo/args"
"go-common/app/tool/gengo/namer"
"go-common/app/tool/gengo/parser"
"go-common/app/tool/gengo/types"
)
func TestRecursive(t *testing.T) {
d := args.Default()
d.InputDirs = []string{"go-common/app/tool/gengo/testdata/a/..."}
b, err := d.NewBuilder()
if err != nil {
t.Fatalf("Fail making builder: %v", err)
}
_, err = b.FindTypes()
if err != nil {
t.Fatalf("Fail finding types: %v", err)
}
foundB := false
for _, p := range b.FindPackages() {
t.Logf("Package: %v", p)
if p == "go-common/app/tool/gengo/testdata/a/b" {
foundB = true
}
}
if !foundB {
t.Errorf("Expected to find packages a and b")
}
}
type file struct {
path string
contents string
}
// Pass files in topological order - deps first!
func construct(t *testing.T, files []file, testNamer namer.Namer) (*parser.Builder, types.Universe, []*types.Type) {
b := parser.New()
for _, f := range files {
if err := b.AddFileForTest(path.Dir(f.path), filepath.FromSlash(f.path), []byte(f.contents)); err != nil {
t.Fatal(err)
}
}
u, err := b.FindTypes()
if err != nil {
t.Fatal(err)
}
orderer := namer.Orderer{Namer: testNamer}
o := orderer.OrderUniverse(u)
return b, u, o
}
func TestBuilder(t *testing.T) {
var testFiles = []file{
{
path: "base/common/proto/common.go", contents: `
package common
type Object struct {
ID int64
}
`,
}, {
path: "base/foo/proto/foo.go", contents: `
package foo
import (
"base/common/proto"
)
type Blah struct {
common.Object
Count int64
Frobbers map[string]*Frobber
Baz []Object
Nickname *string
NumberIsAFavorite map[int]bool
}
type Frobber struct {
Name string
Amount int64
}
type Object struct {
common.Object
}
func AFunc(obj1 common.Object, obj2 Object) Frobber {
}
var AVar Frobber
var (
AnotherVar = Frobber{}
)
`,
},
}
var tmplText = `
package o
{{define "Struct"}}type {{Name .}} interface { {{range $m := .Members}}{{$n := Name $m.Type}}
{{if $m.Embedded}}{{$n}}{{else}}{{$m.Name}}() {{$n}}{{if $m.Type.Elem}}{{else}}
Set{{$m.Name}}({{$n}}){{end}}{{end}}{{end}}
}
{{end}}
{{define "Func"}}{{$s := .Underlying.Signature}}var {{Name .}} func({{range $index,$elem := $s.Parameters}}{{if $index}}, {{end}}{{Raw $elem}}{{end}}) {{if $s.Results|len |gt 1}}({{end}}{{range $index,$elem := $s.Results}}{{if $index}}, {{end}}{{Raw .}}{{end}}{{if $s.Results|len |gt 1}}){{end}} = {{Raw .}}
{{end}}
{{define "Var"}}{{$t := .Underlying}}var {{Name .}} {{Raw $t}} = {{Raw .}}
{{end}}
{{range $t := .}}{{if eq $t.Kind "Struct"}}{{template "Struct" $t}}{{end}}{{end}}
{{range $t := .}}{{if eq $t.Kind "DeclarationOf"}}{{if eq $t.Underlying.Kind "Func"}}{{template "Func" $t}}{{end}}{{end}}{{end}}
{{range $t := .}}{{if eq $t.Kind "DeclarationOf"}}{{if ne $t.Underlying.Kind "Func"}}{{template "Var" $t}}{{end}}{{end}}{{end}}`
var expect = `
package o
type CommonObject interface {
ID() Int64
SetID(Int64)
}
type FooBlah interface {
CommonObject
Count() Int64
SetCount(Int64)
Frobbers() MapStringToPointerFooFrobber
Baz() SliceFooObject
Nickname() PointerString
NumberIsAFavorite() MapIntToBool
}
type FooFrobber interface {
Name() String
SetName(String)
Amount() Int64
SetAmount(Int64)
}
type FooObject interface {
CommonObject
}
var FooAFunc func(proto.Object, proto.Object) proto.Frobber = proto.AFunc
var FooAVar proto.Frobber = proto.AVar
var FooAnotherVar proto.Frobber = proto.AnotherVar
`
testNamer := namer.NewPublicNamer(1, "proto")
rawNamer := namer.NewRawNamer("o", nil)
_, u, o := construct(t, testFiles, testNamer)
t.Logf("\n%v\n\n", o)
args := map[string]interface{}{
"Name": testNamer.Name,
"Raw": rawNamer.Name,
}
tmpl := template.Must(
template.New("").
Funcs(args).
Parse(tmplText),
)
buf := &bytes.Buffer{}
tmpl.Execute(buf, o)
if e, a := expect, buf.String(); e != a {
t.Errorf("Wanted, got:\n%v\n-----\n%v\n", e, a)
}
if p := u.Package("base/foo/proto"); !p.HasImport("base/common/proto") {
t.Errorf("Unexpected lack of import line: %s", p.Imports)
}
}
func TestStructParse(t *testing.T) {
var structTest = file{
path: "base/foo/proto/foo.go",
contents: `
package foo
// Blah is a test.
// A test, I tell you.
type Blah struct {
// A is the first field.
A int64 ` + "`" + `json:"a"` + "`" + `
// B is the second field.
// Multiline comments work.
B string ` + "`" + `json:"b"` + "`" + `
}
`,
}
_, u, o := construct(t, []file{structTest}, namer.NewPublicNamer(0))
t.Logf("%#v", o)
blahT := u.Type(types.Name{Package: "base/foo/proto", Name: "Blah"})
if blahT == nil {
t.Fatal("type not found")
}
if e, a := types.Struct, blahT.Kind; e != a {
t.Errorf("struct kind wrong, wanted %v, got %v", e, a)
}
if e, a := []string{"Blah is a test.", "A test, I tell you."}, blahT.CommentLines; !reflect.DeepEqual(e, a) {
t.Errorf("struct comment wrong, wanted %q, got %q", e, a)
}
m := types.Member{
Name: "B",
Embedded: false,
CommentLines: []string{"B is the second field.", "Multiline comments work."},
Tags: `json:"b"`,
Type: types.String,
}
if e, a := m, blahT.Members[1]; !reflect.DeepEqual(e, a) {
t.Errorf("wanted, got:\n%#v\n%#v", e, a)
}
}
func TestParseSecondClosestCommentLines(t *testing.T) {
const fileName = "base/foo/proto/foo.go"
testCases := []struct {
testFile file
expected []string
}{
{
testFile: file{
path: fileName, contents: `
package foo
// Blah's SecondClosestCommentLines.
// Another line.
// Blah is a test.
// A test, I tell you.
type Blah struct {
a int
}
`},
expected: []string{"Blah's SecondClosestCommentLines.", "Another line."},
},
{
testFile: file{
path: fileName, contents: `
package foo
// Blah's SecondClosestCommentLines.
// Another line.
type Blah struct {
a int
}
`},
expected: []string{"Blah's SecondClosestCommentLines.", "Another line."},
},
}
for _, test := range testCases {
_, u, o := construct(t, []file{test.testFile}, namer.NewPublicNamer(0))
t.Logf("%#v", o)
blahT := u.Type(types.Name{Package: "base/foo/proto", Name: "Blah"})
if e, a := test.expected, blahT.SecondClosestCommentLines; !reflect.DeepEqual(e, a) {
t.Errorf("struct second closest comment wrong, wanted %q, got %q", e, a)
}
}
}
func TestTypeKindParse(t *testing.T) {
var testFiles = []file{
{path: "a/foo.go", contents: "package a\ntype Test string\n"},
{path: "b/foo.go", contents: "package b\ntype Test map[int]string\n"},
{path: "c/foo.go", contents: "package c\ntype Test []string\n"},
{path: "d/foo.go", contents: "package d\ntype Test struct{a int; b struct{a int}; c map[int]string; d *string}\n"},
{path: "e/foo.go", contents: "package e\ntype Test *string\n"},
{path: "f/foo.go", contents: `
package f
import (
"a"
"b"
)
type Test []a.Test
type Test2 *a.Test
type Test3 map[a.Test]b.Test
type Test4 struct {
a struct {a a.Test; b b.Test}
b map[a.Test]b.Test
c *a.Test
d []a.Test
e []string
}
`},
{path: "g/foo.go", contents: `
package g
type Test func(a, b string) (c, d string)
func (t Test) Method(a, b string) (c, d string) { return t(a, b) }
type Interface interface{Method(a, b string) (c, d string)}
`},
}
// Check that the right types are found, and the namers give the expected names.
assertions := []struct {
Package, Name string
k types.Kind
names []string
}{
{
Package: "a", Name: "Test", k: types.Alias,
names: []string{"Test", "ATest", "test", "aTest", "a.Test"},
},
{
Package: "b", Name: "Test", k: types.Map,
names: []string{"Test", "BTest", "test", "bTest", "b.Test"},
},
{
Package: "c", Name: "Test", k: types.Slice,
names: []string{"Test", "CTest", "test", "cTest", "c.Test"},
},
{
Package: "d", Name: "Test", k: types.Struct,
names: []string{"Test", "DTest", "test", "dTest", "d.Test"},
},
{
Package: "e", Name: "Test", k: types.Pointer,
names: []string{"Test", "ETest", "test", "eTest", "e.Test"},
},
{
Package: "f", Name: "Test", k: types.Slice,
names: []string{"Test", "FTest", "test", "fTest", "f.Test"},
},
{
Package: "g", Name: "Test", k: types.Func,
names: []string{"Test", "GTest", "test", "gTest", "g.Test"},
},
{
Package: "g", Name: "Interface", k: types.Interface,
names: []string{"Interface", "GInterface", "interface", "gInterface", "g.Interface"},
},
{
Package: "", Name: "string", k: types.Builtin,
names: []string{"String", "String", "string", "string", "string"},
},
{
Package: "", Name: "int", k: types.Builtin,
names: []string{"Int", "Int", "int", "int", "int"},
},
{
Package: "", Name: "struct{a int}", k: types.Struct,
names: []string{"StructInt", "StructInt", "structInt", "structInt", "struct{a int}"},
},
{
Package: "", Name: "struct{a a.Test; b b.Test}", k: types.Struct,
names: []string{"StructTestTest", "StructATestBTest", "structTestTest", "structATestBTest", "struct{a a.Test; b b.Test}"},
},
{
Package: "", Name: "map[int]string", k: types.Map,
names: []string{"MapIntToString", "MapIntToString", "mapIntToString", "mapIntToString", "map[int]string"},
},
{
Package: "", Name: "map[a.Test]b.Test", k: types.Map,
names: []string{"MapTestToTest", "MapATestToBTest", "mapTestToTest", "mapATestToBTest", "map[a.Test]b.Test"},
},
{
Package: "", Name: "[]string", k: types.Slice,
names: []string{"SliceString", "SliceString", "sliceString", "sliceString", "[]string"},
},
{
Package: "", Name: "[]a.Test", k: types.Slice,
names: []string{"SliceTest", "SliceATest", "sliceTest", "sliceATest", "[]a.Test"},
},
{
Package: "", Name: "*string", k: types.Pointer,
names: []string{"PointerString", "PointerString", "pointerString", "pointerString", "*string"},
},
{
Package: "", Name: "*a.Test", k: types.Pointer,
names: []string{"PointerTest", "PointerATest", "pointerTest", "pointerATest", "*a.Test"},
},
}
namers := []namer.Namer{
namer.NewPublicNamer(0),
namer.NewPublicNamer(1),
namer.NewPrivateNamer(0),
namer.NewPrivateNamer(1),
namer.NewRawNamer("", nil),
}
for nameIndex, namer := range namers {
_, u, _ := construct(t, testFiles, namer)
t.Logf("Found types:\n")
for pkgName, pkg := range u {
for typeName, cur := range pkg.Types {
t.Logf("%q-%q: %s %s", pkgName, typeName, cur.Name, cur.Kind)
}
}
t.Logf("\n\n")
for _, item := range assertions {
n := types.Name{Package: item.Package, Name: item.Name}
thisType := u.Type(n)
if thisType == nil {
t.Errorf("type %s not found", n)
continue
}
underlyingType := thisType
if item.k != types.Alias && thisType.Kind == types.Alias {
underlyingType = thisType.Underlying
if underlyingType == nil {
t.Errorf("underlying type %s not found", n)
continue
}
}
if e, a := item.k, underlyingType.Kind; e != a {
t.Errorf("%v-%s: type kind wrong, wanted %v, got %v (%#v)", nameIndex, n, e, a, underlyingType)
}
if e, a := item.names[nameIndex], namer.Name(thisType); e != a {
t.Errorf("%v-%s: Expected %q, got %q", nameIndex, n, e, a)
}
}
// Also do some one-off checks
gtest := u.Type(types.Name{Package: "g", Name: "Test"})
if e, a := 1, len(gtest.Methods); e != a {
t.Errorf("expected %v but found %v methods: %#v", e, a, gtest)
}
iface := u.Type(types.Name{Package: "g", Name: "Interface"})
if e, a := 1, len(iface.Methods); e != a {
t.Errorf("expected %v but found %v methods: %#v", e, a, iface)
}
}
}

31
app/tool/gengo/testdata/a/BUILD vendored Normal file
View File

@@ -0,0 +1,31 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["a.go"],
importpath = "go-common/app/tool/gengo/testdata/a",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//app/tool/gengo/testdata/a/b:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

3
app/tool/gengo/testdata/a/a.go vendored Normal file
View File

@@ -0,0 +1,3 @@
package a
type A string

28
app/tool/gengo/testdata/a/b/BUILD vendored Normal file
View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["b.go"],
importpath = "go-common/app/tool/gengo/testdata/a/b",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

4
app/tool/gengo/testdata/a/b/b.go vendored Normal file
View File

@@ -0,0 +1,4 @@
package b
// B is a type for testing
type B string

28
app/tool/gengo/testdata/fake/dep/BUILD vendored Normal file
View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["doc.go"],
importpath = "go-common/app/tool/gengo/testdata/fake/dep",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,3 @@
// this exists as a fake vendored dependency so that the vendor path canonicalization
// can be reliably tested
package dep

View File

@@ -0,0 +1,46 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"comments_test.go",
"flatten_test.go",
"types_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"comments.go",
"doc.go",
"flatten.go",
"types.go",
],
importpath = "go-common/app/tool/gengo/types",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,66 @@
// Package types contains go type information, packaged in a way that makes
// auto-generation convenient, whether by template or straight go functions.
package types
import (
"fmt"
"strings"
)
// ExtractCommentTags parses comments for lines of the form:
//
// 'marker' + "key=value".
//
// Values are optional; "" is the default. A tag can be specified more than
// one time and all values are returned. If the resulting map has an entry for
// a key, the value (a slice) is guaranteed to have at least 1 element.
//
// Example: if you pass "+" for 'marker', and the following lines are in
// the comments:
// +foo=value1
// +bar
// +foo=value2
// +baz="qux"
// Then this function will return:
// map[string][]string{"foo":{"value1, "value2"}, "bar": {""}, "baz": {"qux"}}
func ExtractCommentTags(marker string, lines []string) map[string][]string {
out := map[string][]string{}
for _, line := range lines {
line = strings.Trim(line, " ")
if len(line) == 0 {
continue
}
if !strings.HasPrefix(line, marker) {
continue
}
// TODO: we could support multiple values per key if we split on spaces
kv := strings.SplitN(line[len(marker):], "=", 2)
if len(kv) == 2 {
out[kv[0]] = append(out[kv[0]], kv[1])
} else if len(kv) == 1 {
out[kv[0]] = append(out[kv[0]], "")
}
}
return out
}
// ExtractSingleBoolCommentTag parses comments for lines of the form:
//
// 'marker' + "key=value1"
//
// If the tag is not found, the default value is returned. Values are asserted
// to be boolean ("true" or "false"), and any other value will cause an error
// to be returned. If the key has multiple values, the first one will be used.
func ExtractSingleBoolCommentTag(marker string, key string, defaultVal bool, lines []string) (bool, error) {
values := ExtractCommentTags(marker, lines)[key]
if values == nil {
return defaultVal, nil
}
if values[0] == "true" {
return true, nil
}
if values[0] == "false" {
return false, nil
}
return false, fmt.Errorf("tag value for %q is not boolean: %q", key, values[0])
}

View File

@@ -0,0 +1,70 @@
package types
import (
"reflect"
"strings"
"testing"
)
func TestExtractCommentTags(t *testing.T) {
commentLines := []string{
"Human comment that is ignored.",
"+foo=value1",
"+bar",
"+foo=value2",
"+baz=qux,zrb=true",
}
a := ExtractCommentTags("+", commentLines)
e := map[string][]string{
"foo": {"value1", "value2"},
"bar": {""},
"baz": {"qux,zrb=true"},
}
if !reflect.DeepEqual(e, a) {
t.Errorf("Wanted %q, got %q", e, a)
}
}
func TestExtractSingleBoolCommentTag(t *testing.T) {
commentLines := []string{
"Human comment that is ignored.",
"+TRUE=true",
"+FALSE=false",
"+MULTI=true",
"+MULTI=false",
"+MULTI=multi",
"+NOTBOOL=blue",
"+EMPTY",
}
testCases := []struct {
key string
def bool
exp bool
err string // if set, ignore exp.
}{
{"TRUE", false, true, ""},
{"FALSE", true, false, ""},
{"MULTI", false, true, ""},
{"NOTBOOL", false, true, "is not boolean"},
{"EMPTY", false, true, "is not boolean"},
{"ABSENT", true, true, ""},
{"ABSENT", false, false, ""},
}
for i, tc := range testCases {
v, err := ExtractSingleBoolCommentTag("+", tc.key, tc.def, commentLines)
if err != nil && tc.err == "" {
t.Errorf("[%d]: unexpected failure: %v", i, err)
} else if err == nil && tc.err != "" {
t.Errorf("[%d]: expected failure: %v", i, tc.err)
} else if err != nil {
if !strings.Contains(err.Error(), tc.err) {
t.Errorf("[%d]: unexpected error: expected %q, got %q", i, tc.err, err)
}
} else if v != tc.exp {
t.Errorf("[%d]: unexpected value: expected %t, got %t", i, tc.exp, v)
}
}
}

View File

@@ -0,0 +1,3 @@
// Package types contains go type information, packaged in a way that makes
// auto-generation convenient, whether by template or straight go functions.
package types // import "go-common/app/tool/gengo/types"

View File

@@ -0,0 +1,41 @@
package types
// FlattenMembers recursively takes any embedded members and puts them in the
// top level, correctly hiding them if the top level hides them. There must not
// be a cycle-- that implies infinite members.
//
// This is useful for e.g. computing all the valid keys in a json struct,
// properly considering any configuration of embedded structs.
func FlattenMembers(m []Member) []Member {
embedded := []Member{}
normal := []Member{}
type nameInfo struct {
top bool
i int
}
names := map[string]nameInfo{}
for i := range m {
if m[i].Embedded && m[i].Type.Kind == Struct {
embedded = append(embedded, m[i])
} else {
normal = append(normal, m[i])
names[m[i].Name] = nameInfo{true, len(normal) - 1}
}
}
for i := range embedded {
for _, e := range FlattenMembers(embedded[i].Type.Members) {
if info, found := names[e.Name]; found {
if info.top {
continue
}
if n := normal[info.i]; n.Name == e.Name && n.Type == e.Type {
continue
}
panic("conflicting members")
}
normal = append(normal, e)
names[e.Name] = nameInfo{false, len(normal) - 1}
}
}
return normal
}

View File

@@ -0,0 +1,52 @@
package types
import (
"reflect"
"testing"
)
func TestFlatten(t *testing.T) {
mapType := &Type{
Name: Name{Package: "", Name: "map[string]string"},
Kind: Map,
Key: String,
Elem: String,
}
m := []Member{
{
Name: "Baz",
Embedded: true,
Type: &Type{
Name: Name{Package: "pkg", Name: "Baz"},
Kind: Struct,
Members: []Member{
{Name: "Foo", Type: String},
{
Name: "Qux",
Embedded: true,
Type: &Type{
Name: Name{Package: "pkg", Name: "Qux"},
Kind: Struct,
Members: []Member{{Name: "Zot", Type: String}},
},
},
},
},
},
{Name: "Bar", Type: String},
{
Name: "NotSureIfLegal",
Embedded: true,
Type: mapType,
},
}
e := []Member{
{Name: "Bar", Type: String},
{Name: "NotSureIfLegal", Type: mapType, Embedded: true},
{Name: "Foo", Type: String},
{Name: "Zot", Type: String},
}
if a := FlattenMembers(m); !reflect.DeepEqual(e, a) {
t.Errorf("Expected \n%#v\n, got \n%#v\n", e, a)
}
}

View File

@@ -0,0 +1,485 @@
package types
import "strings"
// Ref makes a reference to the given type. It can only be used for e.g.
// passing to namers.
func Ref(packageName, typeName string) *Type {
return &Type{Name: Name{
Name: typeName,
Package: packageName,
}}
}
// Name is a type name may have a package qualifier.
type Name struct {
// Empty if embedded or builtin. This is the package path unless Path is specified.
Package string
// The type name.
Name string
// An optional location of the type definition for languages that can have disjoint
// packages and paths.
Path string
}
// String returns the name formatted as a string.
func (n Name) String() string {
if n.Package == "" {
return n.Name
}
return n.Package + "." + n.Name
}
// ParseFullyQualifiedName parses a name like k8s.io/kubernetes/pkg/api.Pod into a Name.
func ParseFullyQualifiedName(fqn string) Name {
cs := strings.Split(fqn, ".")
pkg := ""
if len(cs) > 1 {
pkg = strings.Join(cs[0:len(cs)-1], ".")
}
return Name{
Name: cs[len(cs)-1],
Package: pkg,
}
}
// Kind is the possible classes of types.
type Kind string
// consts
const (
// Builtin is a primitive, like bool, string, int.
Builtin Kind = "Builtin"
Struct Kind = "Struct"
Map Kind = "Map"
Slice Kind = "Slice"
Pointer Kind = "Pointer"
// Alias is an alias of another type, e.g. in:
// type Foo string
// type Bar Foo
// Bar is an alias of Foo.
//
// In the real go type system, Foo is a "Named" string; but to simplify
// generation, this type system will just say that Foo *is* a builtin.
// We then need "Alias" as a way for us to say that Bar *is* a Foo.
Alias Kind = "Alias"
// Interface is any type that could have differing types at run time.
Interface Kind = "Interface"
// The remaining types are included for completeness, but are not well
// supported.
Array Kind = "Array" // Array is just like slice, but has a fixed length.
Chan Kind = "Chan"
Func Kind = "Func"
// DeclarationOf is different from other Kinds; it indicates that instead of
// representing an actual Type, the type is a declaration of an instance of
// a type. E.g., a top-level function, variable, or constant. See the
// comment for Type.Name for more detail.
DeclarationOf Kind = "DeclarationOf"
Unknown Kind = ""
Unsupported Kind = "Unsupported"
// Protobuf is protobuf type.
Protobuf Kind = "Protobuf"
)
// Package holds package-level information.
// Fields are public, as everything in this package, to enable consumption by
// templates (for example). But it is strongly encouraged for code to build by
// using the provided functions.
type Package struct {
// Canonical name of this package-- its path.
Path string
// The location this package was loaded from
SourcePath string
// Short name of this package; the name that appears in the
// 'package x' line.
Name string
// The comment right above the package declaration in doc.go, if any.
DocComments []string
// All comments from doc.go, if any.
// TODO: remove Comments and use DocComments everywhere.
Comments []string
// Types within this package, indexed by their name (*not* including
// package name).
Types map[string]*Type
// Functions within this package, indexed by their name (*not* including
// package name).
Functions map[string]*Type
// Global variables within this package, indexed by their name (*not* including
// package name).
Variables map[string]*Type
// Packages imported by this package, indexed by (canonicalized)
// package path.
Imports map[string]*Package
}
// Has returns true if the given name references a type known to this package.
func (p *Package) Has(name string) bool {
_, has := p.Types[name]
return has
}
// Type gets the given Type in this Package. If the Type is not already
// defined, this will add it and return the new Type value. The caller is
// expected to finish initialization.
func (p *Package) Type(typeName string) *Type {
if t, ok := p.Types[typeName]; ok {
return t
}
if p.Path == "" {
// Import the standard builtin types!
if t, ok := builtins.Types[typeName]; ok {
p.Types[typeName] = t
return t
}
}
t := &Type{Name: Name{Package: p.Path, Name: typeName}}
p.Types[typeName] = t
return t
}
// Function gets the given function Type in this Package. If the function is
// not already defined, this will add it. If a function is added, it's the
// caller's responsibility to finish construction of the function by setting
// Underlying to the correct type.
func (p *Package) Function(funcName string) *Type {
if t, ok := p.Functions[funcName]; ok {
return t
}
t := &Type{Name: Name{Package: p.Path, Name: funcName}}
t.Kind = DeclarationOf
p.Functions[funcName] = t
return t
}
// Variable gets the given variable Type in this Package. If the variable is
// not already defined, this will add it. If a variable is added, it's the caller's
// responsibility to finish construction of the variable by setting Underlying
// to the correct type.
func (p *Package) Variable(varName string) *Type {
if t, ok := p.Variables[varName]; ok {
return t
}
t := &Type{Name: Name{Package: p.Path, Name: varName}}
t.Kind = DeclarationOf
p.Variables[varName] = t
return t
}
// HasImport returns true if p imports packageName. Package names include the
// package directory.
func (p *Package) HasImport(packageName string) bool {
_, has := p.Imports[packageName]
return has
}
// Universe is a map of all packages. The key is the package name, but you
// should use Package(), Type(), Function(), or Variable() instead of direct
// access.
type Universe map[string]*Package
// Type returns the canonical type for the given fully-qualified name. Builtin
// types will always be found, even if they haven't been explicitly added to
// the map. If a non-existing type is requested, this will create (a marker for)
// it.
func (u Universe) Type(n Name) *Type {
return u.Package(n.Package).Type(n.Name)
}
// Function returns the canonical function for the given fully-qualified name.
// If a non-existing function is requested, this will create (a marker for) it.
// If a marker is created, it's the caller's responsibility to finish
// construction of the function by setting Underlying to the correct type.
func (u Universe) Function(n Name) *Type {
return u.Package(n.Package).Function(n.Name)
}
// Variable returns the canonical variable for the given fully-qualified name.
// If a non-existing variable is requested, this will create (a marker for) it.
// If a marker is created, it's the caller's responsibility to finish
// construction of the variable by setting Underlying to the correct type.
func (u Universe) Variable(n Name) *Type {
return u.Package(n.Package).Variable(n.Name)
}
// AddImports registers import lines for packageName. May be called multiple times.
// You are responsible for canonicalizing all package paths.
func (u Universe) AddImports(packagePath string, importPaths ...string) {
p := u.Package(packagePath)
for _, i := range importPaths {
p.Imports[i] = u.Package(i)
}
}
// Package returns the Package for the given path.
// If a non-existing package is requested, this will create (a marker for) it.
// If a marker is created, it's the caller's responsibility to finish
// construction of the package.
func (u Universe) Package(packagePath string) *Package {
if p, ok := u[packagePath]; ok {
return p
}
p := &Package{
Path: packagePath,
Types: map[string]*Type{},
Functions: map[string]*Type{},
Variables: map[string]*Type{},
Imports: map[string]*Package{},
}
u[packagePath] = p
return p
}
// Type represents a subset of possible go types.
type Type struct {
// There are two general categories of types, those explicitly named
// and those anonymous. Named ones will have a non-empty package in the
// name field.
//
// An exception: If Kind == DeclarationOf, then this name is the name of a
// top-level function, variable, or const, and the type can be found in Underlying.
// We do this to allow the naming system to work against these objects, even
// though they aren't strictly speaking types.
Name Name
// The general kind of this type.
Kind Kind
// If there are comment lines immediately before the type definition,
// they will be recorded here.
CommentLines []string
// If there are comment lines preceding the `CommentLines`, they will be
// recorded here. There are two cases:
// ---
// SecondClosestCommentLines
// a blank line
// CommentLines
// type definition
// ---
//
// or
// ---
// SecondClosestCommentLines
// a blank line
// type definition
// ---
SecondClosestCommentLines []string
// If Kind == Struct
Members []Member
// If Kind == Map, Slice, Pointer, or Chan
Elem *Type
// If Kind == Map, this is the map's key type.
Key *Type
// If Kind == Alias, this is the underlying type.
// If Kind == DeclarationOf, this is the type of the declaration.
Underlying *Type
// If Kind == Interface, this is the set of all required functions.
// Otherwise, if this is a named type, this is the list of methods that
// type has. (All elements will have Kind=="Func")
Methods map[string]*Type
// If Kind == func, this is the signature of the function.
Signature *Signature
// TODO: Add:
// * channel direction
// * array length
}
// String returns the name of the type.
func (t *Type) String() string {
return t.Name.String()
}
// IsPrimitive returns whether the type is a built-in type or is an alias to a
// built-in type. For example: strings and aliases of strings are primitives,
// structs are not.
func (t *Type) IsPrimitive() bool {
if t.Kind == Builtin || (t.Kind == Alias && t.Underlying.Kind == Builtin) {
return true
}
return false
}
// IsAssignable returns whether the type is deep-assignable. For example,
// slices and maps and pointers are shallow copies, but ints and strings are
// complete.
func (t *Type) IsAssignable() bool {
if t.IsPrimitive() {
return true
}
if t.Kind == Struct {
for _, m := range t.Members {
if !m.Type.IsAssignable() {
return false
}
}
return true
}
return false
}
// IsAnonymousStruct returns true if the type is an anonymous struct or an alias
// to an anonymous struct.
func (t *Type) IsAnonymousStruct() bool {
return (t.Kind == Struct && t.Name.Name == "struct{}") || (t.Kind == Alias && t.Underlying.IsAnonymousStruct())
}
// Member is a single struct member
type Member struct {
// The name of the member.
Name string
// If the member is embedded (anonymous) this will be true, and the
// Name will be the type name.
Embedded bool
// If there are comment lines immediately before the member in the type
// definition, they will be recorded here.
CommentLines []string
// If there are tags along with this member, they will be saved here.
Tags string
// The type of this member.
Type *Type
}
// String returns the name and type of the member.
func (m Member) String() string {
return m.Name + " " + m.Type.String()
}
// Signature is a function's signature.
type Signature struct {
// TODO: store the parameter names, not just types.
// If a method of some type, this is the type it's a member of.
Receiver *Type
Parameters []*Type
Results []*Type
// True if the last in parameter is of the form ...T.
Variadic bool
// If there are comment lines immediately before this
// signature/method/function declaration, they will be recorded here.
CommentLines []string
}
// Built in types.
var (
String = &Type{
Name: Name{Name: "string"},
Kind: Builtin,
}
Int64 = &Type{
Name: Name{Name: "int64"},
Kind: Builtin,
}
Int32 = &Type{
Name: Name{Name: "int32"},
Kind: Builtin,
}
Int16 = &Type{
Name: Name{Name: "int16"},
Kind: Builtin,
}
Int = &Type{
Name: Name{Name: "int"},
Kind: Builtin,
}
Uint64 = &Type{
Name: Name{Name: "uint64"},
Kind: Builtin,
}
Uint32 = &Type{
Name: Name{Name: "uint32"},
Kind: Builtin,
}
Uint16 = &Type{
Name: Name{Name: "uint16"},
Kind: Builtin,
}
Uint = &Type{
Name: Name{Name: "uint"},
Kind: Builtin,
}
Uintptr = &Type{
Name: Name{Name: "uintptr"},
Kind: Builtin,
}
Float64 = &Type{
Name: Name{Name: "float64"},
Kind: Builtin,
}
Float32 = &Type{
Name: Name{Name: "float32"},
Kind: Builtin,
}
Float = &Type{
Name: Name{Name: "float"},
Kind: Builtin,
}
Bool = &Type{
Name: Name{Name: "bool"},
Kind: Builtin,
}
Byte = &Type{
Name: Name{Name: "byte"},
Kind: Builtin,
}
builtins = &Package{
Types: map[string]*Type{
"bool": Bool,
"string": String,
"int": Int,
"int64": Int64,
"int32": Int32,
"int16": Int16,
"int8": Byte,
"uint": Uint,
"uint64": Uint64,
"uint32": Uint32,
"uint16": Uint16,
"uint8": Byte,
"uintptr": Uintptr,
"byte": Byte,
"float": Float,
"float64": Float64,
"float32": Float32,
},
Imports: map[string]*Package{},
Path: "",
Name: "",
}
)
// IsInteger is
func IsInteger(t *Type) bool {
switch t {
case Int, Int64, Int32, Int16, Uint, Uint64, Uint32, Uint16, Byte:
return true
default:
return false
}
}

View File

@@ -0,0 +1,194 @@
package types
import (
"testing"
)
func TestGetBuiltin(t *testing.T) {
u := Universe{}
if builtinPkg := u.Package(""); builtinPkg.Has("string") {
t.Errorf("Expected builtin package to not have builtins until they're asked for explicitly. %#v", builtinPkg)
}
s := u.Type(Name{Package: "", Name: "string"})
if s != String {
t.Errorf("Expected canonical string type.")
}
if builtinPkg := u.Package(""); !builtinPkg.Has("string") {
t.Errorf("Expected builtin package to exist and have builtins by default. %#v", builtinPkg)
}
if builtinPkg := u.Package(""); len(builtinPkg.Types) != 1 {
t.Errorf("Expected builtin package to not have builtins until they're asked for explicitly. %#v", builtinPkg)
}
}
func TestGetMarker(t *testing.T) {
u := Universe{}
n := Name{Package: "path/to/package", Name: "Foo"}
f := u.Type(n)
if f == nil || f.Name != n {
t.Errorf("Expected marker type.")
}
}
func Test_Type_IsPrimitive(t *testing.T) {
testCases := []struct {
typ Type
expect bool
}{
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Builtin,
},
expect: true,
},
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Alias,
Underlying: &Type{
Name: Name{Package: "pkgname", Name: "underlying"},
Kind: Builtin,
},
},
expect: true,
},
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Pointer,
Elem: &Type{
Name: Name{Package: "pkgname", Name: "pointee"},
Kind: Builtin,
},
},
expect: false,
},
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Struct,
},
expect: false,
},
}
for i, tc := range testCases {
r := tc.typ.IsPrimitive()
if r != tc.expect {
t.Errorf("case[%d]: expected %t, got %t", i, tc.expect, r)
}
}
}
func Test_Type_IsAssignable(t *testing.T) {
testCases := []struct {
typ Type
expect bool
}{
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Builtin,
},
expect: true,
},
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Alias,
Underlying: &Type{
Name: Name{Package: "pkgname", Name: "underlying"},
Kind: Builtin,
},
},
expect: true,
},
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Pointer,
Elem: &Type{
Name: Name{Package: "pkgname", Name: "pointee"},
Kind: Builtin,
},
},
expect: false,
},
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Struct, // Empty struct
},
expect: true,
},
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Struct,
Members: []Member{
{
Name: "m1",
Type: &Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Builtin,
},
},
{
Name: "m2",
Type: &Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Alias,
Underlying: &Type{
Name: Name{Package: "pkgname", Name: "underlying"},
Kind: Builtin,
},
},
},
{
Name: "m3",
Type: &Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Struct, // Empty struct
},
},
},
},
expect: true,
},
{
typ: Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Struct,
Members: []Member{
{
Name: "m1",
Type: &Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Builtin,
},
},
{
Name: "m2",
Type: &Type{
Name: Name{Package: "pkgname", Name: "typename"},
Kind: Pointer,
Elem: &Type{
Name: Name{Package: "pkgname", Name: "pointee"},
Kind: Builtin,
},
},
},
},
},
expect: false,
},
}
for i, tc := range testCases {
r := tc.typ.IsAssignable()
if r != tc.expect {
t.Errorf("case[%d]: expected %t, got %t", i, tc.expect, r)
}
}
}