internal/lsp: port fill struct into analysis framework

The current implementation of the fill struct tool is not a part of
the analysis framework. This commit moves the functionality from the
source directory to the analysis directory.

Change-Id: Ibe37b57f3e6680c8729932dbbe010a4642600e4a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/237258
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/internal/lsp/analysis/fillstruct/fillstruct.go b/internal/lsp/analysis/fillstruct/fillstruct.go
new file mode 100644
index 0000000..b23a05f
--- /dev/null
+++ b/internal/lsp/analysis/fillstruct/fillstruct.go
@@ -0,0 +1,145 @@
+// Copyright 2020 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 fillstruct defines an Analyzer that automatically
+// fills in a struct declaration with zero value elements for each field.
+package fillstruct
+
+import (
+	"bytes"
+	"go/ast"
+	"go/format"
+	"go/types"
+	"strings"
+
+	"golang.org/x/tools/go/analysis"
+	"golang.org/x/tools/go/analysis/passes/inspect"
+	"golang.org/x/tools/go/ast/inspector"
+	"golang.org/x/tools/internal/analysisinternal"
+)
+
+const Doc = `suggested input for incomplete struct initializations
+
+This analyzer provides the appropriate zero values for all
+uninitialized fields of a struct. For example, given the following struct:
+	type Foo struct {
+		ID   int64
+		Name string
+	}
+the initialization
+	var _ = Foo{}
+will turn into
+	var _ = Foo{
+		ID: 0,
+		Name: "",
+	}
+`
+
+var Analyzer = &analysis.Analyzer{
+	Name:             "fillstruct",
+	Doc:              Doc,
+	Requires:         []*analysis.Analyzer{inspect.Analyzer},
+	Run:              run,
+	RunDespiteErrors: true,
+}
+
+func run(pass *analysis.Pass) (interface{}, error) {
+	inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+	nodeFilter := []ast.Node{(*ast.CompositeLit)(nil)}
+	inspect.Preorder(nodeFilter, func(n ast.Node) {
+		info := pass.TypesInfo
+		if info == nil {
+			return
+		}
+		expr := n.(*ast.CompositeLit)
+
+		// TODO: Handle partially-filled structs as well.
+		if len(expr.Elts) != 0 {
+			return
+		}
+
+		var file *ast.File
+		for _, f := range pass.Files {
+			if f.Pos() <= expr.Pos() && expr.Pos() <= f.End() {
+				file = f
+				break
+			}
+		}
+		if file == nil {
+			return
+		}
+
+		typ := info.TypeOf(expr)
+		if typ == nil {
+			return
+		}
+
+		// Find reference to the type declaration of the struct being initialized.
+		for {
+			p, ok := typ.Underlying().(*types.Pointer)
+			if !ok {
+				break
+			}
+			typ = p.Elem()
+		}
+		typ = typ.Underlying()
+
+		if typ == nil {
+			return
+		}
+
+		switch obj := typ.(type) {
+		case *types.Struct:
+			fieldCount := obj.NumFields()
+			if fieldCount == 0 {
+				return
+			}
+			var fieldSourceCode strings.Builder
+			for i := 0; i < fieldCount; i++ {
+				field := obj.Field(i)
+				// Ignore fields that are not accessible in the current package.
+				if field.Pkg() != nil && field.Pkg() != pass.Pkg && !field.Exported() {
+					continue
+				}
+
+				label := field.Name()
+				value := analysisinternal.ZeroValue(pass.Fset, file, pass.Pkg, field.Type())
+				if value == nil {
+					continue
+				}
+				var valBuf bytes.Buffer
+				if err := format.Node(&valBuf, pass.Fset, value); err != nil {
+					return
+				}
+				fieldSourceCode.WriteString("\n")
+				fieldSourceCode.WriteString(label)
+				fieldSourceCode.WriteString(" : ")
+				fieldSourceCode.WriteString(valBuf.String())
+				fieldSourceCode.WriteString(",")
+			}
+
+			if fieldSourceCode.Len() == 0 {
+				return
+			}
+
+			fieldSourceCode.WriteString("\n")
+
+			buf := []byte(fieldSourceCode.String())
+
+			pass.Report(analysis.Diagnostic{
+				Pos: expr.Lbrace,
+				End: expr.Rbrace,
+				SuggestedFixes: []analysis.SuggestedFix{{
+					Message: "Fill struct with empty values",
+					TextEdits: []analysis.TextEdit{{
+						Pos:     expr.Lbrace + 1,
+						End:     expr.Rbrace,
+						NewText: buf,
+					}},
+				}},
+			})
+		}
+	})
+	return nil, nil
+}
diff --git a/internal/lsp/analysis/fillstruct/fillstruct_test.go b/internal/lsp/analysis/fillstruct/fillstruct_test.go
new file mode 100644
index 0000000..cdd1bde
--- /dev/null
+++ b/internal/lsp/analysis/fillstruct/fillstruct_test.go
@@ -0,0 +1,17 @@
+// Copyright 2020 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 fillstruct_test
+
+import (
+	"testing"
+
+	"golang.org/x/tools/go/analysis/analysistest"
+	"golang.org/x/tools/internal/lsp/analysis/fillstruct"
+)
+
+func Test(t *testing.T) {
+	testdata := analysistest.TestData()
+	analysistest.RunWithSuggestedFixes(t, testdata, fillstruct.Analyzer, "a")
+}
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
new file mode 100644
index 0000000..447ed15
--- /dev/null
+++ b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
@@ -0,0 +1,39 @@
+// Copyright 2020 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 fillstruct
+
+import (
+	data "b"
+)
+
+type emptyStruct struct{}
+
+var _ = emptyStruct{}
+
+type basicStruct struct {
+	foo int
+}
+
+var _ = basicStruct{} // want ""
+
+type twoArgStruct struct {
+	foo int
+	bar string
+}
+
+var _ = twoArgStruct{} // want ""
+
+var _ = twoArgStruct{
+	bar: "bar",
+}
+
+type nestedStruct struct {
+	bar   string
+	basic basicStruct
+}
+
+var _ = nestedStruct{} // want ""
+
+var _ = data.B{} // want ""
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden
new file mode 100644
index 0000000..3fc564b
--- /dev/null
+++ b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden
@@ -0,0 +1,49 @@
+// Copyright 2020 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 fillstruct
+
+import (
+	data "b"
+)
+
+type emptyStruct struct{}
+
+var _ = emptyStruct{}
+
+type basicStruct struct {
+	foo int
+}
+
+var _ = basicStruct{
+	foo: 0,
+} // want ""
+
+type twoArgStruct struct {
+	foo int
+	bar string
+}
+
+var _ = twoArgStruct{
+	foo: 0,
+	bar: "",
+} // want ""
+
+var _ = twoArgStruct{
+	bar: "bar",
+}
+
+type nestedStruct struct {
+	bar   string
+	basic basicStruct
+}
+
+var _ = nestedStruct{
+	bar:   "",
+	basic: basicStruct{},
+} // want ""
+
+var _ = data.B{
+	ExportedInt: 0,
+} // want ""
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/b/b.go b/internal/lsp/analysis/fillstruct/testdata/src/b/b.go
new file mode 100644
index 0000000..a4b3946
--- /dev/null
+++ b/internal/lsp/analysis/fillstruct/testdata/src/b/b.go
@@ -0,0 +1,6 @@
+package fillstruct
+
+type B struct {
+	ExportedInt   int
+	unexportedInt int
+}
diff --git a/internal/lsp/source/options.go b/internal/lsp/source/options.go
index 11eaf05..13aec4c 100644
--- a/internal/lsp/source/options.go
+++ b/internal/lsp/source/options.go
@@ -38,6 +38,7 @@
 	"golang.org/x/tools/go/analysis/passes/unsafeptr"
 	"golang.org/x/tools/go/analysis/passes/unusedresult"
 	"golang.org/x/tools/internal/lsp/analysis/fillreturns"
+	"golang.org/x/tools/internal/lsp/analysis/fillstruct"
 	"golang.org/x/tools/internal/lsp/analysis/nonewvars"
 	"golang.org/x/tools/internal/lsp/analysis/noresultvalues"
 	"golang.org/x/tools/internal/lsp/analysis/simplifycompositelit"
@@ -130,11 +131,12 @@
 			TempModfile: true,
 		},
 		Hooks: Hooks{
-			ComputeEdits:       myers.ComputeEdits,
-			URLRegexp:          regexp.MustCompile(`(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?`),
-			DefaultAnalyzers:   defaultAnalyzers(),
-			TypeErrorAnalyzers: typeErrorAnalyzers(),
-			GoDiff:             true,
+			ComputeEdits:         myers.ComputeEdits,
+			URLRegexp:            regexp.MustCompile(`(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?`),
+			DefaultAnalyzers:     defaultAnalyzers(),
+			TypeErrorAnalyzers:   typeErrorAnalyzers(),
+			ConvenienceAnalyzers: convenienceAnalyzers(),
+			GoDiff:               true,
 		},
 	}
 }
@@ -243,11 +245,12 @@
 // Hooks contains configuration that is provided to the Gopls command by the
 // main package.
 type Hooks struct {
-	GoDiff             bool
-	ComputeEdits       diff.ComputeEdits
-	URLRegexp          *regexp.Regexp
-	DefaultAnalyzers   map[string]Analyzer
-	TypeErrorAnalyzers map[string]Analyzer
+	GoDiff               bool
+	ComputeEdits         diff.ComputeEdits
+	URLRegexp            *regexp.Regexp
+	DefaultAnalyzers     map[string]Analyzer
+	TypeErrorAnalyzers   map[string]Analyzer
+	ConvenienceAnalyzers map[string]Analyzer
 }
 
 func (o Options) AddDefaultAnalyzer(a *analysis.Analyzer) {
@@ -625,6 +628,15 @@
 	}
 }
 
+func convenienceAnalyzers() map[string]Analyzer {
+	return map[string]Analyzer{
+		fillstruct.Analyzer.Name: {
+			Analyzer: fillstruct.Analyzer,
+			enabled:  true,
+		},
+	}
+}
+
 func defaultAnalyzers() map[string]Analyzer {
 	return map[string]Analyzer{
 		// The traditional vet suite: