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() -} diff --git a/go.mod b/go.mod index 5eb1e8b16..13ff5eb00 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ module golang.org/x/text -require golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e +require golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de diff --git a/go.sum b/go.sum index 6a308d730..5910ca4cd 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,10 @@ +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e h1:FDhOuMEY4JVRztM/gsbk+IKUQ8kj74bxZrgw87eMMVc= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de h1:VNumCimp/Bwk6fRqgPHkjiUPZ/vzlpi23/kQTuQ4gBA= +golang.org/x/tools v0.0.0-20190807223507-b346f7fd45de/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/message/pipeline/extract.go b/message/pipeline/extract.go index 39b3dd5c4..27bb40443 100644 --- a/message/pipeline/extract.go +++ b/message/pipeline/extract.go @@ -21,9 +21,8 @@ import ( 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" ) const debug = false @@ -49,8 +48,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, @@ -59,8 +57,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 @@ -72,17 +70,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) @@ -100,26 +101,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, @@ -488,14 +509,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 } @@ -508,14 +529,13 @@ func (px packageExtracter) getComment(n ast.Node) string { } func (x *extracter) extractMessages() { - prog := x.iprog files := []packageExtracter{} - for _, info := range x.iprog.AllPackages { - 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) } @@ -609,13 +629,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: @@ -624,12 +644,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. @@ -675,7 +695,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) } @@ -704,11 +724,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) diff --git a/message/pipeline/generate.go b/message/pipeline/generate.go index 5d329b2f4..7c58e649d 100644 --- a/message/pipeline/generate.go +++ b/message/pipeline/generate.go @@ -20,7 +20,7 @@ import ( "golang.org/x/text/internal/catmsg" "golang.org/x/text/internal/gen" "golang.org/x/text/language" - "golang.org/x/tools/go/loader" + "golang.org/x/tools/go/packages" ) var transRe = regexp.MustCompile(`messages\.(.*)\.json`) @@ -34,15 +34,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,10 +48,10 @@ func (s *State) Generate() error { } if !isDir { gopath := build.Default.GOPATH - path = filepath.Join(gopath, filepath.FromSlash(pkgs[0].Pkg.Path())) + path = filepath.Join(gopath, filepath.FromSlash(pkgs[0].PkgPath)) } 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 } diff --git a/message/pipeline/pipeline.go b/message/pipeline/pipeline.go index cafd6f29b..b2b2d4677 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" @@ -25,7 +23,9 @@ import ( "golang.org/x/text/internal" "golang.org/x/text/language" "golang.org/x/text/runes" - "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" ) const ( @@ -125,7 +125,6 @@ type State struct { Config Config Package string - program *loader.Program Extracted Messages `json:"messages"` @@ -403,20 +402,19 @@ 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.LoadAllSyntax + 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 } 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 }