internal/lsp/analysis: analyzer for //go:embed directive

This CL adds a new analyzer for //go:embed directive, which
checks for the "embed" import.

Along with it, it improves doc for analysistest.Run for
comments of the the form  "//...// want..." or
 "//...// want..."

 Updates #50262

Change-Id: I60ef0ab740feadd4fff3a4d758123b27ceda0bc6
Reviewed-on: https://go-review.googlesource.com/c/tools/+/400854
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Peter Weinberger <pjw@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Nooras Saba‎ <saba@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
diff --git a/go/analysis/analysistest/analysistest.go b/go/analysis/analysistest/analysistest.go
index dac75d8..40b2da3 100644
--- a/go/analysis/analysistest/analysistest.go
+++ b/go/analysis/analysistest/analysistest.go
@@ -249,7 +249,8 @@
 // directory using golang.org/x/tools/go/packages, runs the analysis on
 // them, and checks that each analysis emits the expected diagnostics
 // and facts specified by the contents of '// want ...' comments in the
-// package's source files.
+// package's source files. It treats a comment of the form
+// "//...// want..." or "/*...// want... */" as if it starts at 'want'
 //
 // An expectation of a Diagnostic is specified by a string literal
 // containing a regular expression that must match the diagnostic
diff --git a/gopls/doc/analyzers.md b/gopls/doc/analyzers.md
index 07f846d..f5c83d5 100644
--- a/gopls/doc/analyzers.md
+++ b/gopls/doc/analyzers.md
@@ -108,6 +108,15 @@
 
 **Enabled by default.**
 
+## **embed**
+
+check for //go:embed directive import
+
+This analyzer checks that the embed package is imported when source code contains //go:embed comment directives.
+The embed package must be imported for //go:embed directives to function.import _ "embed".
+
+**Enabled by default.**
+
 ## **errorsas**
 
 report passing non-pointer or non-error values to errors.As
diff --git a/internal/lsp/analysis/embeddirective/embeddirective.go b/internal/lsp/analysis/embeddirective/embeddirective.go
new file mode 100644
index 0000000..c925d8e
--- /dev/null
+++ b/internal/lsp/analysis/embeddirective/embeddirective.go
@@ -0,0 +1,58 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package embeddirective defines an Analyzer that validates import for //go:embed directive.
+package embeddirective
+
+import (
+	"go/ast"
+	"strings"
+
+	"golang.org/x/tools/go/analysis"
+)
+
+const Doc = `check for //go:embed directive import
+
+This analyzer checks that the embed package is imported when source code contains //go:embed comment directives.
+The embed package must be imported for //go:embed directives to function.import _ "embed".`
+
+var Analyzer = &analysis.Analyzer{
+	Name:             "embed",
+	Doc:              Doc,
+	Requires:         []*analysis.Analyzer{},
+	Run:              run,
+	RunDespiteErrors: true,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+	for _, f := range pass.Files {
+		com := hasEmbedDirectiveComment(f)
+		if com != nil {
+			assertEmbedImport(pass, com, f)
+		}
+	}
+	return nil, nil
+}
+
+// Check if comment contains //go:embed directive.
+func hasEmbedDirectiveComment(f *ast.File) *ast.Comment {
+	for _, cg := range f.Comments {
+		for _, c := range cg.List {
+			if strings.HasPrefix(c.Text, "//go:embed ") {
+				return c
+			}
+		}
+	}
+	return nil
+}
+
+// Verifies that "embed" import exists for //go:embed directive.
+func assertEmbedImport(pass *analysis.Pass, com *ast.Comment, f *ast.File) {
+	for _, imp := range f.Imports {
+		if "\"embed\"" == imp.Path.Value {
+			return
+		}
+	}
+	pass.Report(analysis.Diagnostic{Pos: com.Pos(), End: com.Pos() + 10, Message: "The \"embed\" package must be imported when using go:embed directives."})
+}
diff --git a/internal/lsp/analysis/embeddirective/embeddirective_test.go b/internal/lsp/analysis/embeddirective/embeddirective_test.go
new file mode 100644
index 0000000..1165c0b
--- /dev/null
+++ b/internal/lsp/analysis/embeddirective/embeddirective_test.go
@@ -0,0 +1,22 @@
+// Copyright 2022 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package embeddirective
+
+import (
+	"testing"
+
+	"golang.org/x/tools/go/analysis/analysistest"
+	"golang.org/x/tools/internal/typeparams"
+)
+
+func Test(t *testing.T) {
+	testdata := analysistest.TestData()
+	tests := []string{"a"}
+	if typeparams.Enabled {
+		tests = append(tests)
+	}
+
+	analysistest.RunWithSuggestedFixes(t, testdata, Analyzer, tests...)
+}
diff --git a/internal/lsp/analysis/embeddirective/testdata/src/a/a.go b/internal/lsp/analysis/embeddirective/testdata/src/a/a.go
new file mode 100644
index 0000000..4203f6c
--- /dev/null
+++ b/internal/lsp/analysis/embeddirective/testdata/src/a/a.go
@@ -0,0 +1,13 @@
+package a
+
+import (
+	"fmt"
+)
+
+//go:embed embedText // want "The \"embed\" package must be imported when using go:embed directives"
+var s string
+
+// This is main function
+func main() {
+	fmt.Println(s)
+}
diff --git a/internal/lsp/analysis/embeddirective/testdata/src/a/b.go b/internal/lsp/analysis/embeddirective/testdata/src/a/b.go
new file mode 100644
index 0000000..c8c701e
--- /dev/null
+++ b/internal/lsp/analysis/embeddirective/testdata/src/a/b.go
@@ -0,0 +1,14 @@
+package a
+
+import (
+	_ "embed"
+	"fmt"
+)
+
+//go:embed embedText // ok
+var s string
+
+// This is main function
+func main() {
+	fmt.Println(s)
+}
diff --git a/internal/lsp/analysis/embeddirective/testdata/src/a/embedText b/internal/lsp/analysis/embeddirective/testdata/src/a/embedText
new file mode 100644
index 0000000..5e1c309
--- /dev/null
+++ b/internal/lsp/analysis/embeddirective/testdata/src/a/embedText
@@ -0,0 +1 @@
+Hello World
\ No newline at end of file
diff --git a/internal/lsp/source/api_json.go b/internal/lsp/source/api_json.go
index 14140bb..c2c1f82 100755
--- a/internal/lsp/source/api_json.go
+++ b/internal/lsp/source/api_json.go
@@ -269,6 +269,11 @@
 							Default: "true",
 						},
 						{
+							Name:    "\"embed\"",
+							Doc:     "check for //go:embed directive import\n\nThis analyzer checks that the embed package is imported when source code contains //go:embed comment directives.\nThe embed package must be imported for //go:embed directives to function.import _ \"embed\".",
+							Default: "true",
+						},
+						{
 							Name:    "\"errorsas\"",
 							Doc:     "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analysis reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
 							Default: "true",
@@ -805,6 +810,11 @@
 			Default: true,
 		},
 		{
+			Name:    "embed",
+			Doc:     "check for //go:embed directive import\n\nThis analyzer checks that the embed package is imported when source code contains //go:embed comment directives.\nThe embed package must be imported for //go:embed directives to function.import _ \"embed\".",
+			Default: true,
+		},
+		{
 			Name:    "errorsas",
 			Doc:     "report passing non-pointer or non-error values to errors.As\n\nThe errorsas analysis reports calls to errors.As where the type\nof the second argument is not a pointer to a type implementing error.",
 			Default: true,
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 123bbe1..de82cb2 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -49,6 +49,7 @@
 	"golang.org/x/tools/go/analysis/passes/unusedresult"
 	"golang.org/x/tools/go/analysis/passes/unusedwrite"
 	"golang.org/x/tools/go/packages"
+	"golang.org/x/tools/internal/lsp/analysis/embeddirective"
 	"golang.org/x/tools/internal/lsp/analysis/fillreturns"
 	"golang.org/x/tools/internal/lsp/analysis/fillstruct"
 	"golang.org/x/tools/internal/lsp/analysis/infertypeargs"
@@ -1308,6 +1309,7 @@
 		unusedwrite.Analyzer.Name:      {Analyzer: unusedwrite.Analyzer, Enabled: false},
 		useany.Analyzer.Name:           {Analyzer: useany.Analyzer, Enabled: false},
 		infertypeargs.Analyzer.Name:    {Analyzer: infertypeargs.Analyzer, Enabled: true},
+		embeddirective.Analyzer.Name:   {Analyzer: embeddirective.Analyzer, Enabled: true},
 
 		// gofmt -s suite:
 		simplifycompositelit.Analyzer.Name: {