go/analysis: validate report if analyzer.Run is empty

The Run function is necessary for an analyzer, if left a nil Run, the runner will panic in run-time(singlechecker.Main, etc).

Change-Id: Ibbeea2d8c740f73e6c083647df6ded445036e272
Reviewed-on: https://go-review.googlesource.com/c/tools/+/329451
Trust: Carlos Amedee <carlos@golang.org>
Reviewed-by: Tim King <taking@google.com>
Run-TryBot: Tim King <taking@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/go/analysis/validate.go b/go/analysis/validate.go
index 23e57bf..9da5692 100644
--- a/go/analysis/validate.go
+++ b/go/analysis/validate.go
@@ -14,6 +14,8 @@
 // Validate reports an error if any of the analyzers are misconfigured.
 // Checks include:
 // that the name is a valid identifier;
+// that the Doc is not empty;
+// that the Run is non-nil;
 // that the Requires graph is acyclic;
 // that analyzer fact types are unique;
 // that each fact type is a pointer.
@@ -46,6 +48,9 @@
 				return fmt.Errorf("analyzer %q is undocumented", a)
 			}
 
+			if a.Run == nil {
+				return fmt.Errorf("analyzer %q has nil Run", a)
+			}
 			// fact types
 			for _, f := range a.FactTypes {
 				if f == nil {
diff --git a/go/analysis/validate_test.go b/go/analysis/validate_test.go
index 1116034..7f4ee2c 100644
--- a/go/analysis/validate_test.go
+++ b/go/analysis/validate_test.go
@@ -11,33 +11,43 @@
 
 func TestValidate(t *testing.T) {
 	var (
+		run = func(p *Pass) (interface{}, error) {
+			return nil, nil
+		}
 		dependsOnSelf = &Analyzer{
 			Name: "dependsOnSelf",
 			Doc:  "this analyzer depends on itself",
+			Run:  run,
 		}
 		inCycleA = &Analyzer{
 			Name: "inCycleA",
 			Doc:  "this analyzer depends on inCycleB",
+			Run:  run,
 		}
 		inCycleB = &Analyzer{
 			Name: "inCycleB",
 			Doc:  "this analyzer depends on inCycleA and notInCycleA",
+			Run:  run,
 		}
 		pointsToCycle = &Analyzer{
 			Name: "pointsToCycle",
 			Doc:  "this analyzer depends on inCycleA",
+			Run:  run,
 		}
 		notInCycleA = &Analyzer{
 			Name: "notInCycleA",
 			Doc:  "this analyzer depends on notInCycleB and notInCycleC",
+			Run:  run,
 		}
 		notInCycleB = &Analyzer{
 			Name: "notInCycleB",
 			Doc:  "this analyzer depends on notInCycleC",
+			Run:  run,
 		}
 		notInCycleC = &Analyzer{
 			Name: "notInCycleC",
 			Doc:  "this analyzer has no dependencies",
+			Run:  run,
 		}
 	)
 
@@ -116,3 +126,27 @@
 		t.Errorf("error string %s does not contain expected substring %q", errMsg, wantSubstring)
 	}
 }
+
+func TestValidateEmptyDoc(t *testing.T) {
+	withoutDoc := &Analyzer{
+		Name: "withoutDoc",
+		Run: func(p *Pass) (interface{}, error) {
+			return nil, nil
+		},
+	}
+	err := Validate([]*Analyzer{withoutDoc})
+	if err == nil || !strings.Contains(err.Error(), "is undocumented") {
+		t.Errorf("got unexpected error while validating analyzers withoutDoc: %v", err)
+	}
+}
+
+func TestValidateNoRun(t *testing.T) {
+	withoutRun := &Analyzer{
+		Name: "withoutRun",
+		Doc:  "this analyzer has no Run",
+	}
+	err := Validate([]*Analyzer{withoutRun})
+	if err == nil || !strings.Contains(err.Error(), "has nil Run") {
+		t.Errorf("got unexpected error while validating analyzers withoutRun: %v", err)
+	}
+}