message/pipeline: hoist CallExpr handling Change-Id: I01ed5f06814dc71268407e302bebeefd7b608b8f Reviewed-on: https://go-review.googlesource.com/105015 Run-TryBot: Marcel van Lohuizen <mpvl@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ross Light <light@google.com>
diff --git a/message/pipeline/extract.go b/message/pipeline/extract.go index e006216..327670f 100644 --- a/message/pipeline/extract.go +++ b/message/pipeline/extract.go
@@ -348,138 +348,42 @@ } } -func (x *extracter) extractMessages() { - // print returns Go syntax for the specified node. - print := func(n ast.Node) string { - var buf bytes.Buffer - format.Node(&buf, x.conf.Fset, n) - return buf.String() +// 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) + return buf.String() +} + +type packageExtracter struct { + x *extracter + info *loader.PackageInfo + cmap ast.CommentMap +} + +func (px packageExtracter) getComment(n ast.Node) string { + cs := px.cmap.Filter(n).Comments() + if len(cs) > 0 { + return strings.TrimSpace(cs[0].Text()) } + return "" +} + +func (x *extracter) extractMessages() { prog := x.iprog for _, info := range x.iprog.AllPackages { for _, f := range info.Files { // Associate comments with nodes. - cmap := ast.NewCommentMap(prog.Fset, f, f.Comments) - getComment := func(n ast.Node) string { - cs := cmap.Filter(n).Comments() - if len(cs) > 0 { - return strings.TrimSpace(cs[0].Text()) - } - return "" + px := packageExtracter{ + x, info, + ast.NewCommentMap(prog.Fset, f, f.Comments), } // Find function calls. ast.Inspect(f, func(n ast.Node) bool { - call, ok := n.(*ast.CallExpr) - if !ok { - return true - } - data := x.funcs[call.Lparen] - if data == nil || len(data.formats) == 0 { - return true - } - x.debug(data.call, "INSERT", data.formats) - - argn := data.callFormatPos() - if argn >= len(call.Args) { - return true - } - format := call.Args[argn] - - comment := "" - key := []string{} - if ident, ok := format.(*ast.Ident); ok { - key = append(key, ident.Name) - if v, ok := ident.Obj.Decl.(*ast.ValueSpec); ok && v.Comment != nil { - // TODO: get comment above ValueSpec as well - comment = v.Comment.Text() - } - } - - arguments := []argument{} - simArgs := []interface{}{} - if data.callArgsStart() >= 0 { - args := call.Args[data.callArgsStart():] - simArgs = make([]interface{}, len(args)) - for i, arg := range args { - expr := print(arg) - val := "" - if v := info.Types[arg].Value; v != nil { - val = v.ExactString() - simArgs[i] = val - switch arg.(type) { - case *ast.BinaryExpr, *ast.UnaryExpr: - expr = val - } - } - arguments = append(arguments, argument{ - ArgNum: i + 1, - Type: info.Types[arg].Type.String(), - UnderlyingType: info.Types[arg].Type.Underlying().String(), - Expr: expr, - Value: val, - Comment: getComment(arg), - Position: posString(&x.conf, info.Pkg, arg.Pos()), - // TODO report whether it implements - // interfaces plural.Interface, - // gender.Interface. - }) - } - } - - formats := data.formats - for _, c := range formats { - key := append([]string{}, key...) - fmtMsg := constant.StringVal(c) - msg := "" - - ph := placeholders{index: map[string]string{}} - - trimmed, _, _ := trimWS(fmtMsg) - - p := fmtparser.Parser{} - p.Reset(simArgs) - for p.SetFormat(trimmed); p.Scan(); { - switch p.Status { - case fmtparser.StatusText: - msg += p.Text() - case fmtparser.StatusSubstitution, - fmtparser.StatusBadWidthSubstitution, - fmtparser.StatusBadPrecSubstitution: - arguments[p.ArgNum-1].used = true - arg := arguments[p.ArgNum-1] - sub := p.Text() - if !p.HasIndex { - r, sz := utf8.DecodeLastRuneInString(sub) - sub = fmt.Sprintf("%s[%d]%c", sub[:len(sub)-sz], p.ArgNum, r) - } - msg += fmt.Sprintf("{%s}", ph.addArg(&arg, sub)) - } - } - key = append(key, msg) - - // Add additional Placeholders that can be used in translations - // that are not present in the string. - for _, arg := range arguments { - if arg.used { - continue - } - ph.addArg(&arg, fmt.Sprintf("%%[%d]v", arg.ArgNum)) - } - - if c := getComment(call.Args[0]); c != "" { - comment = c - } - - x.messages = append(x.messages, Message{ - ID: key, - Key: fmtMsg, - Message: Text{Msg: msg}, - // TODO(fix): this doesn't get the before comment. - Comment: comment, - Placeholders: ph.slice, - Position: posString(&x.conf, info.Pkg, call.Lparen), - }) + switch v := n.(type) { + case *ast.CallExpr: + return px.handleCall(v) } return true }) @@ -487,6 +391,119 @@ } } +func (px packageExtracter) handleCall(call *ast.CallExpr) bool { + x := px.x + info := px.info + data := x.funcs[call.Lparen] + if data == nil || len(data.formats) == 0 { + return true + } + x.debug(data.call, "INSERT", data.formats) + + argn := data.callFormatPos() + if argn >= len(call.Args) { + return true + } + format := call.Args[argn] + + comment := "" + key := []string{} + if ident, ok := format.(*ast.Ident); ok { + key = append(key, ident.Name) + if v, ok := ident.Obj.Decl.(*ast.ValueSpec); ok && v.Comment != nil { + // TODO: get comment above ValueSpec as well + comment = v.Comment.Text() + } + } + + arguments := []argument{} + simArgs := []interface{}{} + if data.callArgsStart() >= 0 { + args := call.Args[data.callArgsStart():] + simArgs = make([]interface{}, len(args)) + for i, arg := range args { + expr := x.print(arg) + val := "" + if v := info.Types[arg].Value; v != nil { + val = v.ExactString() + simArgs[i] = val + switch arg.(type) { + case *ast.BinaryExpr, *ast.UnaryExpr: + expr = val + } + } + arguments = append(arguments, argument{ + ArgNum: i + 1, + Type: info.Types[arg].Type.String(), + UnderlyingType: info.Types[arg].Type.Underlying().String(), + Expr: expr, + Value: val, + Comment: px.getComment(arg), + Position: posString(&x.conf, info.Pkg, arg.Pos()), + // TODO report whether it implements + // interfaces plural.Interface, + // gender.Interface. + }) + } + } + + formats := data.formats + for _, c := range formats { + key := append([]string{}, key...) + fmtMsg := constant.StringVal(c) + msg := "" + + ph := placeholders{index: map[string]string{}} + + trimmed, _, _ := trimWS(fmtMsg) + + p := fmtparser.Parser{} + p.Reset(simArgs) + for p.SetFormat(trimmed); p.Scan(); { + switch p.Status { + case fmtparser.StatusText: + msg += p.Text() + case fmtparser.StatusSubstitution, + fmtparser.StatusBadWidthSubstitution, + fmtparser.StatusBadPrecSubstitution: + arguments[p.ArgNum-1].used = true + arg := arguments[p.ArgNum-1] + sub := p.Text() + if !p.HasIndex { + r, sz := utf8.DecodeLastRuneInString(sub) + sub = fmt.Sprintf("%s[%d]%c", sub[:len(sub)-sz], p.ArgNum, r) + } + msg += fmt.Sprintf("{%s}", ph.addArg(&arg, sub)) + } + } + key = append(key, msg) + + // Add additional Placeholders that can be used in translations + // that are not present in the string. + for _, arg := range arguments { + if arg.used { + continue + } + ph.addArg(&arg, fmt.Sprintf("%%[%d]v", arg.ArgNum)) + } + + if c := px.getComment(call.Args[0]); c != "" { + comment = c + } + + x.messages = append(x.messages, Message{ + ID: key, + Key: fmtMsg, + Message: Text{Msg: msg}, + // TODO(fix): this doesn't get the before comment. + Comment: comment, + Placeholders: ph.slice, + Position: posString(&x.conf, info.Pkg, call.Lparen), + }) + } + return true +} + func posString(conf *loader.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)