internal/lsp: add error handling for self imports

This change will provide a more useful error when you
are self importing a package. It has TODOs in place to propagate the
"import cycle not allowed" error from go list to the user.

Updates golang/go#33085

Change-Id: Ia868a7c688b0f0a7a9689cfda5ea8cea8ae1faff
Reviewed-on: https://go-review.googlesource.com/c/tools/+/209857
Run-TryBot: Rohan Challa <rohan@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index 8bd21b8..5d637e2 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -331,18 +331,15 @@
 		return nil, ctx.Err()
 	}
 
-	// We don't care about a package's errors unless we have parsed it in full.
-	if mode == source.ParseFull {
-		for _, e := range rawErrors {
-			srcErr, err := sourceError(ctx, fset, pkg, e)
-			if err != nil {
-				log.Error(ctx, "unable to compute error positions", err, telemetry.Package.Of(pkg.ID()))
-				continue
-			}
-			pkg.errors = append(pkg.errors, srcErr)
+	// TODO(golang/go#35964): Propagate `go list` errors here when go/packages supports it.
+	for _, e := range rawErrors {
+		srcErr, err := sourceError(ctx, fset, pkg, e)
+		if err != nil {
+			log.Error(ctx, "unable to compute error positions", err, telemetry.Package.Of(pkg.ID()))
+			continue
 		}
+		pkg.errors = append(pkg.errors, srcErr)
 	}
-
 	return pkg, nil
 }
 
diff --git a/internal/lsp/cache/errors.go b/internal/lsp/cache/errors.go
index 9afdf6d..2504c33 100644
--- a/internal/lsp/cache/errors.go
+++ b/internal/lsp/cache/errors.go
@@ -38,6 +38,14 @@
 		msg = e.Msg
 		kind = toSourceErrorKind(e.Kind)
 
+		// If the range can't be derived from the parseGoListError function, then we do not have a valid position.
+		if _, err := spanToRange(ctx, pkg, spn); err != nil && e.Pos == "" {
+			return &source.Error{
+				Message: msg,
+				Kind:    kind,
+			}, nil
+		}
+
 	case *scanner.Error:
 		msg = e.Msg
 		kind = source.ParseError
diff --git a/internal/lsp/cmd/test/check.go b/internal/lsp/cmd/test/check.go
index 024d987..65e9688 100644
--- a/internal/lsp/cmd/test/check.go
+++ b/internal/lsp/cmd/test/check.go
@@ -50,8 +50,8 @@
 			expect = fmt.Sprintf("%v:%v: %v", uri.Filename(), diag.Range.Start.Line+1, diag.Message)
 		}
 		expect = r.NormalizePrefix(expect)
-		// Skip the badimport test for now, until we do a better job with diagnostic ranges.
-		if strings.Contains(uri.Filename(), "badimport") {
+		// Skip the badimport and import cycle not allowed test for now, until we do a better job with diagnostic ranges.
+		if strings.Contains(uri.Filename(), "badimport") || strings.Contains(expect, "import cycle") {
 			continue
 		}
 		_, found := got[expect]
@@ -62,8 +62,8 @@
 		}
 	}
 	for extra := range got {
-		// Skip the badimport test for now, until we do a better job with diagnostic ranges.
-		if strings.Contains(extra, "badimport") {
+		// Skip the badimport and import cycle not allowed test for now, until we do a better job with diagnostic ranges.
+		if strings.Contains(extra, "badimport") || strings.Contains(extra, "import cycle") {
 			continue
 		}
 		t.Errorf("extra diagnostic %q", extra)
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index d53249b..dbc9450 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -71,9 +71,14 @@
 	}
 	// Prepare any additional reports for the errors in this package.
 	for _, e := range pkg.GetErrors() {
-		if e.Kind != ListError {
+		// We only need to handle lower-level errors.
+		if !(e.Kind == UnknownError || e.Kind == ListError) {
 			continue
 		}
+		// If no file is associated with the error, default to the current file.
+		if e.File.URI.Filename() == "" {
+			e.File = fh.Identity()
+		}
 		clearReports(snapshot, reports, e.File)
 	}
 	// Run diagnostics for the package that this URI belongs to.
@@ -133,7 +138,7 @@
 		case TypeError:
 			set.typeErrors = append(set.typeErrors, diag)
 			diag.Source = "compiler"
-		case ListError:
+		case ListError, UnknownError:
 			set.listErrors = append(set.listErrors, diag)
 			diag.Source = "go list"
 		}
diff --git a/internal/lsp/testdata/circular/b/b.go b/internal/lsp/testdata/circular/b/b.go
new file mode 100644
index 0000000..950abba
--- /dev/null
+++ b/internal/lsp/testdata/circular/b/b.go
@@ -0,0 +1,9 @@
+package b //@diag("", "go list", "import cycle not allowed")
+
+import (
+	"golang.org/x/tools/internal/lsp/circular/one"
+)
+
+func Test1() {
+	one.Test()
+}
diff --git a/internal/lsp/testdata/circular/one/one.go b/internal/lsp/testdata/circular/one/one.go
new file mode 100644
index 0000000..8e74e8b
--- /dev/null
+++ b/internal/lsp/testdata/circular/one/one.go
@@ -0,0 +1,9 @@
+package one
+
+import (
+	"golang.org/x/tools/internal/lsp/circular/b"
+)
+
+func Test() {
+	b.Test1()
+}
diff --git a/internal/lsp/testdata/circular/self.go b/internal/lsp/testdata/circular/self.go
new file mode 100644
index 0000000..452ab50
--- /dev/null
+++ b/internal/lsp/testdata/circular/self.go
@@ -0,0 +1,12 @@
+package circular //@diag("", "go list", "import cycle not allowed")
+
+import (
+	"golang.org/x/tools/internal/lsp/circular"
+)
+
+func print() {
+	Test()
+}
+
+func Test() {
+}
diff --git a/internal/lsp/testdata/summary.txt.golden b/internal/lsp/testdata/summary.txt.golden
index 60d49aa..3c952c0 100644
--- a/internal/lsp/testdata/summary.txt.golden
+++ b/internal/lsp/testdata/summary.txt.golden
@@ -6,7 +6,7 @@
 FuzzyCompletionsCount = 7
 RankedCompletionsCount = 26
 CaseSensitiveCompletionsCount = 4
-DiagnosticsCount = 22
+DiagnosticsCount = 24
 FoldingRangesCount = 2
 FormatCount = 6
 ImportCount = 7