internal/lsp: pass a parsed module to go mod tidy

This allows us to show parsing errors as part of module diagnostics.

Change-Id: I558b95c145135482fdfceef8a5c68c62a6d32721
Reviewed-on: https://go-review.googlesource.com/c/tools/+/271630
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/cache/mod.go b/internal/lsp/cache/mod.go
index 95a9132..0504799 100644
--- a/internal/lsp/cache/mod.go
+++ b/internal/lsp/cache/mod.go
@@ -69,25 +69,24 @@
 			Converter: span.NewContentConverter(modFH.URI().Filename(), contents),
 			Content:   contents,
 		}
-		data := &parseModData{
+		file, err := modfile.Parse(modFH.URI().Filename(), contents, nil)
+
+		// Attempt to convert the error to a standardized parse error.
+		var parseErrors []source.Error
+		if err != nil {
+			if parseErr, extractErr := extractModParseErrors(modFH.URI(), m, err, contents); extractErr == nil {
+				parseErrors = []source.Error{*parseErr}
+			}
+		}
+		return &parseModData{
 			parsed: &source.ParsedModule{
-				Mapper: m,
+				URI:         modFH.URI(),
+				Mapper:      m,
+				File:        file,
+				ParseErrors: parseErrors,
 			},
+			err: err,
 		}
-		data.parsed.File, data.err = modfile.Parse(modFH.URI().Filename(), contents, nil)
-		if data.err != nil {
-			// Attempt to convert the error to a standardized parse error.
-			if parseErr, extractErr := extractModParseErrors(modFH.URI(), m, data.err, contents); extractErr == nil {
-				data.parsed.ParseErrors = []source.Error{*parseErr}
-			}
-			// If the file was still parsed, we don't want to treat this as a
-			// fatal error. Note: This currently cannot happen as modfile.Parse
-			// always returns an error when the file is nil.
-			if data.parsed.File != nil {
-				data.err = nil
-			}
-		}
-		return data
 	}, nil)
 
 	pmh := &parseModHandle{handle: h}
diff --git a/internal/lsp/cache/mod_tidy.go b/internal/lsp/cache/mod_tidy.go
index 76479ee..4d94090 100644
--- a/internal/lsp/cache/mod_tidy.go
+++ b/internal/lsp/cache/mod_tidy.go
@@ -53,13 +53,17 @@
 	return data.tidied, data.err
 }
 
-func (s *snapshot) ModTidy(ctx context.Context, fh source.FileHandle) (*source.TidiedModule, error) {
-	if fh.Kind() != source.Mod {
-		return nil, fmt.Errorf("%s is not a go.mod file", fh.URI())
+func (s *snapshot) ModTidy(ctx context.Context, pm *source.ParsedModule) (*source.TidiedModule, error) {
+	if pm.File == nil {
+		return nil, fmt.Errorf("cannot tidy unparseable go.mod file: %v", pm.URI)
 	}
-	if handle := s.getModTidyHandle(fh.URI()); handle != nil {
+	if handle := s.getModTidyHandle(pm.URI); handle != nil {
 		return handle.tidy(ctx, s)
 	}
+	fh, err := s.GetFile(ctx, pm.URI)
+	if err != nil {
+		return nil, err
+	}
 	// If the file handle is an overlay, it may not be written to disk.
 	// The go.mod file has to be on disk for `go mod tidy` to work.
 	if _, ok := fh.(*overlay); ok {
@@ -96,23 +100,6 @@
 		defer done()
 
 		snapshot := arg.(*snapshot)
-		pm, err := snapshot.ParseMod(ctx, fh)
-		if err != nil || len(pm.ParseErrors) > 0 {
-			if err == nil {
-				err = fmt.Errorf("could not parse module to tidy: %v", pm.ParseErrors)
-			}
-			var errors []source.Error
-			if pm != nil {
-				errors = pm.ParseErrors
-			}
-			return &modTidyData{
-				tidied: &source.TidiedModule{
-					Parsed: pm,
-					Errors: errors,
-				},
-				err: err,
-			}
-		}
 		inv := &gocommand.Invocation{
 			Verb:       "mod",
 			Args:       []string{"tidy"},
@@ -149,7 +136,6 @@
 		return &modTidyData{
 			tidied: &source.TidiedModule{
 				Errors:        errors,
-				Parsed:        pm,
 				TidiedContent: tempContents,
 			},
 		}
@@ -187,7 +173,6 @@
 			return nil, false
 		}
 		return &source.TidiedModule{
-			Parsed: pmf,
 			Errors: []source.Error{{
 				URI:   fh.URI(),
 				Range: rng,
diff --git a/internal/lsp/mod/diagnostics.go b/internal/lsp/mod/diagnostics.go
index 41a462c..20fd606 100644
--- a/internal/lsp/mod/diagnostics.go
+++ b/internal/lsp/mod/diagnostics.go
@@ -52,7 +52,14 @@
 }
 
 func ErrorsForMod(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]source.Error, error) {
-	tidied, err := snapshot.ModTidy(ctx, fh)
+	pm, err := snapshot.ParseMod(ctx, fh)
+	if err != nil {
+		if pm == nil || len(pm.ParseErrors) == 0 {
+			return nil, err
+		}
+		return pm.ParseErrors, nil
+	}
+	tidied, err := snapshot.ModTidy(ctx, pm)
 
 	if source.IsNonFatalGoModError(err) {
 		return nil, nil
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 5a9ee40..cc7b207 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -111,7 +111,7 @@
 
 	// ModTidy returns the results of `go mod tidy` for the module specified by
 	// the given go.mod file.
-	ModTidy(ctx context.Context, fh FileHandle) (*TidiedModule, error)
+	ModTidy(ctx context.Context, pm *ParsedModule) (*TidiedModule, error)
 
 	// GoModForFile returns the URI of the go.mod file for the given URI.
 	GoModForFile(ctx context.Context, uri span.URI) span.URI
@@ -249,6 +249,7 @@
 
 // A ParsedModule contains the results of parsing a go.mod file.
 type ParsedModule struct {
+	URI         span.URI
 	File        *modfile.File
 	Mapper      *protocol.ColumnMapper
 	ParseErrors []Error
@@ -256,8 +257,6 @@
 
 // A TidiedModule contains the results of running `go mod tidy` on a module.
 type TidiedModule struct {
-	// The parsed module, which is guaranteed to have parsed successfully.
-	Parsed *ParsedModule
 	// Diagnostics representing changes made by `go mod tidy`.
 	Errors []Error
 	// The bytes of the go.mod file after it was tidied.