Repository: googleapis/api-linter
Pull request #325 (2019-11-08 22:41:11)

Rewrite lint configuration.
The configurations work in two steps:
First, we check if a configuration is matched against the given path.
1. If none is matched, the rule is enabled by default;
2. Otherwise, we apply the matched configuration against the rule:
(a) if the given rule is in the disabled list, the rule is disabled;
(b) if the given rule is in the enabled list, the rule is enabled;
(c) if the given rule is in neither list, the rule remains the state determined by the previous matched configuration.


Patch:
diff --git a/cmd/api-linter/cli.go b/cmd/api-linter/cli.go
index ac559f0..724b626 100644
--- a/cmd/api-linter/cli.go
+++ b/cmd/api-linter/cli.go
@@ -92,23 +92,13 @@ func (c *cli) lint(rules lint.RuleRegistry, configs lint.Configs) error {
 		configs = append(configs, config...)
 	}
 	// Add configs for the enabled rules.
-	for _, ruleName := range c.EnabledRules {
-		configs = append(configs, lint.Config{
-			IncludedPaths: []string{"**/*.proto"},
-			RuleConfigs: map[string]lint.RuleConfig{
-				ruleName: {}, // default is enabled.
-			},
-		})
-	}
+	configs = append(configs, lint.Config{
+		EnabledRules: c.EnabledRules,
+	})
 	// Add configs for the disabled rules.
-	for _, ruleName := range c.DisabledRules {
-		configs = append(configs, lint.Config{
-			IncludedPaths: []string{"**/*.proto"},
-			RuleConfigs: map[string]lint.RuleConfig{
-				ruleName: {Disabled: true},
-			},
-		})
-	}
+	configs = append(configs, lint.Config{
+		DisabledRules: c.DisabledRules,
+	})
 	// Prepare proto import lookup.
 	var lookupImport func(string) (*desc.FileDescriptor, error)
 	if c.ProtoDescPath != "" {
diff --git a/cmd/api-linter/main.go b/cmd/api-linter/main.go
index 8970be8..7fa3d60 100644
--- a/cmd/api-linter/main.go
+++ b/cmd/api-linter/main.go
@@ -38,13 +38,10 @@ func runCLI(args []string) error {
 	return c.lint(globalRules, globalConfigs)
 }
 
+// Enable all rules by default.
+// Once we have rules other than core,
+// we will determine if we want to
+// change this policy.
 func defaultConfigs() lint.Configs {
-	return lint.Configs{
-		lint.Config{
-			IncludedPaths: []string{"**/*.proto"},
-			RuleConfigs: map[string]lint.RuleConfig{
-				"core": {},
-			},
-		},
-	}
+	return lint.Configs{}
 }
diff --git a/docs/configuration.md b/docs/configuration.md
index a271a9e..211aa77 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -34,10 +34,8 @@ directory `tests` using a JSON config file:
 ```json
 [
   {
-    "included_paths": ["tests/*.proto"],
-    "rule_configs": {
-      "core::0140::lower-snake": { "disabled": true }
-    }
+    "included_paths": ["tests/**/*.proto"],
+    "disabled_rules": ["core::0140::lower-snake"]
   }
 ]
 ```
@@ -47,10 +45,9 @@ Disable the same rule using a YAML config file:
 ```yaml
 ---
 - included_paths:
-    - '**/*.proto'
-  rule_configs:
-    core::proto_version:
-      disabled: true
+    - 'tests/**/*.proto'
+  disabled_rules:
+    - 'core::0140::lower-snake'
 ```
 
 ## Proto comments
diff --git a/lint/config.go b/lint/config.go
index cd2da3e..a812882 100644
--- a/lint/config.go
+++ b/lint/config.go
@@ -21,43 +21,23 @@ import (
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"strings"
 
 	"github.com/bmatcuk/doublestar"
 	"gopkg.in/yaml.v2"
 )
 
-// Configs stores a list of Config.
-//
-// Note: for a path, if multiple configs match it, the rule configs of the later one
-// will always override those in the former one. For example, given a list of configs
-//
-// [
-//		{
-//			included_paths: ["/a/**/*.proto"],
-//			rule_configs: {"my::rule": {disabled, error}},
-//		},
-//		{
-//			included_paths: ["/a/b.proto"],
-//			rule_configs: {"my::rule": {enabled}},
-//		}
-// ]
-//
-// that match path "/a/b.proto", the resulted config for rule "my::rule" will be {enabled, error}.
+// Configs determine if a rule is enabled or not on a file path.
 type Configs []Config
 
 // Config stores rule configurations for certain files
 // that the file path must match any of the included paths
 // but none of the excluded ones.
 type Config struct {
-	IncludedPaths []string              `json:"included_paths" yaml:"included_paths"`
-	ExcludedPaths []string              `json:"excluded_paths" yaml:"excluded_paths"`
-	RuleConfigs   map[string]RuleConfig `json:"rule_configs" yaml:"rule_configs"`
-}
-
-// RuleConfig stores configurable status and category of a rule.
-type RuleConfig struct {
-	Disabled bool   `json:"disabled" yaml:"disabled"`
-	Category string `json:"category" yaml:"category"`
+	IncludedPaths []string `json:"included_paths" yaml:"included_paths"`
+	ExcludedPaths []string `json:"excluded_paths" yaml:"excluded_paths"`
+	EnabledRules  []string `json:"enabled_rules" yaml:"enabled_rules"`
+	DisabledRules []string `json:"disabled_rules" yaml:"disabled_rules"`
 }
 
 // ReadConfigsFromFile reads Configs from a file.
@@ -109,55 +89,50 @@ func ReadConfigsYAML(f io.Reader) (Configs, error) {
 	return c, nil
 }
 
-// GetRuleConfig returns a RuleConfig that matches the given path and rule.
-// Returns an error if a config is not found for the path.
-func (c Configs) GetRuleConfig(path string, rule RuleName) (result RuleConfig, err error) {
-	err = fmt.Errorf("failed to find a config for path %q", path)
-	for _, cfg := range c {
-		if cfg.match(path) {
-			err = nil
-			if r, ok := cfg.getRuleConfig(rule); ok {
-				result = result.withOverride(r)
+// IsRuleEnabled returns true if a rule is enabled by the configs.
+func (configs Configs) IsRuleEnabled(rule string, path string) bool {
+	enabled := true
+	for _, c := range configs {
+		if c.matchPath(path) {
+			if matchRule(rule, c.DisabledRules...) {
+				enabled = false
+			}
+			if matchRule(rule, c.EnabledRules...) {
+				enabled = true
 			}
-		}
-	}
-	return
-}
-
-func (c Config) getRuleConfig(rule RuleName) (RuleConfig, bool) {
-	for r := rule; ; r = r.parent() {
-		if ruleConfig, ok := c.RuleConfigs[string(r)]; ok {
-			return ruleConfig, true
-		}
-
-		if r == "" {
-			break
 		}
 	}
 
-	return RuleConfig{}, false
+	return enabled
 }
 
-// withOverride returns a copy of r, overridden with non-zero values in r2
-func (r RuleConfig) withOverride(r2 RuleConfig) RuleConfig {
-	r.Disabled = r2.Disabled
-
-	if r2.Category != "" {
-		r.Category = r2.Category
+func (c Config) matchPath(path string) bool {
+	if matchPath(path, c.ExcludedPaths...) {
+		return false
 	}
-
-	return r
+	return len(c.IncludedPaths) == 0 || matchPath(path, c.IncludedPaths...)
 }
 
-// match returns if a Config matches path based on its included and excluded paths
-func (c Config) match(path string) bool {
-	for _, pattern := range c.ExcludedPaths {
-		if matched, err := doublestar.Match(pattern, path); matched || err != nil {
-			return false
+func matchPath(path string, pathPatterns ...string) bool {
+	for _, pattern := range pathPatterns {
+		if matched, _ := doublestar.Match(pattern, path); matched {
+			return true
 		}
 	}
-	for _, pattern := range c.IncludedPaths {
-		if matched, err := doublestar.Match(pattern, path); matched && err == nil {
+	return false
+}
+
+func matchRule(rule string, rulePrefixes ...string) bool {
+	rule = strings.ToLower(rule)
+	for _, prefix := range rulePrefixes {
+		prefix = strings.ToLower(prefix)
+		prefix = strings.TrimSuffix(prefix, nameSeparator) // "core::" -> "core"
+		prefix = strings.TrimPrefix(prefix, nameSeparator) // "::http-body" -> "http-body"
+		if prefix == "all" ||
+			prefix == rule ||
+			strings.HasPrefix(rule, prefix+nameSeparator) || // e.g., "core" matches "core::http-body", but not "core-rules::http-body"
+			strings.HasSuffix(rule, nameSeparator+prefix) || // e.g., "http-body" matches "core::http-body", but not "core::google-http-body"
+			strings.Contains(rule, nameSeparator+prefix+nameSeparator) { // e.g., "http-body" matches "core::http-body::post", but not "core::google-http-body::post"
 			return true
 		}
 	}
diff --git a/lint/lint.go b/lint/lint.go
index fc78fba..be19534 100644
--- a/lint/lint.go
+++ b/lint/lint.go
@@ -65,23 +65,13 @@ func (l *Linter) lintFileDescriptor(fd *desc.FileDescriptor) (Response, error) {
 	var errMessages []string
 
 	for name, rule := range l.rules {
-		var config RuleConfig
-
-		if c, err := l.configs.GetRuleConfig(fd.GetName(), name); err == nil {
-			config = config.withOverride(c)
-		} else {
-			errMessages = append(errMessages, err.Error())
-			continue
-		}
-
 		// Run the linter rule against this file, and throw away any problems
 		// which should have been disabled.
-		if !config.Disabled {
+		if l.configs.IsRuleEnabled(string(name), fd.GetName()) {
 			if problems, err := l.runAndRecoverFromPanics(rule, fd); err == nil {
 				for _, p := range problems {
 					if ruleIsEnabled(rule, p.Descriptor, aliasMap) {
 						p.RuleID = rule.GetName()
-						p.category = config.Category
 						resp.Problems = append(resp.Problems, p)
 					}
 				}


Test patch:
diff --git a/cmd/api-linter/integration_test.go b/cmd/api-linter/integration_test.go
index dd490ba..cf805c6 100644
--- a/cmd/api-linter/integration_test.go
+++ b/cmd/api-linter/integration_test.go
@@ -72,25 +72,11 @@ func TestRules_EnabledByDefault(t *testing.T) {
 }
 
 func TestRules_DisabledByFileComments(t *testing.T) {
-	config := `
-	[
-		{
-			"included_paths": ["*.proto"],
-			"rule_configs": {
-				"": {
-					"status": "enabled",
-					"category": "warning"
-				}
-			}
-		}
-	]
-	`
-
 	for _, test := range testCases {
 		t.Run(test.testName, func(t *testing.T) {
 			disableInFile := fmt.Sprintf("// (-- api-linter: %s=disabled --)", test.rule)
 			proto := disableInFile + "\n" + test.proto
-			result := runLinter(t, proto, config)
+			result := runLinter(t, proto, "")
 			if strings.Contains(result, test.rule) {
 				t.Errorf("Rule %q should be disabled by file comments", test.rule)
 			}
@@ -99,25 +85,11 @@ func TestRules_DisabledByFileComments(t *testing.T) {
 }
 
 func TestRules_DisabledByInlineComments(t *testing.T) {
-	config := `
-	[
-		{
-			"included_paths": ["*.proto"],
-			"rule_configs": {
-				"": {
-					"status": "enabled",
-					"category": "warning"
-				}
-			}
-		}
-	]
-	`
-
 	for _, test := range testCases {
 		t.Run(test.testName, func(t *testing.T) {
 			disableInline := fmt.Sprintf("(-- api-linter: %s=disabled --)", test.rule)
 			proto := strings.Replace(test.proto, "disable-me-here", disableInline, -1)
-			result := runLinter(t, proto, config)
+			result := runLinter(t, proto, "")
 			if strings.Contains(result, test.rule) {
 				t.Errorf("Rule %q should be disabled by in-line comments", test.rule)
 			}
@@ -129,21 +101,7 @@ func TestRules_DisabledByConfig(t *testing.T) {
 	config := `
 	[
 		{
-			"included_paths": ["*.proto"],
-			"rule_configs": {
-				"": {
-					"disabled": false,
-					"category": "warning"
-				}
-			}
-		},
-		{
-			"included_paths": ["*.proto"],
-			"rule_configs": {
-				"replace-me-here": {
-					"disabled": true
-				}
-			}
+			"disabled_rules": ["replace-me-here"]
 		}
 	]
 	`
diff --git a/lint/config_test.go b/lint/config_test.go
index a3eb1c4..980339f 100644
--- a/lint/config_test.go
+++ b/lint/config_test.go
@@ -20,91 +20,203 @@ import (
 	"testing"
 )
 
-func TestRuleConfigs_getRuleConfig(t *testing.T) {
-	matchConfig := RuleConfig{Disabled: false, Category: "warning"}
+func TestRuleConfigs_IsRuleEnabled(t *testing.T) {
+	enabled := true
+	disabled := false
 
 	tests := []struct {
+		name    string
 		configs Configs
 		path    string
-		rule    RuleName
-		result  RuleConfig
+		rule    string
+		want    bool
 	}{
-		{nil, "a", "b", RuleConfig{}},
+		{"EmptyConfig", nil, "a", "b", enabled},
 		{
+			"NoConfigMatched_Enabled",
 			Configs{
 				{
 					IncludedPaths: []string{"a.proto"},
-					RuleConfigs: map[string]RuleConfig{
-						"foo":      {},
-						"testrule": matchConfig,
-					},
+					DisabledRules: []string{"testrule"},
 				},
 			},
 			"b.proto",
 			"testrule",
-			RuleConfig{},
+			enabled,
 		},
 		{
+			"PathMatched_DisabledRulesNotMatch_Enabled",
 			Configs{
 				{
 					IncludedPaths: []string{"a.proto"},
-					RuleConfigs: map[string]RuleConfig{
-						"foo": matchConfig,
-					},
+					DisabledRules: []string{"somerule"},
 				},
 			},
 			"a.proto",
 			"testrule",
-			RuleConfig{},
+			enabled,
 		},
 		{
+			"PathExactMatched_DisabledRulesMatched_Disabled",
 			Configs{
 				{
 					IncludedPaths: []string{"a.proto"},
-					RuleConfigs: map[string]RuleConfig{
-						"foo":      {},
-						"testrule": matchConfig,
-					},
+					DisabledRules: []string{"somerule", "testrule"},
 				},
 			},
 			"a.proto",
 			"testrule",
-			matchConfig,
+			disabled,
 		},
 		{
+			"PathExactMatched_DisabledRulesMatchedAll_Disabled",
 			Configs{
 				{
-					IncludedPaths: []string{"a/**/*.proto"},
-					RuleConfigs: map[string]RuleConfig{
-						"foo":     {},
-						"a::b::c": matchConfig,
-					},
+					IncludedPaths: []string{"a.proto"},
+					DisabledRules: []string{"all"},
 				},
 			},
-			"a/with/long/sub/dir/ect/ory/e.proto",
-			"a::b::c",
-			matchConfig,
+			"a.proto",
+			"testrule",
+			disabled,
 		},
 		{
+			"PathDoubleStartMatched_DisabledRulesMatched_Disabled",
 			Configs{
 				{
 					IncludedPaths: []string{"a/**/*.proto"},
-					RuleConfigs: map[string]RuleConfig{
-						"foo":       {},
-						"a::module": matchConfig,
-					},
+					DisabledRules: []string{"somerule", "testrule"},
 				},
 			},
 			"a/with/long/sub/dir/ect/ory/e.proto",
-			"a::module::test_rule",
-			matchConfig,
+			"testrule",
+			disabled,
+		},
+		{
+			"PathMatched_DisabledRulesPrefixMatched_Disabled",
+			Configs{
+				{
+					IncludedPaths: []string{"a/b/c.proto"},
+					DisabledRules: []string{"parent"},
+				},
+			},
+			"a/b/c.proto",
+			"parent::test_rule",
+			disabled,
+		},
+		{
+			"PathMatched_DisabledRulesSuffixMatched_Disabled",
+			Configs{
+				{
+					IncludedPaths: []string{"a/b/c.proto"},
+					DisabledRules: []string{"child"},
+				},
+			},
+			"a/b/c.proto",
+			"parent::child",
+			disabled,
+		},
+		{
+			"PathMatched_DisabledRulesMiddleMatched_Disabled",
+			Configs{
+				{
+					IncludedPaths: []string{"a/b/c.proto"},
+					DisabledRules: []string{"middle"},
+				},
+			},
+			"a/b/c.proto",
+			"parent::middle::child",
+			disabled,
+		},
+		{
+			"EmptyIncludePath_ConfigMatched_DisabledRulesMatched_Disabled",
+			Configs{
+				{
+					DisabledRules: []string{"testrule"},
+				},
+			},
+			"a.proto",
+			"testrule",
+			disabled,
+		},
+		{
+			"ExcludedPathMatch_ConfigNotMatched_DisabledRulesMatched_Enabled",
+			Configs{
+				{
+					ExcludedPaths: []string{"a.proto"},
+					DisabledRules: []string{"testrule"},
+				},
+			},
+			"a.proto",
+			"testrule",
+			enabled,
+		},
+		{
+			"TwoConfigs_Override_Enabled",
+			Configs{
+				{
+					DisabledRules: []string{"testrule"},
+				},
+				{
+					EnabledRules: []string{"testrule::a"},
+				},
+			},
+			"a.proto",
+			"testrule::a",
+			enabled,
+		},
+		{
+			"TwoConfigs_Override_Disabled",
+			Configs{
+				{
+					EnabledRules: []string{"testrule"},
+				},
+				{
+					DisabledRules: []string{"testrule::a"},
+				},
+			},
+			"a.proto",
+			"testrule::a",
+			disabled,
+		},
+		{
+			"TwoConfigs_DoubleEnable_Enabled",
+			Configs{
+				{
+					EnabledRules: []string{"testrule"},
+				},
+				{
+					EnabledRules: []string{"testrule::a"},
+				},
+			},
+			"a.proto",
+			"testrule::a",
+			enabled,
+		},
+		{
+			"TwoConfigs_DoubleDisabled_Disabled",
+			Configs{
+				{
+					DisabledRules: []string{"testrule"},
+				},
+				{
+					DisabledRules: []string{"testrule::a"},
+				},
+			},
+			"a.proto",
+			"testrule::a",
+			disabled,
 		},
 	}
-	for ind, test := range tests {
-		cfg, _ := test.configs.GetRuleConfig(test.path, test.rule)
-		if cfg != test.result {
-			t.Errorf("Test #%d: %+v.GetRuleConfig(%q, %q)=%+v; want %+v", ind+1, test.configs, test.path, test.rule, cfg, test.result)
-		}
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			got := test.configs.IsRuleEnabled(test.rule, test.path)
+
+			if got != test.want {
+				t.Errorf("IsRuleEnabled: got %t, but want %t", got, test.want)
+			}
+		})
+
 	}
 }
 
@@ -112,14 +224,10 @@ func TestReadConfigsJSON(t *testing.T) {
 	content := `
 	[
 		{
-			"included_paths": ["a"],
-			"excluded_paths": ["b"],
-			"rule_configs": {
-				"rule_a": {
-					"status": "enabled",
-					"category": "warning"
-				}
-			}
+			"included_paths": ["path_a"],
+			"excluded_paths": ["path_b"],
+			"disabled_rules": ["rule_a", "rule_b"],
+			"enabled_rules": ["rule_c", "rule_d"]
 		}
 	]
 	`
@@ -131,68 +239,13 @@ func TestReadConfigsJSON(t *testing.T) {
 
 	expected := Configs{
 		{
-			IncludedPaths: []string{"a"},
-			ExcludedPaths: []string{"b"},
-			RuleConfigs: map[string]RuleConfig{
-				"rule_a": {
-					Disabled: false,
-					Category: "warning",
-				},
-			},
+			IncludedPaths: []string{"path_a"},
+			ExcludedPaths: []string{"path_b"},
+			DisabledRules: []string{"rule_a", "rule_b"},
+			EnabledRules:  []string{"rule_c", "rule_d"},
 		},
 	}
 	if !reflect.DeepEqual(configs, expected) {
 		t.Errorf("ReadConfigsJSON returns %v, but want %v", configs, expected)
 	}
 }
-
-func TestRuleConfig_WithOverride(t *testing.T) {
-	tests := []struct {
-		original RuleConfig
-		override RuleConfig
-		result   RuleConfig
-	}{
-		{
-			RuleConfig{Disabled: false, Category: "warning"},
-			RuleConfig{Disabled: false, Category: "warning"},
-			RuleConfig{Disabled: false, Category: "warning"},
-		},
-		{
-			RuleConfig{},
-			RuleConfig{Disabled: false, Category: "warning"},
-			RuleConfig{Disabled: false, Category: "warning"},
-		},
-		{
-			RuleConfig{Category: ""},
-			RuleConfig{Disabled: true, Category: "warning"},
-			RuleConfig{Disabled: true, Category: "warning"},
-		},
-		{
-			RuleConfig{Disabled: false, Category: "warning"},
-			RuleConfig{Disabled: true, Category: "error"},
-			RuleConfig{Disabled: true, Category: "error"},
-		},
-		{
-			RuleConfig{Disabled: false, Category: "warning"},
-			RuleConfig{Category: ""},
-			RuleConfig{Disabled: false, Category: "warning"},
-		},
-		{
-			RuleConfig{Disabled: false, Category: "warning"},
-			RuleConfig{Disabled: true, Category: ""},
-			RuleConfig{Disabled: true, Category: "warning"},
-		},
-		{
-			RuleConfig{Disabled: false, Category: "warning"},
-			RuleConfig{Category: "error"},
-			RuleConfig{Disabled: false, Category: "error"},
-		},
-	}
-
-	for _, test := range tests {
-		result := test.original.withOverride(test.override)
-		if result != test.result {
-			t.Errorf("%+v.WithOverride(%+v)=%+v; want %+v", test.original, test.override, result, test.result)
-		}
-	}
-}
diff --git a/lint/lint_test.go b/lint/lint_test.go
index 3ea4596..d1a2668 100644
--- a/lint/lint_test.go
+++ b/lint/lint_test.go
@@ -29,15 +29,14 @@ func TestLinter_run(t *testing.T) {
 	if err != nil {
 		t.Fatalf("Failed to build a file descriptor.")
 	}
-	defaultConfigs := Configs{
-		{[]string{"**"}, []string{}, map[string]RuleConfig{}},
-	}
+	defaultConfigs := Configs{}
 
+	testRuleID := "test::rule1"
 	ruleProblems := []Problem{{
 		Message:    "rule1_problem",
 		Descriptor: fd,
 		category:   "",
-		RuleID:     "test::rule1",
+		RuleID:     RuleName(testRuleID),
 	}}
 
 	tests := []struct {
@@ -52,7 +51,6 @@ func TestLinter_run(t *testing.T) {
 				defaultConfigs,
 				Config{
 					IncludedPaths: []string{"nofile"},
-					RuleConfigs:   map[string]RuleConfig{"": {Disabled: true}},
 				},
 			),
 			ruleProblems,
@@ -62,8 +60,7 @@ func TestLinter_run(t *testing.T) {
 			append(
 				defaultConfigs,
 				Config{
-					IncludedPaths: []string{"*"},
-					RuleConfigs:   map[string]RuleConfig{"foo::bar": {Disabled: true}},
+					DisabledRules: []string{"foo::bar"},
 				},
 			),
 			ruleProblems,
@@ -73,32 +70,11 @@ func TestLinter_run(t *testing.T) {
 			append(
 				defaultConfigs,
 				Config{
-					IncludedPaths: []string{"*"},
-					RuleConfigs: map[string]RuleConfig{
-						"test::rule1": {Disabled: true},
-					},
+					DisabledRules: []string{testRuleID},
 				},
 			),
 			[]Problem{},
 		},
-		{
-			"CategoryOverride",
-			append(
-				defaultConfigs,
-				Config{
-					IncludedPaths: []string{"*"},
-					RuleConfigs: map[string]RuleConfig{
-						"test::rule1": {Category: "error"},
-					},
-				},
-			),
-			[]Problem{{
-				Descriptor: fd,
-				Message:    "rule1_problem",
-				RuleID:     "test::rule1",
-				category:   "error",
-			}},
-		},
 	}
 
 	for _, test := range tests {
@@ -163,10 +139,7 @@ func TestLinter_LintProtos_RulePanics(t *testing.T) {
 			}
 
 			// Instantiate a linter with the given rule.
-			l := New(r, []Config{{
-				IncludedPaths: []string{"**"},
-				RuleConfigs:   map[string]RuleConfig{"": {}},
-			}})
+			l := New(r, nil)
 
 			_, err = l.LintProtos(fd)
 			if err == nil || !strings.Contains(err.Error(), "panic") {