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