Repository: googleapis/api-linter
Pull request #431 (2019-12-06 19:08:36)

[feat] Add location for method signature annotations.


Patch:
diff --git a/locations/method_locations.go b/locations/method_locations.go
index 6505734..e325e76 100644
--- a/locations/method_locations.go
+++ b/locations/method_locations.go
@@ -35,3 +35,9 @@ func MethodResponseType(m *desc.MethodDescriptor) *dpb.SourceCodeInfo_Location {
 func MethodHTTPRule(m *desc.MethodDescriptor) *dpb.SourceCodeInfo_Location {
 	return pathLocation(m, 4, int(apb.E_Http.Field)) // MethodDescriptor.options == 4
 }
+
+// MethodSignature returns the precise location of the method's
+// `google.api.method_signature` annotation, if any.
+func MethodSignature(m *desc.MethodDescriptor, index int) *dpb.SourceCodeInfo_Location {
+	return pathLocation(m, 4, int(apb.E_MethodSignature.Field), index) // MethodDescriptor.options == 4
+}
diff --git a/rules/internal/utils/extension.go b/rules/internal/utils/extension.go
index 8e214aa..020edac 100644
--- a/rules/internal/utils/extension.go
+++ b/rules/internal/utils/extension.go
@@ -15,6 +15,8 @@
 package utils
 
 import (
+	"strings"
+
 	"bitbucket.org/creachadair/stringset"
 	"github.com/golang/protobuf/proto"
 	"github.com/jhump/protoreflect/desc"
@@ -45,6 +47,18 @@ func GetOperationInfo(m *desc.MethodDescriptor) *lrpb.OperationInfo {
 	return nil
 }
 
+// GetMethodSignatures returns the `google.api.method_signature` annotations.
+func GetMethodSignatures(m *desc.MethodDescriptor) [][]string {
+	answer := [][]string{}
+	opts := m.GetMethodOptions()
+	if x, err := proto.GetExtension(opts, apb.E_MethodSignature); err == nil {
+		for _, sig := range x.([]string) {
+			answer = append(answer, strings.Split(sig, ","))
+		}
+	}
+	return answer
+}
+
 // GetResource returns the google.api.resource annotation.
 func GetResource(m *desc.MessageDescriptor) *apb.ResourceDescriptor {
 	opts := m.GetMessageOptions()


Test patch:
diff --git a/locations/method_locations_test.go b/locations/method_locations_test.go
index 4a4600b..f1b61f5 100644
--- a/locations/method_locations_test.go
+++ b/locations/method_locations_test.go
@@ -69,3 +69,31 @@ func TestMethodHTTPRule(t *testing.T) {
 		t.Errorf(diff)
 	}
 }
+
+func TestMethodSignature(t *testing.T) {
+	f := parse(t, `
+		import "google/api/client.proto";
+		service Library {
+		  rpc GetBook(GetBookRequest) returns (Book) {
+		    option (google.api.method_signature) = "name";
+		    option (google.api.method_signature) = "name,read_mask";
+		  }
+		}
+		message GetBookRequest{}
+		message Book {}
+	`)
+	for _, test := range []struct {
+		name  string
+		index int
+		want  []int32
+	}{
+		{"First", 0, []int32{5, 4, 50}},
+		{"Second", 1, []int32{6, 4, 60}},
+	} {
+		loc := MethodSignature(f.GetServices()[0].GetMethods()[0], test.index)
+		// Four character span: start line, start column, end line, end column.
+		if diff := cmp.Diff(loc.GetSpan(), test.want); diff != "" {
+			t.Errorf(diff)
+		}
+	}
+}
diff --git a/rules/internal/utils/extension_test.go b/rules/internal/utils/extension_test.go
index 9840801..fb1dc07 100644
--- a/rules/internal/utils/extension_test.go
+++ b/rules/internal/utils/extension_test.go
@@ -55,6 +55,39 @@ func TestGetFieldBehavior(t *testing.T) {
 	}
 }
 
+func TestGetMethodSignatures(t *testing.T) {
+	for _, test := range []struct {
+		name       string
+		want       [][]string
+		Signatures string
+	}{
+		{"Zero", [][]string{}, ""},
+		{"One", [][]string{{"name"}}, `option (google.api.method_signature) = "name";`},
+		{"Two",
+			[][]string{{"name"}, {"name", "read_mask"}},
+			`option (google.api.method_signature) = "name";
+			 option (google.api.method_signature) = "name,read_mask";`,
+		},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/client.proto";
+				service Library {
+					rpc GetBook(GetBookRequest) returns (Book) {
+						{{.Signatures}}
+					}
+				}
+				message Book {}
+				message GetBookRequest {}
+			`, test)
+			method := f.GetServices()[0].GetMethods()[0]
+			if diff := cmp.Diff(GetMethodSignatures(method), test.want); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
+
 func TestGetOperationInfo(t *testing.T) {
 	fd := testutils.ParseProto3String(t, `
 		import "google/longrunning/operations.proto";