internal/lsp/cache: invalidate metadata on magic comment changes

When a //go:embed or //go:build (//+build) line changes, we need to
invalidate metadata. Do so. It might be preferable to only invalidate on
save, but we don't currently have an approach for doing that. So for now
we'll load on each keystroke in a magic comment.

Fixes golang/go#38732, golang/go#44342.

Change-Id: Id05fb84f44215ea6242a7cf8b2bca4e85f74680e
Reviewed-on: https://go-review.googlesource.com/c/tools/+/296549
Trust: Heschi Kreinick <heschi@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
diff --git a/gopls/internal/regtest/diagnostics/diagnostics_test.go b/gopls/internal/regtest/diagnostics/diagnostics_test.go
index 4986000..00a7f58 100644
--- a/gopls/internal/regtest/diagnostics/diagnostics_test.go
+++ b/gopls/internal/regtest/diagnostics/diagnostics_test.go
@@ -1789,3 +1789,30 @@
 		)
 	})
 }
+
+func TestBuildTagChange(t *testing.T) {
+	const files = `
+-- go.mod --
+module mod.com
+
+go 1.12
+-- foo.go --
+// decoy comment
+// +build hidden
+// decoy comment
+
+package foo
+var Foo = 1
+-- bar.go --
+package foo
+var Bar = Foo
+`
+
+	Run(t, files, func(t *testing.T, env *Env) {
+		env.OpenFile("foo.go")
+		env.Await(env.DiagnosticAtRegexpWithMessage("bar.go", `Foo`, "undeclared name"))
+		env.RegexpReplace("foo.go", `\+build`, "")
+		env.Await(EmptyDiagnostics("bar.go"))
+	})
+
+}
diff --git a/gopls/internal/regtest/misc/embed_test.go b/gopls/internal/regtest/misc/embed_test.go
index 4b74fee..76d1225 100644
--- a/gopls/internal/regtest/misc/embed_test.go
+++ b/gopls/internal/regtest/misc/embed_test.go
@@ -26,6 +26,9 @@
 var foo string
 `
 	Run(t, files, func(t *testing.T, env *Env) {
+		env.OpenFile("x.go")
 		env.Await(env.DiagnosticAtRegexpWithMessage("x.go", `NONEXISTENT`, "no matching files found"))
+		env.RegexpReplace("x.go", `NONEXISTENT`, "x.go")
+		env.Await(EmptyDiagnostics("x.go"))
 	})
 }
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index ddfefc7..5069f28 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -15,6 +15,7 @@
 	"io/ioutil"
 	"os"
 	"path/filepath"
+	"regexp"
 	"sort"
 	"strconv"
 	"strings"
@@ -1680,9 +1681,38 @@
 		}
 		return true, false
 	}
+
+	// Re-evaluate build constraints and embed patterns. It would be preferable
+	// to only do this on save, but we don't have the prior versions accessible.
+	oldComments := extractMagicComments(original.File)
+	newComments := extractMagicComments(current.File)
+	if len(oldComments) != len(newComments) {
+		return true, false
+	}
+	for i := range oldComments {
+		if oldComments[i] != newComments[i] {
+			return true, false
+		}
+	}
+
 	return false, false
 }
 
+var buildConstraintOrEmbedRe = regexp.MustCompile(`^//(go:embed|go:build|\s*\+build).*`)
+
+// extractMagicComments finds magic comments that affect metadata in f.
+func extractMagicComments(f *ast.File) []string {
+	var results []string
+	for _, cg := range f.Comments {
+		for _, c := range cg.List {
+			if buildConstraintOrEmbedRe.MatchString(c.Text) {
+				results = append(results, c.Text)
+			}
+		}
+	}
+	return results
+}
+
 func (s *snapshot) BuiltinPackage(ctx context.Context) (*source.BuiltinPackage, error) {
 	s.AwaitInitialized(ctx)