func()

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
}