internal/lsp: format files in packages with errors

Packages with errors may still contain files that can be formatted.
Try to format the source of the files in packages that have errors.
This change will still not format files with parse errors.

Updates golang/go#31291

Change-Id: Ia5168d7908948d201eac7f2ee28534022a2d4eb0
Reviewed-on: https://go-review.googlesource.com/c/tools/+/187757
Run-TryBot: Suzy Mueller <suzmue@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/internal/lsp/source/format.go b/internal/lsp/source/format.go
index e9f12d2..7a52cbc 100644
--- a/internal/lsp/source/format.go
+++ b/internal/lsp/source/format.go
@@ -31,7 +31,15 @@
 	}
 	pkg := f.GetPackage(ctx)
 	if hasListErrors(pkg.GetErrors()) || hasParseErrors(pkg.GetErrors()) {
-		return nil, fmt.Errorf("%s has parse errors, not formatting", f.URI())
+		// Even if this package has list or parse errors, this file may not
+		// have any parse errors and can still be formatted. Using format.Node
+		// on an ast with errors may result in code being added or removed.
+		// Attempt to format the source of this file instead.
+		formatted, err := formatSource(ctx, f)
+		if err != nil {
+			return nil, err
+		}
+		return computeTextEdits(ctx, f, string(formatted)), nil
 	}
 	path, exact := astutil.PathEnclosingInterval(file, rng.Start, rng.End)
 	if !exact || len(path) == 0 {
@@ -52,6 +60,16 @@
 	return computeTextEdits(ctx, f, buf.String()), nil
 }
 
+func formatSource(ctx context.Context, file File) ([]byte, error) {
+	ctx, done := trace.StartSpan(ctx, "source.formatSource")
+	defer done()
+	data, _, err := file.Handle(ctx).Read(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return format.Source(data)
+}
+
 // Imports formats a file using the goimports tool.
 func Imports(ctx context.Context, view View, f GoFile, rng span.Range) ([]TextEdit, error) {
 	ctx, done := trace.StartSpan(ctx, "source.Imports")
diff --git a/internal/lsp/testdata/noparse_format/parse_format.go.golden b/internal/lsp/testdata/noparse_format/parse_format.go.golden
new file mode 100644
index 0000000..474ad90
--- /dev/null
+++ b/internal/lsp/testdata/noparse_format/parse_format.go.golden
@@ -0,0 +1,9 @@
+-- gofmt --
+package noparse_format //@format("package")
+
+func _() {
+	f()
+}
+
+-- gofmt-d --
+
diff --git a/internal/lsp/testdata/noparse_format/parse_format.go.in b/internal/lsp/testdata/noparse_format/parse_format.go.in
new file mode 100644
index 0000000..4b98cf8
--- /dev/null
+++ b/internal/lsp/testdata/noparse_format/parse_format.go.in
@@ -0,0 +1,5 @@
+package noparse_format //@format("package")
+
+func _() {
+f()
+}
\ No newline at end of file
diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go
index 767d817..d649864 100644
--- a/internal/lsp/tests/tests.go
+++ b/internal/lsp/tests/tests.go
@@ -29,7 +29,7 @@
 	ExpectedCompletionsCount       = 144
 	ExpectedCompletionSnippetCount = 15
 	ExpectedDiagnosticsCount       = 17
-	ExpectedFormatCount            = 5
+	ExpectedFormatCount            = 6
 	ExpectedImportCount            = 2
 	ExpectedDefinitionsCount       = 38
 	ExpectedTypeDefinitionsCount   = 2