1
0
mirror of https://github.com/golang/go synced 2024-11-18 09:04:49 -07:00

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>
This commit is contained in:
Josh Baum 2020-06-09 17:09:06 -04:00 committed by Rebecca Stambler
parent 4651fa3054
commit a1e2396bbd
6 changed files with 278 additions and 10 deletions

View File

@ -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
}

View File

@ -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")
}

View File

@ -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 ""

View File

@ -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 ""

View File

@ -0,0 +1,6 @@
package fillstruct
type B struct {
ExportedInt int
unexportedInt int
}

View File

@ -38,6 +38,7 @@ import (
"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 @@ func DefaultOptions() Options {
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 @@ type completionOptions struct {
// 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 typeErrorAnalyzers() map[string]Analyzer {
}
}
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: