jojo/build/lint-locale-usage/handle-go.go
Έλλεν Εμίλια Άννα Zscheile 1acf630dbf feat(build): Support go "fmt" format strings as masked usage patterns (#12013)
This idea is perhaps a bit more far-fetched. It implements the ability in `lint-locale-usage` to basically fully handle "printf" invocations by transforming format strings to regexps when "%" wildcards are present.

Currently, it doesn't cache the transformation from format string to compiled regex because this doesn't make a performance difference (yet), given that most of these wildcards are only hit once or twice.

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12013
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
2026-05-01 02:46:01 +02:00

123 lines
3.5 KiB
Go

// Copyright 2023 The Gitea Authors. All rights reserved.
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package lintLocaleUsage
import (
"fmt"
"go/ast"
"go/token"
"strconv"
"strings"
)
func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit, prefix string) {
if argLit.Kind == token.STRING {
// extract string content
arg, err := strconv.Unquote(argLit.Value)
if err != nil {
return
}
// found interesting strings
arg = prefix + arg
if strings.HasSuffix(arg, ".") || strings.HasSuffix(arg, "_") {
prep, trunc := PrepareMsgidPrefix(arg)
if trunc {
handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf("needed to truncate message id prefix: %s", arg))
}
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
} else {
handler.OnMsgid(fset, argLit.ValuePos, arg, false)
}
}
}
func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr, prefix string) {
switch n := n.(type) {
case *ast.BasicLit:
handler.HandleGoTrBasicLit(fset, n, prefix)
case *ast.BinaryExpr:
if n.Op != token.ADD {
// pass
} else if argLit, ok := n.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
// extract string content
arg, err := strconv.Unquote(argLit.Value)
if err != nil {
return
}
// found interesting strings
arg = prefix + arg
prep, trunc := PrepareMsgidPrefix(arg)
if trunc {
handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf("needed to truncate message id prefix: %s", arg))
}
handler.OnMsgidPrefix(fset, argLit.ValuePos, prep, trunc)
}
case *ast.CallExpr:
if selExpr, ok := n.Fun.(*ast.SelectorExpr); ok {
if xIdent, xok := selExpr.X.(*ast.Ident); !xok || xIdent.Name != "fmt" {
return
}
if selExpr.Sel.Name != "Sprintf" {
handler.OnWarning(fset, selExpr.Sel.NamePos, fmt.Sprintf("unexpected formatting function encountered: %s", selExpr.Sel.Name))
return
}
if len(n.Args) == 0 {
handler.OnWarning(fset, selExpr.Sel.NamePos, fmt.Sprintf("unexpected formatting function invocation (no arguments) of '%s'", selExpr.Sel.Name))
return
}
if argLit, ok := n.Args[0].(*ast.BasicLit); ok && argLit.Kind == token.STRING {
// extract string content
arg, err := strconv.Unquote(argLit.Value)
if err != nil {
return
}
if strings.Contains(arg, " ") {
handler.OnWarning(fset, argLit.ValuePos, fmt.Sprintf(
"formatting function invocation of '%s' with weird msgid format string: %s",
selExpr.Sel.Name,
arg,
))
return
}
// found interesting strings
handler.OnMsgidPattern(fset, argLit.ValuePos, prefix+arg)
}
}
}
}
func (handler Handler) HandleGoCommentGroup(fset *token.FileSet, cg *ast.CommentGroup, commentPrefix string) *string {
if cg == nil {
return nil
}
var matches []token.Pos
matchInsPrefix := ""
commentPrefix = "//" + commentPrefix
for _, comment := range cg.List {
ctxt := strings.TrimSpace(comment.Text)
if ctxt == commentPrefix {
matches = append(matches, comment.Slash)
} else if after, found := strings.CutPrefix(ctxt, commentPrefix+"Suffix "); found {
matches = append(matches, comment.Slash)
matchInsPrefix = strings.TrimSpace(after)
}
}
switch len(matches) {
case 0:
return nil
case 1:
return &matchInsPrefix
default:
handler.OnWarning(
fset,
matches[0],
fmt.Sprintf("encountered multiple %s... directives, ignoring", strings.TrimSpace(commentPrefix)),
)
return &matchInsPrefix
}
}