From 7609aaa7f7a638a45a71ffbd32358c28f7575351 Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 28 Jan 2026 10:29:29 +0100 Subject: [PATCH 1/2] runner: allow to create plugin-specific exceptions at rule level --- README.md | 29 ++++++++++ pkg/analysis/analysis.go | 2 +- pkg/runner/runner.go | 31 +++++++--- pkg/runner/runner_test.go | 58 +++++++++++++++++++ .../myorg-plugin-panel/plugin.json | 5 ++ 5 files changed, 117 insertions(+), 8 deletions(-) create mode 100644 pkg/runner/testdata/RuleExceptions/myorg-plugin-panel/plugin.json diff --git a/README.md b/README.md index 7590c165..3bb95693 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,35 @@ Severity levels could be: `error`, `warning`, or `ok`. > Note: Grafana Labs enforces its own configuration for plugins submissions and your own config file can't change these rules. +#### Excluding a plugin from an analyzer or rule + +It's also possible to exclude a specific plugin from an analyzer or a specific rule within an analyzer. This is useful when a particular check is not applicable to your plugin. + +To disable an entire analyzer for a plugin, add an `exceptions` list with the plugin ID. + +```yaml +analyzers: + some-analyzer: + enabled: true + # This entire analyzer will be skipped for 'my-plugin-id' + exceptions: + - my-plugin-id +``` + +To disable a single rule for a plugin, add the `exceptions` list to the rule's configuration. + +```yaml +analyzers: + some-analyzer: + rules: + some-rule: + enabled: true + # This rule will be skipped for 'my-plugin-id' + exceptions: + - my-plugin-id +``` + + ### Source code You can specify the location of the plugin source code to the validator with the `-sourceCodeUri` option. Doing so allows for additional [analyzers](#analyzers) to be run and for a more complete scan. diff --git a/pkg/analysis/analysis.go b/pkg/analysis/analysis.go index b859d596..e2fe248a 100644 --- a/pkg/analysis/analysis.go +++ b/pkg/analysis/analysis.go @@ -20,7 +20,7 @@ type Pass struct { AnalyzerName string RootDir string CheckParams CheckParams - ResultOf map[*Analyzer]interface{} + ResultOf map[*Analyzer]any Report func(string, Diagnostic) } diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index e5b3d419..f7687b24 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -29,8 +29,9 @@ type AnalyzerConfig struct { } type RuleConfig struct { - Enabled *bool `yaml:"enabled"` - Severity *analysis.Severity `yaml:"severity"` + Enabled *bool `yaml:"enabled"` + Severity *analysis.Severity `yaml:"severity"` + Exceptions []string `yaml:"exceptions"` } var defaultSeverity = analysis.Warning @@ -54,11 +55,27 @@ func Check( pass := &analysis.Pass{ RootDir: params.ArchiveDir, CheckParams: params, - ResultOf: make(map[*analysis.Analyzer]interface{}), - Report: func(name string, d analysis.Diagnostic) { - // Collect all diagnostics for presenting at the end. - diagnostics[name] = append(diagnostics[name], d) - }, + ResultOf: make(map[*analysis.Analyzer]any), + } + + pass.Report = func(ruleName string, d analysis.Diagnostic) { + // Check for exceptions at the rule level + if analyzerConfig, ok := cfg.Analyzers[pass.AnalyzerName]; ok { + if ruleConfig, ok := analyzerConfig.Rules[ruleName]; ok { + for _, exceptedPluginID := range ruleConfig.Exceptions { + if exceptedPluginID == pluginId { + logme.DebugFln( + "Diagnostic for rule '%s' skipped for plugin '%s' due to a rule-level exception.", + ruleName, + pluginId, + ) + return + } + } + } + } + // Collect all diagnostics for presenting at the end. + diagnostics[ruleName] = append(diagnostics[ruleName], d) } seen := make(map[*analysis.Analyzer]bool) diff --git a/pkg/runner/runner_test.go b/pkg/runner/runner_test.go index 09cc24af..a82ea357 100644 --- a/pkg/runner/runner_test.go +++ b/pkg/runner/runner_test.go @@ -111,6 +111,64 @@ func contains(strs []string, str string) bool { return false } +func TestRuleExceptions(t *testing.T) { + analyzer := &analysis.Analyzer{ + Name: "testanalyzer", + Rules: []*analysis.Rule{ + {Name: "rule1"}, + {Name: "rule2"}, + }, + Run: func(pass *analysis.Pass) (interface{}, error) { + pass.Report("rule1", analysis.Diagnostic{ + Title: "diagnostic from rule1", + }) + pass.Report("rule2", analysis.Diagnostic{ + Title: "diagnostic from rule2", + }) + return nil, nil + }, + } + + config := Config{ + Global: GlobalConfig{Enabled: true}, + Analyzers: map[string]AnalyzerConfig{ + "testanalyzer": { + Rules: map[string]RuleConfig{ + "rule1": { + Exceptions: []string{"myorg-plugin-panel"}, + }, + }, + }, + }, + } + + pluginDir := filepath.Join("testdata", "RuleExceptions", "myorg-plugin-panel") + + ds, err := Check([]*analysis.Analyzer{analyzer}, + analysis.CheckParams{ + ArchiveDir: pluginDir, + }, + config, + analysis.Severity(""), + ) + + if err != nil { + t.Fatal(err) + } + + var diagnostics []string + for name := range ds { + for _, d := range ds[name] { + diagnostics = append(diagnostics, d.Title) + } + } + + // rule1 should be skipped + assert.NotContains(t, diagnostics, "diagnostic from rule1") + // rule2 should be reported + assert.Contains(t, diagnostics, "diagnostic from rule2") +} + func TestLinearDependencies(t *testing.T) { res := make(map[string]bool) first := &analysis.Analyzer{ diff --git a/pkg/runner/testdata/RuleExceptions/myorg-plugin-panel/plugin.json b/pkg/runner/testdata/RuleExceptions/myorg-plugin-panel/plugin.json new file mode 100644 index 00000000..b83283c6 --- /dev/null +++ b/pkg/runner/testdata/RuleExceptions/myorg-plugin-panel/plugin.json @@ -0,0 +1,5 @@ +{ + "id": "myorg-plugin-panel", + "name": "my-plugin", + "type": "panel" +} From b8b1a99ba3a0e71f4d96deaa92a9152d42030d6f Mon Sep 17 00:00:00 2001 From: Esteban Beltran Date: Wed, 28 Jan 2026 11:24:45 +0100 Subject: [PATCH 2/2] replace with slices.Contains --- pkg/runner/runner.go | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/pkg/runner/runner.go b/pkg/runner/runner.go index f7687b24..0d8ec6c7 100644 --- a/pkg/runner/runner.go +++ b/pkg/runner/runner.go @@ -2,6 +2,7 @@ package runner import ( "fmt" + "slices" "github.com/grafana/plugin-validator/pkg/analysis" "github.com/grafana/plugin-validator/pkg/logme" @@ -62,15 +63,13 @@ func Check( // Check for exceptions at the rule level if analyzerConfig, ok := cfg.Analyzers[pass.AnalyzerName]; ok { if ruleConfig, ok := analyzerConfig.Rules[ruleName]; ok { - for _, exceptedPluginID := range ruleConfig.Exceptions { - if exceptedPluginID == pluginId { - logme.DebugFln( - "Diagnostic for rule '%s' skipped for plugin '%s' due to a rule-level exception.", - ruleName, - pluginId, - ) - return - } + if slices.Contains(ruleConfig.Exceptions, pluginId) { + logme.DebugFln( + "Diagnostic for rule '%s' skipped for plugin '%s' due to a rule-level exception.", + ruleName, + pluginId, + ) + return } } } @@ -186,10 +185,8 @@ func initAnalyzers( func isExcepted(pluginId string, cfg *AnalyzerConfig) bool { if len(pluginId) > 0 && cfg != nil && len(cfg.Exceptions) > 0 { - for _, exception := range cfg.Exceptions { - if exception == pluginId { - return true - } + if slices.Contains(cfg.Exceptions, pluginId) { + return true } } return false