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:
parent
4651fa3054
commit
a1e2396bbd
145
internal/lsp/analysis/fillstruct/fillstruct.go
Normal file
145
internal/lsp/analysis/fillstruct/fillstruct.go
Normal 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
|
||||
}
|
17
internal/lsp/analysis/fillstruct/fillstruct_test.go
Normal file
17
internal/lsp/analysis/fillstruct/fillstruct_test.go
Normal 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")
|
||||
}
|
39
internal/lsp/analysis/fillstruct/testdata/src/a/a.go
vendored
Normal file
39
internal/lsp/analysis/fillstruct/testdata/src/a/a.go
vendored
Normal 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 ""
|
49
internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden
vendored
Normal file
49
internal/lsp/analysis/fillstruct/testdata/src/a/a.go.golden
vendored
Normal 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 ""
|
6
internal/lsp/analysis/fillstruct/testdata/src/b/b.go
vendored
Normal file
6
internal/lsp/analysis/fillstruct/testdata/src/b/b.go
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
package fillstruct
|
||||
|
||||
type B struct {
|
||||
ExportedInt int
|
||||
unexportedInt int
|
||||
}
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user