Repository: googleapis/api-linter
Pull request #341 (2019-11-13 00:04:24)

[AIP-216] Add rule for catching "Status".


Patch:
diff --git a/docs/rules/core/0126-unspecified.md b/docs/rules/core/0126-unspecified.md
index e0fcf6a..7ab0e87 100644
--- a/docs/rules/core/0126-unspecified.md
+++ b/docs/rules/core/0126-unspecified.md
@@ -53,13 +53,17 @@ enum Format {
 ## Disabling
 
 If you need to violate this rule, use a leading comment above the enum value.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
 
 ```proto
 enum Format {
-  // (-- api-linter: core::0126::unspecified=disabled --)
+  // (-- api-linter: core::0126::unspecified=disabled
+  //     aip.dev/not-precedent: We need to do this because reasons. --)
   HARDCOVER = 0;
 }
 ```
 
 If you need to violate this rule for an entire file, place the comment at the
 top of the file.
+
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/core/0216-synonyms.md b/docs/rules/core/0216-synonyms.md
new file mode 100644
index 0000000..d18f656
--- /dev/null
+++ b/docs/rules/core/0216-synonyms.md
@@ -0,0 +1,63 @@
+---
+rule:
+  aip: 216
+  name: [core, '0216', synonyms]
+  summary: Lifecycle state enums should be called "State", not "Status".
+---
+
+# States
+
+This rule enforces that all lifecycle state enums are called `State` rather
+than `Status`, as mandated in [AIP-216][].
+
+## Details
+
+This rule iterates over enumerations and looks for enums with a name of
+`Status` or ending in `Status`, and suggests the use of `State` instead.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+enum Status {  // Should be `State`.
+  STATUS_UNSPECIFIED = 0;
+}
+```
+
+```proto
+// Incorrect.
+enum BookStatus {  // Should be `Book.State` or `BookState`.
+  BOOK_STATUS_UNSPECIFIED = 0;
+  HARDCOVER = 1;
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+enum State {
+  STATE_UNSPECIFIED = 0;
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the enum value.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0216::synonyms=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+enum Status {
+  STATUS_UNSPECIFIED = 0;
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-216]: https://aip.dev/216
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/rules/aip0216/aip0216.go b/rules/aip0216/aip0216.go
new file mode 100644
index 0000000..80194f0
--- /dev/null
+++ b/rules/aip0216/aip0216.go
@@ -0,0 +1,27 @@
+// Copyright 2019 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 aip0216 contains rules defined in https://aip.dev/216.
+package aip0216
+
+import "github.com/googleapis/api-linter/lint"
+
+// AddRules accepts a register function and registers each of
+// this AIP's rules to it.
+func AddRules(r lint.RuleRegistry) error {
+	return r.Register(
+		216,
+		synonyms,
+	)
+}
diff --git a/rules/aip0216/synonyms.go b/rules/aip0216/synonyms.go
new file mode 100644
index 0000000..7f375b0
--- /dev/null
+++ b/rules/aip0216/synonyms.go
@@ -0,0 +1,38 @@
+// Copyright 2019 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 aip0216
+
+import (
+	"strings"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/locations"
+	"github.com/jhump/protoreflect/desc"
+)
+
+var synonyms = &lint.EnumRule{
+	Name: lint.NewRuleName(216, "synonyms"),
+	LintEnum: func(e *desc.EnumDescriptor) []lint.Problem {
+		if strings.HasSuffix(e.GetName(), "Status") {
+			return []lint.Problem{{
+				Message:    `Prefer "State" over "Status" for lifecycle state enums.`,
+				Suggestion: strings.Replace(e.GetName(), "Status", "State", 1),
+				Descriptor: e,
+				Location:   locations.DescriptorName(e),
+			}}
+		}
+		return nil
+	},
+}
diff --git a/rules/rules.go b/rules/rules.go
index 2b9cbcf..741a77c 100644
--- a/rules/rules.go
+++ b/rules/rules.go
@@ -69,6 +69,7 @@ import (
 	"github.com/googleapis/api-linter/rules/aip0191"
 	"github.com/googleapis/api-linter/rules/aip0192"
 	"github.com/googleapis/api-linter/rules/aip0203"
+	"github.com/googleapis/api-linter/rules/aip0216"
 	"github.com/googleapis/api-linter/rules/aip0231"
 	"github.com/googleapis/api-linter/rules/aip0233"
 )
@@ -94,6 +95,7 @@ var aipAddRulesFuncs = []addRulesFuncType{
 	aip0191.AddRules,
 	aip0192.AddRules,
 	aip0203.AddRules,
+	aip0216.AddRules,
 	aip0231.AddRules,
 	aip0233.AddRules,
 }


Test patch:
diff --git a/rules/aip0216/aip0216_test.go b/rules/aip0216/aip0216_test.go
new file mode 100644
index 0000000..1c3d51e
--- /dev/null
+++ b/rules/aip0216/aip0216_test.go
@@ -0,0 +1,27 @@
+// Copyright 2019 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 aip0216
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/lint"
+)
+
+func TestAddRules(t *testing.T) {
+	if err := AddRules(lint.NewRuleRegistry()); err != nil {
+		t.Errorf("AddRules got an error: %v", err)
+	}
+}
diff --git a/rules/aip0216/synonyms_test.go b/rules/aip0216/synonyms_test.go
new file mode 100644
index 0000000..efdda07
--- /dev/null
+++ b/rules/aip0216/synonyms_test.go
@@ -0,0 +1,46 @@
+// Copyright 2019 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 aip0216
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestSynonyms(t *testing.T) {
+	tests := []struct {
+		Name     string
+		problems testutils.Problems
+	}{
+		{"Status", testutils.Problems{{Suggestion: "State"}}},
+		{"FooStatus", testutils.Problems{{Suggestion: "FooState"}}},
+		{"State", testutils.Problems{}},
+		{"Foo", testutils.Problems{}},
+	}
+	for _, test := range tests {
+		t.Run(test.Name, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+			enum {{.Name}} {
+				UNSPECIFIED = 0;
+			}
+		`, test)
+			e := f.GetEnumTypes()[0]
+			if diff := test.problems.SetDescriptor(e).Diff(synonyms.Lint(f)); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0231/aip0231_test.go b/rules/aip0231/aip0231_test.go
index ac537b8..e9b7229 100644
--- a/rules/aip0231/aip0231_test.go
+++ b/rules/aip0231/aip0231_test.go
@@ -1,3 +1,17 @@
+// Copyright 2019 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 aip0231
 
 import (