summaryrefslogtreecommitdiff
path: root/vendor/golang.org/x/text/message/pipeline/rewrite.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/golang.org/x/text/message/pipeline/rewrite.go')
-rw-r--r--vendor/golang.org/x/text/message/pipeline/rewrite.go268
1 files changed, 268 insertions, 0 deletions
diff --git a/vendor/golang.org/x/text/message/pipeline/rewrite.go b/vendor/golang.org/x/text/message/pipeline/rewrite.go
new file mode 100644
index 0000000..fa78324
--- /dev/null
+++ b/vendor/golang.org/x/text/message/pipeline/rewrite.go
@@ -0,0 +1,268 @@
+// Copyright 2017 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 pipeline
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/constant"
+ "go/format"
+ "go/token"
+ "io"
+ "os"
+ "strings"
+
+ "golang.org/x/tools/go/loader"
+)
+
+const printerType = "golang.org/x/text/message.Printer"
+
+// Rewrite rewrites the Go files in a single package to use the localization
+// machinery and rewrites strings to adopt best practices when possible.
+// If w is not nil the generated files are written to it, each files with a
+// "--- <filename>" header. Otherwise the files are overwritten.
+func Rewrite(w io.Writer, goPackage string) error {
+ conf := &loader.Config{
+ AllowErrors: true, // Allow unused instances of message.Printer.
+ }
+ prog, err := loadPackages(conf, []string{goPackage})
+ if err != nil {
+ return wrap(err, "")
+ }
+
+ for _, info := range prog.InitialPackages() {
+ for _, f := range info.Files {
+ // 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.printerVar = n.Lhs[0].Name()
+ }
+ }
+
+ ast.Walk(&r, f)
+
+ w := w
+ if w == nil {
+ var err error
+ if w, err = os.Create(conf.Fset.File(f.Pos()).Name()); err != nil {
+ return wrap(err, "open failed")
+ }
+ } else {
+ fmt.Fprintln(w, "---", conf.Fset.File(f.Pos()).Name())
+ }
+
+ if err := format.Node(w, conf.Fset, f); err != nil {
+ return wrap(err, "go format failed")
+ }
+ }
+ }
+
+ return nil
+}
+
+type rewriter struct {
+ info *loader.PackageInfo
+ conf *loader.Config
+ printerVar string
+}
+
+// print returns Go syntax for the specified node.
+func (r *rewriter) print(n ast.Node) string {
+ var buf bytes.Buffer
+ format.Node(&buf, r.conf.Fset, n)
+ return buf.String()
+}
+
+func (r *rewriter) Visit(n ast.Node) ast.Visitor {
+ // Save the state by scope.
+ if _, ok := n.(*ast.BlockStmt); ok {
+ r := *r
+ return &r
+ }
+ // Find Printers created by assignment.
+ stmt, ok := n.(*ast.AssignStmt)
+ if ok {
+ for _, v := range stmt.Lhs {
+ if r.printerVar == r.print(v) {
+ r.printerVar = ""
+ }
+ }
+ for i, v := range stmt.Rhs {
+ if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) {
+ r.printerVar = r.print(stmt.Lhs[i])
+ return r
+ }
+ }
+ }
+ // Find Printers created by variable declaration.
+ spec, ok := n.(*ast.ValueSpec)
+ if ok {
+ for _, v := range spec.Names {
+ if r.printerVar == r.print(v) {
+ r.printerVar = ""
+ }
+ }
+ for i, v := range spec.Values {
+ if t := r.info.Types[v].Type.String(); strings.HasSuffix(t, printerType) {
+ r.printerVar = r.print(spec.Names[i])
+ return r
+ }
+ }
+ }
+ if r.printerVar == "" {
+ return r
+ }
+ call, ok := n.(*ast.CallExpr)
+ if !ok {
+ return r
+ }
+
+ // TODO: Handle literal values?
+ sel, ok := call.Fun.(*ast.SelectorExpr)
+ if !ok {
+ return r
+ }
+ meth := r.info.Selections[sel]
+
+ source := r.print(sel.X)
+ fun := r.print(sel.Sel)
+ if meth != nil {
+ source = meth.Recv().String()
+ fun = meth.Obj().Name()
+ }
+
+ // TODO: remove cheap hack and check if the type either
+ // implements some interface or is specifically of type
+ // "golang.org/x/text/message".Printer.
+ m, ok := rewriteFuncs[source]
+ if !ok {
+ return r
+ }
+
+ rewriteType, ok := m[fun]
+ if !ok {
+ return r
+ }
+ ident := ast.NewIdent(r.printerVar)
+ ident.NamePos = sel.X.Pos()
+ sel.X = ident
+ if rewriteType.method != "" {
+ sel.Sel.Name = rewriteType.method
+ }
+
+ // Analyze arguments.
+ argn := rewriteType.arg
+ if rewriteType.format || argn >= len(call.Args) {
+ return r
+ }
+ hasConst := false
+ for _, a := range call.Args[argn:] {
+ if v := r.info.Types[a].Value; v != nil && v.Kind() == constant.String {
+ hasConst = true
+ break
+ }
+ }
+ if !hasConst {
+ return r
+ }
+ sel.Sel.Name = rewriteType.methodf
+
+ // 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])
+ if ok && !strings.Contains(s, "%") && !rewriteType.newLine {
+ return r
+ }
+ }
+
+ // Rewrite arguments as format string.
+ expr := &ast.BasicLit{
+ ValuePos: call.Lparen,
+ Kind: token.STRING,
+ }
+ newArgs := append(call.Args[:argn:argn], expr)
+ newStr := []string{}
+ for i, a := range call.Args[argn:] {
+ if s, ok := constStr(r.info, a); ok {
+ newStr = append(newStr, strings.Replace(s, "%", "%%", -1))
+ } else {
+ newStr = append(newStr, "%v")
+ newArgs = append(newArgs, call.Args[argn+i])
+ }
+ }
+ s := strings.Join(newStr, rewriteType.sep)
+ if rewriteType.newLine {
+ s += "\n"
+ }
+ expr.Value = fmt.Sprintf("%q", s)
+
+ call.Args = newArgs
+
+ // TODO: consider creating an expression instead of a constant string and
+ // then wrapping it in an escape function or so:
+ // call.Args[argn+i] = &ast.CallExpr{
+ // Fun: &ast.SelectorExpr{
+ // X: ast.NewIdent("message"),
+ // Sel: ast.NewIdent("Lookup"),
+ // },
+ // Args: []ast.Expr{a},
+ // }
+ // }
+
+ return r
+}
+
+type rewriteType struct {
+ // method is the name of the equivalent method on a printer, or "" if it is
+ // the same.
+ method string
+
+ // methodf is the method to use if the arguments can be rewritten as a
+ // arguments to a printf-style call.
+ methodf string
+
+ // format is true if the method takes a formatting string followed by
+ // substitution arguments.
+ format bool
+
+ // arg indicates the position of the argument to extract. If all is
+ // positive, all arguments from this argument onwards needs to be extracted.
+ arg int
+
+ sep string
+ newLine bool
+}
+
+// rewriteFuncs list functions that can be directly mapped to the printer
+// functions of the message package.
+var rewriteFuncs = map[string]map[string]rewriteType{
+ // TODO: Printer -> *golang.org/x/text/message.Printer
+ "fmt": {
+ "Print": rewriteType{methodf: "Printf"},
+ "Sprint": rewriteType{methodf: "Sprintf"},
+ "Fprint": rewriteType{methodf: "Fprintf"},
+
+ "Println": rewriteType{methodf: "Printf", sep: " ", newLine: true},
+ "Sprintln": rewriteType{methodf: "Sprintf", sep: " ", newLine: true},
+ "Fprintln": rewriteType{methodf: "Fprintf", sep: " ", newLine: true},
+
+ "Printf": rewriteType{method: "Printf", format: true},
+ "Sprintf": rewriteType{method: "Sprintf", format: true},
+ "Fprintf": rewriteType{method: "Fprintf", format: true},
+ },
+}
+
+func constStr(info *loader.PackageInfo, e ast.Expr) (s string, ok bool) {
+ v := info.Types[e].Value
+ if v == nil || v.Kind() != constant.String {
+ return "", false
+ }
+ return constant.StringVal(v), true
+}