internal/lsp: add an error result to findFile

This change allows us to return diagnostics in the case of a file that
doesn't exist.

Change-Id: I6275c0dc9103a3f44070919937afe27c64545828
Reviewed-on: https://go-review.googlesource.com/c/tools/+/170009
Reviewed-by: Ian Cottrell <iancottrell@google.com>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/cache/check.go b/internal/lsp/cache/check.go
index 707730a..c70fec9 100644
--- a/internal/lsp/cache/check.go
+++ b/internal/lsp/cache/check.go
@@ -150,7 +150,7 @@
 	m.name = pkg.Name
 	m.files = pkg.CompiledGoFiles
 	for _, filename := range m.files {
-		if f := v.findFile(span.FileURI(filename)); f != nil {
+		if f, _ := v.findFile(span.FileURI(filename)); f != nil {
 			f.meta = m
 		}
 	}
@@ -341,7 +341,10 @@
 		}
 
 		// First, check if we have already cached an AST for this file.
-		f := v.findFile(span.FileURI(filename))
+		f, err := v.findFile(span.FileURI(filename))
+		if err != nil {
+			parsed[i], errors[i] = nil, err
+		}
 		var fAST *ast.File
 		if f != nil {
 			fAST = f.ast
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 3f3675c..d0936f2 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -191,7 +191,7 @@
 	// All of the files in the package may also be holding a pointer to the
 	// invalidated package.
 	for _, filename := range m.files {
-		if f := v.findFile(span.FileURI(filename)); f != nil {
+		if f, _ := v.findFile(span.FileURI(filename)); f != nil {
 			f.pkg = nil
 		}
 	}
@@ -213,7 +213,9 @@
 
 // getFile is the unlocked internal implementation of GetFile.
 func (v *View) getFile(uri span.URI) (*File, error) {
-	if f := v.findFile(uri); f != nil {
+	if f, err := v.findFile(uri); err != nil {
+		return nil, err
+	} else if f != nil {
 		return f, nil
 	}
 	filename, err := uri.Filename()
@@ -228,34 +230,41 @@
 	return f, nil
 }
 
-func (v *View) findFile(uri span.URI) *File {
+// findFile checks the cache for any file matching the given uri.
+//
+// An error is only returned for an irreparable failure, for example, if the
+// filename in question does not exist.
+func (v *View) findFile(uri span.URI) (*File, error) {
 	if f := v.filesByURI[uri]; f != nil {
 		// a perfect match
-		return f
+		return f, nil
 	}
 	// no exact match stored, time to do some real work
 	// check for any files with the same basename
 	fname, err := uri.Filename()
 	if err != nil {
-		return nil
+		return nil, err
 	}
 	basename := basename(fname)
 	if candidates := v.filesByBase[basename]; candidates != nil {
 		pathStat, err := os.Stat(fname)
-		if err != nil {
-			return nil
+		if os.IsNotExist(err) {
+			return nil, err
+		} else if err != nil {
+			return nil, nil // the file may exist, return without an error
 		}
 		for _, c := range candidates {
 			if cStat, err := os.Stat(c.filename); err == nil {
 				if os.SameFile(pathStat, cStat) {
 					// same file, map it
 					v.mapFile(uri, c)
-					return c
+					return c, nil
 				}
 			}
 		}
 	}
-	return nil
+	// no file with a matching name was found, it wasn't in our cache
+	return nil, nil
 }
 
 func (v *View) mapFile(uri span.URI, f *File) {
diff --git a/internal/lsp/source/diagnostics.go b/internal/lsp/source/diagnostics.go
index f6f3906..a20175f 100644
--- a/internal/lsp/source/diagnostics.go
+++ b/internal/lsp/source/diagnostics.go
@@ -54,18 +54,11 @@
 func Diagnostics(ctx context.Context, v View, uri span.URI) (map[span.URI][]Diagnostic, error) {
 	f, err := v.GetFile(ctx, uri)
 	if err != nil {
-		return nil, err
+		return singleDiagnostic(uri, "no file found for %s", uri), nil
 	}
 	pkg := f.GetPackage(ctx)
 	if pkg == nil {
-		return map[span.URI][]Diagnostic{
-			uri: []Diagnostic{{
-				Source:   "LSP",
-				Span:     span.New(uri, span.Point{}, span.Point{}),
-				Message:  fmt.Sprintf("not part of a package"),
-				Severity: SeverityError,
-			}},
-		}, nil
+		return singleDiagnostic(uri, "%s is not part of a package", uri), nil
 	}
 	// Prepare the reports we will send for this package.
 	reports := make(map[span.URI][]Diagnostic)
@@ -149,6 +142,17 @@
 	return reports, nil
 }
 
+func singleDiagnostic(uri span.URI, format string, a ...interface{}) map[span.URI][]Diagnostic {
+	return map[span.URI][]Diagnostic{
+		uri: []Diagnostic{{
+			Source:   "LSP",
+			Span:     span.New(uri, span.Point{}, span.Point{}),
+			Message:  fmt.Sprintf(format, a...),
+			Severity: SeverityError,
+		}},
+	}
+}
+
 func runAnalyses(ctx context.Context, v View, pkg Package, report func(a *analysis.Analyzer, diag analysis.Diagnostic)) error {
 	// the traditional vet suite:
 	analyzers := []*analysis.Analyzer{