loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(build): lint-locale-usage should detect more Tr functions (#7278)

Followup to https://codeberg.org/forgejo/forgejo/pulls/7109

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7278
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Co-authored-by: Ellen Emilia Anna Zscheile <fogti+devel@ytrizja.de>
Co-committed-by: Ellen Emilia Anna Zscheile <fogti+devel@ytrizja.de>

+120 -61
+102 -53
build/lint-locale-usage/lint-locale-usage.go
··· 26 26 // this works by first gathering all valid source string IDs from `en-US` reference files 27 27 // and then checking if all used source strings are actually defined 28 28 29 - type OnMsgidHandler func(fset *token.FileSet, pos token.Pos, msgid string) 30 - 31 29 type LocatedError struct { 32 30 Location string 33 31 Kind string ··· 49 47 return sb.String() 50 48 } 51 49 52 - func isLocaleTrFunction(funcname string) bool { 53 - return funcname == "Tr" || funcname == "TrN" 50 + func InitLocaleTrFunctions() map[string][]uint { 51 + ret := make(map[string][]uint) 52 + 53 + f0 := []uint{0} 54 + ret["Tr"] = f0 55 + ret["TrString"] = f0 56 + ret["TrHTML"] = f0 57 + 58 + ret["TrPluralString"] = []uint{1} 59 + ret["TrN"] = []uint{1, 2} 60 + 61 + return ret 62 + } 63 + 64 + type Handler struct { 65 + OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string) 66 + OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int) 67 + LocaleTrFunctions map[string][]uint 54 68 } 55 69 56 70 // the `Handle*File` functions follow the following calling convention: ··· 58 72 // * `src` is either `nil` (then the function invokes `ReadFile` to read the file) 59 73 // or the contents of the file as {`[]byte`, or a `string`} 60 74 61 - func (omh OnMsgidHandler) HandleGoFile(fname string, src any) error { 75 + func (handler Handler) HandleGoFile(fname string, src any) error { 62 76 fset := token.NewFileSet() 63 77 node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution) 64 78 if err != nil { ··· 70 84 } 71 85 72 86 ast.Inspect(node, func(n ast.Node) bool { 73 - // search for function calls of the form `anything.Tr(any-string-lit)` 87 + // search for function calls of the form `anything.Tr(any-string-lit, ...)` 74 88 75 89 call, ok := n.(*ast.CallExpr) 76 - if !ok || len(call.Args) != 1 { 90 + if !ok || len(call.Args) < 1 { 77 91 return true 78 92 } 79 93 80 94 funSel, ok := call.Fun.(*ast.SelectorExpr) 81 - if (!ok) || !isLocaleTrFunction(funSel.Sel.Name) { 95 + if !ok { 82 96 return true 83 97 } 84 98 85 - argLit, ok := call.Args[0].(*ast.BasicLit) 86 - if (!ok) || argLit.Kind != token.STRING { 99 + ltf, ok := handler.LocaleTrFunctions[funSel.Sel.Name] 100 + if !ok { 87 101 return true 88 102 } 89 103 90 - // extract string content 91 - arg, err := strconv.Unquote(argLit.Value) 92 - if err != nil { 93 - return true 104 + var gotUnexpectedInvoke *int 105 + 106 + for _, argNum := range ltf { 107 + if len(call.Args) >= int(argNum+1) { 108 + argLit, ok := call.Args[int(argNum)].(*ast.BasicLit) 109 + if !ok || argLit.Kind != token.STRING { 110 + continue 111 + } 112 + 113 + // extract string content 114 + arg, err := strconv.Unquote(argLit.Value) 115 + if err == nil { 116 + // found interesting strings 117 + handler.OnMsgid(fset, argLit.ValuePos, arg) 118 + } 119 + } else { 120 + argc := len(call.Args) 121 + gotUnexpectedInvoke = &argc 122 + } 94 123 } 95 124 96 - // found interesting string 97 - omh(fset, argLit.ValuePos, arg) 125 + if gotUnexpectedInvoke != nil { 126 + handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke) 127 + } 98 128 99 129 return true 100 130 }) ··· 103 133 } 104 134 105 135 // derived from source: modules/templates/scopedtmpl/scopedtmpl.go, L169-L213 106 - func (omh OnMsgidHandler) handleTemplateNode(fset *token.FileSet, node tmplParser.Node) { 136 + func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.Node) { 107 137 switch node.Type() { 108 138 case tmplParser.NodeAction: 109 - omh.handleTemplatePipeNode(fset, node.(*tmplParser.ActionNode).Pipe) 139 + handler.handleTemplatePipeNode(fset, node.(*tmplParser.ActionNode).Pipe) 110 140 case tmplParser.NodeList: 111 141 nodeList := node.(*tmplParser.ListNode) 112 - omh.handleTemplateFileNodes(fset, nodeList.Nodes) 142 + handler.handleTemplateFileNodes(fset, nodeList.Nodes) 113 143 case tmplParser.NodePipe: 114 - omh.handleTemplatePipeNode(fset, node.(*tmplParser.PipeNode)) 144 + handler.handleTemplatePipeNode(fset, node.(*tmplParser.PipeNode)) 115 145 case tmplParser.NodeTemplate: 116 - omh.handleTemplatePipeNode(fset, node.(*tmplParser.TemplateNode).Pipe) 146 + handler.handleTemplatePipeNode(fset, node.(*tmplParser.TemplateNode).Pipe) 117 147 case tmplParser.NodeIf: 118 148 nodeIf := node.(*tmplParser.IfNode) 119 - omh.handleTemplateBranchNode(fset, nodeIf.BranchNode) 149 + handler.handleTemplateBranchNode(fset, nodeIf.BranchNode) 120 150 case tmplParser.NodeRange: 121 151 nodeRange := node.(*tmplParser.RangeNode) 122 - omh.handleTemplateBranchNode(fset, nodeRange.BranchNode) 152 + handler.handleTemplateBranchNode(fset, nodeRange.BranchNode) 123 153 case tmplParser.NodeWith: 124 154 nodeWith := node.(*tmplParser.WithNode) 125 - omh.handleTemplateBranchNode(fset, nodeWith.BranchNode) 155 + handler.handleTemplateBranchNode(fset, nodeWith.BranchNode) 126 156 127 157 case tmplParser.NodeCommand: 128 158 nodeCommand := node.(*tmplParser.CommandNode) 129 159 130 - omh.handleTemplateFileNodes(fset, nodeCommand.Args) 160 + handler.handleTemplateFileNodes(fset, nodeCommand.Args) 131 161 132 - if len(nodeCommand.Args) != 2 { 162 + if len(nodeCommand.Args) < 2 { 133 163 return 134 164 } 135 165 ··· 138 168 return 139 169 } 140 170 141 - nodeString, ok := nodeCommand.Args[1].(*tmplParser.StringNode) 142 - if !ok { 171 + nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode) 172 + if !ok || nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" { 143 173 return 144 174 } 145 175 146 - nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode) 147 - if !ok || nodeIdent.Ident != "ctx" { 176 + ltf, ok := handler.LocaleTrFunctions[nodeChain.Field[1]] 177 + if !ok { 148 178 return 149 179 } 150 180 151 - if len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" || !isLocaleTrFunction(nodeChain.Field[1]) { 152 - return 181 + var gotUnexpectedInvoke *int 182 + 183 + for _, argNum := range ltf { 184 + if len(nodeCommand.Args) >= int(argNum+2) { 185 + nodeString, ok := nodeCommand.Args[int(argNum+1)].(*tmplParser.StringNode) 186 + if ok { 187 + // found interesting strings 188 + // the column numbers are a bit "off", but much better than nothing 189 + handler.OnMsgid(fset, token.Pos(nodeString.Pos), nodeString.Text) 190 + } 191 + } else { 192 + argc := len(nodeCommand.Args) - 1 193 + gotUnexpectedInvoke = &argc 194 + } 153 195 } 154 196 155 - // found interesting string 156 - // the column numbers are a bit "off", but much better than nothing 157 - omh(fset, token.Pos(nodeString.Pos), nodeString.Text) 197 + if gotUnexpectedInvoke != nil { 198 + handler.OnUnexpectedInvoke(fset, token.Pos(nodeChain.Pos), nodeChain.Field[1], *gotUnexpectedInvoke) 199 + } 158 200 159 201 default: 160 202 } 161 203 } 162 204 163 - func (omh OnMsgidHandler) handleTemplatePipeNode(fset *token.FileSet, pipeNode *tmplParser.PipeNode) { 205 + func (handler Handler) handleTemplatePipeNode(fset *token.FileSet, pipeNode *tmplParser.PipeNode) { 164 206 if pipeNode == nil { 165 207 return 166 208 } 167 209 168 210 // NOTE: we can't pass `pipeNode.Cmds` to handleTemplateFileNodes due to incompatible argument types 169 211 for _, node := range pipeNode.Cmds { 170 - omh.handleTemplateNode(fset, node) 212 + handler.handleTemplateNode(fset, node) 171 213 } 172 214 } 173 215 174 - func (omh OnMsgidHandler) handleTemplateBranchNode(fset *token.FileSet, branchNode tmplParser.BranchNode) { 175 - omh.handleTemplatePipeNode(fset, branchNode.Pipe) 176 - omh.handleTemplateFileNodes(fset, branchNode.List.Nodes) 216 + func (handler Handler) handleTemplateBranchNode(fset *token.FileSet, branchNode tmplParser.BranchNode) { 217 + handler.handleTemplatePipeNode(fset, branchNode.Pipe) 218 + handler.handleTemplateFileNodes(fset, branchNode.List.Nodes) 177 219 if branchNode.ElseList != nil { 178 - omh.handleTemplateFileNodes(fset, branchNode.ElseList.Nodes) 220 + handler.handleTemplateFileNodes(fset, branchNode.ElseList.Nodes) 179 221 } 180 222 } 181 223 182 - func (omh OnMsgidHandler) handleTemplateFileNodes(fset *token.FileSet, nodes []tmplParser.Node) { 224 + func (handler Handler) handleTemplateFileNodes(fset *token.FileSet, nodes []tmplParser.Node) { 183 225 for _, node := range nodes { 184 - omh.handleTemplateNode(fset, node) 226 + handler.handleTemplateNode(fset, node) 185 227 } 186 228 } 187 229 188 - func (omh OnMsgidHandler) HandleTemplateFile(fname string, src any) error { 230 + func (handler Handler) HandleTemplateFile(fname string, src any) error { 189 231 var tmplContent []byte 190 232 switch src2 := src.(type) { 191 233 case nil: ··· 222 264 Err: err, 223 265 } 224 266 } 225 - omh.handleTemplateFileNodes(fset, tmplParsed.Tree.Root.Nodes) 267 + handler.handleTemplateFileNodes(fset, tmplParsed.Tree.Root.Nodes) 226 268 return nil 227 269 } 228 270 ··· 289 331 290 332 gotAnyMsgidError := false 291 333 292 - omh := OnMsgidHandler(func(fset *token.FileSet, pos token.Pos, msgid string) { 293 - if !msgids.Contains(msgid) { 334 + handler := Handler{ 335 + OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) { 336 + if !msgids.Contains(msgid) { 337 + gotAnyMsgidError = true 338 + fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid) 339 + } 340 + }, 341 + OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) { 294 342 gotAnyMsgidError = true 295 - fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid) 296 - } 297 - }) 343 + fmt.Printf("%s:\tunexpected invocation of %s with %d arguments\n", fset.Position(pos).String(), funcname, argc) 344 + }, 345 + LocaleTrFunctions: InitLocaleTrFunctions(), 346 + } 298 347 299 348 if err := filepath.WalkDir(".", func(fpath string, d fs.DirEntry, err error) error { 300 349 if err != nil { ··· 308 357 if name == "docker" || name == ".git" || name == "node_modules" { 309 358 return fs.SkipDir 310 359 } 311 - } else if name == "bindata.go" { 360 + } else if name == "bindata.go" || fpath == "modules/translation/i18n/i18n_test.go" { 312 361 // skip false positives 313 362 } else if strings.HasSuffix(name, ".go") { 314 - onError(omh.HandleGoFile(fpath, nil)) 363 + onError(handler.HandleGoFile(fpath, nil)) 315 364 } else if strings.HasSuffix(name, ".tmpl") { 316 365 if strings.HasPrefix(fpath, "tests") && strings.HasSuffix(name, ".ini.tmpl") { 317 366 // skip false positives 318 367 } else { 319 - onError(omh.HandleTemplateFile(fpath, nil)) 368 + onError(handler.HandleTemplateFile(fpath, nil)) 320 369 } 321 370 } 322 371 return nil
+14 -8
build/lint-locale-usage/lint-locale-usage_test.go
··· 11 11 "github.com/stretchr/testify/require" 12 12 ) 13 13 14 + func buildHandler(ret *[]string) Handler { 15 + return Handler{ 16 + OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) { 17 + *ret = append(*ret, msgid) 18 + }, 19 + OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {}, 20 + LocaleTrFunctions: InitLocaleTrFunctions(), 21 + } 22 + } 23 + 14 24 func HandleGoFileWrapped(t *testing.T, fname, src string) []string { 15 25 var ret []string 16 - omh := OnMsgidHandler(func(fset *token.FileSet, pos token.Pos, msgid string) { 17 - ret = append(ret, msgid) 18 - }) 19 - require.NoError(t, omh.HandleGoFile(fname, src)) 26 + handler := buildHandler(&ret) 27 + require.NoError(t, handler.HandleGoFile(fname, src)) 20 28 return ret 21 29 } 22 30 23 31 func HandleTemplateFileWrapped(t *testing.T, fname, src string) []string { 24 32 var ret []string 25 - omh := OnMsgidHandler(func(fset *token.FileSet, pos token.Pos, msgid string) { 26 - ret = append(ret, msgid) 27 - }) 28 - require.NoError(t, omh.HandleTemplateFile(fname, src)) 33 + handler := buildHandler(&ret) 34 + require.NoError(t, handler.HandleTemplateFile(fname, src)) 29 35 return ret 30 36 } 31 37
+4
modules/translation/translation.go
··· 27 27 var ContextKey any = &contextKey{} 28 28 29 29 // Locale represents an interface to translation 30 + // 31 + // If this gets modified, remember to also adjust 32 + // build/lint-locale-usage/lint-locale-usage.go's InitLocaleTrFunctions(), 33 + // which requires to know in what argument positions `trKey`'s are given. 30 34 type Locale interface { 31 35 Language() string 32 36 TrString(string, ...any) string