Repository: googleapis/api-linter
Pull request #221 (2019-10-09 21:03:24)

AIP-203: Field behavior / immutable format
AIP-203: Field behavior / immutable format

Rename the existing file aip203_test.go to aip0203_test.go


Patch:
diff --git a/rules/aip0203/aip0203.go b/rules/aip0203/aip0203.go
index 3a4abfd..8df7281 100644
--- a/rules/aip0203/aip0203.go
+++ b/rules/aip0203/aip0203.go
@@ -27,6 +27,7 @@ import (
 func AddRules(r lint.RuleRegistry) {
 	r.Register(
 		inputOnly,
+		immutable,
 	)
 }
 
diff --git a/rules/aip0203/aip203_test.go b/rules/aip0203/aip0203_test.go
similarity index 100%
rename from rules/aip0203/aip203_test.go
rename to rules/aip0203/aip0203_test.go
diff --git a/rules/aip0203/immutable.go b/rules/aip0203/immutable.go
new file mode 100644
index 0000000..f98dc2c
--- /dev/null
+++ b/rules/aip0203/immutable.go
@@ -0,0 +1,57 @@
+package aip0203
+
+import (
+	"regexp"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+	"google.golang.org/genproto/googleapis/api/annotations"
+)
+
+// This rule inspects the leading comments of each field
+// and if anything looks similar to "Immutable.", it throws
+// a problem.
+//
+// Examples:
+// Incorrect code for this rule:
+//
+//	message Book {
+//	// The title of the book.
+//	// @Immutable
+//	string title = 1;
+//	}
+//
+// or
+//
+//	message Book {
+//	// Immutable. The title of the book.
+//	string title = 1;
+//	}
+//
+//
+// Correct code for this rule:
+//
+//	message Book {
+//	// The title of the book.
+//	string title = 1 [(google.api.field_behavior) = IMMUTABLE];
+//	}
+var immutable = &lint.FieldRule{
+	Name:   lint.NewRuleName("core", "0203", "immutable"),
+	URI:    "http://api.dev/203#guidance",
+	OnlyIf: withoutImmutableFieldBehavior,
+	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
+		return checkLeadingComments(f, immutableRegexp, "IMMUTABLE")
+	},
+}
+
+var immutableRegexp = regexp.MustCompile("(?i).*immutable.*")
+
+func withoutImmutableFieldBehavior(f *desc.FieldDescriptor) bool {
+	for _, v := range utils.GetFieldBehavior(f) {
+		if v == annotations.FieldBehavior_IMMUTABLE {
+			return false
+		}
+	}
+	return true
+}


Test patch:
diff --git a/rules/aip0203/immutable_test.go b/rules/aip0203/immutable_test.go
new file mode 100644
index 0000000..03e2d13
--- /dev/null
+++ b/rules/aip0203/immutable_test.go
@@ -0,0 +1,118 @@
+package aip0203
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+var fieldPart = "string title = 1;"
+var fieldPartWithImmtutableBehavior = "string title = 1 [(google.api.field_behavior) = IMMUTABLE];"
+
+func TestImmutable(t *testing.T) {
+	testCases := []struct {
+		name     string
+		comment  string
+		field    string
+		problems testutils.Problems
+	}{
+		{
+			name:     "Valid",
+			comment:  "Immutable",
+			field:    fieldPartWithImmtutableBehavior,
+			problems: nil,
+		},
+		{
+			name:     "Valid",
+			comment:  "@immutable",
+			field:    fieldPartWithImmtutableBehavior,
+			problems: nil,
+		},
+		{
+			name:    "Invalid-immutable",
+			comment: "immutable",
+			field:   fieldPart,
+			problems: testutils.Problems{{
+				Message: "google.api.field_behavior",
+			}},
+		},
+		{
+			name:    "Invalid-Immutable",
+			comment: "Immutable",
+			field:   fieldPart,
+			problems: testutils.Problems{{
+				Message: "google.api.field_behavior",
+			}},
+		},
+		{
+			name:    "Invalid-@immutable",
+			comment: "@immutable",
+			field:   fieldPart,
+			problems: testutils.Problems{{
+				Message: "google.api.field_behavior",
+			}},
+		},
+		{
+			name:    "Invalid-@immutable",
+			comment: "@Immutable",
+			field:   fieldPart,
+			problems: testutils.Problems{{
+				Message: "google.api.field_behavior",
+			}},
+		},
+		{
+			name:    "Invalid-IMMUTABLE",
+			comment: "IMMUTABLE",
+			field:   fieldPart,
+			problems: testutils.Problems{{
+				Message: "google.api.field_behavior",
+			}},
+		},
+		{
+			name:    "Invalid-@IMMUTABLE",
+			comment: "@IMMUTABLE",
+			field:   fieldPart,
+			problems: testutils.Problems{{
+				Message: "google.api.field_behavior",
+			}},
+		},
+		{
+			name:    "Invalid-immutable_free_text",
+			comment: "This field is immutable",
+			field:   fieldPart,
+			problems: testutils.Problems{{
+				Message: "google.api.field_behavior",
+			}},
+		},
+		{
+			name:    "Invalid-!immutable",
+			comment: "!immutable",
+			field:   fieldPart,
+			problems: testutils.Problems{{
+				Message: "google.api.field_behavior",
+			}},
+		},
+	}
+
+	for _, test := range testCases {
+		t.Run(test.name, func(t *testing.T) {
+			template := `
+import "google/api/field_behavior.proto";
+message Book {
+	// Title of the book
+	// {{.Comment}}
+	{{.Field}}
+}`
+			file := testutils.ParseProto3Tmpl(t, template,
+				struct {
+					Comment string
+					Field   string
+				}{test.comment, test.field})
+			f := file.GetMessageTypes()[0].GetFields()[0]
+			problems := immutable.Lint(file)
+			if diff := test.problems.SetDescriptor(f).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}