aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/analysis/fillstruct
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/analysis/fillstruct')
-rw-r--r--internal/lsp/analysis/fillstruct/fillstruct.go495
-rw-r--r--internal/lsp/analysis/fillstruct/fillstruct_test.go22
-rw-r--r--internal/lsp/analysis/fillstruct/testdata/src/a/a.go106
-rw-r--r--internal/lsp/analysis/fillstruct/testdata/src/b/b.go6
-rw-r--r--internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go41
5 files changed, 0 insertions, 670 deletions
diff --git a/internal/lsp/analysis/fillstruct/fillstruct.go b/internal/lsp/analysis/fillstruct/fillstruct.go
deleted file mode 100644
index a4dd8ccb8..000000000
--- a/internal/lsp/analysis/fillstruct/fillstruct.go
+++ /dev/null
@@ -1,495 +0,0 @@
-// 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"
- "fmt"
- "go/ast"
- "go/format"
- "go/token"
- "go/types"
- "strings"
- "unicode"
-
- "golang.org/x/tools/go/analysis"
- "golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/astutil"
- "golang.org/x/tools/go/ast/inspector"
- "golang.org/x/tools/internal/analysisinternal"
- "golang.org/x/tools/internal/span"
- "golang.org/x/tools/internal/typeparams"
-)
-
-const Doc = `note incomplete struct initializations
-
-This analyzer provides diagnostics for any struct literals that do not have
-any fields initialized. Because the suggested fix for this analysis is
-expensive to compute, callers should compute it separately, using the
-SuggestedFix function below.
-`
-
-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)
-
- 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
- }
-
- // Ignore types that have type parameters for now.
- // TODO: support type params.
- if typ, ok := typ.(*types.Named); ok {
- if tparams := typeparams.ForNamed(typ); tparams != nil && tparams.Len() > 0 {
- 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()
-
- obj, ok := typ.(*types.Struct)
- if !ok {
- return
- }
- fieldCount := obj.NumFields()
-
- // Skip any struct that is already populated or that has no fields.
- if fieldCount == 0 || fieldCount == len(expr.Elts) {
- return
- }
-
- var fillable bool
- var fillableFields []string
- 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
- }
- // Ignore structs containing fields that have type parameters for now.
- // TODO: support type params.
- if typ, ok := field.Type().(*types.Named); ok {
- if tparams := typeparams.ForNamed(typ); tparams != nil && tparams.Len() > 0 {
- return
- }
- }
- if _, ok := field.Type().(*typeparams.TypeParam); ok {
- return
- }
- fillable = true
- fillableFields = append(fillableFields, fmt.Sprintf("%s: %s", field.Name(), field.Type().String()))
- }
- if !fillable {
- return
- }
- var name string
- switch typ := expr.Type.(type) {
- case *ast.Ident:
- name = typ.Name
- case *ast.SelectorExpr:
- name = fmt.Sprintf("%s.%s", typ.X, typ.Sel.Name)
- default:
- totalFields := len(fillableFields)
- maxLen := 20
- // Find the index to cut off printing of fields.
- var i, fieldLen int
- for i = range fillableFields {
- if fieldLen > maxLen {
- break
- }
- fieldLen += len(fillableFields[i])
- }
- fillableFields = fillableFields[:i]
- if i < totalFields {
- fillableFields = append(fillableFields, "...")
- }
- name = fmt.Sprintf("anonymous struct { %s }", strings.Join(fillableFields, ", "))
- }
- pass.Report(analysis.Diagnostic{
- Message: fmt.Sprintf("Fill %s", name),
- Pos: expr.Pos(),
- End: expr.End(),
- })
- })
- return nil, nil
-}
-
-func SuggestedFix(fset *token.FileSet, rng span.Range, content []byte, file *ast.File, pkg *types.Package, info *types.Info) (*analysis.SuggestedFix, error) {
- pos := rng.Start // don't use the end
-
- // TODO(rstambler): Using ast.Inspect would probably be more efficient than
- // calling PathEnclosingInterval. Switch this approach.
- path, _ := astutil.PathEnclosingInterval(file, pos, pos)
- if len(path) == 0 {
- return nil, fmt.Errorf("no enclosing ast.Node")
- }
- var expr *ast.CompositeLit
- for _, n := range path {
- if node, ok := n.(*ast.CompositeLit); ok {
- expr = node
- break
- }
- }
-
- if info == nil {
- return nil, fmt.Errorf("nil types.Info")
- }
- typ := info.TypeOf(expr)
- if typ == nil {
- return nil, fmt.Errorf("no composite literal")
- }
-
- // 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()
-
- obj, ok := typ.(*types.Struct)
- if !ok {
- return nil, fmt.Errorf("unexpected type %v (%T), expected *types.Struct", typ, typ)
- }
- fieldCount := obj.NumFields()
-
- // Check which types have already been filled in. (we only want to fill in
- // the unfilled types, or else we'll blat user-supplied details)
- prefilledTypes := map[string]ast.Expr{}
- for _, e := range expr.Elts {
- if kv, ok := e.(*ast.KeyValueExpr); ok {
- if key, ok := kv.Key.(*ast.Ident); ok {
- prefilledTypes[key.Name] = kv.Value
- }
- }
- }
-
- // Use a new fileset to build up a token.File for the new composite
- // literal. We need one line for foo{, one line for }, and one line for
- // each field we're going to set. format.Node only cares about line
- // numbers, so we don't need to set columns, and each line can be
- // 1 byte long.
- fakeFset := token.NewFileSet()
- tok := fakeFset.AddFile("", -1, fieldCount+2)
-
- line := 2 // account for 1-based lines and the left brace
- var elts []ast.Expr
- var fieldTyps []types.Type
- 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() != pkg && !field.Exported() {
- fieldTyps = append(fieldTyps, nil)
- continue
- }
- fieldTyps = append(fieldTyps, field.Type())
- }
- matches := analysisinternal.FindMatchingIdents(fieldTyps, file, rng.Start, info, pkg)
- for i, fieldTyp := range fieldTyps {
- if fieldTyp == nil {
- continue
- }
-
- tok.AddLine(line - 1) // add 1 byte per line
- if line > tok.LineCount() {
- panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
- }
- pos := tok.LineStart(line)
-
- kv := &ast.KeyValueExpr{
- Key: &ast.Ident{
- NamePos: pos,
- Name: obj.Field(i).Name(),
- },
- Colon: pos,
- }
- if expr, ok := prefilledTypes[obj.Field(i).Name()]; ok {
- kv.Value = expr
- } else {
- idents, ok := matches[fieldTyp]
- if !ok {
- return nil, fmt.Errorf("invalid struct field type: %v", fieldTyp)
- }
-
- // Find the identifer whose name is most similar to the name of the field's key.
- // If we do not find any identifer that matches the pattern, generate a new value.
- // NOTE: We currently match on the name of the field key rather than the field type.
- value := analysisinternal.FindBestMatch(obj.Field(i).Name(), idents)
- if value == nil {
- value = populateValue(fset, file, pkg, fieldTyp)
- }
- if value == nil {
- return nil, nil
- }
-
- kv.Value = value
- }
- elts = append(elts, kv)
- line++
- }
-
- // If all of the struct's fields are unexported, we have nothing to do.
- if len(elts) == 0 {
- return nil, fmt.Errorf("no elements to fill")
- }
-
- // Add the final line for the right brace. Offset is the number of
- // bytes already added plus 1.
- tok.AddLine(len(elts) + 1)
- line = len(elts) + 2
- if line > tok.LineCount() {
- panic(fmt.Sprintf("invalid line number %v (of %v) for fillstruct", line, tok.LineCount()))
- }
-
- cl := &ast.CompositeLit{
- Type: expr.Type,
- Lbrace: tok.LineStart(1),
- Elts: elts,
- Rbrace: tok.LineStart(line),
- }
-
- // Find the line on which the composite literal is declared.
- split := bytes.Split(content, []byte("\n"))
- lineNumber := fset.Position(expr.Lbrace).Line
- firstLine := split[lineNumber-1] // lines are 1-indexed
-
- // Trim the whitespace from the left of the line, and use the index
- // to get the amount of whitespace on the left.
- trimmed := bytes.TrimLeftFunc(firstLine, unicode.IsSpace)
- index := bytes.Index(firstLine, trimmed)
- whitespace := firstLine[:index]
-
- // First pass through the formatter: turn the expr into a string.
- var formatBuf bytes.Buffer
- if err := format.Node(&formatBuf, fakeFset, cl); err != nil {
- return nil, fmt.Errorf("failed to run first format on:\n%s\ngot err: %v", cl.Type, err)
- }
- sug := indent(formatBuf.Bytes(), whitespace)
-
- if len(prefilledTypes) > 0 {
- // Attempt a second pass through the formatter to line up columns.
- sourced, err := format.Source(sug)
- if err == nil {
- sug = indent(sourced, whitespace)
- }
- }
-
- return &analysis.SuggestedFix{
- TextEdits: []analysis.TextEdit{
- {
- Pos: expr.Pos(),
- End: expr.End(),
- NewText: sug,
- },
- },
- }, nil
-}
-
-// indent works line by line through str, indenting (prefixing) each line with
-// ind.
-func indent(str, ind []byte) []byte {
- split := bytes.Split(str, []byte("\n"))
- newText := bytes.NewBuffer(nil)
- for i, s := range split {
- if len(s) == 0 {
- continue
- }
- // Don't add the extra indentation to the first line.
- if i != 0 {
- newText.Write(ind)
- }
- newText.Write(s)
- if i < len(split)-1 {
- newText.WriteByte('\n')
- }
- }
- return newText.Bytes()
-}
-
-// populateValue constructs an expression to fill the value of a struct field.
-//
-// When the type of a struct field is a basic literal or interface, we return
-// default values. For other types, such as maps, slices, and channels, we create
-// expressions rather than using default values.
-//
-// The reasoning here is that users will call fillstruct with the intention of
-// initializing the struct, in which case setting these fields to nil has no effect.
-func populateValue(fset *token.FileSet, f *ast.File, pkg *types.Package, typ types.Type) ast.Expr {
- under := typ
- if n, ok := typ.(*types.Named); ok {
- under = n.Underlying()
- }
- switch u := under.(type) {
- case *types.Basic:
- switch {
- case u.Info()&types.IsNumeric != 0:
- return &ast.BasicLit{Kind: token.INT, Value: "0"}
- case u.Info()&types.IsBoolean != 0:
- return &ast.Ident{Name: "false"}
- case u.Info()&types.IsString != 0:
- return &ast.BasicLit{Kind: token.STRING, Value: `""`}
- default:
- panic("unknown basic type")
- }
- case *types.Map:
- k := analysisinternal.TypeExpr(fset, f, pkg, u.Key())
- v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
- if k == nil || v == nil {
- return nil
- }
- return &ast.CompositeLit{
- Type: &ast.MapType{
- Key: k,
- Value: v,
- },
- }
- case *types.Slice:
- s := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
- if s == nil {
- return nil
- }
- return &ast.CompositeLit{
- Type: &ast.ArrayType{
- Elt: s,
- },
- }
- case *types.Array:
- a := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
- if a == nil {
- return nil
- }
- return &ast.CompositeLit{
- Type: &ast.ArrayType{
- Elt: a,
- Len: &ast.BasicLit{
- Kind: token.INT, Value: fmt.Sprintf("%v", u.Len()),
- },
- },
- }
- case *types.Chan:
- v := analysisinternal.TypeExpr(fset, f, pkg, u.Elem())
- if v == nil {
- return nil
- }
- dir := ast.ChanDir(u.Dir())
- if u.Dir() == types.SendRecv {
- dir = ast.SEND | ast.RECV
- }
- return &ast.CallExpr{
- Fun: ast.NewIdent("make"),
- Args: []ast.Expr{
- &ast.ChanType{
- Dir: dir,
- Value: v,
- },
- },
- }
- case *types.Struct:
- s := analysisinternal.TypeExpr(fset, f, pkg, typ)
- if s == nil {
- return nil
- }
- return &ast.CompositeLit{
- Type: s,
- }
- case *types.Signature:
- var params []*ast.Field
- for i := 0; i < u.Params().Len(); i++ {
- p := analysisinternal.TypeExpr(fset, f, pkg, u.Params().At(i).Type())
- if p == nil {
- return nil
- }
- params = append(params, &ast.Field{
- Type: p,
- Names: []*ast.Ident{
- {
- Name: u.Params().At(i).Name(),
- },
- },
- })
- }
- var returns []*ast.Field
- for i := 0; i < u.Results().Len(); i++ {
- r := analysisinternal.TypeExpr(fset, f, pkg, u.Results().At(i).Type())
- if r == nil {
- return nil
- }
- returns = append(returns, &ast.Field{
- Type: r,
- })
- }
- return &ast.FuncLit{
- Type: &ast.FuncType{
- Params: &ast.FieldList{
- List: params,
- },
- Results: &ast.FieldList{
- List: returns,
- },
- },
- Body: &ast.BlockStmt{},
- }
- case *types.Pointer:
- switch u.Elem().(type) {
- case *types.Basic:
- return &ast.CallExpr{
- Fun: &ast.Ident{
- Name: "new",
- },
- Args: []ast.Expr{
- &ast.Ident{
- Name: u.Elem().String(),
- },
- },
- }
- default:
- return &ast.UnaryExpr{
- Op: token.AND,
- X: populateValue(fset, f, pkg, u.Elem()),
- }
- }
- case *types.Interface:
- return ast.NewIdent("nil")
- }
- return nil
-}
diff --git a/internal/lsp/analysis/fillstruct/fillstruct_test.go b/internal/lsp/analysis/fillstruct/fillstruct_test.go
deleted file mode 100644
index 51a516cdf..000000000
--- a/internal/lsp/analysis/fillstruct/fillstruct_test.go
+++ /dev/null
@@ -1,22 +0,0 @@
-// 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"
- "golang.org/x/tools/internal/typeparams"
-)
-
-func Test(t *testing.T) {
- testdata := analysistest.TestData()
- tests := []string{"a"}
- if typeparams.Enabled {
- tests = append(tests, "typeparams")
- }
- analysistest.Run(t, testdata, fillstruct.Analyzer, tests...)
-}
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go b/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
deleted file mode 100644
index f69fe8339..000000000
--- a/internal/lsp/analysis/fillstruct/testdata/src/a/a.go
+++ /dev/null
@@ -1,106 +0,0 @@
-// 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"
- "go/ast"
- "go/token"
-)
-
-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{ // want ""
- bar: "bar",
-}
-
-type nestedStruct struct {
- bar string
- basic basicStruct
-}
-
-var _ = nestedStruct{} // want ""
-
-var _ = data.B{} // want ""
-
-type typedStruct struct {
- m map[string]int
- s []int
- c chan int
- c1 <-chan int
- a [2]string
-}
-
-var _ = typedStruct{} // want ""
-
-type funStruct struct {
- fn func(i int) int
-}
-
-var _ = funStruct{} // want ""
-
-type funStructCompex struct {
- fn func(i int, s string) (string, int)
-}
-
-var _ = funStructCompex{} // want ""
-
-type funStructEmpty struct {
- fn func()
-}
-
-var _ = funStructEmpty{} // want ""
-
-type Foo struct {
- A int
-}
-
-type Bar struct {
- X *Foo
- Y *Foo
-}
-
-var _ = Bar{} // want ""
-
-type importedStruct struct {
- m map[*ast.CompositeLit]ast.Field
- s []ast.BadExpr
- a [3]token.Token
- c chan ast.EmptyStmt
- fn func(ast_decl ast.DeclStmt) ast.Ellipsis
- st ast.CompositeLit
-}
-
-var _ = importedStruct{} // want ""
-
-type pointerBuiltinStruct struct {
- b *bool
- s *string
- i *int
-}
-
-var _ = pointerBuiltinStruct{} // want ""
-
-var _ = []ast.BasicLit{
- {}, // want ""
-}
-
-var _ = []ast.BasicLit{{}, // want ""
-}
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/b/b.go b/internal/lsp/analysis/fillstruct/testdata/src/b/b.go
deleted file mode 100644
index a4b394605..000000000
--- a/internal/lsp/analysis/fillstruct/testdata/src/b/b.go
+++ /dev/null
@@ -1,6 +0,0 @@
-package fillstruct
-
-type B struct {
- ExportedInt int
- unexportedInt int
-}
diff --git a/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go b/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go
deleted file mode 100644
index 90290613d..000000000
--- a/internal/lsp/analysis/fillstruct/testdata/src/typeparams/typeparams.go
+++ /dev/null
@@ -1,41 +0,0 @@
-// 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
-
-type emptyStruct[A any] struct{}
-
-var _ = emptyStruct[int]{}
-
-type basicStruct[T any] struct {
- foo T
-}
-
-var _ = basicStruct[int]{}
-
-type fooType[T any] T
-
-type twoArgStruct[F, B any] struct {
- foo fooType[F]
- bar fooType[B]
-}
-
-var _ = twoArgStruct[string, int]{}
-
-var _ = twoArgStruct[int, string]{
- bar: "bar",
-}
-
-type nestedStruct struct {
- bar string
- basic basicStruct[int]
-}
-
-var _ = nestedStruct{}
-
-func _[T any]() {
- type S struct{ t T }
- x := S{}
- _ = x
-}