From 6e1b5d4d1d783982eff823399dbbf525e00a4ef9 Mon Sep 17 00:00:00 2001 From: Janar Todesk Date: Thu, 9 May 2024 12:12:20 +0300 Subject: [PATCH 1/3] Redesign `util.ObjectToKey()` helper to return simplified identifier values Symbol names must be unique within a package, which allows using a more generalized (and coincidentally less convoluted) keying pattern. --- goextractors/definition_test.go | 9 +++++---- util/ast.go | 6 ++++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/goextractors/definition_test.go b/goextractors/definition_test.go index eef95db..f376ec1 100644 --- a/goextractors/definition_test.go +++ b/goextractors/definition_test.go @@ -30,12 +30,13 @@ func TestDefinitionExtractor(t *testing.T) { assert.Empty(t, issues) defs := extractCtx.Definitions + key := "github.com/vorlif/testdata.M" if assert.Contains(t, defs, key) { assert.Contains(t, defs[key], "Test") } - key = "github.com/vorlif/testdata.func github.com/vorlif/testdata.noop(sing string, plural string, context string, domain string)" + key = "github.com/vorlif/testdata.noop" if assert.Contains(t, defs, key) { assert.Contains(t, defs[key], "sing") assert.Contains(t, defs[key], "plural") @@ -43,19 +44,19 @@ func TestDefinitionExtractor(t *testing.T) { assert.Contains(t, defs[key], "domain") } - key = "github.com/vorlif/testdata.func github.com/vorlif/testdata.multiNamesFunc(a string, b string)" + key = "github.com/vorlif/testdata.multiNamesFunc" if assert.Contains(t, defs, key) { assert.Contains(t, defs[key], "a") assert.Contains(t, defs[key], "b") } - key = "github.com/vorlif/testdata.func github.com/vorlif/testdata.noParamNames(string, string)" + key = "github.com/vorlif/testdata.noParamNames" if assert.Contains(t, defs, key) { assert.Contains(t, defs[key], "0") assert.Contains(t, defs[key], "1") } - key = "github.com/vorlif/testdata.func github.com/vorlif/testdata.variadicFunc(a string, vars ...string)" + key = "github.com/vorlif/testdata.variadicFunc" if assert.Contains(t, defs, key) { if assert.Contains(t, defs[key], "a") { assert.Equal(t, 0, defs[key]["a"].FieldPos) diff --git a/util/ast.go b/util/ast.go index 022e296..70cf7b9 100644 --- a/util/ast.go +++ b/util/ast.go @@ -8,8 +8,10 @@ import ( func ObjToKey(obj types.Object) string { switch v := obj.Type().(type) { case *types.Signature: - return fmt.Sprintf("%s.%s", obj.Pkg().Path(), obj.String()) - case *types.Named: + if recv := v.Recv(); recv != nil { + return fmt.Sprintf("%s.%s", recv, obj.Name()) + } + return fmt.Sprintf("%s.%s", obj.Pkg().Path(), obj.Name()) case *types.Pointer: return v.Elem().String() From fd694ca6ed08307079093a8d60eed13425e60588 Mon Sep 17 00:00:00 2001 From: Janar Todesk Date: Thu, 9 May 2024 12:14:53 +0300 Subject: [PATCH 2/3] Add a test assertion for extracting translations from struct methods The solution supports this, but there is no test coverage. --- goextractors/funccall_test.go | 2 ++ testdata/project/funccall.go | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/goextractors/funccall_test.go b/goextractors/funccall_test.go index 1b92092..ca78ac4 100644 --- a/goextractors/funccall_test.go +++ b/goextractors/funccall_test.go @@ -31,6 +31,8 @@ func TestFuncCallExtractor(t *testing.T) { "inline function", "constCtxMsg", "constCtxVal", + + "struct-method-call", } got := collectIssueStrings(issues) assert.ElementsMatch(t, want, got) diff --git a/testdata/project/funccall.go b/testdata/project/funccall.go index 40fcb06..ea5367a 100644 --- a/testdata/project/funccall.go +++ b/testdata/project/funccall.go @@ -20,6 +20,10 @@ func GenericFunc[V int64 | float64](log alias.Singular, i V) V { return i } +type methodStruct struct{} + +func (methodStruct) Method(alias.Singular) {} + func outerFuncDef() { f := func(msgid alias.Singular, plural alias.Plural, context alias.Context, domain alias.Domain) {} @@ -77,3 +81,7 @@ func builtInFunctions() { t.DPGetf("domain-dp", "context-dp", "singular-dp") t.DNPGetf("domain-dnp", "context-dnp", "msgid-dnp", "pluralid-dnp", 10) } + +func methodCall() { + (methodStruct{}).Method("struct-method-call") +} From 617279a9336d5115bb7177765825e454d0629373 Mon Sep 17 00:00:00 2001 From: Janar Todesk Date: Thu, 9 May 2024 12:16:15 +0300 Subject: [PATCH 3/3] Add generic struct and generic struct method support --- goextractors/definition_test.go | 10 ++++++++++ goextractors/funccall_test.go | 2 +- goextractors/structdef.go | 21 ++++++++++++--------- goextractors/structdef_test.go | 2 ++ testdata/project/funccall.go | 5 +++++ testdata/project/struct.go | 13 +++++++++++++ util/ast.go | 8 ++++++++ 7 files changed, 51 insertions(+), 10 deletions(-) diff --git a/goextractors/definition_test.go b/goextractors/definition_test.go index f376ec1..86eeda7 100644 --- a/goextractors/definition_test.go +++ b/goextractors/definition_test.go @@ -36,6 +36,16 @@ func TestDefinitionExtractor(t *testing.T) { assert.Contains(t, defs[key], "Test") } + key = "github.com/vorlif/testdata.methodStruct.Method" + if assert.Contains(t, defs, key) { + assert.Contains(t, defs[key], "0") + } + + key = "github.com/vorlif/testdata.genericMethodStruct.Method" + if assert.Contains(t, defs, key) { + assert.Contains(t, defs[key], "0") + } + key = "github.com/vorlif/testdata.noop" if assert.Contains(t, defs, key) { assert.Contains(t, defs[key], "sing") diff --git a/goextractors/funccall_test.go b/goextractors/funccall_test.go index ca78ac4..63a98a1 100644 --- a/goextractors/funccall_test.go +++ b/goextractors/funccall_test.go @@ -32,7 +32,7 @@ func TestFuncCallExtractor(t *testing.T) { "constCtxMsg", "constCtxVal", - "struct-method-call", + "struct-method-call", "generic-struct-method-call", } got := collectIssueStrings(issues) assert.ElementsMatch(t, want, got) diff --git a/goextractors/structdef.go b/goextractors/structdef.go index 4f643bf..0038c38 100644 --- a/goextractors/structdef.go +++ b/goextractors/structdef.go @@ -35,23 +35,26 @@ func (v structDefExtractor) Run(_ context.Context, extractCtx *extractors.Contex return } - var obj types.Object - var pkg *packages.Package + var ident *ast.Ident switch val := node.Type.(type) { case *ast.SelectorExpr: - pkg, obj = extractCtx.GetType(val.Sel) - if pkg == nil { - return - } + ident = val.Sel case *ast.Ident: - pkg, obj = extractCtx.GetType(val) - if pkg == nil { - return + ident = val + case *ast.IndexExpr: + switch x := val.X.(type) { + case *ast.Ident: + ident = x } default: return } + pkg, obj := extractCtx.GetType(ident) + if pkg == nil { + return + } + if structAttr := extractCtx.Definitions.GetFields(util.ObjToKey(obj)); structAttr == nil { return } diff --git a/goextractors/structdef_test.go b/goextractors/structdef_test.go index 5d8b420..7849162 100644 --- a/goextractors/structdef_test.go +++ b/goextractors/structdef_test.go @@ -18,6 +18,8 @@ func TestStructDefExtractor(t *testing.T) { "struct msgid arr2", "struct plural arr2", "A3", "B3", "C3", "A4", "B4", "C4", + "GA3", "GB3", "GC3", + "GA4", "GB4", "GC4", } assert.ElementsMatch(t, want, got) } diff --git a/testdata/project/funccall.go b/testdata/project/funccall.go index ea5367a..6ddb9f2 100644 --- a/testdata/project/funccall.go +++ b/testdata/project/funccall.go @@ -24,6 +24,10 @@ type methodStruct struct{} func (methodStruct) Method(alias.Singular) {} +type genericMethodStruct[T any] struct{} + +func (genericMethodStruct[T]) Method(alias.Singular) {} + func outerFuncDef() { f := func(msgid alias.Singular, plural alias.Plural, context alias.Context, domain alias.Domain) {} @@ -84,4 +88,5 @@ func builtInFunctions() { func methodCall() { (methodStruct{}).Method("struct-method-call") + (genericMethodStruct[string]{}).Method("generic-struct-method-call") } diff --git a/testdata/project/struct.go b/testdata/project/struct.go index a70575e..7054e58 100644 --- a/testdata/project/struct.go +++ b/testdata/project/struct.go @@ -15,6 +15,10 @@ type OneLineStruct struct { A, B, C localize.Singular } +type OneLineGenericStruct[T any] struct { + A, B, C localize.Singular +} + func structLocalTest() []*sub.Sub { // TRANSLATORS: Struct init @@ -26,6 +30,15 @@ func structLocalTest() []*sub.Sub { _ = OneLineStruct{"A4", "B4", "C4"} + // TRANSLATORS: Generic struct init + _ = OneLineGenericStruct[string]{ + A: "GA3", + B: "GB3", + C: "GC3", + } + + _ = OneLineGenericStruct[string]{"GA4", "GB4", "GC4"} + item := &sub.Sub{ Text: "local struct msgid", Plural: "local struct plural", diff --git a/util/ast.go b/util/ast.go index 70cf7b9..36afe60 100644 --- a/util/ast.go +++ b/util/ast.go @@ -3,12 +3,20 @@ package util import ( "fmt" "go/types" + "strings" ) func ObjToKey(obj types.Object) string { switch v := obj.Type().(type) { case *types.Signature: if recv := v.Recv(); recv != nil { + // Strip out the generic type declaration from the type name. + // The ast.CallExpr reports its receiver as the actual type + // (e.g.`Generic[string]`), whereas the ast.FuncDecl on the + // same type as `Generic[T]`. The returned key values need + // to be consistent between different invocation patterns. + recv, _, _ := strings.Cut(recv.Type().String(), "[") + return fmt.Sprintf("%s.%s", recv, obj.Name()) }