in message/pipeline/rewrite.go [82:220]
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
}