Repository: googleapis/api-linter
Pull request #1212 (2023-07-28 19:44:14)

fix: refactor Standard Method message helpers into utils
All Standard Method request message `Is{Method}(Request|Response)Message` helpers moved into to `utils` and exported for other rules to use. This is also necessary for some internal rules.

Some already exported helpers in `aip0162` are left behind, marked deprecated, and made to be passthroughs to the migrated methods. rules should not be importing eachother, it was probably not a good idea to export a function from a rule group package.


Patch:
diff --git a/rules/aip0131/aip0131.go b/rules/aip0131/aip0131.go
index 262e3dc..b4c3e5c 100644
--- a/rules/aip0131/aip0131.go
+++ b/rules/aip0131/aip0131.go
@@ -16,10 +16,7 @@
 package aip0131
 
 import (
-	"regexp"
-
 	"github.com/googleapis/api-linter/lint"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // AddRules accepts a register function and registers each of
@@ -43,12 +40,3 @@ func AddRules(r lint.RuleRegistry) error {
 		unknownFields,
 	)
 }
-
-var (
-	getReqMessageRegexp = regexp.MustCompile("^Get[A-Za-z0-9]*Request$")
-)
-
-// Returns true if this is an AIP-131 Get request message, false otherwise.
-func isGetRequestMessage(m *desc.MessageDescriptor) bool {
-	return getReqMessageRegexp.MatchString(m.GetName())
-}
diff --git a/rules/aip0131/request_name_behavior.go b/rules/aip0131/request_name_behavior.go
index 160b136..2c4818f 100644
--- a/rules/aip0131/request_name_behavior.go
+++ b/rules/aip0131/request_name_behavior.go
@@ -23,7 +23,7 @@ import (
 var requestNameBehavior = &lint.FieldRule{
 	Name: lint.NewRuleName(131, "request-name-behavior"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isGetRequestMessage(f.GetOwner()) && f.GetName() == "name"
+		return utils.IsGetRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
 	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0131/request_name_field.go b/rules/aip0131/request_name_field.go
index cb419a4..c34a3b5 100644
--- a/rules/aip0131/request_name_field.go
+++ b/rules/aip0131/request_name_field.go
@@ -24,7 +24,7 @@ import (
 var requestNameField = &lint.FieldRule{
 	Name: lint.NewRuleName(131, "request-name-field"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isGetRequestMessage(f.GetOwner()) && f.GetName() == "name"
+		return utils.IsGetRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
 	LintField: utils.LintSingularStringField,
 }
diff --git a/rules/aip0131/request_name_reference.go b/rules/aip0131/request_name_reference.go
index 4826dbd..10e4853 100644
--- a/rules/aip0131/request_name_reference.go
+++ b/rules/aip0131/request_name_reference.go
@@ -23,7 +23,7 @@ import (
 var requestNameReference = &lint.FieldRule{
 	Name: lint.NewRuleName(131, "request-name-reference"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isGetRequestMessage(f.GetOwner()) && f.GetName() == "name"
+		return utils.IsGetRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
 	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0131/request_name_reference_type.go b/rules/aip0131/request_name_reference_type.go
index 97f6803..6fd8f24 100644
--- a/rules/aip0131/request_name_reference_type.go
+++ b/rules/aip0131/request_name_reference_type.go
@@ -25,7 +25,7 @@ import (
 var requestNameReferenceType = &lint.FieldRule{
 	Name: lint.NewRuleName(131, "request-name-reference-type"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isGetRequestMessage(f.GetOwner()) && f.GetName() == "name" && utils.GetResourceReference(f) != nil
+		return utils.IsGetRequestMessage(f.GetOwner()) && f.GetName() == "name" && utils.GetResourceReference(f) != nil
 	},
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		if ref := utils.GetResourceReference(f); ref.GetType() == "" {
diff --git a/rules/aip0131/request_name_required.go b/rules/aip0131/request_name_required.go
index 591636a..1b99d76 100644
--- a/rules/aip0131/request_name_required.go
+++ b/rules/aip0131/request_name_required.go
@@ -18,13 +18,14 @@ import (
 	"fmt"
 
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
 // The Get standard method should have some required fields.
 var requestNameRequired = &lint.MessageRule{
 	Name:   lint.NewRuleName(131, "request-name-required"),
-	OnlyIf: isGetRequestMessage,
+	OnlyIf: utils.IsGetRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		if m.FindFieldByName("name") == nil {
 			return []lint.Problem{{
diff --git a/rules/aip0131/request_required_fields.go b/rules/aip0131/request_required_fields.go
index 9458a79..c28aef8 100644
--- a/rules/aip0131/request_required_fields.go
+++ b/rules/aip0131/request_required_fields.go
@@ -26,7 +26,7 @@ import (
 // The get request message should not have unrecognized fields.
 var requestRequiredFields = &lint.MessageRule{
 	Name:   lint.NewRuleName(131, "request-required-fields"),
-	OnlyIf: isGetRequestMessage,
+	OnlyIf: utils.IsGetRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that there are no unexpected fields.
 		allowedRequiredFields := stringset.New("name")
diff --git a/rules/aip0131/request_unknown_fields.go b/rules/aip0131/request_unknown_fields.go
index 6d0dfc2..b896548 100644
--- a/rules/aip0131/request_unknown_fields.go
+++ b/rules/aip0131/request_unknown_fields.go
@@ -20,6 +20,7 @@ import (
 	"bitbucket.org/creachadair/stringset"
 
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
@@ -27,7 +28,7 @@ import (
 var unknownFields = &lint.FieldRule{
 	Name: lint.NewRuleName(131, "request-unknown-fields"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isGetRequestMessage(f.GetOwner())
+		return utils.IsGetRequestMessage(f.GetOwner())
 	},
 	LintField: func(field *desc.FieldDescriptor) []lint.Problem {
 		allowedFields := stringset.New("name", "read_mask", "view")
diff --git a/rules/aip0132/aip0132.go b/rules/aip0132/aip0132.go
index ab0172a..d786652 100644
--- a/rules/aip0132/aip0132.go
+++ b/rules/aip0132/aip0132.go
@@ -16,11 +16,7 @@
 package aip0132
 
 import (
-	"regexp"
-
 	"github.com/googleapis/api-linter/lint"
-	"github.com/googleapis/api-linter/rules/aip0162"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // AddRules adds all of the AIP-132 rules to the provided registry.
@@ -46,18 +42,3 @@ func AddRules(r lint.RuleRegistry) error {
 		unknownFields,
 	)
 }
-
-var (
-	listReqMessageRegexp  = regexp.MustCompile("^List[A-Za-z0-9]*Request$")
-	listRespMessageRegexp = regexp.MustCompile("^List([A-Za-z0-9]*)Response$")
-)
-
-// Return true if this is an AIP-132 List request message, false otherwise.
-func isListRequestMessage(m *desc.MessageDescriptor) bool {
-	return listReqMessageRegexp.MatchString(m.GetName()) && !aip0162.IsListRevisionsRequestMessage(m)
-}
-
-// Return true if this is an AIP-132 List response message, false otherwise.
-func isListResponseMessage(m *desc.MessageDescriptor) bool {
-	return listRespMessageRegexp.MatchString(m.GetName()) && !aip0162.IsListRevisionsResponseMessage(m)
-}
diff --git a/rules/aip0132/request_field_types.go b/rules/aip0132/request_field_types.go
index 4bc2d61..e1b0994 100644
--- a/rules/aip0132/request_field_types.go
+++ b/rules/aip0132/request_field_types.go
@@ -30,7 +30,7 @@ var knownFields = map[string]func(*desc.FieldDescriptor) []lint.Problem{
 var requestFieldTypes = &lint.FieldRule{
 	Name: lint.NewRuleName(132, "request-field-types"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isListRequestMessage(f.GetOwner()) && knownFields[f.GetName()] != nil
+		return utils.IsListRequestMessage(f.GetOwner()) && knownFields[f.GetName()] != nil
 	},
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		return knownFields[f.GetName()](f)
diff --git a/rules/aip0132/request_parent_behavior.go b/rules/aip0132/request_parent_behavior.go
index 9a4731d..732fd43 100644
--- a/rules/aip0132/request_parent_behavior.go
+++ b/rules/aip0132/request_parent_behavior.go
@@ -23,7 +23,7 @@ import (
 var requestParentBehavior = &lint.FieldRule{
 	Name: lint.NewRuleName(132, "request-parent-behavior"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
+		return utils.IsListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
 	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0132/request_parent_field.go b/rules/aip0132/request_parent_field.go
index f72af26..ca681d1 100644
--- a/rules/aip0132/request_parent_field.go
+++ b/rules/aip0132/request_parent_field.go
@@ -25,7 +25,7 @@ import (
 var requestParentField = &lint.FieldRule{
 	Name: lint.NewRuleName(132, "request-parent-field"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
+		return utils.IsListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
 	LintField: utils.LintSingularStringField,
 }
diff --git a/rules/aip0132/request_parent_reference.go b/rules/aip0132/request_parent_reference.go
index ec9c511..e47dbfa 100644
--- a/rules/aip0132/request_parent_reference.go
+++ b/rules/aip0132/request_parent_reference.go
@@ -23,7 +23,7 @@ import (
 var requestParentReference = &lint.FieldRule{
 	Name: lint.NewRuleName(132, "request-parent-reference"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
+		return utils.IsListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
 	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0132/request_parent_required.go b/rules/aip0132/request_parent_required.go
index 368e1de..412f1cd 100644
--- a/rules/aip0132/request_parent_required.go
+++ b/rules/aip0132/request_parent_required.go
@@ -13,7 +13,7 @@ import (
 // The List standard method should contain a parent field.
 var requestParentRequired = &lint.MessageRule{
 	Name:   lint.NewRuleName(132, "request-parent-required"),
-	OnlyIf: isListRequestMessage,
+	OnlyIf: utils.IsListRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		// Rule check: Establish that a `parent` field is present.
 		if m.FindFieldByName("parent") == nil {
diff --git a/rules/aip0132/request_parent_valid_reference.go b/rules/aip0132/request_parent_valid_reference.go
index 30d47f0..a931651 100644
--- a/rules/aip0132/request_parent_valid_reference.go
+++ b/rules/aip0132/request_parent_valid_reference.go
@@ -28,7 +28,7 @@ var requestParentValidReference = &lint.FieldRule{
 	Name: lint.NewRuleName(132, "request-parent-valid-reference"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		ref := utils.GetResourceReference(f)
-		return isListRequestMessage(f.GetOwner()) && f.GetName() == "parent" && ref != nil && ref.GetType() != ""
+		return utils.IsListRequestMessage(f.GetOwner()) && f.GetName() == "parent" && ref != nil && ref.GetType() != ""
 	},
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		p := f.GetParent()
diff --git a/rules/aip0132/request_required_fields.go b/rules/aip0132/request_required_fields.go
index a6ab465..1605a65 100644
--- a/rules/aip0132/request_required_fields.go
+++ b/rules/aip0132/request_required_fields.go
@@ -26,7 +26,7 @@ import (
 // The list request message should not have unrecognized fields.
 var requestRequiredFields = &lint.MessageRule{
 	Name:   lint.NewRuleName(132, "request-required-fields"),
-	OnlyIf: isListRequestMessage,
+	OnlyIf: utils.IsListRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that there are no unexpected fields.
 		allowedRequiredFields := stringset.New("parent")
diff --git a/rules/aip0132/request_show_deleted_required.go b/rules/aip0132/request_show_deleted_required.go
index 3b64d79..97ca30f 100644
--- a/rules/aip0132/request_show_deleted_required.go
+++ b/rules/aip0132/request_show_deleted_required.go
@@ -15,7 +15,7 @@ import (
 var requestShowDeletedRequired = &lint.MessageRule{
 	Name: lint.NewRuleName(132, "request-show-deleted-required"),
 	OnlyIf: func(m *desc.MessageDescriptor) bool {
-		if !isListRequestMessage(m) {
+		if !utils.IsListRequestMessage(m) {
 			return false
 		}
 		// Check for soft-delete support by getting the resource name
diff --git a/rules/aip0132/request_unknown_fields.go b/rules/aip0132/request_unknown_fields.go
index fe75657..961e67b 100644
--- a/rules/aip0132/request_unknown_fields.go
+++ b/rules/aip0132/request_unknown_fields.go
@@ -17,6 +17,7 @@ package aip0132
 import (
 	"bitbucket.org/creachadair/stringset"
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
@@ -36,7 +37,7 @@ var allowedFields = stringset.New(
 var unknownFields = &lint.FieldRule{
 	Name: lint.NewRuleName(132, "request-unknown-fields"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isListRequestMessage(f.GetOwner())
+		return utils.IsListRequestMessage(f.GetOwner())
 	},
 	LintField: func(field *desc.FieldDescriptor) []lint.Problem {
 		if !allowedFields.Contains(field.GetName()) {
diff --git a/rules/aip0132/response_unknown_fields.go b/rules/aip0132/response_unknown_fields.go
index d617f73..5913674 100644
--- a/rules/aip0132/response_unknown_fields.go
+++ b/rules/aip0132/response_unknown_fields.go
@@ -21,7 +21,6 @@ import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
-	"github.com/stoewer/go-strcase"
 )
 
 // The resource itself is not included here, but also permitted.
@@ -36,12 +35,11 @@ var respAllowedFields = stringset.New(
 var responseUnknownFields = &lint.FieldRule{
 	Name: lint.NewRuleName(132, "response-unknown-fields"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isListResponseMessage(f.GetOwner())
+		return utils.IsListResponseMessage(f.GetOwner())
 	},
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		// A repeated variant of the resource should be permitted.
-		msgName := f.GetOwner().GetName()
-		resource := strcase.SnakeCase(listRespMessageRegexp.FindStringSubmatch(msgName)[1])
+		resource := utils.ListResponseResourceName(f.GetOwner())
 		if strings.HasSuffix(resource, "_revisions") {
 			// This is an AIP-162 ListFooRevisions response, which is subtly
 			// different from an AIP-132 List response. We need to modify the RPC
diff --git a/rules/aip0133/aip0133.go b/rules/aip0133/aip0133.go
index 66a126a..0f7956f 100644
--- a/rules/aip0133/aip0133.go
+++ b/rules/aip0133/aip0133.go
@@ -16,7 +16,6 @@
 package aip0133
 
 import (
-	"regexp"
 	"strings"
 
 	"github.com/googleapis/api-linter/lint"
@@ -50,15 +49,6 @@ func AddRules(r lint.RuleRegistry) error {
 	)
 }
 
-var (
-	createReqMessageRegexp = regexp.MustCompile("^Create[A-Za-z0-9]*Request$")
-)
-
-// Returns true if this is an AIP-133 Get request message, false otherwise.
-func isCreateRequestMessage(m *desc.MessageDescriptor) bool {
-	return createReqMessageRegexp.MatchString(m.GetName())
-}
-
 // get resource message type name from method
 func getResourceMsgName(m *desc.MethodDescriptor) string {
 	// Usually the response message will be the resource message, and its name will
diff --git a/rules/aip0133/request_id_field.go b/rules/aip0133/request_id_field.go
index 52088af..bbd8db4 100644
--- a/rules/aip0133/request_id_field.go
+++ b/rules/aip0133/request_id_field.go
@@ -26,7 +26,7 @@ import (
 
 var requestIDField = &lint.MessageRule{
 	Name:   lint.NewRuleName(133, "request-id-field"),
-	OnlyIf: isCreateRequestMessage,
+	OnlyIf: utils.IsCreateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		idField := strcase.SnakeCase(strings.TrimPrefix(strings.TrimSuffix(m.GetName(), "Request"), "Create")) + "_id"
 		if field := m.FindFieldByName(idField); field == nil || utils.GetTypeName(field) != "string" || field.IsRepeated() {
diff --git a/rules/aip0133/request_parent_behavior.go b/rules/aip0133/request_parent_behavior.go
index 6368cd5..389cf3b 100644
--- a/rules/aip0133/request_parent_behavior.go
+++ b/rules/aip0133/request_parent_behavior.go
@@ -23,7 +23,7 @@ import (
 var requestParentBehavior = &lint.FieldRule{
 	Name: lint.NewRuleName(133, "request-parent-behavior"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
+		return utils.IsCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
 	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0133/request_parent_field.go b/rules/aip0133/request_parent_field.go
index bca5f3b..3117f37 100644
--- a/rules/aip0133/request_parent_field.go
+++ b/rules/aip0133/request_parent_field.go
@@ -24,7 +24,7 @@ import (
 var requestParentField = &lint.FieldRule{
 	Name: lint.NewRuleName(133, "request-parent-field"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
+		return utils.IsCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
 	LintField: utils.LintSingularStringField,
 }
diff --git a/rules/aip0133/request_parent_reference.go b/rules/aip0133/request_parent_reference.go
index c966baa..2dc48e1 100644
--- a/rules/aip0133/request_parent_reference.go
+++ b/rules/aip0133/request_parent_reference.go
@@ -23,7 +23,7 @@ import (
 var requestParentReference = &lint.FieldRule{
 	Name: lint.NewRuleName(133, "request-parent-reference"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
+		return utils.IsCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
 	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0133/request_parent_required.go b/rules/aip0133/request_parent_required.go
index 483e964..58bc5c5 100644
--- a/rules/aip0133/request_parent_required.go
+++ b/rules/aip0133/request_parent_required.go
@@ -12,7 +12,7 @@ import (
 
 var requestParentRequired = &lint.MessageRule{
 	Name:   lint.NewRuleName(133, "request-parent-required"),
-	OnlyIf: isCreateRequestMessage,
+	OnlyIf: utils.IsCreateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		if m.FindFieldByName("parent") == nil {
 			// Sanity check: If the resource has a pattern, and that pattern
diff --git a/rules/aip0133/request_resource_behavior.go b/rules/aip0133/request_resource_behavior.go
index b49db30..2d0cbd0 100644
--- a/rules/aip0133/request_resource_behavior.go
+++ b/rules/aip0133/request_resource_behavior.go
@@ -24,7 +24,7 @@ var requestResourceBehavior = &lint.FieldRule{
 	Name: lint.NewRuleName(133, "request-resource-behavior"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		message := f.GetOwner()
-		if !isCreateRequestMessage(message) {
+		if !utils.IsCreateRequestMessage(message) {
 			return false
 		}
 		resourceMsgName := getResourceMsgNameFromReq(message)
diff --git a/rules/aip0133/request_resource_field.go b/rules/aip0133/request_resource_field.go
index 89f59a1..668d5b1 100644
--- a/rules/aip0133/request_resource_field.go
+++ b/rules/aip0133/request_resource_field.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/locations"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 	"github.com/stoewer/go-strcase"
 )
@@ -26,7 +27,7 @@ import (
 // The create request message should have resource field.
 var resourceField = &lint.MessageRule{
 	Name:   lint.NewRuleName(133, "request-resource-field"),
-	OnlyIf: isCreateRequestMessage,
+	OnlyIf: utils.IsCreateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		resourceMsgName := getResourceMsgNameFromReq(m)
 
diff --git a/rules/aip0133/request_unknown_fields.go b/rules/aip0133/request_unknown_fields.go
index 33f4345..1fee8a3 100644
--- a/rules/aip0133/request_unknown_fields.go
+++ b/rules/aip0133/request_unknown_fields.go
@@ -19,6 +19,7 @@ import (
 	"strings"
 
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 	"github.com/jhump/protoreflect/desc/builder"
 	"github.com/stoewer/go-strcase"
@@ -27,7 +28,7 @@ import (
 // The create request message should not have unrecognized fields.
 var unknownFields = &lint.MessageRule{
 	Name:   lint.NewRuleName(133, "request-unknown-fields"),
-	OnlyIf: isCreateRequestMessage,
+	OnlyIf: utils.IsCreateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		resourceMsgName := getResourceMsgNameFromReq(m)
 
diff --git a/rules/aip0134/aip0134.go b/rules/aip0134/aip0134.go
index 690d4ce..46c8143 100644
--- a/rules/aip0134/aip0134.go
+++ b/rules/aip0134/aip0134.go
@@ -16,11 +16,9 @@
 package aip0134
 
 import (
-	"regexp"
 	"strings"
 
 	"github.com/googleapis/api-linter/lint"
-	"github.com/jhump/protoreflect/desc"
 	"github.com/stoewer/go-strcase"
 )
 
@@ -47,15 +45,6 @@ func AddRules(r lint.RuleRegistry) error {
 	)
 }
 
-var (
-	updateReqMessageRegexp = regexp.MustCompile("^Update[A-Za-z0-9]*Request$")
-)
-
-// Returns true if this is an AIP-134 Update request message, false otherwise.
-func isUpdateRequestMessage(m *desc.MessageDescriptor) bool {
-	return updateReqMessageRegexp.MatchString(m.GetName())
-}
-
 func extractResource(reqName string) string {
 	// Strips "Update" from the beginning and "Request" from the end.
 	return reqName[6 : len(reqName)-7]
diff --git a/rules/aip0134/request_allow_missing_field.go b/rules/aip0134/request_allow_missing_field.go
index 0fe5284..cd17783 100644
--- a/rules/aip0134/request_allow_missing_field.go
+++ b/rules/aip0134/request_allow_missing_field.go
@@ -23,7 +23,7 @@ import (
 var allowMissing = &lint.MessageRule{
 	Name: lint.NewRuleName(134, "request-allow-missing-field"),
 	OnlyIf: func(m *desc.MessageDescriptor) bool {
-		if !isUpdateRequestMessage(m) {
+		if !utils.IsUpdateRequestMessage(m) {
 			return false
 		}
 		r := utils.DeclarativeFriendlyResource(m)
diff --git a/rules/aip0134/request_mask_field.go b/rules/aip0134/request_mask_field.go
index 7e99faf..a4b388c 100644
--- a/rules/aip0134/request_mask_field.go
+++ b/rules/aip0134/request_mask_field.go
@@ -23,7 +23,7 @@ import (
 var requestMaskField = &lint.FieldRule{
 	Name: lint.NewRuleName(134, "request-mask-field"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isUpdateRequestMessage(f.GetOwner()) && f.GetName() == "update_mask"
+		return utils.IsUpdateRequestMessage(f.GetOwner()) && f.GetName() == "update_mask"
 	},
 	LintField: utils.LintFieldMask,
 }
diff --git a/rules/aip0134/request_mask_required.go b/rules/aip0134/request_mask_required.go
index 1ca2853..25f7d41 100644
--- a/rules/aip0134/request_mask_required.go
+++ b/rules/aip0134/request_mask_required.go
@@ -16,12 +16,13 @@ package aip0134
 
 import (
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
 var requestMaskRequired = &lint.MessageRule{
 	Name:   lint.NewRuleName(134, "request-mask-required"),
-	OnlyIf: isUpdateRequestMessage,
+	OnlyIf: utils.IsUpdateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		updateMask := m.FindFieldByName("update_mask")
 		if updateMask == nil {
diff --git a/rules/aip0134/request_resource_field.go b/rules/aip0134/request_resource_field.go
index 2919588..3e43328 100644
--- a/rules/aip0134/request_resource_field.go
+++ b/rules/aip0134/request_resource_field.go
@@ -19,6 +19,7 @@ import (
 
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/locations"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 	"github.com/stoewer/go-strcase"
 )
@@ -28,7 +29,7 @@ var requestResourceField = &lint.FieldRule{
 	Name: lint.NewRuleName(134, "request-resource-field"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		message := f.GetOwner()
-		return isUpdateRequestMessage(message) &&
+		return utils.IsUpdateRequestMessage(message) &&
 			f.GetMessageType() != nil &&
 			f.GetMessageType().GetName() == extractResource(message.GetName())
 	},
diff --git a/rules/aip0134/request_resource_required.go b/rules/aip0134/request_resource_required.go
index 681aef5..b30c4c6 100644
--- a/rules/aip0134/request_resource_required.go
+++ b/rules/aip0134/request_resource_required.go
@@ -4,13 +4,14 @@ import (
 	"fmt"
 
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
 // The create request message should have resource field.
 var requestResourceRequired = &lint.MessageRule{
 	Name:   lint.NewRuleName(134, "request-resource-required"),
-	OnlyIf: isUpdateRequestMessage,
+	OnlyIf: utils.IsUpdateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		resourceMsgName := extractResource(m.GetName())
 		for _, fieldDesc := range m.GetFields() {
diff --git a/rules/aip0134/request_unknown_fields.go b/rules/aip0134/request_unknown_fields.go
index 7fdf678..aea0b99 100644
--- a/rules/aip0134/request_unknown_fields.go
+++ b/rules/aip0134/request_unknown_fields.go
@@ -19,13 +19,14 @@ import (
 
 	"bitbucket.org/creachadair/stringset"
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
 // Update methods should not have unrecognized fields.
 var unknownFields = &lint.MessageRule{
 	Name:   lint.NewRuleName(134, "request-unknown-fields"),
-	OnlyIf: isUpdateRequestMessage,
+	OnlyIf: utils.IsUpdateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		resource := extractResource(m.GetName())
 		// Rule check: Establish that there are no unexpected fields.
diff --git a/rules/aip0135/aip0135.go b/rules/aip0135/aip0135.go
index 61d1a06..beaba38 100644
--- a/rules/aip0135/aip0135.go
+++ b/rules/aip0135/aip0135.go
@@ -16,10 +16,7 @@
 package aip0135
 
 import (
-	"regexp"
-
 	"github.com/googleapis/api-linter/lint"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // AddRules accepts a register function and registers each of
@@ -44,19 +41,3 @@ func AddRules(r lint.RuleRegistry) error {
 		unknownFields,
 	)
 }
-
-var (
-	deleteMethodRegexp         = regexp.MustCompile("^Delete(?:[A-Z]|$)")
-	deleteReqMessageRegexp     = regexp.MustCompile("^Delete[A-Za-z0-9]*Request$")
-	deleteRevisionMethodRegexp = regexp.MustCompile("^Delete[A-Za-z0-9]*Revision$")
-)
-
-// Returns true if this is a AIP-135 Delete method, false otherwise.
-func isDeleteMethod(m *desc.MethodDescriptor) bool {
-	return deleteMethodRegexp.MatchString(m.GetName()) && !deleteRevisionMethodRegexp.MatchString(m.GetName())
-}
-
-// Returns true if this is an AIP-135 Delete request message, false otherwise.
-func isDeleteRequestMessage(m *desc.MessageDescriptor) bool {
-	return deleteReqMessageRegexp.MatchString(m.GetName())
-}
diff --git a/rules/aip0135/force_field.go b/rules/aip0135/force_field.go
index 1c11fac..48f8c58 100644
--- a/rules/aip0135/force_field.go
+++ b/rules/aip0135/force_field.go
@@ -28,7 +28,7 @@ var forceField = &lint.MessageRule{
 		ref := utils.GetResourceReference(name)
 		validRef := ref != nil && ref.GetType() != "" && utils.FindResource(ref.GetType(), m.GetFile()) != nil
 
-		return isDeleteRequestMessage(m) && validRef
+		return utils.IsDeleteRequestMessage(m) && validRef
 	},
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		force := m.FindFieldByName("force")
diff --git a/rules/aip0135/http_body.go b/rules/aip0135/http_body.go
index eaba7bb..1bd0f89 100644
--- a/rules/aip0135/http_body.go
+++ b/rules/aip0135/http_body.go
@@ -22,6 +22,6 @@ import (
 // Delete methods should not have an HTTP body.
 var httpBody = &lint.MethodRule{
 	Name:       lint.NewRuleName(135, "http-body"),
-	OnlyIf:     isDeleteMethod,
+	OnlyIf:     utils.IsDeleteMethod,
 	LintMethod: utils.LintNoHTTPBody,
 }
diff --git a/rules/aip0135/http_method.go b/rules/aip0135/http_method.go
index 36f41f9..e05ec47 100644
--- a/rules/aip0135/http_method.go
+++ b/rules/aip0135/http_method.go
@@ -22,6 +22,6 @@ import (
 // Delete methods should use the HTTP DELETE method.
 var httpMethod = &lint.MethodRule{
 	Name:       lint.NewRuleName(135, "http-method"),
-	OnlyIf:     isDeleteMethod,
+	OnlyIf:     utils.IsDeleteMethod,
 	LintMethod: utils.LintHTTPMethod("DELETE"),
 }
diff --git a/rules/aip0135/http_uri_name.go b/rules/aip0135/http_uri_name.go
index 5bd6112..bc6cef0 100644
--- a/rules/aip0135/http_uri_name.go
+++ b/rules/aip0135/http_uri_name.go
@@ -22,6 +22,6 @@ import (
 // Delete methods should have a proper HTTP pattern.
 var httpNameField = &lint.MethodRule{
 	Name:       lint.NewRuleName(135, "http-uri-name"),
-	OnlyIf:     isDeleteMethod,
+	OnlyIf:     utils.IsDeleteMethod,
 	LintMethod: utils.LintHTTPURIHasNameVariable,
 }
diff --git a/rules/aip0135/method_signature.go b/rules/aip0135/method_signature.go
index 02cdfcf..e2370a3 100644
--- a/rules/aip0135/method_signature.go
+++ b/rules/aip0135/method_signature.go
@@ -27,7 +27,7 @@ import (
 
 var methodSignature = &lint.MethodRule{
 	Name:   lint.NewRuleName(135, "method-signature"),
-	OnlyIf: isDeleteMethod,
+	OnlyIf: utils.IsDeleteMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		signatures := utils.GetMethodSignatures(m)
 		in := m.GetInputType()
diff --git a/rules/aip0135/request_force_field.go b/rules/aip0135/request_force_field.go
index 5c4347e..3d304a0 100644
--- a/rules/aip0135/request_force_field.go
+++ b/rules/aip0135/request_force_field.go
@@ -23,7 +23,7 @@ import (
 var requestForceField = &lint.FieldRule{
 	Name: lint.NewRuleName(135, "request-force-field"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isDeleteRequestMessage(f.GetOwner()) && f.GetName() == "force"
+		return utils.IsDeleteRequestMessage(f.GetOwner()) && f.GetName() == "force"
 	},
 	LintField: utils.LintSingularBoolField,
 }
diff --git a/rules/aip0135/request_message_name.go b/rules/aip0135/request_message_name.go
index ff1cea0..c5b31fa 100644
--- a/rules/aip0135/request_message_name.go
+++ b/rules/aip0135/request_message_name.go
@@ -22,6 +22,6 @@ import (
 // Delete messages should have a properly named Request message.
 var requestMessageName = &lint.MethodRule{
 	Name:       lint.NewRuleName(135, "request-message-name"),
-	OnlyIf:     isDeleteMethod,
+	OnlyIf:     utils.IsDeleteMethod,
 	LintMethod: utils.LintMethodHasMatchingRequestName,
 }
diff --git a/rules/aip0135/request_name_behavior.go b/rules/aip0135/request_name_behavior.go
index c642695..1cb332c 100644
--- a/rules/aip0135/request_name_behavior.go
+++ b/rules/aip0135/request_name_behavior.go
@@ -23,7 +23,7 @@ import (
 var requestNameBehavior = &lint.FieldRule{
 	Name: lint.NewRuleName(135, "request-name-behavior"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isDeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
+		return utils.IsDeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
 	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0135/request_name_field.go b/rules/aip0135/request_name_field.go
index ae93d9f..0f8a556 100644
--- a/rules/aip0135/request_name_field.go
+++ b/rules/aip0135/request_name_field.go
@@ -23,7 +23,7 @@ import (
 var requestNameField = &lint.FieldRule{
 	Name: lint.NewRuleName(135, "request-name-field"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isDeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
+		return utils.IsDeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
 	LintField: utils.LintSingularStringField,
 }
diff --git a/rules/aip0135/request_name_reference.go b/rules/aip0135/request_name_reference.go
index 9b7cc8c..31aa65c 100644
--- a/rules/aip0135/request_name_reference.go
+++ b/rules/aip0135/request_name_reference.go
@@ -23,7 +23,7 @@ import (
 var requestNameReference = &lint.FieldRule{
 	Name: lint.NewRuleName(135, "request-name-reference"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
-		return isDeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
+		return utils.IsDeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
 	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0135/request_name_required.go b/rules/aip0135/request_name_required.go
index 551cc2b..c3f5405 100644
--- a/rules/aip0135/request_name_required.go
+++ b/rules/aip0135/request_name_required.go
@@ -18,12 +18,13 @@ import (
 	"fmt"
 
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
 var requestNameRequired = &lint.MessageRule{
 	Name:   lint.NewRuleName(135, "request-name-required"),
-	OnlyIf: isDeleteRequestMessage,
+	OnlyIf: utils.IsDeleteRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		if m.FindFieldByName("name") == nil {
 			return []lint.Problem{{
diff --git a/rules/aip0135/request_required_fields.go b/rules/aip0135/request_required_fields.go
index e59d122..ac2adf6 100644
--- a/rules/aip0135/request_required_fields.go
+++ b/rules/aip0135/request_required_fields.go
@@ -26,7 +26,7 @@ import (
 // The delete request message should not have unrecognized fields.
 var requestRequiredFields = &lint.MessageRule{
 	Name:   lint.NewRuleName(135, "request-required-fields"),
-	OnlyIf: isDeleteRequestMessage,
+	OnlyIf: utils.IsDeleteRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that there are no unexpected fields.
 		allowedRequiredFields := stringset.New("name")
diff --git a/rules/aip0135/request_unknown_fields.go b/rules/aip0135/request_unknown_fields.go
index ec27ec9..27193d2 100644
--- a/rules/aip0135/request_unknown_fields.go
+++ b/rules/aip0135/request_unknown_fields.go
@@ -18,13 +18,14 @@ import (
 	"fmt"
 
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
 // Delete methods should not have unrecognized fields.
 var unknownFields = &lint.MessageRule{
 	Name:   lint.NewRuleName(135, "request-unknown-fields"),
-	OnlyIf: isDeleteRequestMessage,
+	OnlyIf: utils.IsDeleteRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that there are no unexpected fields.
 		allowedFields := map[string]struct{}{
diff --git a/rules/aip0135/response_lro.go b/rules/aip0135/response_lro.go
index d22b07a..df641eb 100644
--- a/rules/aip0135/response_lro.go
+++ b/rules/aip0135/response_lro.go
@@ -24,7 +24,7 @@ import (
 var responseLRO = &lint.MethodRule{
 	Name: lint.NewRuleName(135, "response-lro"),
 	OnlyIf: func(m *desc.MethodDescriptor) bool {
-		return isDeleteMethod(m) && utils.IsDeclarativeFriendlyMethod(m)
+		return utils.IsDeleteMethod(m) && utils.IsDeclarativeFriendlyMethod(m)
 	},
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		if m.GetOutputType().GetFullyQualifiedName() != "google.longrunning.Operation" {
diff --git a/rules/aip0135/response_message_name.go b/rules/aip0135/response_message_name.go
index 4c22502..c0c8953 100644
--- a/rules/aip0135/response_message_name.go
+++ b/rules/aip0135/response_message_name.go
@@ -30,7 +30,7 @@ import (
 // message.
 var responseMessageName = &lint.MethodRule{
 	Name:   lint.NewRuleName(135, "response-message-name"),
-	OnlyIf: isDeleteMethod,
+	OnlyIf: utils.IsDeleteMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		resource := strings.Replace(m.GetName(), "Delete", "", 1)
 
diff --git a/rules/aip0162/aip0162.go b/rules/aip0162/aip0162.go
index e899663..88ae14b 100644
--- a/rules/aip0162/aip0162.go
+++ b/rules/aip0162/aip0162.go
@@ -19,6 +19,7 @@ import (
 	"regexp"
 
 	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
 )
 
@@ -140,19 +141,21 @@ func isDeleteRevisionRequestMessage(m *desc.MessageDescriptor) bool {
 }
 
 var (
-	listRevisionsReqMessageRegexp  = regexp.MustCompile(`^List(?:[A-Za-z0-9]+)RevisionsRequest$`)
-	listRevisionsRespMessageRegexp = regexp.MustCompile(`^List(?:[A-Za-z0-9]+)RevisionsResponse$`)
-	listRevisionsURINameRegexp     = regexp.MustCompile(`:listRevisions$`)
+	listRevisionsURINameRegexp = regexp.MustCompile(`:listRevisions$`)
 )
 
 // IsListRevisionsRequestMessage returns true if this is an AIP-162 List
 // Revisions request message, false otherwise.
+//
+// Deprecated: Use the same method from the utils package instead.
 func IsListRevisionsRequestMessage(m *desc.MessageDescriptor) bool {
-	return listRevisionsReqMessageRegexp.MatchString(m.GetName())
+	return utils.IsListRevisionsRequestMessage(m)
 }
 
 // IsListRevisionsResponseMessage returns true if this is an AIP-162 List
 // Revisions response message, false otherwise.
+//
+// Deprecated: Use the same method from the utils package instead.
 func IsListRevisionsResponseMessage(m *desc.MessageDescriptor) bool {
-	return listRevisionsRespMessageRegexp.MatchString(m.GetName())
+	return utils.IsListRevisionsResponseMessage(m)
 }
diff --git a/rules/internal/utils/message.go b/rules/internal/utils/message.go
new file mode 100644
index 0000000..0ea933c
--- /dev/null
+++ b/rules/internal/utils/message.go
@@ -0,0 +1,86 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//	https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package utils
+
+import (
+	"regexp"
+
+	"github.com/jhump/protoreflect/desc"
+	"github.com/stoewer/go-strcase"
+)
+
+var (
+	getReqMessageRegexp            = regexp.MustCompile("^Get[A-Za-z0-9]*Request$")
+	listReqMessageRegexp           = regexp.MustCompile("^List[A-Za-z0-9]*Request$")
+	listRespMessageRegexp          = regexp.MustCompile("^List([A-Za-z0-9]*)Response$")
+	listRevisionsReqMessageRegexp  = regexp.MustCompile(`^List(?:[A-Za-z0-9]+)RevisionsRequest$`)
+	listRevisionsRespMessageRegexp = regexp.MustCompile(`^List(?:[A-Za-z0-9]+)RevisionsResponse$`)
+	createReqMessageRegexp         = regexp.MustCompile("^Create[A-Za-z0-9]*Request$")
+	updateReqMessageRegexp         = regexp.MustCompile("^Update[A-Za-z0-9]*Request$")
+	deleteReqMessageRegexp         = regexp.MustCompile("^Delete[A-Za-z0-9]*Request$")
+)
+
+// Returns true if this is an AIP-131 Get request message, false otherwise.
+func IsGetRequestMessage(m *desc.MessageDescriptor) bool {
+	return getReqMessageRegexp.MatchString(m.GetName())
+}
+
+// Return true if this is an AIP-132 List request message, false otherwise.
+func IsListRequestMessage(m *desc.MessageDescriptor) bool {
+	return listReqMessageRegexp.MatchString(m.GetName()) && !IsListRevisionsRequestMessage(m)
+}
+
+// Return true if this is an AIP-132 List response message, false otherwise.
+func IsListResponseMessage(m *desc.MessageDescriptor) bool {
+	return listRespMessageRegexp.MatchString(m.GetName()) && !IsListRevisionsResponseMessage(m)
+}
+
+// Returns the name of the resource type from the response message name based on
+// Standard List response message naming convention. If the message is not a
+// Standard List response message, empty string is returned.
+func ListResponseResourceName(m *desc.MessageDescriptor) string {
+	if !IsListResponseMessage(m) {
+		return ""
+	}
+
+	return strcase.SnakeCase(listRespMessageRegexp.FindStringSubmatch(m.GetName())[1])
+}
+
+// IsListRevisionsRequestMessage returns true if this is an AIP-162 List
+// Revisions request message, false otherwise.
+func IsListRevisionsRequestMessage(m *desc.MessageDescriptor) bool {
+	return listRevisionsReqMessageRegexp.MatchString(m.GetName())
+}
+
+// IsListRevisionsResponseMessage returns true if this is an AIP-162 List
+// Revisions response message, false otherwise.
+func IsListRevisionsResponseMessage(m *desc.MessageDescriptor) bool {
+	return listRevisionsRespMessageRegexp.MatchString(m.GetName())
+}
+
+// Returns true if this is an AIP-133 Get request message, false otherwise.
+func IsCreateRequestMessage(m *desc.MessageDescriptor) bool {
+	return createReqMessageRegexp.MatchString(m.GetName())
+}
+
+// Returns true if this is an AIP-134 Update request message, false otherwise.
+func IsUpdateRequestMessage(m *desc.MessageDescriptor) bool {
+	return updateReqMessageRegexp.MatchString(m.GetName())
+}
+
+// Returns true if this is an AIP-135 Delete request message, false otherwise.
+func IsDeleteRequestMessage(m *desc.MessageDescriptor) bool {
+	return deleteReqMessageRegexp.MatchString(m.GetName())
+}
diff --git a/rules/internal/utils/method.go b/rules/internal/utils/method.go
index 1e73f99..e5531e8 100644
--- a/rules/internal/utils/method.go
+++ b/rules/internal/utils/method.go
@@ -21,11 +21,13 @@ import (
 )
 
 var (
-	createMethodRegexp        = regexp.MustCompile("^Create(?:[A-Z]|$)")
-	getMethodRegexp           = regexp.MustCompile("^Get(?:[A-Z]|$)")
-	listMethodRegexp          = regexp.MustCompile("^List(?:[A-Z]|$)")
-	listRevisionsMethodRegexp = regexp.MustCompile(`^List(?:[A-Za-z0-9]+)Revisions$`)
-	updateMethodRegexp        = regexp.MustCompile("^Update(?:[A-Z]|$)")
+	createMethodRegexp         = regexp.MustCompile("^Create(?:[A-Z]|$)")
+	getMethodRegexp            = regexp.MustCompile("^Get(?:[A-Z]|$)")
+	listMethodRegexp           = regexp.MustCompile("^List(?:[A-Z]|$)")
+	listRevisionsMethodRegexp  = regexp.MustCompile(`^List(?:[A-Za-z0-9]+)Revisions$`)
+	updateMethodRegexp         = regexp.MustCompile("^Update(?:[A-Z]|$)")
+	deleteMethodRegexp         = regexp.MustCompile("^Delete(?:[A-Z]|$)")
+	deleteRevisionMethodRegexp = regexp.MustCompile("^Delete[A-Za-z0-9]*Revision$")
 )
 
 // IsCreateMethod returns true if this is a AIP-133 Create method.
@@ -59,6 +61,11 @@ func IsUpdateMethod(m *desc.MethodDescriptor) bool {
 	return updateMethodRegexp.MatchString(methodName)
 }
 
+// Returns true if this is a AIP-135 Delete method, false otherwise.
+func IsDeleteMethod(m *desc.MethodDescriptor) bool {
+	return deleteMethodRegexp.MatchString(m.GetName()) && !deleteRevisionMethodRegexp.MatchString(m.GetName())
+}
+
 // GetListResourceMessage returns the resource for a list method,
 // nil otherwise.
 func GetListResourceMessage(m *desc.MethodDescriptor) *desc.MessageDescriptor {


Test patch:
diff --git a/rules/internal/utils/message_test.go b/rules/internal/utils/message_test.go
new file mode 100644
index 0000000..ba49c77
--- /dev/null
+++ b/rules/internal/utils/message_test.go
@@ -0,0 +1,46 @@
+// Copyright 2023 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//	https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package utils
+
+import (
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestListResponseResourceName(t *testing.T) {
+	for _, test := range []struct {
+		name string
+		RPC  string
+		want string
+	}{
+		{"ValidBooks", "ListBooks", "books"},
+		{"ValidCamelCase", "ListBookShelves", "book_shelves"},
+		{"InvalidListRevisions", "ListBookRevisions", ""},
+		{"InvalidNotList", "WriteBook", ""},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				message {{.RPC}}Response {}
+			`, test)
+			m := file.GetMessageTypes()[0]
+			got := ListResponseResourceName(m)
+			if diff := cmp.Diff(got, test.want); diff != "" {
+				t.Errorf("got(-),want(+):\n%s", diff)
+			}
+
+		})
+	}
+}