From cdebcee827f69f8a4f4767497908d0b9fe5f209d Mon Sep 17 00:00:00 2001 From: "David R. Jenni" Date: Wed, 13 Dec 2017 16:26:34 +0100 Subject: [PATCH] cmd/fillstruct: add option to select by line number. (#8) The new command line parameter -line can be used to specify the position of one or more struct literals by providing the corresponding line number. If both parameters, -offset and -line, are provided, then the tool first uses the more specific offset information. If there was no struct literal found at the given offset, then the line information is used. If more than one struct literal was found, code to fill all of them is generated. The output is a JSON array with the elements in reverse order of their occurence. Fixes #3. --- cmd/fillstruct/Readme.md | 9 ++- cmd/fillstruct/main.go | 153 ++++++++++++++++++++++++++++++++------- 2 files changed, 134 insertions(+), 28 deletions(-) diff --git a/cmd/fillstruct/Readme.md b/cmd/fillstruct/Readme.md index 464fb14..91f6d55 100644 --- a/cmd/fillstruct/Readme.md +++ b/cmd/fillstruct/Readme.md @@ -48,11 +48,16 @@ after applying fillstruct. ## Usage ``` -% fillstruct [-modified] -file= -offset= +% fillstruct [-modified] -file= -offset= -line= ``` Flags: -file: filename -modified: read an archive of modified files from stdin - -offset: byte offset of the struct literal + -offset: byte offset of the struct literal, optional if -line is present + -line: line number of the struct literal, optional if -offset is present + +If -offset as well as -line are present, then the tool first uses the +more specific offset information. If there was no struct literal found +at the given offset, then the line information is used. diff --git a/cmd/fillstruct/main.go b/cmd/fillstruct/main.go index 3528370..e94b18a 100644 --- a/cmd/fillstruct/main.go +++ b/cmd/fillstruct/main.go @@ -41,7 +41,7 @@ // // Usage: // -// % fillstruct [-modified] -file= -offset= +// % fillstruct [-modified] -file= -offset= -line= // // Flags: // @@ -49,7 +49,14 @@ // // -modified: read an archive of modified files from stdin // -// -offset: byte offset of the struct literal +// -offset: byte offset of the struct literal, optional if -line is present +// +// -line: line number of the struct literal, optional if -offset is present +// +// +// If -offset as well as -line are present, then the tool first uses the +// more specific offset information. If there was no struct literal found +// at the given offset, then the line information is used. // package main @@ -330,6 +337,8 @@ func isImported(pkg *types.Package, n *types.Named) bool { return n != nil && pkg != n.Obj().Pkg() } +var errNotFound = errors.New("no struct literal found at selection") + func main() { log.SetFlags(0) log.SetPrefix("fillstruct: ") @@ -337,11 +346,12 @@ func main() { var ( filename = flag.String("file", "", "filename") modified = flag.Bool("modified", false, "read an archive of modified files from stdin") - offset = flag.Int("offset", 0, "byte offset of the struct literal") + offset = flag.Int("offset", 0, "byte offset of the struct literal, optional if -line is present") + line = flag.Int("line", 0, "line number of the struct literal, optional if -offset is present") ) flag.Parse() - if *offset == 0 || *filename == "" { + if (*offset == 0 && *line == 0) || *filename == "" { flag.PrintDefaults() os.Exit(1) } @@ -357,23 +367,29 @@ func main() { } pkg := lprog.InitialPackages()[0] - f, pos, err := findPos(lprog, path, *offset) - if err != nil { - log.Fatal(err) + if *offset > 0 { + err = byOffset(lprog, path, pkg, *offset) + switch err { + case nil: + return + case errNotFound: + // try to use line information + default: + log.Fatal(err) + } } - lit, typ, name, err := findCompositeLit(f, pkg.Info, pos) - if err != nil { - log.Fatal(err) + if *line > 0 { + err = byLine(lprog, path, pkg, *line) + switch err { + case nil: + return + default: + log.Fatal(err) + } } - start := lprog.Fset.Position(lit.Pos()).Offset - end := lprog.Fset.Position(lit.End()).Offset - - newlit, lines := zeroValue(pkg.Pkg, lit, typ, name) - if err := print(newlit, lines, start, end); err != nil { - log.Fatal(err) - } + log.Fatal(errNotFound) } func absPath(filename string) (string, error) { @@ -407,6 +423,28 @@ func load(path string, modified bool) (*loader.Program, error) { return conf.Load() } +func byOffset(lprog *loader.Program, path string, pkg *loader.PackageInfo, offset int) error { + f, pos, err := findPos(lprog, path, offset) + if err != nil { + return err + } + + lit, typ, name, err := findCompositeLit(f, pkg.Info, pos) + if err != nil { + return err + } + + start := lprog.Fset.Position(lit.Pos()).Offset + end := lprog.Fset.Position(lit.End()).Offset + + newlit, lines := zeroValue(pkg.Pkg, lit, typ, name) + out, err := prepareOutput(newlit, lines, start, end) + if err != nil { + return err + } + return json.NewEncoder(os.Stdout).Encode([]output{out}) +} + func findPos(lprog *loader.Program, filename string, off int) (*ast.File, token.Pos, error) { for _, f := range lprog.InitialPackages()[0].Files { if file := lprog.Fset.File(f.Pos()); file.Name() == filename { @@ -429,15 +467,78 @@ func findCompositeLit(f *ast.File, info types.Info, pos token.Pos) (*ast.Composi name, _ := info.Types[lit].Type.(*types.Named) typ, ok := info.Types[lit].Type.Underlying().(*types.Struct) if !ok { - return nil, nil, nil, errors.New("no struct literal found at selection") + return nil, nil, nil, errNotFound } return lit, typ, name, nil } } - return nil, nil, nil, errors.New("no struct literal found at selection") + return nil, nil, nil, errNotFound } -func print(n ast.Node, lines, start, end int) error { +func byLine(lprog *loader.Program, path string, pkg *loader.PackageInfo, line int) (err error) { + var f *ast.File + for _, af := range lprog.InitialPackages()[0].Files { + if file := lprog.Fset.File(af.Pos()); file.Name() == path { + f = af + } + } + if f == nil { + return fmt.Errorf("could not find file %q", path) + } + + var outs []output + ast.Inspect(f, func(n ast.Node) bool { + lit, ok := n.(*ast.CompositeLit) + if !ok { + return true + } + startLine := lprog.Fset.Position(lit.Pos()).Line + endLine := lprog.Fset.Position(lit.End()).Line + if !(startLine <= line && line <= endLine) { + return true + } + + name, _ := pkg.Types[lit].Type.(*types.Named) + typ, ok := pkg.Types[lit].Type.Underlying().(*types.Struct) + if !ok { + err = errNotFound + return false + } + + startOff := lprog.Fset.Position(lit.Pos()).Offset + endOff := lprog.Fset.Position(lit.End()).Offset + newlit, lines := zeroValue(pkg.Pkg, lit, typ, name) + + var out output + out, err = prepareOutput(newlit, lines, startOff, endOff) + if err != nil { + return false + } + outs = append(outs, out) + return false + }) + if err != nil { + return err + } + if len(outs) == 0 { + return errNotFound + } + + for i := len(outs)/2 - 1; i >= 0; i-- { + opp := len(outs) - 1 - i + outs[i], outs[opp] = outs[opp], outs[i] + } + + return json.NewEncoder(os.Stdout).Encode(outs) +} + +type output struct { + Start int `json:"start"` + End int `json:"end"` + Code string `json:"code"` +} + +func prepareOutput(n ast.Node, lines, start, end int) (output, error) { fset := token.NewFileSet() file := fset.AddFile("", -1, lines) for i := 1; i <= lines; i++ { @@ -446,13 +547,13 @@ func print(n ast.Node, lines, start, end int) error { var buf bytes.Buffer if err := format.Node(&buf, fset, n); err != nil { - return err + return output{}, err } - return json.NewEncoder(os.Stdout).Encode(struct { - Start int `json:"start"` - End int `json:"end"` - Code string `json:"code"` - }{Start: start, End: end, Code: buf.String()}) + return output{ + Start: start, + End: end, + Code: buf.String(), + }, nil } func allowErrors(conf *loader.Config) {