Repository: googleapis/api-linter
Pull request #310 (2019-11-06 05:19:55)

[refactor] Make rule names stricter (again).
This commit refactors `NewRuleName` to take just an AIP number
and the final segment.

It creates `NewGenericRuleName` for cases not covered by this
(for example, rules to enforce internal AIPs).

Basically: It makes the common case better and leaves the advanced
case mostly alone.


Patch:
diff --git a/docs/contributing.md b/docs/contributing.md
index b36a4ed..d28d717 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -62,8 +62,8 @@ Consider a bare-bones message rule:
 
 ```go
 var myRule = &lint.MessageRule{
-  Name: lint.NewRuleName("core", "0000", "my-rule"),
-  URI:  "https://aip.dev/0000",
+  Name: lint.NewRuleName(0, "my-rule"),
+  URI:  "https://aip.dev/0",
   LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
     // This lint rule does nothing and always passes.
     return nil
diff --git a/lint/rule_name.go b/lint/rule_name.go
index 4335bfa..0db18dd 100644
--- a/lint/rule_name.go
+++ b/lint/rule_name.go
@@ -15,6 +15,7 @@
 package lint
 
 import (
+	"fmt"
 	"regexp"
 	"strings"
 )
@@ -29,10 +30,30 @@ const nameSeparator string = "::"
 
 var ruleNameValidator = regexp.MustCompile("^([a-z0-9][a-z0-9-]*(::[a-z0-9][a-z0-9-]*)?)+$")
 
-// NewRuleName creates a RuleName from segments.
-// It will join the segments with the "::" separator.
-func NewRuleName(segments ...string) RuleName {
-	return RuleName(strings.Join(segments, nameSeparator))
+// getRuleGroup takes an AIP number and returns the appropriate group.
+func getRuleGroup(aip int) string {
+	// Determine the group.
+	group := ""
+	if aip > 0 && aip < 1000 {
+		group = "core"
+	}
+
+	// Sanity check: If the group does not exist, complain.
+	if group == "" {
+		panic("Invalid AIP; no available group.")
+	}
+
+	return group
+}
+
+// NewRuleName creates a RuleName from an AIP number and a unique name within
+// that AIP.
+func NewRuleName(aip int, name string) RuleName {
+	return RuleName(strings.Join([]string{
+		getRuleGroup(aip),
+		fmt.Sprintf("%04d", aip),
+		name,
+	}, nameSeparator))
 }
 
 // IsValid checks if a RuleName is syntactically valid.
diff --git a/rules/aip0122/camel_case_uris.go b/rules/aip0122/camel_case_uris.go
index 295af1e..20ff6a5 100644
--- a/rules/aip0122/camel_case_uris.go
+++ b/rules/aip0122/camel_case_uris.go
@@ -24,7 +24,7 @@ import (
 
 // HTTP URL pattern shouldn't include underscore("_")
 var httpURICase = &lint.MethodRule{
-	Name: lint.NewRuleName("core", "0122", "camel-case-uris"),
+	Name: lint.NewRuleName(122, "camel-case-uris"),
 	LintMethod: func(m *desc.MethodDescriptor) (problems []lint.Problem) {
 		// Establish that the URI does not include a `_` character.
 		for _, httpRule := range utils.GetHTTPRules(m) {
diff --git a/rules/aip0126/unspecified.go b/rules/aip0126/unspecified.go
index f27a76c..eba8b7f 100644
--- a/rules/aip0126/unspecified.go
+++ b/rules/aip0126/unspecified.go
@@ -25,7 +25,7 @@ import (
 )
 
 var unspecified = &lint.EnumRule{
-	Name: lint.NewRuleName("core", "0126", "unspecified"),
+	Name: lint.NewRuleName(126, "unspecified"),
 	LintEnum: func(e *desc.EnumDescriptor) []lint.Problem {
 		firstValue := e.GetValues()[0]
 		want := strings.ToUpper(strcase.SnakeCase(e.GetName()) + "_UNSPECIFIED")
diff --git a/rules/aip0126/upper_snake_values.go b/rules/aip0126/upper_snake_values.go
index b210f46..6ec7f5f 100644
--- a/rules/aip0126/upper_snake_values.go
+++ b/rules/aip0126/upper_snake_values.go
@@ -12,7 +12,7 @@ import (
 
 // All enum values must use UPPER_SNAKE_CASE.
 var enumValueUpperSnakeCase = &lint.EnumRule{
-	Name: lint.NewRuleName("core", "0126", "upper-snake-values"),
+	Name: lint.NewRuleName(126, "upper-snake-values"),
 	LintEnum: func(e *desc.EnumDescriptor) []lint.Problem {
 		var problems []lint.Problem
 		for _, v := range e.GetValues() {
diff --git a/rules/aip0131/http_body.go b/rules/aip0131/http_body.go
index 1f56365..2eaa781 100644
--- a/rules/aip0131/http_body.go
+++ b/rules/aip0131/http_body.go
@@ -22,7 +22,7 @@ import (
 
 // Get methods should not have an HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "http-body"),
+	Name:   lint.NewRuleName(131, "http-body"),
 	OnlyIf: isGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Establish that the RPC has no HTTP body.
diff --git a/rules/aip0131/http_method.go b/rules/aip0131/http_method.go
index 28aec22..5da9fdc 100644
--- a/rules/aip0131/http_method.go
+++ b/rules/aip0131/http_method.go
@@ -22,7 +22,7 @@ import (
 
 // Get methods should use the HTTP GET verb.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "http-method"),
+	Name:   lint.NewRuleName(131, "http-method"),
 	OnlyIf: isGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that the RPC uses HTTP GET.
diff --git a/rules/aip0131/http_uri_name.go b/rules/aip0131/http_uri_name.go
index 096e356..4330a54 100644
--- a/rules/aip0131/http_uri_name.go
+++ b/rules/aip0131/http_uri_name.go
@@ -22,7 +22,7 @@ import (
 
 // Get methods should have a proper HTTP pattern.
 var httpNameField = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "http-uri-name"),
+	Name:   lint.NewRuleName(131, "http-uri-name"),
 	OnlyIf: isGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Establish that the RPC has no HTTP body.
diff --git a/rules/aip0131/request_message_name.go b/rules/aip0131/request_message_name.go
index 02bc172..cda4e17 100644
--- a/rules/aip0131/request_message_name.go
+++ b/rules/aip0131/request_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // Get messages should have a properly named Request message.
 var requestMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "request-message-name"),
+	Name:   lint.NewRuleName(131, "request-message-name"),
 	OnlyIf: isGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `GetFoo`, the request
diff --git a/rules/aip0131/request_name_field.go b/rules/aip0131/request_name_field.go
index 14ca0f2..3d22faf 100644
--- a/rules/aip0131/request_name_field.go
+++ b/rules/aip0131/request_name_field.go
@@ -24,7 +24,7 @@ import (
 
 // The Get standard method should only have expected fields.
 var standardFields = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0131", "request-name-field"),
+	Name:   lint.NewRuleName(131, "request-name-field"),
 	OnlyIf: isGetRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		// Rule check: Establish that a name field is present.
diff --git a/rules/aip0131/request_unknown_fields.go b/rules/aip0131/request_unknown_fields.go
index 00b0be9..83cd9a3 100644
--- a/rules/aip0131/request_unknown_fields.go
+++ b/rules/aip0131/request_unknown_fields.go
@@ -23,7 +23,7 @@ import (
 
 // Get methods should not have unrecognized fields.
 var unknownFields = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0131", "request-unknown-fields"),
+	Name:   lint.NewRuleName(131, "request-unknown-fields"),
 	OnlyIf: isGetRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that there are no unexpected fields.
diff --git a/rules/aip0131/response_message_name.go b/rules/aip0131/response_message_name.go
index 8965dda..40db464 100644
--- a/rules/aip0131/response_message_name.go
+++ b/rules/aip0131/response_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // Get messages should use the resource as the response message
 var responseMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "response-message-name"),
+	Name:   lint.NewRuleName(131, "response-message-name"),
 	OnlyIf: isGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `GetFoo`, the response
diff --git a/rules/aip0131/synonyms.go b/rules/aip0131/synonyms.go
index b5aa6bc..75a52d4 100644
--- a/rules/aip0131/synonyms.go
+++ b/rules/aip0131/synonyms.go
@@ -25,7 +25,7 @@ import (
 
 // Get methods should not generally use synonyms for "get".
 var synonyms = &lint.MethodRule{
-	Name: lint.NewRuleName("core", "0131", "synonyms"),
+	Name: lint.NewRuleName(131, "synonyms"),
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		name := m.GetName()
 		for _, syn := range []string{"Acquire", "Fetch", "Lookup", "Read", "Retrieve"} {
diff --git a/rules/aip0132/request_field_types.go b/rules/aip0132/request_field_types.go
index 5648716..3888f41 100644
--- a/rules/aip0132/request_field_types.go
+++ b/rules/aip0132/request_field_types.go
@@ -27,7 +27,7 @@ var knownFields = stringset.New("filter", "order_by")
 
 // List methods should not have unrecognized fields.
 var requestFieldTypes = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0132", "request-field-types"),
+	Name: lint.NewRuleName(132, "request-field-types"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isListRequestMessage(f.GetOwner()) && knownFields.Contains(f.GetName())
 	},
diff --git a/rules/aip0132/request_message_name.go b/rules/aip0132/request_message_name.go
index c181bc2..7aca9dc 100644
--- a/rules/aip0132/request_message_name.go
+++ b/rules/aip0132/request_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // List messages should have a properly named Request message.
 var requestMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0132", "request-message-name"),
+	Name:   lint.NewRuleName(132, "request-message-name"),
 	OnlyIf: isListMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `ListFoos`, the request
diff --git a/rules/aip0132/request_parent_field.go b/rules/aip0132/request_parent_field.go
index f713a27..9bafbed 100644
--- a/rules/aip0132/request_parent_field.go
+++ b/rules/aip0132/request_parent_field.go
@@ -24,7 +24,7 @@ import (
 
 // The List standard method should contain a parent field.
 var standardFields = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0132", "request-message", "parent-field"),
+	Name:   lint.NewRuleName(132, "request-parent-field"),
 	OnlyIf: isListRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		// Rule check: Establish that a `parent` field is present.
diff --git a/rules/aip0132/request_unknown_fields.go b/rules/aip0132/request_unknown_fields.go
index e8abb32..faff49b 100644
--- a/rules/aip0132/request_unknown_fields.go
+++ b/rules/aip0132/request_unknown_fields.go
@@ -33,7 +33,7 @@ var allowedFields = stringset.New(
 
 // List methods should not have unrecognized fields.
 var unknownFields = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0132", "request-unknown-fields"),
+	Name:   lint.NewRuleName(132, "request-unknown-fields"),
 	OnlyIf: isListRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		for _, field := range m.GetFields() {
diff --git a/rules/aip0132/response_message_name.go b/rules/aip0132/response_message_name.go
index bbf0ee4..0285ee9 100644
--- a/rules/aip0132/response_message_name.go
+++ b/rules/aip0132/response_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // List messages should use a `ListFoosResponse` response message.
 var responseMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0132", "response-message-name"),
+	Name:   lint.NewRuleName(132, "response-message-name"),
 	OnlyIf: isListMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `ListFoos`, the response
diff --git a/rules/aip0133/http_body.go b/rules/aip0133/http_body.go
index 8fdcf0e..a01c178 100644
--- a/rules/aip0133/http_body.go
+++ b/rules/aip0133/http_body.go
@@ -25,7 +25,7 @@ import (
 
 // Create methods should have an HTTP body, and the body value should be resource.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0133", "http-body"),
+	Name:   lint.NewRuleName(133, "http-body"),
 	OnlyIf: isCreateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		resourceMsgName := getResourceMsgName(m)
diff --git a/rules/aip0133/http_method.go b/rules/aip0133/http_method.go
index d27ed15..28e4de3 100644
--- a/rules/aip0133/http_method.go
+++ b/rules/aip0133/http_method.go
@@ -22,7 +22,7 @@ import (
 
 // Create methods should use the HTTP POST verb.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0133", "http-method"),
+	Name:   lint.NewRuleName(133, "http-method"),
 	OnlyIf: isCreateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that the RPC uses HTTP POST.
diff --git a/rules/aip0133/http_uri_parent.go b/rules/aip0133/http_uri_parent.go
index 8ffafcb..b805b83 100644
--- a/rules/aip0133/http_uri_parent.go
+++ b/rules/aip0133/http_uri_parent.go
@@ -22,7 +22,7 @@ import (
 
 // Create methods should have a proper HTTP pattern.
 var httpURIField = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0133", "http-uri-parent"),
+	Name:   lint.NewRuleName(133, "http-uri-parent"),
 	OnlyIf: isCreateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Establish that the RPC uri should include the `parent` field.
diff --git a/rules/aip0133/request_message_name.go b/rules/aip0133/request_message_name.go
index a8954a8..ed5134c 100644
--- a/rules/aip0133/request_message_name.go
+++ b/rules/aip0133/request_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // Create method should have a properly named input message.
 var inputName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0133", "request-message-name"),
+	Name:   lint.NewRuleName(133, "request-message-name"),
 	OnlyIf: isCreateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		resourceMsgName := getResourceMsgName(m)
diff --git a/rules/aip0133/request_parent_field.go b/rules/aip0133/request_parent_field.go
index 19a57e0..c31f0e6 100644
--- a/rules/aip0133/request_parent_field.go
+++ b/rules/aip0133/request_parent_field.go
@@ -24,7 +24,7 @@ import (
 
 // The create request message should have parent field.
 var parentField = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0133", "request-parent-field"),
+	Name:   lint.NewRuleName(133, "request-parent-field"),
 	OnlyIf: isCreateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		// Rule check: Establish that a `parent` field is present.
diff --git a/rules/aip0133/request_resource_field.go b/rules/aip0133/request_resource_field.go
index 9475e1c..89f59a1 100644
--- a/rules/aip0133/request_resource_field.go
+++ b/rules/aip0133/request_resource_field.go
@@ -25,7 +25,7 @@ import (
 
 // The create request message should have resource field.
 var resourceField = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0133", "request-resource-field"),
+	Name:   lint.NewRuleName(133, "request-resource-field"),
 	OnlyIf: 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 6b7eb60..22f5b6b 100644
--- a/rules/aip0133/request_unknown_fields.go
+++ b/rules/aip0133/request_unknown_fields.go
@@ -26,7 +26,7 @@ import (
 
 // The create request message should not have unrecognized fields.
 var unknownFields = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0133", "request-message", "unknown-fields"),
+	Name:   lint.NewRuleName(133, "request-unknown-fields"),
 	OnlyIf: isCreateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		resourceMsgName := getResourceMsgNameFromReq(m)
diff --git a/rules/aip0133/response_message_name.go b/rules/aip0133/response_message_name.go
index 53e59a7..a4ac017 100644
--- a/rules/aip0133/response_message_name.go
+++ b/rules/aip0133/response_message_name.go
@@ -24,7 +24,7 @@ import (
 
 // Create method should use the resource as the output message
 var outputName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0133", "response-message-name"),
+	Name:   lint.NewRuleName(133, "response-message-name"),
 	OnlyIf: isCreateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		want := getResourceMsgName(m)
diff --git a/rules/aip0133/synonyms.go b/rules/aip0133/synonyms.go
index 0331174..1be0f05 100644
--- a/rules/aip0133/synonyms.go
+++ b/rules/aip0133/synonyms.go
@@ -25,7 +25,7 @@ import (
 
 // Create methods should use "create", not synonyms.
 var synonyms = &lint.MethodRule{
-	Name: lint.NewRuleName("core", "0133", "synonyms"),
+	Name: lint.NewRuleName(133, "synonyms"),
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		name := m.GetName()
 		for _, syn := range []string{"Insert", "Make", "Post"} {
diff --git a/rules/aip0134/http_body.go b/rules/aip0134/http_body.go
index 1a643e6..08139de 100644
--- a/rules/aip0134/http_body.go
+++ b/rules/aip0134/http_body.go
@@ -25,7 +25,7 @@ import (
 
 // Update methods should have an HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0134", "http-body"),
+	Name:   lint.NewRuleName(134, "http-body"),
 	OnlyIf: isUpdateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		fieldName := strcase.SnakeCase(m.GetName()[6:])
diff --git a/rules/aip0134/http_method.go b/rules/aip0134/http_method.go
index c87cc7e..de74f35 100644
--- a/rules/aip0134/http_method.go
+++ b/rules/aip0134/http_method.go
@@ -22,7 +22,7 @@ import (
 
 // Update methods should use the HTTP PATCH verb.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0134", "http-method"),
+	Name:   lint.NewRuleName(134, "http-method"),
 	OnlyIf: isUpdateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that the RPC uses HTTP PATCH.
diff --git a/rules/aip0134/http_uri_name.go b/rules/aip0134/http_uri_name.go
index 1e52984..e968d27 100644
--- a/rules/aip0134/http_uri_name.go
+++ b/rules/aip0134/http_uri_name.go
@@ -25,7 +25,7 @@ import (
 
 // Update methods should have a proper HTTP pattern.
 var httpNameField = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0134", "http-uri-name"),
+	Name:   lint.NewRuleName(134, "http-uri-name"),
 	OnlyIf: isUpdateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		fieldName := strcase.SnakeCase(m.GetName()[6:])
diff --git a/rules/aip0134/request_mask_field.go b/rules/aip0134/request_mask_field.go
index e22463b..374608f 100644
--- a/rules/aip0134/request_mask_field.go
+++ b/rules/aip0134/request_mask_field.go
@@ -20,7 +20,7 @@ import (
 )
 
 var requestMaskField = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0134", "request-mask-field"),
+	Name:   lint.NewRuleName(134, "request-mask-field"),
 	OnlyIf: isUpdateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		updateMask := m.FindFieldByName("update_mask")
diff --git a/rules/aip0134/request_message_name.go b/rules/aip0134/request_message_name.go
index 2fc4b8c..622fb87 100644
--- a/rules/aip0134/request_message_name.go
+++ b/rules/aip0134/request_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // Update methods should have a properly named Request message.
 var requestMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0134", "request-message-name"),
+	Name:   lint.NewRuleName(134, "request-message-name"),
 	OnlyIf: isUpdateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `UpdateFoo`, the request
diff --git a/rules/aip0134/request_resource_field.go b/rules/aip0134/request_resource_field.go
index f321f98..25e560d 100644
--- a/rules/aip0134/request_resource_field.go
+++ b/rules/aip0134/request_resource_field.go
@@ -25,7 +25,7 @@ import (
 
 // The create request message should have resource field.
 var resourceField = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0134", "request--resource-field"),
+	Name:   lint.NewRuleName(134, "request--resource-field"),
 	OnlyIf: isUpdateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		resourceMsgName := extractResource(m.GetName())
diff --git a/rules/aip0134/request_unknown_fields.go b/rules/aip0134/request_unknown_fields.go
index d56405c..083eeb3 100644
--- a/rules/aip0134/request_unknown_fields.go
+++ b/rules/aip0134/request_unknown_fields.go
@@ -24,7 +24,7 @@ import (
 
 // Update methods should not have unrecognized fields.
 var unknownFields = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0134", "request-unknown-fields"),
+	Name:   lint.NewRuleName(134, "request-unknown-fields"),
 	OnlyIf: isUpdateRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		resource := extractResource(m.GetName())
diff --git a/rules/aip0134/response_message_name.go b/rules/aip0134/response_message_name.go
index 245ea3c..6657f46 100644
--- a/rules/aip0134/response_message_name.go
+++ b/rules/aip0134/response_message_name.go
@@ -25,7 +25,7 @@ import (
 
 // Update methods should use the resource as the response message
 var responseMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0134", "response-message-name"),
+	Name:   lint.NewRuleName(134, "response-message-name"),
 	OnlyIf: isUpdateMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `UpdateFoo`, the response
diff --git a/rules/aip0134/synonyms.go b/rules/aip0134/synonyms.go
index cae753e..888e865 100644
--- a/rules/aip0134/synonyms.go
+++ b/rules/aip0134/synonyms.go
@@ -25,7 +25,7 @@ import (
 
 // Update methods should use the word "update", not synonyms.
 var synonyms = &lint.MethodRule{
-	Name: lint.NewRuleName("core", "0134", "synonyms"),
+	Name: lint.NewRuleName(134, "synonyms"),
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		name := m.GetName()
 		for _, syn := range []string{"Patch", "Put", "Set"} {
diff --git a/rules/aip0135/http_body.go b/rules/aip0135/http_body.go
index 6d6a265..1541c33 100644
--- a/rules/aip0135/http_body.go
+++ b/rules/aip0135/http_body.go
@@ -22,7 +22,7 @@ import (
 
 // Delete methods should not have an HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0135", "http-body"),
+	Name:   lint.NewRuleName(135, "http-body"),
 	OnlyIf: isDeleteMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Establish that the RPC has no HTTP body.
diff --git a/rules/aip0135/http_method.go b/rules/aip0135/http_method.go
index 1f41f4e..d5a07aa 100644
--- a/rules/aip0135/http_method.go
+++ b/rules/aip0135/http_method.go
@@ -22,7 +22,7 @@ import (
 
 // Delete methods should use the HTTP DELETE method.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0135", "http-method"),
+	Name:   lint.NewRuleName(135, "http-method"),
 	OnlyIf: isDeleteMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that the RPC uses HTTP DELETE.
diff --git a/rules/aip0135/http_uri_name.go b/rules/aip0135/http_uri_name.go
index d1929d3..663895e 100644
--- a/rules/aip0135/http_uri_name.go
+++ b/rules/aip0135/http_uri_name.go
@@ -22,7 +22,7 @@ import (
 
 // Delete methods should have a proper HTTP pattern.
 var httpNameField = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0135", "http-uri-name"),
+	Name:   lint.NewRuleName(135, "http-uri-name"),
 	OnlyIf: isDeleteMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Establish that the RPC has no HTTP body.
diff --git a/rules/aip0135/request_message_name.go b/rules/aip0135/request_message_name.go
index 2f81742..fd20490 100644
--- a/rules/aip0135/request_message_name.go
+++ b/rules/aip0135/request_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // Delete messages should have a properly named Request message.
 var requestMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0135", "request-message-name"),
+	Name:   lint.NewRuleName(135, "request-message-name"),
 	OnlyIf: isDeleteMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `DeleteFoo`, the request
diff --git a/rules/aip0135/request_name_field.go b/rules/aip0135/request_name_field.go
index 5df8b5d..5835f20 100644
--- a/rules/aip0135/request_name_field.go
+++ b/rules/aip0135/request_name_field.go
@@ -24,7 +24,7 @@ import (
 
 // The Delete standard method should only have expected fields.
 var standardFields = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0135", "request-name-field"),
+	Name:   lint.NewRuleName(135, "request-name-field"),
 	OnlyIf: isDeleteRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		// Rule check: Establish that a name field is present.
diff --git a/rules/aip0135/request_unknown_fields.go b/rules/aip0135/request_unknown_fields.go
index d255aa6..0fb7247 100644
--- a/rules/aip0135/request_unknown_fields.go
+++ b/rules/aip0135/request_unknown_fields.go
@@ -23,7 +23,7 @@ import (
 
 // Delete methods should not have unrecognized fields.
 var unknownFields = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0135", "request-unknown-fields"),
+	Name:   lint.NewRuleName(135, "request-unknown-fields"),
 	OnlyIf: isDeleteRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that there are no unexpected fields.
diff --git a/rules/aip0135/response_message_name.go b/rules/aip0135/response_message_name.go
index 760737c..618be43 100644
--- a/rules/aip0135/response_message_name.go
+++ b/rules/aip0135/response_message_name.go
@@ -28,7 +28,7 @@ import (
 // google.longrunning.Operation, or the resource itself as the response
 // message.
 var responseMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0135", "response-message-name"),
+	Name:   lint.NewRuleName(135, "response-message-name"),
 	OnlyIf: isDeleteMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `DeleteFoo`, the response
diff --git a/rules/aip0136/http_body.go b/rules/aip0136/http_body.go
index 9e75a85..945235a 100644
--- a/rules/aip0136/http_body.go
+++ b/rules/aip0136/http_body.go
@@ -22,7 +22,7 @@ import (
 )
 
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0136", "http-body"),
+	Name:   lint.NewRuleName(136, "http-body"),
 	OnlyIf: isCustomMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		for _, httpRule := range utils.GetHTTPRules(m) {
diff --git a/rules/aip0136/http_method.go b/rules/aip0136/http_method.go
index 2d938dc..872a831 100644
--- a/rules/aip0136/http_method.go
+++ b/rules/aip0136/http_method.go
@@ -21,7 +21,7 @@ import (
 )
 
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0136", "http-method"),
+	Name:   lint.NewRuleName(136, "http-method"),
 	OnlyIf: isCustomMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		for _, httpRule := range utils.GetHTTPRules(m) {
diff --git a/rules/aip0136/http_uri_suffix.go b/rules/aip0136/http_uri_suffix.go
index 509028f..78ade5c 100644
--- a/rules/aip0136/http_uri_suffix.go
+++ b/rules/aip0136/http_uri_suffix.go
@@ -25,7 +25,7 @@ import (
 )
 
 var uriSuffix = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0136", "http-uri-suffix"),
+	Name:   lint.NewRuleName(136, "http-uri-suffix"),
 	OnlyIf: isCustomMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		for _, httpRule := range utils.GetHTTPRules(m) {
diff --git a/rules/aip0136/prepositions.go b/rules/aip0136/prepositions.go
index c70651b..691e0fc 100644
--- a/rules/aip0136/prepositions.go
+++ b/rules/aip0136/prepositions.go
@@ -26,7 +26,7 @@ import (
 )
 
 var noPrepositions = &lint.MethodRule{
-	Name: lint.NewRuleName("core", "0136", "prepositions"),
+	Name: lint.NewRuleName(136, "prepositions"),
 	LintMethod: func(m *desc.MethodDescriptor) (problems []lint.Problem) {
 		for _, word := range strings.Split(strcase.SnakeCase(m.GetName()), "_") {
 			if data.Prepositions.Contains(word) {
diff --git a/rules/aip0136/verb_noun.go b/rules/aip0136/verb_noun.go
index c48e81d..a9aa789 100644
--- a/rules/aip0136/verb_noun.go
+++ b/rules/aip0136/verb_noun.go
@@ -24,7 +24,7 @@ import (
 )
 
 var verbNoun = &lint.MethodRule{
-	Name: lint.NewRuleName("core", "0136", "verb-noun"),
+	Name: lint.NewRuleName(136, "verb-noun"),
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// We can not detect this precisely without a full dictionary (probably
 		// not worth it), but we can catch some common mistakes.
diff --git a/rules/aip0140/abbreviations.go b/rules/aip0140/abbreviations.go
index ec1fb54..778442e 100644
--- a/rules/aip0140/abbreviations.go
+++ b/rules/aip0140/abbreviations.go
@@ -32,7 +32,7 @@ var expectedAbbreviations = map[string]string{
 }
 
 var abbreviations = &lint.DescriptorRule{
-	Name: lint.NewRuleName("core", "0140", "abbreviations"),
+	Name: lint.NewRuleName(140, "abbreviations"),
 	LintDescriptor: func(d desc.Descriptor) (problems []lint.Problem) {
 		// Determine the correct case function to use.
 		// Most things in protobuf are PascalCase; the two exceptions are
diff --git a/rules/aip0140/base64.go b/rules/aip0140/base64.go
index 669527f..c088ba1 100644
--- a/rules/aip0140/base64.go
+++ b/rules/aip0140/base64.go
@@ -22,7 +22,7 @@ import (
 )
 
 var base64 = &lint.FieldRule{
-	Name:   lint.NewRuleName("core", "0140", "base64"),
+	Name:   lint.NewRuleName(140, "base64"),
 	OnlyIf: isStringField,
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		comment := strings.ToLower(f.GetSourceInfo().GetLeadingComments())
diff --git a/rules/aip0140/lower_snake.go b/rules/aip0140/lower_snake.go
index f83f861..fc84ac1 100644
--- a/rules/aip0140/lower_snake.go
+++ b/rules/aip0140/lower_snake.go
@@ -23,7 +23,7 @@ import (
 
 // Field names must be snake case.
 var lowerSnake = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0140", "lower-snake"),
+	Name: lint.NewRuleName(140, "lower-snake"),
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		if got, want := f.GetName(), toLowerSnakeCase(f.GetName()); got != want {
 			return []lint.Problem{{
diff --git a/rules/aip0140/prepositions.go b/rules/aip0140/prepositions.go
index aded406..9aa1b57 100644
--- a/rules/aip0140/prepositions.go
+++ b/rules/aip0140/prepositions.go
@@ -25,7 +25,7 @@ import (
 )
 
 var noPrepositions = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0140", "prepositions"),
+	Name: lint.NewRuleName(140, "prepositions"),
 	LintField: func(f *desc.FieldDescriptor) (problems []lint.Problem) {
 		for _, word := range strings.Split(f.GetName(), "_") {
 			if data.Prepositions.Contains(word) {
diff --git a/rules/aip0141/count_suffix.go b/rules/aip0141/count_suffix.go
index dd2045d..a58e5a7 100644
--- a/rules/aip0141/count_suffix.go
+++ b/rules/aip0141/count_suffix.go
@@ -24,7 +24,7 @@ import (
 )
 
 var count = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0141", "count-suffix"),
+	Name: lint.NewRuleName(141, "count-suffix"),
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		if n := f.GetName(); strings.HasPrefix(n, "num_") {
 			want := pluralize.NewClient().Singular(n[4:]) + "_count"
diff --git a/rules/aip0141/forbidden_types.go b/rules/aip0141/forbidden_types.go
index 2a7b4d5..a03e729 100644
--- a/rules/aip0141/forbidden_types.go
+++ b/rules/aip0141/forbidden_types.go
@@ -25,7 +25,7 @@ import (
 )
 
 var forbiddenTypes = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0141", "forbidden-types"),
+	Name: lint.NewRuleName(141, "forbidden-types"),
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		// Make a map of the forbidden types.
 		nope := make(map[dpb.FieldDescriptorProto_Type]string)
diff --git a/rules/aip0142/time_field_names.go b/rules/aip0142/time_field_names.go
index 246500e..aa1f231 100644
--- a/rules/aip0142/time_field_names.go
+++ b/rules/aip0142/time_field_names.go
@@ -24,7 +24,7 @@ import (
 )
 
 var fieldNames = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0142", "time-field-names"),
+	Name: lint.NewRuleName(142, "time-field-names"),
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		// Look for common non-imperative terms.
 		mistakes := map[string]string{
diff --git a/rules/aip0142/time_field_type.go b/rules/aip0142/time_field_type.go
index bcbc721..535b15b 100644
--- a/rules/aip0142/time_field_type.go
+++ b/rules/aip0142/time_field_type.go
@@ -24,7 +24,7 @@ import (
 )
 
 var fieldType = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0142", "time-field-type"),
+	Name: lint.NewRuleName(142, "time-field-type"),
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		suffixes := stringset.New(
 			"date", "datetime", "ms", "msec", "msecs", "millis", "nanos", "ns",
diff --git a/rules/aip0143/standardized_codes.go b/rules/aip0143/standardized_codes.go
index 4b186f7..a38bb66 100644
--- a/rules/aip0143/standardized_codes.go
+++ b/rules/aip0143/standardized_codes.go
@@ -23,7 +23,7 @@ import (
 )
 
 var fieldNames = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0143", "standardized-codes"),
+	Name: lint.NewRuleName(143, "standardized-codes"),
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		variants := map[string]string{
 			"content_type": "mime_type",
diff --git a/rules/aip0143/string_type.go b/rules/aip0143/string_type.go
index 4459236..a89145f 100644
--- a/rules/aip0143/string_type.go
+++ b/rules/aip0143/string_type.go
@@ -24,7 +24,7 @@ import (
 )
 
 var fieldTypes = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0143", "string-type"),
+	Name: lint.NewRuleName(143, "string-type"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return stringset.New(
 			"country_code",
diff --git a/rules/aip0151/lro_metadata_type.go b/rules/aip0151/lro_metadata_type.go
index 0eab42a..5996c40 100644
--- a/rules/aip0151/lro_metadata_type.go
+++ b/rules/aip0151/lro_metadata_type.go
@@ -21,7 +21,7 @@ import (
 )
 
 var lroMetadata = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0151", "lro-metadata-type"),
+	Name:   lint.NewRuleName(151, "lro-metadata-type"),
 	OnlyIf: isAnnotatedLRO,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		lro := utils.GetOperationInfo(m)
diff --git a/rules/aip0151/lro_response_type.go b/rules/aip0151/lro_response_type.go
index 64de962..87655fe 100644
--- a/rules/aip0151/lro_response_type.go
+++ b/rules/aip0151/lro_response_type.go
@@ -23,7 +23,7 @@ import (
 )
 
 var lroResponse = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0151", "lro-response-type"),
+	Name:   lint.NewRuleName(151, "lro-response-type"),
 	OnlyIf: isAnnotatedLRO,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		lro := utils.GetOperationInfo(m)
diff --git a/rules/aip0151/lro_types_defined_in_file.go b/rules/aip0151/lro_types_defined_in_file.go
index 1868b55..9ee7536 100644
--- a/rules/aip0151/lro_types_defined_in_file.go
+++ b/rules/aip0151/lro_types_defined_in_file.go
@@ -24,7 +24,7 @@ import (
 )
 
 var lroDefinedInFile = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0151", "lro-types-defined-in-file"),
+	Name:   lint.NewRuleName(151, "lro-types-defined-in-file"),
 	OnlyIf: isAnnotatedLRO,
 	LintMethod: func(m *desc.MethodDescriptor) (problems []lint.Problem) {
 		lro := utils.GetOperationInfo(m)
diff --git a/rules/aip0151/operation_info.go b/rules/aip0151/operation_info.go
index ecf7a58..e905372 100644
--- a/rules/aip0151/operation_info.go
+++ b/rules/aip0151/operation_info.go
@@ -21,7 +21,7 @@ import (
 )
 
 var lroAnnotationExists = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0151", "operation-info"),
+	Name:   lint.NewRuleName(151, "operation-info"),
 	OnlyIf: isLRO,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		if utils.GetOperationInfo(m) == nil {
diff --git a/rules/aip0158/request_page_size_field.go b/rules/aip0158/request_page_size_field.go
index bb99c78..da89aa0 100644
--- a/rules/aip0158/request_page_size_field.go
+++ b/rules/aip0158/request_page_size_field.go
@@ -23,7 +23,7 @@ import (
 )
 
 var requestPaginationPageSize = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0158", "request-page-size-field"),
+	Name:   lint.NewRuleName(158, "request-page-size-field"),
 	OnlyIf: isPaginatedRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that a page_size field is present.
diff --git a/rules/aip0158/request_page_token_field.go b/rules/aip0158/request_page_token_field.go
index e5c2325..1599df1 100644
--- a/rules/aip0158/request_page_token_field.go
+++ b/rules/aip0158/request_page_token_field.go
@@ -23,7 +23,7 @@ import (
 )
 
 var requestPaginationPageToken = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0158", "request-page-token-field"),
+	Name:   lint.NewRuleName(158, "request-page-token-field"),
 	OnlyIf: isPaginatedRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that a page_size field is present.
diff --git a/rules/aip0158/response_next_page_token_field.go b/rules/aip0158/response_next_page_token_field.go
index c0de4b8..bb2e1d0 100644
--- a/rules/aip0158/response_next_page_token_field.go
+++ b/rules/aip0158/response_next_page_token_field.go
@@ -23,7 +23,7 @@ import (
 )
 
 var responsePaginationNextPageToken = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0158", "response-next-page-token-field"),
+	Name:   lint.NewRuleName(158, "response-next-page-token-field"),
 	OnlyIf: isPaginatedResponseMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		// Rule check: Establish that a next_page_token field is present.
diff --git a/rules/aip0191/filenames.go b/rules/aip0191/filenames.go
index 67fe290..0c801af 100644
--- a/rules/aip0191/filenames.go
+++ b/rules/aip0191/filenames.go
@@ -24,7 +24,7 @@ import (
 )
 
 var filename = &lint.FileRule{
-	Name: lint.NewRuleName("core", "0191", "filenames"),
+	Name: lint.NewRuleName(191, "filenames"),
 	LintFile: func(f *desc.FileDescriptor) []lint.Problem {
 		fn := strings.ReplaceAll(filepath.Base(f.GetName()), ".proto", "")
 		if versionRegexp.MatchString(fn) {
diff --git a/rules/aip0191/syntax.go b/rules/aip0191/syntax.go
index eb6720c..25359df 100644
--- a/rules/aip0191/syntax.go
+++ b/rules/aip0191/syntax.go
@@ -22,7 +22,7 @@ import (
 
 // APIs must use proto3.
 var syntax = &lint.FileRule{
-	Name: lint.NewRuleName("core", "0191", "proto-version"),
+	Name: lint.NewRuleName(191, "proto-version"),
 	LintFile: func(f *desc.FileDescriptor) []lint.Problem {
 		if !f.IsProto3() {
 			return []lint.Problem{{
diff --git a/rules/aip0192/has_comments.go b/rules/aip0192/has_comments.go
index 24f8048..db24414 100644
--- a/rules/aip0192/has_comments.go
+++ b/rules/aip0192/has_comments.go
@@ -24,7 +24,7 @@ import (
 
 // hasComments complains if there is no comment above something.
 var hasComments = &lint.DescriptorRule{
-	Name: lint.NewRuleName("core", "0192", "has-comments"),
+	Name: lint.NewRuleName(192, "has-comments"),
 	LintDescriptor: func(d desc.Descriptor) (problems []lint.Problem) {
 		comment := separateInternalComments(d.GetSourceInfo().GetLeadingComments())
 		if len(comment.External) == 0 {
diff --git a/rules/aip0192/leading_comments.go b/rules/aip0192/leading_comments.go
index ddf3094..60b99ac 100644
--- a/rules/aip0192/leading_comments.go
+++ b/rules/aip0192/leading_comments.go
@@ -24,7 +24,7 @@ import (
 // onlyLeadingComments ensures that a descriptor has only leading external
 // comments. (Internal trailing or detached comments are permitted.)
 var onlyLeadingComments = &lint.DescriptorRule{
-	Name: lint.NewRuleName("core", "0192", "only-leading-comments"),
+	Name: lint.NewRuleName(192, "only-leading-comments"),
 	LintDescriptor: func(d desc.Descriptor) []lint.Problem {
 		problems := []lint.Problem{}
 		c := d.GetSourceInfo()
diff --git a/rules/aip0203/immutable.go b/rules/aip0203/immutable.go
index 479976b..6ab3d7a 100644
--- a/rules/aip0203/immutable.go
+++ b/rules/aip0203/immutable.go
@@ -10,7 +10,7 @@ import (
 )
 
 var immutable = &lint.FieldRule{
-	Name:   lint.NewRuleName("core", "0203", "immutable"),
+	Name:   lint.NewRuleName(203, "immutable"),
 	OnlyIf: withoutImmutableFieldBehavior,
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		return checkLeadingComments(f, immutableRegexp, "IMMUTABLE")
diff --git a/rules/aip0203/input_only.go b/rules/aip0203/input_only.go
index 258f92e..d69562f 100644
--- a/rules/aip0203/input_only.go
+++ b/rules/aip0203/input_only.go
@@ -10,7 +10,7 @@ import (
 )
 
 var inputOnly = &lint.FieldRule{
-	Name:   lint.NewRuleName("core", "0203", "input-only"),
+	Name:   lint.NewRuleName(203, "input-only"),
 	OnlyIf: withoutInputOnlyFieldBehavior,
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		return checkLeadingComments(f, inputOnlyRegexp, "INPUT_ONLY")
diff --git a/rules/aip0203/optional.go b/rules/aip0203/optional.go
index 29dbff8..f2fd877 100644
--- a/rules/aip0203/optional.go
+++ b/rules/aip0203/optional.go
@@ -10,7 +10,7 @@ import (
 )
 
 var optional = &lint.FieldRule{
-	Name:   lint.NewRuleName("core", "0203", "optional"),
+	Name:   lint.NewRuleName(203, "optional"),
 	OnlyIf: withoutOptionalFieldBehavior,
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		return checkLeadingComments(f, optionalRegexp, "OPTIONAL")
@@ -18,7 +18,7 @@ var optional = &lint.FieldRule{
 }
 
 var optionalBehaviorConflict = &lint.FieldRule{
-	Name: lint.NewRuleName("core", "0203", "optional-conflict"),
+	Name: lint.NewRuleName(203, "optional-conflict"),
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return !withoutOptionalFieldBehavior(f)
 	},
@@ -39,7 +39,7 @@ var optionalBehaviorConflict = &lint.FieldRule{
 // If a message has a field which is described as optional, ensure that every
 // optional field on the message has this indicator.
 var optionalBehaviorConsistency = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0203", "optional-consistency"),
+	Name:   lint.NewRuleName(203, "optional-consistency"),
 	OnlyIf: messageHasOptionalFieldBehavior,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 
diff --git a/rules/aip0203/output_only.go b/rules/aip0203/output_only.go
index b4f3919..5026afe 100644
--- a/rules/aip0203/output_only.go
+++ b/rules/aip0203/output_only.go
@@ -10,7 +10,7 @@ import (
 )
 
 var outputOnly = &lint.FieldRule{
-	Name:   lint.NewRuleName("core", "0203", "output-only"),
+	Name:   lint.NewRuleName(203, "output-only"),
 	OnlyIf: withoutOutputOnlyFieldBehavior,
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		return checkLeadingComments(f, outputOnlyRegexp, "OUTPUT_ONLY")
diff --git a/rules/aip0203/required.go b/rules/aip0203/required.go
index 41065d9..3db7f66 100644
--- a/rules/aip0203/required.go
+++ b/rules/aip0203/required.go
@@ -10,7 +10,7 @@ import (
 )
 
 var required = &lint.FieldRule{
-	Name:   lint.NewRuleName("core", "0203", "required"),
+	Name:   lint.NewRuleName(203, "required"),
 	OnlyIf: withoutRequiredFieldBehavior,
 	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
 		return checkLeadingComments(f, requiredRegexp, "REQUIRED")
diff --git a/rules/aip0231/http_body.go b/rules/aip0231/http_body.go
index a321325..fe1cb2f 100644
--- a/rules/aip0231/http_body.go
+++ b/rules/aip0231/http_body.go
@@ -22,7 +22,7 @@ import (
 
 // Get methods should not have an HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0231", "http-body"),
+	Name:   lint.NewRuleName(231, "http-body"),
 	OnlyIf: isBatchGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Establish that the RPC has no HTTP body.
diff --git a/rules/aip0231/http_method.go b/rules/aip0231/http_method.go
index 54f9306..3c327c9 100644
--- a/rules/aip0231/http_method.go
+++ b/rules/aip0231/http_method.go
@@ -22,7 +22,7 @@ import (
 
 // Batch Get methods should use the HTTP GET verb.
 var httpVerb = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0231", "http-method"),
+	Name:   lint.NewRuleName(231, "http-method"),
 	OnlyIf: isBatchGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that the RPC uses HTTP GET.
diff --git a/rules/aip0231/http_uri_suffix.go b/rules/aip0231/http_uri_suffix.go
index 290f057..fe2798ba 100644
--- a/rules/aip0231/http_uri_suffix.go
+++ b/rules/aip0231/http_uri_suffix.go
@@ -22,7 +22,7 @@ import (
 
 // Batch Get methods should have a proper HTTP pattern.
 var uriSuffix = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0231", "http-name"),
+	Name:   lint.NewRuleName(231, "http-name"),
 	OnlyIf: isBatchGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Establish that the RPC has no HTTP body.
diff --git a/rules/aip0231/plural_method_name.go b/rules/aip0231/plural_method_name.go
index f544d75..27844fd 100644
--- a/rules/aip0231/plural_method_name.go
+++ b/rules/aip0231/plural_method_name.go
@@ -23,7 +23,7 @@ import (
 )
 
 var pluralMethodResourceName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0231", "plural-method-name"),
+	Name:   lint.NewRuleName(231, "plural-method-name"),
 	OnlyIf: isBatchGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Note: m.GetName()[8:] is used to retrieve the resource name from the
diff --git a/rules/aip0231/request_message_name.go b/rules/aip0231/request_message_name.go
index 9358e04..ba812a3 100644
--- a/rules/aip0231/request_message_name.go
+++ b/rules/aip0231/request_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // Batch Get method should have a properly named Request message.
 var inputName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0231", "request-message-name"),
+	Name:   lint.NewRuleName(231, "request-message-name"),
 	OnlyIf: isBatchGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `BatchGetFoos`, the request
diff --git a/rules/aip0231/request_names_field.go b/rules/aip0231/request_names_field.go
index b44cf58..c379a88 100644
--- a/rules/aip0231/request_names_field.go
+++ b/rules/aip0231/request_names_field.go
@@ -26,7 +26,7 @@ import (
 // The Batch Get standard method should have repeated name field or repeated
 // standard get request message field, but the latter one is not suggested.
 var namesField = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0231", "request-names-field"),
+	Name:   lint.NewRuleName(231, "request-names-field"),
 	OnlyIf: isBatchGetRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
 		// Rule check: Establish that a name field is present.
diff --git a/rules/aip0231/request_parent_field.go b/rules/aip0231/request_parent_field.go
index 88d8524..09db26b 100644
--- a/rules/aip0231/request_parent_field.go
+++ b/rules/aip0231/request_parent_field.go
@@ -24,7 +24,7 @@ import (
 
 // The Batch Get request message should have parent field.
 var parentField = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0231", "request-parent-field"),
+	Name:   lint.NewRuleName(231, "request-parent-field"),
 	OnlyIf: isBatchGetRequestMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		// Rule check: Establish that a `parent` field is present.
diff --git a/rules/aip0231/response_message_name.go b/rules/aip0231/response_message_name.go
index 6131200..f96f264 100644
--- a/rules/aip0231/response_message_name.go
+++ b/rules/aip0231/response_message_name.go
@@ -23,7 +23,7 @@ import (
 
 // Batch Get method should have a properly named Response message.
 var outputName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0231", "output-message", "name"),
+	Name:   lint.NewRuleName(231, "response-message-name"),
 	OnlyIf: isBatchGetMethod,
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		// Rule check: Establish that for methods such as `BatchGetFoos`, the request
diff --git a/rules/aip0231/response_resource_field.go b/rules/aip0231/response_resource_field.go
index 3c716eb..4bed6d1 100644
--- a/rules/aip0231/response_resource_field.go
+++ b/rules/aip0231/response_resource_field.go
@@ -24,7 +24,7 @@ import (
 
 // The Batch Get response message should have resource field.
 var resourceField = &lint.MessageRule{
-	Name:   lint.NewRuleName("core", "0231", "response-resource-field"),
+	Name:   lint.NewRuleName(231, "response-resource-field"),
 	OnlyIf: isBatchGetResponseMessage,
 	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
 		// the singular form the resource name, the first letter is Capitalized.


Test patch:
diff --git a/lint/problem_test.go b/lint/problem_test.go
index 6f0a180..4dd591a 100644
--- a/lint/problem_test.go
+++ b/lint/problem_test.go
@@ -125,7 +125,7 @@ func TestRuleDocURI(t *testing.T) {
 		wantURI  string
 	}{{
 		name:     "CoreRule",
-		ruleName: NewRuleName("core", "0122", "camel-case-uris"),
+		ruleName: NewRuleName(122, "camel-case-uris"),
 		wantURI:  "https://googleapis.github.io/api-linter/rules/core/0122-camel-case-uris.html",
 	}}
 
diff --git a/lint/rule_name_test.go b/lint/rule_name_test.go
index 513856d..6ff32dd 100644
--- a/lint/rule_name_test.go
+++ b/lint/rule_name_test.go
@@ -14,7 +14,9 @@
 
 package lint
 
-import "testing"
+import (
+	"testing"
+)
 
 func TestRuleNameValid(t *testing.T) {
 	tests := []struct {
@@ -68,19 +70,20 @@ func TestRuleNameInvalid(t *testing.T) {
 
 func TestNewRuleName(t *testing.T) {
 	tests := []struct {
-		segments []string
-		name     RuleName
+		testName string
+		aip      int
+		name     string
+		want     string
 	}{
-		{[]string{}, ""},
-		{[]string{""}, ""},
-		{[]string{"my-namespace", "my-rule"}, "my-namespace::my-rule"},
-		{[]string{"my", "name", "space", "foo"}, "my::name::space::foo"},
+		{"ZeroPad", 131, "http-method", "core::0131::http-method"},
 	}
-
 	for _, test := range tests {
-		if NewRuleName(test.segments...) != test.name {
-			t.Errorf("NewRuleName(%v)=%q; want %q", test.segments, NewRuleName(test.segments...), test.name)
-		}
+		t.Run(test.testName, func(t *testing.T) {
+			rn := NewRuleName(test.aip, test.name)
+			if got := string(rn); got != test.want {
+				t.Errorf("Got %q, expected %q.", got, test.want)
+			}
+		})
 	}
 }
 
diff --git a/lint/rule_test.go b/lint/rule_test.go
index c1872db..1f00be9 100644
--- a/lint/rule_test.go
+++ b/lint/rule_test.go
@@ -33,7 +33,7 @@ func TestFileRule(t *testing.T) {
 	for _, test := range makeLintRuleTests(fd) {
 		t.Run(test.testName, func(t *testing.T) {
 			rule := &FileRule{
-				Name: NewRuleName("test", test.testName),
+				Name: RuleName("test"),
 				LintFile: func(fd *desc.FileDescriptor) []Problem {
 					return test.problems
 				},
@@ -61,7 +61,7 @@ func TestMessageRule(t *testing.T) {
 		t.Run(test.testName, func(t *testing.T) {
 			// Create the message rule.
 			rule := &MessageRule{
-				Name: NewRuleName("test", test.testName),
+				Name: RuleName("test"),
 				OnlyIf: func(m *desc.MessageDescriptor) bool {
 					return m.GetName() == "Author"
 				},
@@ -91,7 +91,7 @@ func TestMessageRuleNested(t *testing.T) {
 		t.Run(test.testName, func(t *testing.T) {
 			// Create the message rule.
 			rule := &MessageRule{
-				Name: NewRuleName("test", test.testName),
+				Name: RuleName("test"),
 				OnlyIf: func(m *desc.MessageDescriptor) bool {
 					return m.GetName() == "Author"
 				},
@@ -124,7 +124,7 @@ func TestFieldRule(t *testing.T) {
 		t.Run(test.testName, func(t *testing.T) {
 			// Create the message rule.
 			rule := &FieldRule{
-				Name: NewRuleName("test", test.testName),
+				Name: RuleName("test"),
 				OnlyIf: func(f *desc.FieldDescriptor) bool {
 					return f.GetName() == "edition_count"
 				},
@@ -153,7 +153,7 @@ func TestServiceRule(t *testing.T) {
 		t.Run(test.testName, func(t *testing.T) {
 			// Create the service rule.
 			rule := &ServiceRule{
-				Name: NewRuleName("test", test.testName),
+				Name: RuleName("test"),
 				LintService: func(s *desc.ServiceDescriptor) []Problem {
 					return test.problems
 				},
@@ -192,7 +192,7 @@ func TestMethodRule(t *testing.T) {
 		t.Run(test.testName, func(t *testing.T) {
 			// Create the method rule.
 			rule := &MethodRule{
-				Name: NewRuleName("test", test.testName),
+				Name: RuleName("test"),
 				OnlyIf: func(m *desc.MethodDescriptor) bool {
 					return m.GetName() == "CreateBook"
 				},
@@ -222,7 +222,7 @@ func TestEnumRule(t *testing.T) {
 		t.Run(test.testName, func(t *testing.T) {
 			// Create the enum rule.
 			rule := &EnumRule{
-				Name: NewRuleName("test", test.testName),
+				Name: RuleName("test"),
 				OnlyIf: func(e *desc.EnumDescriptor) bool {
 					return e.GetName() == "Edition"
 				},
@@ -254,7 +254,7 @@ func TestEnumRuleNested(t *testing.T) {
 		t.Run(test.testName, func(t *testing.T) {
 			// Create the enum rule.
 			rule := &EnumRule{
-				Name: NewRuleName("test", test.testName),
+				Name: RuleName("test"),
 				OnlyIf: func(e *desc.EnumDescriptor) bool {
 					return e.GetName() == "Edition"
 				},
@@ -292,7 +292,7 @@ func TestDescriptorRule(t *testing.T) {
 	// Create a rule that lets us verify that each descriptor was visited.
 	visited := make(map[string]desc.Descriptor)
 	rule := &DescriptorRule{
-		Name: NewRuleName("test", "test"),
+		Name: RuleName("test"),
 		LintDescriptor: func(d desc.Descriptor) []Problem {
 			visited[d.GetName()] = d
 			return nil
@@ -308,7 +308,7 @@ func TestDescriptorRule(t *testing.T) {
 		"Author", "Book", "ConjureBook", "Format", "FORMAT_UNSPECIFIED",
 		"name", "Library", "State",
 	}
-	if got, want := rule.GetName(), "test::test"; string(got) != want {
+	if got, want := rule.GetName(), "test"; string(got) != want {
 		t.Errorf("Got name %q, wanted %q", got, want)
 	}
 	if got, want := len(visited), len(wantDescriptors); got != want {
@@ -324,7 +324,7 @@ func TestDescriptorRule(t *testing.T) {
 func TestRuleIsEnabled(t *testing.T) {
 	// Create a no-op rule, which we can check enabled status on.
 	rule := &FileRule{
-		Name: NewRuleName("test"),
+		Name: RuleName("test"),
 		LintFile: func(fd *desc.FileDescriptor) []Problem {
 			return []Problem{}
 		},
@@ -371,7 +371,7 @@ func TestRuleIsEnabled(t *testing.T) {
 func TestRuleIsEnabledFirstMessage(t *testing.T) {
 	// Create a no-op rule, which we can check enabled status on.
 	rule := &FileRule{
-		Name: NewRuleName("test"),
+		Name: RuleName("test"),
 		LintFile: func(fd *desc.FileDescriptor) []Problem {
 			return []Problem{}
 		},
@@ -404,7 +404,7 @@ type lintRuleTest struct {
 // runRule runs a rule within a test environment.
 func (test *lintRuleTest) runRule(rule ProtoRule, fd *desc.FileDescriptor, t *testing.T) {
 	// Establish that the metadata methods work.
-	if got, want := string(rule.GetName()), string(NewRuleName("test", test.testName)); got != want {
+	if got, want := string(rule.GetName()), string(RuleName("test")); got != want {
 		t.Errorf("Got %q for GetName(), expected %q", got, want)
 	}