diff --git a/language/proto/fileinfo.go b/language/proto/fileinfo.go index d7cbe230b..2388f29c8 100644 --- a/language/proto/fileinfo.go +++ b/language/proto/fileinfo.go @@ -36,6 +36,10 @@ type FileInfo struct { Imports []string HasServices bool + + Services []string + Messages []string + Enums []string } // Option represents a top-level option statement in a .proto file. Only @@ -77,6 +81,32 @@ func protoFileInfo(dir, name string) FileInfo { case match[serviceSubexpIndex] != nil: info.HasServices = true + // match is of the format "service ServiceName {". + // extract just the service name + fullMatch := string(match[serviceSubexpIndex]) + if serviceName, ok := extractObjectName(fullMatch); ok { + info.Services = append(info.Services, serviceName) + } + + case match[messageSubexpIndex] != nil: + // match is of the format "message MessageName {". + // extract just the message name + fullMatch := string(match[messageSubexpIndex]) + if messageName, ok := extractObjectName(fullMatch); ok { + info.Messages = append(info.Messages, messageName) + } + + + + case match[enumSubexpIndex] != nil: + // match is of the format "enum EnumName {". + // extract just the enum name + fullMatch := string(match[enumSubexpIndex]) + if enumName, ok := extractObjectName(fullMatch); ok { + info.Enums = append(info.Enums, enumName) + } + + default: // Comment matched. Nothing to extract. } @@ -92,6 +122,8 @@ const ( optkeySubexpIndex = 3 optvalSubexpIndex = 4 serviceSubexpIndex = 5 + messageSubexpIndex = 6 + enumSubexpIndex = 7 ) // Based on https://developers.google.com/protocol-buffers/docs/reference/proto3-spec @@ -107,8 +139,10 @@ func buildProtoRegexp() *regexp.Regexp { packageStmt := `\bpackage\s*(?P` + fullIdent + `)\s*;` optionStmt := `\boption\s*(?P` + fullIdent + `)\s*=\s*(?P` + strLit + `)\s*;` serviceStmt := `(?Pservice\s+` + ident + `\s*{)` + messageStmt := `(?Pmessage\s+` + ident + `\s*{)` + enumStmt := `(?Penum\s+` + ident + `\s*{)` comment := `//[^\n]*` - protoReSrc := strings.Join([]string{importStmt, packageStmt, optionStmt, serviceStmt, comment}, "|") + protoReSrc := strings.Join([]string{importStmt, packageStmt, optionStmt, serviceStmt, messageStmt, enumStmt, comment}, "|") return regexp.MustCompile(protoReSrc) } @@ -136,3 +170,13 @@ func unquoteProtoString(q []byte) string { } return s } + +func extractObjectName(fullMatch string) (response string, ok bool) { + fields := strings.Fields(fullMatch) + if len(fields) < 2 { + // expect as least two fields. Input is malformed + return "", false + } + + return strings.TrimSuffix(fields[1], "{"), true + } diff --git a/language/proto/fileinfo_test.go b/language/proto/fileinfo_test.go index e78c40b2a..0d2c893cf 100644 --- a/language/proto/fileinfo_test.go +++ b/language/proto/fileinfo_test.go @@ -30,6 +30,8 @@ func TestProtoRegexpGroupNames(t *testing.T) { "optkey": optkeySubexpIndex, "optval": optvalSubexpIndex, "service": serviceSubexpIndex, + "message": messageSubexpIndex, + "enum": enumSubexpIndex, } for name, index := range nameMap { if names[index] != name { @@ -109,6 +111,7 @@ import "second.proto";`, proto: `service ChatService {}`, want: FileInfo{ HasServices: true, + Services: []string{"ChatService"}, }, }, { @@ -117,6 +120,7 @@ import "second.proto";`, proto: `service ChatService {}`, want: FileInfo{ HasServices: true, + Services: []string{"ChatService"}, }, }, { @@ -125,6 +129,7 @@ import "second.proto";`, proto: `service ChatService{}`, want: FileInfo{ HasServices: true, + Services: []string{"ChatService"}, }, }, { @@ -141,6 +146,80 @@ import "second.proto";`, proto: `message serviceAccount { string service = 1; }`, want: FileInfo{ HasServices: false, + Messages: []string{"serviceAccount"}, + }, + },{ + desc: "multiple service names", + name: "service.proto", + proto: `service ServiceA { string service = 1; } + + service ServiceB { string service = 1; } + + service ServiceC{ string service = 1; } + + serviceServiceD { string service = 1; } + + service message { string service = 1; } + + service enum { string service = 1; } + `, + want: FileInfo{ + HasServices: true, + Services: []string{"ServiceA", "ServiceB", "ServiceC", "message", "enum"}, + }, + },{ + desc: "multiple message names", + name: "messages.proto", + proto: `message MessageA { string message = 1; } + + message MessageB { string message = 1; } + + message MessageC{ string message = 1; } + + messageMessageD { string message = 1; } + + message service { string service = 1; } + + message enum { string service = 1; } + `, + want: FileInfo{ + Messages: []string{"MessageA", "MessageB", "MessageC", "service", "enum"}, + }, + },{ + desc: "multiple enum names", + name: "enums.proto", + proto: `enum EnumA { + ENUM_VALUE_A = 1; + ENUM_VALUE_B = 2; + } + + enum EnumB { + ENUM_VALUE_C = 1; + ENUM_VALUE_D = 2; + } + + enum EnumC{ + ENUM_VALUE_E = 1; + ENUM_VALUE_F = 2; + } + + enumEnumD { + ENUM_VALUE_G = 1; + ENUM_VALUE_H = 2; + } + + enum service { + ENUM_VALUE_I = 1; + ENUM_VALUE_J = 2; + } + + enum message { + ENUM_VALUE_K = 1; + ENUM_VALUE_L = 2; + } + `, + want: FileInfo{ + Enums: []string{"EnumA", "EnumB", "EnumC", "service", "message"}, }, }, } { @@ -162,6 +241,9 @@ import "second.proto";`, Imports: got.Imports, Options: got.Options, HasServices: got.HasServices, + Services: got.Services, + Messages: got.Messages, + Enums: got.Enums, } if !reflect.DeepEqual(got, tc.want) { t.Errorf("got %#v; want %#v", got, tc.want) diff --git a/language/proto/generate_test.go b/language/proto/generate_test.go index e5783a4c7..e973b65dd 100644 --- a/language/proto/generate_test.go +++ b/language/proto/generate_test.go @@ -175,6 +175,7 @@ func TestGeneratePackage(t *testing.T) { "protos/sub/sub.proto", }, HasServices: true, + Services: []string{"Quux"}, }, }, Imports: map[string]bool{ @@ -235,6 +236,7 @@ func TestFileModeImports(t *testing.T) { Path: filepath.Join(dir, "foo.proto"), Name: "foo.proto", PackageName: "file_mode", + Messages: []string{"Foo"}, }, }, Imports: map[string]bool{}, @@ -252,6 +254,7 @@ func TestFileModeImports(t *testing.T) { Imports: []string{ "file_mode/foo.proto", }, + Messages: []string{"Bar"}, }, }, // Imports should contain foo.proto. This is specific to file mode.