Repository: googleapis/api-linter
Pull request #456 (2019-12-20 20:12:18)

[Refactor] Allow injecting custom rule URL mapping


Patch:
diff --git a/lint/problem.go b/lint/problem.go
index c448d16..2ad4234 100644
--- a/lint/problem.go
+++ b/lint/problem.go
@@ -16,7 +16,6 @@ package lint
 
 import (
 	"encoding/json"
-	"strings"
 
 	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
 	"github.com/jhump/protoreflect/desc"
@@ -96,7 +95,7 @@ func (p Problem) marshal() interface{} {
 		p.Suggestion,
 		fileLocationFromPBLocation(loc),
 		p.RuleID,
-		ruleDocURI(p.RuleID),
+		getRuleURL(string(p.RuleID), ruleURLMappings),
 		p.category,
 	}
 }
@@ -160,11 +159,3 @@ func fileLocationFromPBLocation(l *dpb.SourceCodeInfo_Location) fileLocation {
 		},
 	}
 }
-
-// ruleDocURI returns the link to the rule doc in https://linter.aip.dev/.
-func ruleDocURI(name RuleName) string {
-	base := "https://linter.aip.dev/"
-	nameParts := strings.Split(string(name), "::") // e.g., core::0122::camel-case-uri -> ["core", "0122", "camel-case-uri"]
-	path := strings.TrimPrefix(strings.Join(nameParts[1:], "/"), "0")
-	return base + path
-}
diff --git a/lint/rule_urls.go b/lint/rule_urls.go
new file mode 100644
index 0000000..272cb93
--- /dev/null
+++ b/lint/rule_urls.go
@@ -0,0 +1,39 @@
+package lint
+
+import "strings"
+
+// A list of mapping functions, each of which returns the rule URL for
+// the given rule name, and if not found, return an empty string.
+//
+// At Google, we inject additional rule URL mappings into this list.
+// Example: google_rule_url_mappings.go
+// package lint
+// func init() {
+//   ruleURLMappings = append(ruleURLMappings, internalRuleURLMapping)
+// }
+//
+// func internalRuleURLMapping(ruleName string) string {
+//   ...
+// }
+var ruleURLMappings = []func(string) string{
+	coreRuleURL,
+}
+
+func coreRuleURL(ruleName string) string {
+	base := "https://linter.aip.dev/"
+	nameParts := strings.Split(ruleName, "::") // e.g., core::0122::camel-case-uri -> ["core", "0122", "camel-case-uri"]
+	if len(nameParts) == 0 || nameParts[0] != "core" {
+		return ""
+	}
+	path := strings.TrimPrefix(strings.Join(nameParts[1:], "/"), "0")
+	return base + path
+}
+
+func getRuleURL(ruleName string, nameURLMappings []func(string) string) string {
+	for i := len(nameURLMappings) - 1; i >= 0; i-- {
+		if url := nameURLMappings[i](ruleName); url != "" {
+			return url
+		}
+	}
+	return ""
+}


Test patch:
diff --git a/lint/problem_test.go b/lint/problem_test.go
index 3d933e8..a86c68e 100644
--- a/lint/problem_test.go
+++ b/lint/problem_test.go
@@ -117,23 +117,3 @@ func TestProblemDescriptor(t *testing.T) {
 	}
 
 }
-
-func TestRuleDocURI(t *testing.T) {
-	cases := []struct {
-		name     string
-		ruleName RuleName
-		wantURI  string
-	}{{
-		name:     "CoreRule",
-		ruleName: NewRuleName(122, "camel-case-uris"),
-		wantURI:  "https://linter.aip.dev/122/camel-case-uris",
-	}}
-
-	for _, test := range cases {
-		t.Run(test.name, func(t *testing.T) {
-			if got, want := ruleDocURI(test.ruleName), test.wantURI; got != want {
-				t.Errorf("ruleDocURI(%q): got %q, but want %q", string(test.ruleName), got, want)
-			}
-		})
-	}
-}
diff --git a/lint/rule_urls_test.go b/lint/rule_urls_test.go
new file mode 100644
index 0000000..f501b4d
--- /dev/null
+++ b/lint/rule_urls_test.go
@@ -0,0 +1,58 @@
+package lint
+
+import (
+	"testing"
+)
+
+func TestCoreRuleURL(t *testing.T) {
+	tests := []struct {
+		name string
+		rule string
+		url  string
+	}{
+		{"CoreRule", "core::0122::camel-case-uri", "https://linter.aip.dev/122/camel-case-uri"},
+		{"NotCoreRule", "test::0122::camel-case-uri", ""},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if got := coreRuleURL(test.rule); got != test.url {
+				t.Errorf("coreRuleURL(%s) got %s, but want %s", test.name, got, test.url)
+			}
+		})
+	}
+}
+
+func TestGetRuleURL(t *testing.T) {
+	var mapping1 = func(name string) string {
+		if name == "one" {
+			return "ONE"
+		}
+		return ""
+	}
+	var mapping2 = func(name string) string {
+		if name == "two" {
+			return "TWO"
+		}
+		return ""
+	}
+	ruleURLMappings := []func(string) string{mapping1, mapping2}
+
+	tests := []struct {
+		name     string
+		ruleName string
+		ruleURL  string
+	}{
+		{"MappingOne", "one", "ONE"},
+		{"MappingTwo", "two", "TWO"},
+		{"NoMapping", "zero", ""},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			if got := getRuleURL(test.ruleName, ruleURLMappings); got != test.ruleURL {
+				t.Errorf("getRuleURL(%s) got %s, but want %s", test.ruleName, got, test.ruleURL)
+			}
+		})
+	}
+}