diff --git a/cmd/deprecator/go.mod b/cmd/deprecator/go.mod new file mode 100644 index 000000000..bfc031612 --- /dev/null +++ b/cmd/deprecator/go.mod @@ -0,0 +1,17 @@ +module github.com/ipfs/boxo/cmd/deprecator + +go 1.19 + +require ( + github.com/dave/dst v0.27.2 + github.com/urfave/cli/v2 v2.25.3 +) + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/tools v0.1.12 // indirect +) diff --git a/cmd/deprecator/go.sum b/cmd/deprecator/go.sum new file mode 100644 index 000000000..92bdc5769 --- /dev/null +++ b/cmd/deprecator/go.sum @@ -0,0 +1,18 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/dave/dst v0.27.2 h1:4Y5VFTkhGLC1oddtNwuxxe36pnyLxMFXT51FOzH8Ekc= +github.com/dave/dst v0.27.2/go.mod h1:jHh6EOibnHgcUW3WjKHisiooEkYwqpHLBSX1iOBhEyc= +github.com/dave/jennifer v1.5.0 h1:HmgPN93bVDpkQyYbqhCHj5QlgvUkvEOzMyEvKLgCRrg= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/urfave/cli/v2 v2.25.3 h1:VJkt6wvEBOoSjPFQvOkv6iWIrsJyCrKGtCtxXWwmGeY= +github.com/urfave/cli/v2 v2.25.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= diff --git a/cmd/deprecator/main.go b/cmd/deprecator/main.go new file mode 100644 index 000000000..7b3bc3d17 --- /dev/null +++ b/cmd/deprecator/main.go @@ -0,0 +1,187 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "go/parser" + "go/token" + "io" + "io/fs" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/dave/dst" + "github.com/dave/dst/decorator" + "github.com/urfave/cli/v2" +) + +func main() { + app := &cli.App{ + Name: "deprecator", + Usage: "Adds deprecation comments to all exported types in the module, with pointers to a new location. You should run this in your module root.", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "path", + Usage: "the new package path that the deprecated message should point users to", + Required: true, + }, + }, + Action: func(ctx *cli.Context) error { + newPkgPath := ctx.String("path") + wd, err := os.Getwd() + if err != nil { + return fmt.Errorf("getting working dir: %w", err) + } + fileToPackage, err := buildFileToPackage(wd) + if err != nil { + return fmt.Errorf("building mapping of files to packages: %w", err) + } + + modPath, err := getModulePath(wd) + if err != nil { + return fmt.Errorf("finding current module path: %w", err) + } + + fset := token.NewFileSet() + return filepath.Walk(wd, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() || filepath.Ext(path) != ".go" { + return nil + } + + addComment := func(name string, decs *dst.Decorations) { + oldPkg := fileToPackage[path] + newPkg := strings.Replace(oldPkg, modPath, newPkgPath, 1) + newSym := newPkg + "." + name + comment := fmt.Sprintf("// Deprecated: use %s", newSym) + if len(decs.All()) > 0 { + decs.Append("//") + } + decs.Append(comment) + } + + file, err := decorator.ParseFile(fset, path, nil, parser.ParseComments) + if err != nil { + return fmt.Errorf("parsing %s: %w", path, err) + } + + if _, ok := fileToPackage[path]; !ok { + // this happens in the case of e.g. test files, which we want to skip + return nil + } + + // process the AST, adding comments where necessary + dst.Inspect(file, func(n dst.Node) bool { return inspectASTNode(addComment, n) }) + + outFile, err := os.Create(path) + if err != nil { + return fmt.Errorf("creating %s to write: %w", path, err) + } + defer outFile.Close() + err = decorator.Fprint(outFile, file) + if err != nil { + return fmt.Errorf("writing %s: %w", path, err) + } + + return nil + }) + }, + } + err := app.Run(os.Args) + if err != nil { + log.Fatal(err) + } +} + +type pkg struct { + Dir string + ImportPath string + GoFiles []string +} + +func inspectASTNode(addComment func(string, *dst.Decorations), n dst.Node) bool { + switch x := n.(type) { + case *dst.GenDecl: + if x.Tok == token.CONST || x.Tok == token.VAR || x.Tok == token.TYPE { + for _, spec := range x.Specs { + switch s := spec.(type) { + case *dst.ValueSpec: + // if parenthesized, put a comment above each exported type in the group + if x.Lparen { + for _, name := range s.Names { + if !name.IsExported() { + continue + } + addComment(name.Name, &s.Decs.Start) + } + } else { + name := s.Names[0] + if !name.IsExported() { + continue + } + addComment(name.Name, &x.Decs.Start) + } + case *dst.TypeSpec: + name := s.Name + if !name.IsExported() { + continue + } + addComment(name.Name, &x.Decs.Start) + } + } + } + case *dst.FuncDecl: + // don't add notices to methods + if x.Name.IsExported() && x.Recv == nil { + addComment(x.Name.Name, &x.Decs.Start) + } + } + return true + +} + +func getModulePath(dir string) (string, error) { + cmd := exec.Command("go", "list", "-m") + cmd.Dir = dir + stdout := &bytes.Buffer{} + cmd.Stdout = stdout + err := cmd.Run() + if err != nil { + return "", err + } + return strings.TrimSpace(stdout.String()), nil +} + +func buildFileToPackage(dir string) (map[string]string, error) { + cmd := exec.Command("go", "list", "-json", "./...") + cmd.Dir = dir + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + cmd.Stdout = stdout + cmd.Stderr = stderr + err := cmd.Run() + if err != nil { + return nil, err + } + dec := json.NewDecoder(stdout) + fileToPackage := map[string]string{} + for { + var p pkg + err := dec.Decode(&p) + if err == io.EOF { + return fileToPackage, nil + } + if err != nil { + return nil, err + } + for _, f := range p.GoFiles { + fileToPackage[filepath.Join(p.Dir, f)] = p.ImportPath + } + } +}