From 30a28409daf7506633026c5a38f0b849e34bc137 Mon Sep 17 00:00:00 2001 From: "Kevin C. Krinke" Date: Sun, 23 Oct 2022 15:16:08 -0400 Subject: [PATCH 1/2] gotext: remove unused code (from ssoroka commit 059a5b1dfb6e3c931765fdc5ad8a49299f7bf55b) --- cmd/gotext/common.go | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/cmd/gotext/common.go b/cmd/gotext/common.go index 51322db65..03e85dbff 100644 --- a/cmd/gotext/common.go +++ b/cmd/gotext/common.go @@ -6,16 +6,6 @@ package main import ( "fmt" - "go/build" - "go/parser" - - "golang.org/x/tools/go/loader" -) - -const ( - extractFile = "extracted.gotext.json" - outFile = "out.gotext.json" - gotextSuffix = ".gotext.json" ) // NOTE: The command line tool already prefixes with "gotext:". @@ -28,22 +18,3 @@ var ( } errorf = fmt.Errorf ) - -// TODO: still used. Remove when possible. -func loadPackages(conf *loader.Config, args []string) (*loader.Program, error) { - if len(args) == 0 { - args = []string{"."} - } - - conf.Build = &build.Default - conf.ParserMode = parser.ParseComments - - // Use the initial packages from the command line. - args, err := conf.FromArgs(args, false) - if err != nil { - return nil, wrap(err, "loading packages failed") - } - - // Load, parse and type-check the whole program. - return conf.Load() -} From ca408211cff4721da83fc9014e9a4831d840122e Mon Sep 17 00:00:00 2001 From: "Kevin C. Krinke" Date: Sun, 23 Oct 2022 15:17:16 -0400 Subject: [PATCH 2/2] gotext: implement go modules support (from ssoroka commit 059a5b1dfb6e3c931765fdc5ad8a49299f7bf55b) --- message/pipeline/extract.go | 96 +++++++++++++++++++++--------------- message/pipeline/generate.go | 13 +++-- message/pipeline/pipeline.go | 31 ++++++------ message/pipeline/rewrite.go | 39 +++++++-------- 4 files changed, 97 insertions(+), 82 deletions(-) diff --git a/message/pipeline/extract.go b/message/pipeline/extract.go index a15a7f9d3..1c2355b2a 100644 --- a/message/pipeline/extract.go +++ b/message/pipeline/extract.go @@ -14,17 +14,16 @@ import ( "go/token" "go/types" "path/filepath" - "sort" "strings" "unicode" "unicode/utf8" - fmtparser "golang.org/x/text/internal/format" "golang.org/x/tools/go/callgraph" "golang.org/x/tools/go/callgraph/cha" - "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/packages" "golang.org/x/tools/go/ssa" - "golang.org/x/tools/go/ssa/ssautil" + + fmtparser "golang.org/x/text/internal/format" ) const debug = false @@ -50,8 +49,7 @@ func Extract(c *Config) (*State, error) { x.extractMessages() return &State{ - Config: *c, - program: x.iprog, + Config: *c, Extracted: Messages{ Language: c.SourceLanguage, Messages: x.messages, @@ -60,8 +58,8 @@ func Extract(c *Config) (*State, error) { } type extracter struct { - conf loader.Config - iprog *loader.Program + conf packages.Config + pkgs []*packages.Package prog *ssa.Program callGraph *callgraph.Graph @@ -73,17 +71,20 @@ type extracter struct { func newExtracter(c *Config) (x *extracter, err error) { x = &extracter{ - conf: loader.Config{}, + conf: packages.Config{ + Fset: token.NewFileSet(), + }, globals: map[token.Pos]*constData{}, funcs: map[token.Pos]*callData{}, } - x.iprog, err = loadPackages(&x.conf, c.Packages) + prog, pkgs, err := loadPackages(&x.conf, c.Packages) if err != nil { return nil, wrap(err, "") } + x.prog = prog + x.pkgs = pkgs - x.prog = ssautil.CreateProgram(x.iprog, ssa.GlobalDebug|ssa.BareInits) x.prog.Build() x.callGraph = cha.CallGraph(x.prog) @@ -101,26 +102,46 @@ func (x *extracter) globalData(pos token.Pos) *constData { } func (x *extracter) seedEndpoints() error { - pkgInfo := x.iprog.Package("golang.org/x/text/message") - if pkgInfo == nil { - return errors.New("pipeline: golang.org/x/text/message is not imported") + var pkg *packages.Package + imports := "" + for _, p := range x.pkgs { + for k := range p.Imports { + imports = imports + k + "\n" + } + if p2, ok := p.Imports["golang.org/x/text/message"]; ok { + pkg = p2 + break + } + } + if pkg == nil { + return errors.New("pipeline: golang.org/x/text/message is not imported.\n" + imports) + } + + var typ *types.Pointer + for _, typeAndVal := range pkg.TypesInfo.Types { + if typeAndVal.Type.String() == "golang.org/x/text/message.Printer" { + typ = types.NewPointer(typeAndVal.Type) + break + } + } + + if typ == nil { + return errors.New("pipeline: golang.org/x/text/message.Printer was not found") } - pkg := x.prog.Package(pkgInfo.Pkg) - typ := types.NewPointer(pkg.Type("Printer").Type()) x.processGlobalVars() - x.handleFunc(x.prog.LookupMethod(typ, pkg.Pkg, "Printf"), &callData{ + x.handleFunc(x.prog.LookupMethod(typ, pkg.Types, "Printf"), &callData{ formatPos: 1, argPos: 2, isMethod: true, }) - x.handleFunc(x.prog.LookupMethod(typ, pkg.Pkg, "Sprintf"), &callData{ + x.handleFunc(x.prog.LookupMethod(typ, pkg.Types, "Sprintf"), &callData{ formatPos: 1, argPos: 2, isMethod: true, }) - x.handleFunc(x.prog.LookupMethod(typ, pkg.Pkg, "Fprintf"), &callData{ + x.handleFunc(x.prog.LookupMethod(typ, pkg.Types, "Fprintf"), &callData{ formatPos: 2, argPos: 3, isMethod: true, @@ -489,14 +510,14 @@ func (x *extracter) visitArgs(fd *callData, v ssa.Value) { // print returns Go syntax for the specified node. func (x *extracter) print(n ast.Node) string { var buf bytes.Buffer - format.Node(&buf, x.conf.Fset, n) + _ = format.Node(&buf, x.conf.Fset, n) return buf.String() } type packageExtracter struct { f *ast.File x *extracter - info *loader.PackageInfo + pkg *packages.Package cmap ast.CommentMap } @@ -509,20 +530,13 @@ func (px packageExtracter) getComment(n ast.Node) string { } func (x *extracter) extractMessages() { - prog := x.iprog - keys := make([]*types.Package, 0, len(x.iprog.AllPackages)) - for k := range x.iprog.AllPackages { - keys = append(keys, k) - } - sort.Slice(keys, func(i, j int) bool { return keys[i].Path() < keys[j].Path() }) files := []packageExtracter{} - for _, k := range keys { - info := x.iprog.AllPackages[k] - for _, f := range info.Files { + for _, pkg := range x.pkgs { + for _, f := range pkg.Syntax { // Associate comments with nodes. px := packageExtracter{ - f, x, info, - ast.NewCommentMap(prog.Fset, f, f.Comments), + f, x, pkg, + ast.NewCommentMap(pkg.Fset, f, f.Comments), } files = append(files, px) } @@ -616,13 +630,13 @@ func (px packageExtracter) handleCall(call *ast.CallExpr) bool { func (px packageExtracter) getArguments(data *callData) []argument { arguments := []argument{} x := px.x - info := px.info + pkg := px.pkg if data.callArgsStart() >= 0 { args := data.expr.Args[data.callArgsStart():] for i, arg := range args { expr := x.print(arg) val := "" - if v := info.Types[arg].Value; v != nil { + if v := pkg.TypesInfo.Types[arg].Value; v != nil { val = v.ExactString() switch arg.(type) { case *ast.BinaryExpr, *ast.UnaryExpr: @@ -631,12 +645,12 @@ func (px packageExtracter) getArguments(data *callData) []argument { } arguments = append(arguments, argument{ ArgNum: i + 1, - Type: info.Types[arg].Type.String(), - UnderlyingType: info.Types[arg].Type.Underlying().String(), + Type: pkg.TypesInfo.Types[arg].Type.String(), + UnderlyingType: pkg.TypesInfo.Types[arg].Type.Underlying().String(), Expr: expr, Value: val, Comment: px.getComment(arg), - Position: posString(&x.conf, info.Pkg, arg.Pos()), + Position: posString(&x.conf, pkg.Types, arg.Pos()), // TODO report whether it implements // interfaces plural.Interface, // gender.Interface. @@ -682,7 +696,7 @@ func (px packageExtracter) addMessage( case fmtparser.StatusBadArgNum, fmtparser.StatusMissingArg: arg = &argument{ ArgNum: p.ArgNum, - Position: posString(&x.conf, px.info.Pkg, pos), + Position: posString(&x.conf, px.pkg.Types, pos), } name, arg.UnderlyingType = verbToPlaceholder(p.Text(), p.ArgNum) } @@ -711,11 +725,11 @@ func (px packageExtracter) addMessage( // TODO(fix): this doesn't get the before comment. Comment: comment, Placeholders: ph.slice, - Position: posString(&x.conf, px.info.Pkg, pos), + Position: posString(&x.conf, px.pkg.Types, pos), }) } -func posString(conf *loader.Config, pkg *types.Package, pos token.Pos) string { +func posString(conf *packages.Config, pkg *types.Package, pos token.Pos) string { p := conf.Fset.Position(pos) file := fmt.Sprintf("%s:%d:%d", filepath.Base(p.Filename), p.Line, p.Column) return filepath.Join(pkg.Path(), file) @@ -818,4 +832,4 @@ func isMsg(s string) bool { } } return false -} +} \ No newline at end of file diff --git a/message/pipeline/generate.go b/message/pipeline/generate.go index bb1f85b94..6d574169f 100644 --- a/message/pipeline/generate.go +++ b/message/pipeline/generate.go @@ -14,13 +14,14 @@ import ( "strings" "text/template" + "golang.org/x/tools/go/packages" + "golang.org/x/text/collate" "golang.org/x/text/feature/plural" "golang.org/x/text/internal" "golang.org/x/text/internal/catmsg" "golang.org/x/text/internal/gen" "golang.org/x/text/language" - "golang.org/x/tools/go/loader" ) var transRe = regexp.MustCompile(`messages\.(.*)\.json`) @@ -34,15 +35,13 @@ func (s *State) Generate() error { path = "." } isDir := path[0] == '.' - prog, err := loadPackages(&loader.Config{}, []string{path}) + _, pkgs, err := loadPackages(&packages.Config{}, []string{path}) if err != nil { return wrap(err, "could not load package") } - pkgs := prog.InitialPackages() if len(pkgs) != 1 { return errorf("more than one package selected: %v", pkgs) } - pkg := pkgs[0].Pkg.Name() cw, err := s.generate() if err != nil { @@ -50,14 +49,14 @@ func (s *State) Generate() error { } if !isDir { gopath := filepath.SplitList(build.Default.GOPATH)[0] - path = filepath.Join(gopath, "src", filepath.FromSlash(pkgs[0].Pkg.Path())) + path = filepath.Join(gopath, "src", filepath.FromSlash(pkgs[0].PkgPath)) } if filepath.IsAbs(s.Config.GenFile) { path = s.Config.GenFile } else { path = filepath.Join(path, s.Config.GenFile) } - cw.WriteGoFile(path, pkg) // TODO: WriteGoFile should return error. + cw.WriteGoFile(path, pkgs[0].Name) // TODO: WriteGoFile should return error. return err } @@ -321,4 +320,4 @@ func init() { message.DefaultCatalog = cat } -`)) +`)) \ No newline at end of file diff --git a/message/pipeline/pipeline.go b/message/pipeline/pipeline.go index cafd6f29b..7f544540b 100644 --- a/message/pipeline/pipeline.go +++ b/message/pipeline/pipeline.go @@ -11,8 +11,6 @@ import ( "bytes" "encoding/json" "fmt" - "go/build" - "go/parser" "io/ioutil" "log" "os" @@ -22,10 +20,13 @@ import ( "text/template" "unicode" + "golang.org/x/tools/go/packages" + "golang.org/x/tools/go/ssa" + "golang.org/x/tools/go/ssa/ssautil" + "golang.org/x/text/internal" "golang.org/x/text/language" "golang.org/x/text/runes" - "golang.org/x/tools/go/loader" ) const ( @@ -125,7 +126,6 @@ type State struct { Config Config Package string - program *loader.Program Extracted Messages `json:"messages"` @@ -403,20 +403,23 @@ func warnf(format string, args ...interface{}) { log.Printf(format, args...) } -func loadPackages(conf *loader.Config, args []string) (*loader.Program, error) { +func loadPackages(conf *packages.Config, args []string) (*ssa.Program, []*packages.Package, error) { if len(args) == 0 { args = []string{"."} } - conf.Build = &build.Default - conf.ParserMode = parser.ParseComments - - // Use the initial packages from the command line. - args, err := conf.FromArgs(args, false) + conf.Mode = packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | + packages.NeedImports | + packages.NeedTypes | packages.NeedTypesSizes | + packages.NeedSyntax | packages.NeedTypesInfo | + packages.NeedDeps + pkgs, err := packages.Load(conf, args...) if err != nil { - return nil, wrap(err, "loading packages failed") + packages.PrintErrors(pkgs) + return nil, nil, err } - // Load, parse and type-check the whole program. - return conf.Load() -} + prog, _ := ssautil.Packages(pkgs, 0) + + return prog, pkgs, nil +} \ No newline at end of file diff --git a/message/pipeline/rewrite.go b/message/pipeline/rewrite.go index cf1511f56..acbb60d20 100644 --- a/message/pipeline/rewrite.go +++ b/message/pipeline/rewrite.go @@ -15,7 +15,7 @@ import ( "os" "strings" - "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/packages" ) const printerType = "golang.org/x/text/message.Printer" @@ -25,22 +25,21 @@ const printerType = "golang.org/x/text/message.Printer" // If w is not nil the generated files are written to it, each files with a // "--- " header. Otherwise the files are overwritten. func Rewrite(w io.Writer, args ...string) error { - conf := &loader.Config{ - AllowErrors: true, // Allow unused instances of message.Printer. - } - prog, err := loadPackages(conf, args) + conf := &packages.Config{} + _, pkgs, err := loadPackages(conf, args) if err != nil { return wrap(err, "") } - for _, info := range prog.InitialPackages() { - for _, f := range info.Files { + for _, pkg := range pkgs { + for _, f := range pkg.Syntax { // Associate comments with nodes. // Pick up initialized Printers at the package level. - r := rewriter{info: info, conf: conf} - for _, n := range info.InitOrder { - if t := r.info.Types[n.Rhs].Type.String(); strings.HasSuffix(t, printerType) { + r := rewriter{pkg: pkg, conf: conf} + + for _, n := range pkg.TypesInfo.InitOrder { + if t := r.pkg.TypesInfo.Types[n.Rhs].Type.String(); strings.HasSuffix(t, printerType) { r.printerVar = n.Lhs[0].Name() } } @@ -67,8 +66,8 @@ func Rewrite(w io.Writer, args ...string) error { } type rewriter struct { - info *loader.PackageInfo - conf *loader.Config + pkg *packages.Package + conf *packages.Config printerVar string } @@ -94,7 +93,7 @@ func (r *rewriter) Visit(n ast.Node) ast.Visitor { } } for i, v := range stmt.Rhs { - if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) { + if t := r.pkg.TypesInfo.Types[v].Type.String(); strings.HasSuffix(t, printerType) { r.printerVar = r.print(stmt.Lhs[i]) return r } @@ -109,7 +108,7 @@ func (r *rewriter) Visit(n ast.Node) ast.Visitor { } } for i, v := range spec.Values { - if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) { + if t := r.pkg.TypesInfo.Types[v].Type.String(); strings.HasSuffix(t, printerType) { r.printerVar = r.print(spec.Names[i]) return r } @@ -128,7 +127,7 @@ func (r *rewriter) Visit(n ast.Node) ast.Visitor { if !ok { return r } - meth := r.info.Selections[sel] + meth := r.pkg.TypesInfo.Selections[sel] source := r.print(sel.X) fun := r.print(sel.Sel) @@ -163,7 +162,7 @@ func (r *rewriter) Visit(n ast.Node) ast.Visitor { } hasConst := false for _, a := range call.Args[argn:] { - if v := r.info.Types[a].Value; v != nil && v.Kind() == constant.String { + if v := r.pkg.TypesInfo.Types[a].Value; v != nil && v.Kind() == constant.String { hasConst = true break } @@ -176,7 +175,7 @@ func (r *rewriter) Visit(n ast.Node) ast.Visitor { // We are done if there is only a single string that does not need to be // escaped. if len(call.Args) == 1 { - s, ok := constStr(r.info, call.Args[0]) + s, ok := constStr(r.pkg, call.Args[0]) if ok && !strings.Contains(s, "%") && !rewriteType.newLine { return r } @@ -190,7 +189,7 @@ func (r *rewriter) Visit(n ast.Node) ast.Visitor { newArgs := append(call.Args[:argn:argn], expr) newStr := []string{} for i, a := range call.Args[argn:] { - if s, ok := constStr(r.info, a); ok { + if s, ok := constStr(r.pkg, a); ok { newStr = append(newStr, strings.Replace(s, "%", "%%", -1)) } else { newStr = append(newStr, "%v") @@ -259,8 +258,8 @@ var rewriteFuncs = map[string]map[string]rewriteType{ }, } -func constStr(info *loader.PackageInfo, e ast.Expr) (s string, ok bool) { - v := info.Types[e].Value +func constStr(pkg *packages.Package, e ast.Expr) (s string, ok bool) { + v := pkg.TypesInfo.Types[e].Value if v == nil || v.Kind() != constant.String { return "", false }