mirror of
https://github.com/golang/go
synced 2024-11-18 14:44:41 -07:00
1fdcbd1300
Change-Id: Iaf238dfba6f51aed0a15f6ef2b231e1a1816f683 Reviewed-on: https://go-review.googlesource.com/c/tools/+/237421 Run-TryBot: Heschi Kreinick <heschi@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rebecca Stambler <rstambler@golang.org>
145 lines
4.0 KiB
Go
145 lines
4.0 KiB
Go
// 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 source
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"go/format"
|
|
"go/types"
|
|
"strings"
|
|
|
|
"golang.org/x/tools/go/ast/astutil"
|
|
"golang.org/x/tools/internal/lsp/protocol"
|
|
)
|
|
|
|
// FillStruct completes all of targeted struct's fields with their default values.
|
|
func FillStruct(ctx context.Context, snapshot Snapshot, fh FileHandle, protoRng protocol.Range) ([]protocol.CodeAction, error) {
|
|
|
|
pkg, pgh, err := getParsedFile(ctx, snapshot, fh, NarrowestPackageHandle)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("getting file for struct fill code action: %v", err)
|
|
}
|
|
file, src, m, _, err := pgh.Cached()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
spn, err := m.PointSpan(protoRng.Start)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
spanRng, err := spn.Range(m.Converter)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
path, _ := astutil.PathEnclosingInterval(file, spanRng.Start, spanRng.End)
|
|
if path == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
ecl := enclosingCompositeLiteral(path, spanRng.Start, pkg.GetTypesInfo())
|
|
if ecl == nil || !ecl.isStruct() {
|
|
return nil, nil
|
|
}
|
|
|
|
// If in F{ Bar<> : V} or anywhere in F{Bar : V, ...}
|
|
// we should not fill the struct.
|
|
if ecl.inKey || len(ecl.cl.Elts) != 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
var codeActions []protocol.CodeAction
|
|
qfFunc := qualifier(file, pkg.GetTypes(), pkg.GetTypesInfo())
|
|
switch obj := ecl.clType.(type) {
|
|
case *types.Struct:
|
|
fieldCount := obj.NumFields()
|
|
if fieldCount == 0 {
|
|
return nil, nil
|
|
}
|
|
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() != pkg.GetTypes() && !field.Exported() {
|
|
continue
|
|
}
|
|
|
|
label := field.Name()
|
|
value := formatZeroValue(field.Type(), qfFunc)
|
|
fieldSourceCode.WriteString("\n")
|
|
fieldSourceCode.WriteString(label)
|
|
fieldSourceCode.WriteString(" : ")
|
|
fieldSourceCode.WriteString(value)
|
|
fieldSourceCode.WriteString(",")
|
|
}
|
|
|
|
if fieldSourceCode.Len() == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
fieldSourceCode.WriteString("\n")
|
|
|
|
// the range of all text between '<>', inclusive. E.g. {<> ... <}>
|
|
mappedRange := newMappedRange(snapshot.View().Session().Cache().FileSet(), m, ecl.cl.Lbrace, ecl.cl.Rbrace+1)
|
|
protoRange, err := mappedRange.Range()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// consider formatting from the first character of the line the lbrace is on.
|
|
// ToOffset is 1-based
|
|
beginOffset, err := m.Converter.ToOffset(int(protoRange.Start.Line)+1, 1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
endOffset, err := m.Converter.ToOffset(int(protoRange.Start.Line)+1, int(protoRange.Start.Character)+1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// An increment to make sure the lbrace is included in the slice.
|
|
endOffset++
|
|
// Append the edits. Then append the closing brace.
|
|
var newSourceCode strings.Builder
|
|
newSourceCode.Grow(endOffset - beginOffset + fieldSourceCode.Len() + 1)
|
|
newSourceCode.WriteString(string(src[beginOffset:endOffset]))
|
|
newSourceCode.WriteString(fieldSourceCode.String())
|
|
newSourceCode.WriteString("}")
|
|
|
|
buf, err := format.Source([]byte(newSourceCode.String()))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// it is guaranteed that a left brace exists.
|
|
var edit = string(buf[strings.IndexByte(string(buf), '{'):])
|
|
|
|
codeActions = append(codeActions, protocol.CodeAction{
|
|
Title: "Fill struct",
|
|
Kind: protocol.RefactorRewrite,
|
|
Edit: protocol.WorkspaceEdit{
|
|
DocumentChanges: []protocol.TextDocumentEdit{
|
|
{
|
|
TextDocument: protocol.VersionedTextDocumentIdentifier{
|
|
Version: fh.Identity().Version,
|
|
TextDocumentIdentifier: protocol.TextDocumentIdentifier{
|
|
URI: protocol.URIFromSpanURI(fh.Identity().URI),
|
|
},
|
|
},
|
|
Edits: []protocol.TextEdit{
|
|
{
|
|
Range: protoRange,
|
|
NewText: edit,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
return codeActions, nil
|
|
}
|