gopls/internal/lsp: document analysis-related functions

This change adds doc comments to various functions related
to analysis to try to reveal something of the structure.

Also:
- ensure all type-error analyzers return promptly if
  there are no errors. (Perhaps all analyzers can be
  applied at once?)
- Replace Snapshot or View with a mere Options in various
  functions related to "are analyzers enabled?".

Change-Id: I0dd19708b16a69474fd7ac2e2192043c6ab9f2a7
Reviewed-on: https://go-review.googlesource.com/c/tools/+/449895
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Alan Donovan <adonovan@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/gopls/internal/lsp/analysis/nonewvars/nonewvars.go b/gopls/internal/lsp/analysis/nonewvars/nonewvars.go
index 2d2044e..6937b36 100644
--- a/gopls/internal/lsp/analysis/nonewvars/nonewvars.go
+++ b/gopls/internal/lsp/analysis/nonewvars/nonewvars.go
@@ -39,6 +39,9 @@
 
 func run(pass *analysis.Pass) (interface{}, error) {
 	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+	if len(pass.TypeErrors) == 0 {
+		return nil, nil
+	}
 
 	nodeFilter := []ast.Node{(*ast.AssignStmt)(nil)}
 	inspect.Preorder(nodeFilter, func(n ast.Node) {
diff --git a/gopls/internal/lsp/analysis/noresultvalues/noresultvalues.go b/gopls/internal/lsp/analysis/noresultvalues/noresultvalues.go
index b3fd84b..41952a5 100644
--- a/gopls/internal/lsp/analysis/noresultvalues/noresultvalues.go
+++ b/gopls/internal/lsp/analysis/noresultvalues/noresultvalues.go
@@ -38,6 +38,9 @@
 
 func run(pass *analysis.Pass) (interface{}, error) {
 	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+	if len(pass.TypeErrors) == 0 {
+		return nil, nil
+	}
 
 	nodeFilter := []ast.Node{(*ast.ReturnStmt)(nil)}
 	inspect.Preorder(nodeFilter, func(n ast.Node) {
diff --git a/gopls/internal/lsp/cache/analysis.go b/gopls/internal/lsp/cache/analysis.go
index 69bb393..0b1fa6a 100644
--- a/gopls/internal/lsp/cache/analysis.go
+++ b/gopls/internal/lsp/cache/analysis.go
@@ -23,6 +23,10 @@
 	"golang.org/x/tools/internal/memoize"
 )
 
+// Analyze returns a new slice of diagnostics from running the
+// specified analyzers on the package denoted by id.
+//
+// (called from: (*snapshot).DiagnosePackage, source.Analyze.)
 func (s *snapshot) Analyze(ctx context.Context, id PackageID, analyzers []*source.Analyzer) ([]*source.Diagnostic, error) {
 	// TODO(adonovan): merge these two loops. There's no need to
 	// construct all the root action handles before beginning
@@ -31,7 +35,7 @@
 	// called in parallel.)
 	var roots []*actionHandle
 	for _, a := range analyzers {
-		if !a.IsEnabled(s.view) {
+		if !a.IsEnabled(s.view.Options()) {
 			continue
 		}
 		ah, err := s.actionHandle(ctx, id, a.Analyzer)
@@ -413,7 +417,7 @@
 
 	var diagnostics []*source.Diagnostic
 	for _, diag := range rawDiagnostics {
-		srcDiags, err := analysisDiagnosticDiagnostics(snapshot, pkg, analyzer, &diag)
+		srcDiags, err := analysisDiagnosticDiagnostics(snapshot.view.Options(), pkg, analyzer, &diag)
 		if err != nil {
 			event.Error(ctx, "unable to compute analysis error position", err, tag.Category.Of(diag.Category), tag.Package.Of(string(pkg.ID())))
 			continue
diff --git a/gopls/internal/lsp/cache/errors.go b/gopls/internal/lsp/cache/errors.go
index 1c9c210..92d58e5 100644
--- a/gopls/internal/lsp/cache/errors.go
+++ b/gopls/internal/lsp/cache/errors.go
@@ -192,10 +192,10 @@
 	return []source.SuggestedFix{source.SuggestedFixFromCommand(cmd, protocol.QuickFix)}, nil
 }
 
-func analysisDiagnosticDiagnostics(snapshot *snapshot, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) {
+func analysisDiagnosticDiagnostics(options *source.Options, pkg *pkg, a *analysis.Analyzer, e *analysis.Diagnostic) ([]*source.Diagnostic, error) {
 	var srcAnalyzer *source.Analyzer
 	// Find the analyzer that generated this diagnostic.
-	for _, sa := range source.EnabledAnalyzers(snapshot) {
+	for _, sa := range source.EnabledAnalyzers(options) {
 		if a == sa.Analyzer {
 			srcAnalyzer = sa
 			break
diff --git a/gopls/internal/lsp/lsp_test.go b/gopls/internal/lsp/lsp_test.go
index 59f286b..8a9e24d 100644
--- a/gopls/internal/lsp/lsp_test.go
+++ b/gopls/internal/lsp/lsp_test.go
@@ -73,11 +73,11 @@
 
 	// Enable type error analyses for tests.
 	// TODO(golang/go#38212): Delete this once they are enabled by default.
-	tests.EnableAllAnalyzers(view, options)
+	tests.EnableAllAnalyzers(options)
 	session.SetViewOptions(ctx, view, options)
 
 	// Enable all inlay hints for tests.
-	tests.EnableAllInlayHints(view, options)
+	tests.EnableAllInlayHints(options)
 
 	// Only run the -modfile specific tests in module mode with Go 1.14 or above.
 	datum.ModfileFlagAvailable = len(snapshot.ModFiles()) > 0 && testenv.Go1Point() >= 14
diff --git a/gopls/internal/lsp/server.go b/gopls/internal/lsp/server.go
index 6d1775f..7e70d0a 100644
--- a/gopls/internal/lsp/server.go
+++ b/gopls/internal/lsp/server.go
@@ -130,6 +130,9 @@
 	switch method {
 	case "gopls/diagnoseFiles":
 		paramMap := params.(map[string]interface{})
+		// TODO(adonovan): opt: parallelize FileDiagnostics(URI...), either
+		// by calling it in multiple goroutines or, better, by making
+		// the relevant APIs accept a set of URIs/packages.
 		for _, file := range paramMap["files"].([]interface{}) {
 			snapshot, fh, ok, release, err := s.beginFileRequest(ctx, protocol.DocumentURI(file.(string)), source.UnknownKind)
 			defer release()
diff --git a/gopls/internal/lsp/source/diagnostics.go b/gopls/internal/lsp/source/diagnostics.go
index db4fc90..60ef7e6 100644
--- a/gopls/internal/lsp/source/diagnostics.go
+++ b/gopls/internal/lsp/source/diagnostics.go
@@ -25,6 +25,7 @@
 	Message string
 }
 
+// Analyze reports go/analysis-framework diagnostics in the specified package.
 func Analyze(ctx context.Context, snapshot Snapshot, pkgid PackageID, includeConvenience bool) (map[span.URI][]*Diagnostic, error) {
 	// Exit early if the context has been canceled. This also protects us
 	// from a race on Options, see golang/go#36699.
@@ -61,6 +62,15 @@
 	return reports, nil
 }
 
+// FileDiagnostics reports diagnostics in the specified file.
+//
+// TODO(adonovan): factor in common with (*Server).codeAction, which
+// executes { PackageForFile; DiagnosePackage; Analyze } too?
+//
+// TODO(adonovan): opt: this function is called in a loop from the
+// "gopls/diagnoseFiles" nonstandard request handler. It would be more
+// efficient to compute the set of packages and DiagnosePackage and
+// Analyze them all at once.
 func FileDiagnostics(ctx context.Context, snapshot Snapshot, uri span.URI) (VersionedFileIdentity, []*Diagnostic, error) {
 	fh, err := snapshot.GetVersionedFile(ctx, uri)
 	if err != nil {
diff --git a/gopls/internal/lsp/source/options.go b/gopls/internal/lsp/source/options.go
index fb596a8..cf8c641 100644
--- a/gopls/internal/lsp/source/options.go
+++ b/gopls/internal/lsp/source/options.go
@@ -1394,26 +1394,25 @@
 	}
 }
 
-// EnabledAnalyzers returns all of the analyzers enabled for the given
-// snapshot.
-func EnabledAnalyzers(snapshot Snapshot) (analyzers []*Analyzer) {
-	for _, a := range snapshot.View().Options().DefaultAnalyzers {
-		if a.IsEnabled(snapshot.View()) {
+// EnabledAnalyzers returns all of the analyzers enabled by the options.
+func EnabledAnalyzers(options *Options) (analyzers []*Analyzer) {
+	for _, a := range options.DefaultAnalyzers {
+		if a.IsEnabled(options) {
 			analyzers = append(analyzers, a)
 		}
 	}
-	for _, a := range snapshot.View().Options().TypeErrorAnalyzers {
-		if a.IsEnabled(snapshot.View()) {
+	for _, a := range options.TypeErrorAnalyzers {
+		if a.IsEnabled(options) {
 			analyzers = append(analyzers, a)
 		}
 	}
-	for _, a := range snapshot.View().Options().ConvenienceAnalyzers {
-		if a.IsEnabled(snapshot.View()) {
+	for _, a := range options.ConvenienceAnalyzers {
+		if a.IsEnabled(options) {
 			analyzers = append(analyzers, a)
 		}
 	}
-	for _, a := range snapshot.View().Options().StaticcheckAnalyzers {
-		if a.IsEnabled(snapshot.View()) {
+	for _, a := range options.StaticcheckAnalyzers {
+		if a.IsEnabled(options) {
 			analyzers = append(analyzers, a)
 		}
 	}
diff --git a/gopls/internal/lsp/source/source_test.go b/gopls/internal/lsp/source/source_test.go
index ae5906f..ed8d9a4 100644
--- a/gopls/internal/lsp/source/source_test.go
+++ b/gopls/internal/lsp/source/source_test.go
@@ -62,7 +62,7 @@
 
 	// Enable type error analyses for tests.
 	// TODO(golang/go#38212): Delete this once they are enabled by default.
-	tests.EnableAllAnalyzers(view, options)
+	tests.EnableAllAnalyzers(options)
 	view, err = session.SetViewOptions(ctx, view, options)
 	if err != nil {
 		t.Fatal(err)
diff --git a/gopls/internal/lsp/source/view.go b/gopls/internal/lsp/source/view.go
index 14a568a..39a9a3a 100644
--- a/gopls/internal/lsp/source/view.go
+++ b/gopls/internal/lsp/source/view.go
@@ -692,6 +692,8 @@
 	// Enabled reports whether the analyzer is enabled. This value can be
 	// configured per-analysis in user settings. For staticcheck analyzers,
 	// the value of the Staticcheck setting overrides this field.
+	//
+	// Most clients should use the IsEnabled method.
 	Enabled bool
 
 	// Fix is the name of the suggested fix name used to invoke the suggested
@@ -709,14 +711,15 @@
 	Severity protocol.DiagnosticSeverity
 }
 
-func (a Analyzer) IsEnabled(view View) bool {
+// Enabled reports whether this analyzer is enabled by the given options.
+func (a Analyzer) IsEnabled(options *Options) bool {
 	// Staticcheck analyzers can only be enabled when staticcheck is on.
-	if _, ok := view.Options().StaticcheckAnalyzers[a.Analyzer.Name]; ok {
-		if !view.Options().Staticcheck {
+	if _, ok := options.StaticcheckAnalyzers[a.Analyzer.Name]; ok {
+		if !options.Staticcheck {
 			return false
 		}
 	}
-	if enabled, ok := view.Options().Analyses[a.Analyzer.Name]; ok {
+	if enabled, ok := options.Analyses[a.Analyzer.Name]; ok {
 		return enabled
 	}
 	return a.Enabled
diff --git a/gopls/internal/lsp/tests/util.go b/gopls/internal/lsp/tests/util.go
index 02f60cb..4c6bfda 100644
--- a/gopls/internal/lsp/tests/util.go
+++ b/gopls/internal/lsp/tests/util.go
@@ -460,33 +460,33 @@
 	return msg.String()
 }
 
-func EnableAllAnalyzers(view source.View, opts *source.Options) {
+func EnableAllAnalyzers(opts *source.Options) {
 	if opts.Analyses == nil {
 		opts.Analyses = make(map[string]bool)
 	}
 	for _, a := range opts.DefaultAnalyzers {
-		if !a.IsEnabled(view) {
+		if !a.IsEnabled(opts) {
 			opts.Analyses[a.Analyzer.Name] = true
 		}
 	}
 	for _, a := range opts.TypeErrorAnalyzers {
-		if !a.IsEnabled(view) {
+		if !a.IsEnabled(opts) {
 			opts.Analyses[a.Analyzer.Name] = true
 		}
 	}
 	for _, a := range opts.ConvenienceAnalyzers {
-		if !a.IsEnabled(view) {
+		if !a.IsEnabled(opts) {
 			opts.Analyses[a.Analyzer.Name] = true
 		}
 	}
 	for _, a := range opts.StaticcheckAnalyzers {
-		if !a.IsEnabled(view) {
+		if !a.IsEnabled(opts) {
 			opts.Analyses[a.Analyzer.Name] = true
 		}
 	}
 }
 
-func EnableAllInlayHints(view source.View, opts *source.Options) {
+func EnableAllInlayHints(opts *source.Options) {
 	if opts.Hints == nil {
 		opts.Hints = make(map[string]bool)
 	}