Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module github.com/jstemmer/gotags

go 1.25.5
Empty file added go.sum
Empty file.
2 changes: 2 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,8 @@ func getType(node ast.Node, star bool) (paramType string) {
paramType = "interface{}"
case *ast.Ellipsis:
paramType = fmt.Sprintf("...%s", getType(t.Elt, true))
case *ast.IndexExpr: // for generics type receiver
paramType = getType(t.X, star)
}
return
}
Expand Down
116 changes: 67 additions & 49 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ func (t TagSlice) Dump() {
type F map[TagField]string

var testCases = []struct {
label string
filename string
relative bool
basepath string
minversion int
withExtraSymbols bool
tags []Tag
}{
{filename: "testdata/const.go", tags: []Tag{
{label: "const", filename: "testdata/const.go", tags: []Tag{
tag("Test", 1, "p", F{}),
tag("Constant", 3, "c", F{"access": "public", "type": "string"}),
tag("OtherConst", 4, "c", F{"access": "public"}),
Expand All @@ -57,7 +58,7 @@ var testCases = []struct {
tag("C", 8, "c", F{"access": "public"}),
tag("D", 9, "c", F{"access": "public"}),
}},
{filename: "testdata/const.go", withExtraSymbols: true, tags: []Tag{
{label: "const with extra symbols", filename: "testdata/const.go", withExtraSymbols: true, tags: []Tag{
tag("Test", 1, "p", F{}),
tag("Constant", 3, "c", F{"access": "public", "type": "string"}),
tag("OtherConst", 4, "c", F{"access": "public"}),
Expand All @@ -72,7 +73,7 @@ var testCases = []struct {
tag("Test.C", 8, "c", F{"access": "public"}),
tag("Test.D", 9, "c", F{"access": "public"}),
}},
{filename: "testdata/func.go", tags: []Tag{
{label: "func", filename: "testdata/func.go", tags: []Tag{
tag("Test", 1, "p", F{}),
tag("Function1", 3, "f", F{"access": "public", "signature": "()", "type": "string"}),
tag("function2", 6, "f", F{"access": "private", "signature": "(p1, p2 int, p3 *string)"}),
Expand All @@ -81,8 +82,9 @@ var testCases = []struct {
tag("function5", 15, "f", F{"access": "private", "signature": "()", "type": "string, string, error"}),
tag("function6", 18, "f", F{"access": "private", "signature": "(v ...interface{})"}),
tag("function7", 21, "f", F{"access": "private", "signature": "(s ...string)"}),
tag("function8", 24, "f", F{"access": "private", "signature": "(input T)"}),
}},
{filename: "testdata/func.go", withExtraSymbols: true, tags: []Tag{
{label: "func with extra symbols", filename: "testdata/func.go", withExtraSymbols: true, tags: []Tag{
tag("Test", 1, "p", F{}),
tag("Test.Function1", 3, "f", F{"access": "public", "signature": "()", "type": "string"}),
tag("Test.function2", 6, "f", F{"access": "private", "signature": "(p1, p2 int, p3 *string)"}),
Expand All @@ -91,42 +93,44 @@ var testCases = []struct {
tag("Test.function5", 15, "f", F{"access": "private", "signature": "()", "type": "string, string, error"}),
tag("Test.function6", 18, "f", F{"access": "private", "signature": "(v ...interface{})"}),
tag("Test.function7", 21, "f", F{"access": "private", "signature": "(s ...string)"}),
tag("Test.function8", 24, "f", F{"access": "private", "signature": "(input T)"}),
tag("Function1", 3, "f", F{"access": "public", "signature": "()", "type": "string"}),
tag("function2", 6, "f", F{"access": "private", "signature": "(p1, p2 int, p3 *string)"}),
tag("function3", 9, "f", F{"access": "private", "signature": "()", "type": "bool"}),
tag("function4", 12, "f", F{"access": "private", "signature": "(p interface{})", "type": "interface{}"}),
tag("function5", 15, "f", F{"access": "private", "signature": "()", "type": "string, string, error"}),
tag("function6", 18, "f", F{"access": "private", "signature": "(v ...interface{})"}),
tag("function7", 21, "f", F{"access": "private", "signature": "(s ...string)"}),
tag("function8", 24, "f", F{"access": "private", "signature": "(input T)"}),
}},
{filename: "testdata/import.go", tags: []Tag{
{label: "import", filename: "testdata/import.go", tags: []Tag{
tag("Test", 1, "p", F{}),
tag("fmt", 3, "i", F{}),
tag("go/ast", 6, "i", F{}),
tag("go/parser", 7, "i", F{}),
}},
{filename: "testdata/import.go", withExtraSymbols: true, tags: []Tag{
{label: "import with extra symbols", filename: "testdata/import.go", withExtraSymbols: true, tags: []Tag{
tag("Test", 1, "p", F{}),
tag("fmt", 3, "i", F{}),
tag("go/ast", 6, "i", F{}),
tag("go/parser", 7, "i", F{}),
}},
{filename: "testdata/interface.go", tags: []Tag{
{label: "interface", filename: "testdata/interface.go", tags: []Tag{
tag("Test", 1, "p", F{}),
tag("InterfaceMethod", 4, "m", F{"access": "public", "signature": "(int)", "ntype": "Interface", "type": "string"}),
tag("OtherMethod", 5, "m", F{"access": "public", "signature": "()", "ntype": "Interface"}),
tag("io.Reader", 6, "e", F{"access": "public", "ntype": "Interface"}),
tag("Interface", 3, "n", F{"access": "public", "type": "interface"}),
}},
{filename: "testdata/interface.go", withExtraSymbols: true, tags: []Tag{
{label: "interface with extra symbols", filename: "testdata/interface.go", withExtraSymbols: true, tags: []Tag{
tag("Test", 1, "p", F{}),
tag("InterfaceMethod", 4, "m", F{"access": "public", "signature": "(int)", "ntype": "Interface", "type": "string"}),
tag("OtherMethod", 5, "m", F{"access": "public", "signature": "()", "ntype": "Interface"}),
tag("io.Reader", 6, "e", F{"access": "public", "ntype": "Interface"}),
tag("Interface", 3, "n", F{"access": "public", "type": "interface"}),
tag("Test.Interface", 3, "n", F{"access": "public", "type": "interface"}),
}},
{filename: "testdata/struct.go", tags: []Tag{
{label: "struct", filename: "testdata/struct.go", tags: []Tag{
tag("Test", 1, "p", F{}),
tag("Field1", 4, "w", F{"access": "public", "ctype": "Struct", "type": "int"}),
tag("Field2", 4, "w", F{"access": "public", "ctype": "Struct", "type": "int"}),
Expand All @@ -146,8 +150,11 @@ var testCases = []struct {
tag("Dial", 33, "f", F{"access": "public", "ctype": "Connection", "signature": "()", "type": "*Connection, error"}),
tag("Dial2", 39, "f", F{"access": "public", "ctype": "Connection", "signature": "()", "type": "*Connection, *Struct2"}),
tag("Dial3", 42, "f", F{"access": "public", "signature": "()", "type": "*Connection, *Connection"}),
tag("WithGenerics", 45, "t", F{"access": "public", "type": "struct"}),
tag("Field1", 46, "w", F{"access": "public", "ctype": "WithGenerics", "type": "T"}),
tag("Do", 49, "m", F{"access": "public", "ctype": "WithGenerics", "signature": "(input T)"}),
}},
{filename: "testdata/struct.go", withExtraSymbols: true, tags: []Tag{
{label: "struct with extra symbols", filename: "testdata/struct.go", withExtraSymbols: true, tags: []Tag{
tag("Test", 1, "p", F{}),
tag("Field1", 4, "w", F{"access": "public", "ctype": "Struct", "type": "int"}),
tag("Field2", 4, "w", F{"access": "public", "ctype": "Struct", "type": "int"}),
Expand Down Expand Up @@ -183,8 +190,15 @@ var testCases = []struct {
tag("Test.Dial2", 39, "f", F{"access": "public", "ctype": "Connection", "signature": "()", "type": "*Connection, *Struct2"}),
tag("Dial3", 42, "f", F{"access": "public", "signature": "()", "type": "*Connection, *Connection"}),
tag("Test.Dial3", 42, "f", F{"access": "public", "signature": "()", "type": "*Connection, *Connection"}),
tag("WithGenerics", 45, "t", F{"access": "public", "type": "struct"}),
tag("Test.WithGenerics", 45, "t", F{"access": "public", "type": "struct"}),
tag("Field1", 46, "w", F{"access": "public", "ctype": "WithGenerics", "type": "T"}),
tag("Do", 49, "m", F{"access": "public", "ctype": "WithGenerics", "signature": "(input T)"}),
tag("WithGenerics.Do", 49, "m", F{"access": "public", "ctype": "WithGenerics", "signature": "(input T)"}),
tag("Test.WithGenerics.Do", 49, "m", F{"access": "public", "ctype": "WithGenerics", "signature": "(input T)"}),
tag("Test.Do", 49, "m", F{"access": "public", "ctype": "WithGenerics", "signature": "(input T)"}),
}},
{filename: "testdata/type.go", tags: []Tag{
{label: "type", filename: "testdata/type.go", tags: []Tag{
tag("Test", 1, "p", F{}),
tag("testType", 3, "t", F{"access": "private", "type": "int"}),
tag("testArrayType", 4, "t", F{"access": "private", "type": "[4]int"}),
Expand All @@ -195,7 +209,7 @@ var testCases = []struct {
tag("testMapType", 9, "t", F{"access": "private", "type": "map[string]bool"}),
tag("testChanType", 10, "t", F{"access": "private", "type": "chan bool"}),
}},
{filename: "testdata/type.go", withExtraSymbols: true, tags: []Tag{
{label: "type with extra symbols", filename: "testdata/type.go", withExtraSymbols: true, tags: []Tag{
tag("Test", 1, "p", F{}),
tag("testType", 3, "t", F{"access": "private", "type": "int"}),
tag("testArrayType", 4, "t", F{"access": "private", "type": "[4]int"}),
Expand All @@ -214,7 +228,7 @@ var testCases = []struct {
tag("Test.testMapType", 9, "t", F{"access": "private", "type": "map[string]bool"}),
tag("Test.testChanType", 10, "t", F{"access": "private", "type": "chan bool"}),
}},
{filename: "testdata/var.go", tags: []Tag{
{label: "var", filename: "testdata/var.go", tags: []Tag{
tag("Test", 1, "p", F{}),
tag("variable1", 3, "v", F{"access": "private", "type": "int"}),
tag("variable2", 4, "v", F{"access": "private", "type": "string"}),
Expand All @@ -223,7 +237,7 @@ var testCases = []struct {
tag("C", 8, "v", F{"access": "public"}),
tag("D", 9, "v", F{"access": "public"}),
}},
{filename: "testdata/var.go", withExtraSymbols: true, tags: []Tag{
{label: "var with extra symbols", filename: "testdata/var.go", withExtraSymbols: true, tags: []Tag{
tag("Test", 1, "p", F{}),
tag("variable1", 3, "v", F{"access": "private", "type": "int"}),
tag("variable2", 4, "v", F{"access": "private", "type": "string"}),
Expand All @@ -238,18 +252,18 @@ var testCases = []struct {
tag("Test.C", 8, "v", F{"access": "public"}),
tag("Test.D", 9, "v", F{"access": "public"}),
}},
{filename: "testdata/simple.go", relative: true, basepath: "dir", tags: []Tag{
{label: "simple", filename: "testdata/simple.go", relative: true, basepath: "dir", tags: []Tag{
{Name: "main", File: "../testdata/simple.go", Address: "1", Type: "p", Fields: F{"line": "1"}},
}},
{filename: "testdata/simple.go", withExtraSymbols: true, relative: true, basepath: "dir", tags: []Tag{
{label: "simple with extra symbols", filename: "testdata/simple.go", withExtraSymbols: true, relative: true, basepath: "dir", tags: []Tag{
{Name: "main", File: "../testdata/simple.go", Address: "1", Type: "p", Fields: F{"line": "1"}},
}},
{filename: "testdata/range.go", minversion: 4, tags: []Tag{
{label: "range", filename: "testdata/range.go", minversion: 4, tags: []Tag{
tag("main", 1, "p", F{}),
tag("fmt", 3, "i", F{}),
tag("main", 5, "f", F{"access": "private", "signature": "()"}),
}},
{filename: "testdata/range.go", withExtraSymbols: true, minversion: 4, tags: []Tag{
{label: "range with extra symbols", filename: "testdata/range.go", withExtraSymbols: true, minversion: 4, tags: []Tag{
tag("main", 1, "p", F{}),
tag("fmt", 3, "i", F{}),
tag("main", 5, "f", F{"access": "private", "signature": "()"}),
Expand All @@ -259,44 +273,48 @@ var testCases = []struct {

func TestParse(t *testing.T) {
for _, testCase := range testCases {
if testCase.minversion > 0 && extractVersionCode(runtime.Version()) < testCase.minversion {
t.Skipf("[%s] skipping test. Version is %s, but test requires at least go1.%d", testCase.filename, runtime.Version(), testCase.minversion)
continue
}

basepath, err := filepath.Abs(testCase.basepath)
if err != nil {
t.Errorf("[%s] could not determine base path: %s\n", testCase.filename, err)
continue
}
t.Run(testCase.label, func(t *testing.T) {
if testCase.minversion > 0 && extractVersionCode(runtime.Version()) < testCase.minversion {
t.Skipf("[%s] skipping test. Version is %s, but test requires at least go1.%d", testCase.filename, runtime.Version(), testCase.minversion)
}

var extra FieldSet
if testCase.withExtraSymbols {
extra = FieldSet{ExtraTags: true}
}
basepath, err := filepath.Abs(testCase.basepath)
if err != nil {
t.Fatalf("[%s] could not determine base path: %s\n", testCase.filename, err)
}

tags, err := Parse(testCase.filename, testCase.relative, basepath, extra)
if err != nil {
t.Errorf("[%s] Parse error: %s", testCase.filename, err)
continue
}
var extra FieldSet
if testCase.withExtraSymbols {
extra = FieldSet{ExtraTags: true}
}

sort.Sort(TagSlice(tags))
sort.Sort(TagSlice(testCase.tags))
tags, err := Parse(testCase.filename, testCase.relative, basepath, extra)
if err != nil {
t.Fatalf("[%s] Parse error: %s", testCase.filename, err)
}

if len(tags) != len(testCase.tags) {
t.Errorf("[%s] len(tags) == %d, want %d", testCase.filename, len(tags), len(testCase.tags))
continue
}
sort.Sort(TagSlice(tags))
sort.Sort(TagSlice(testCase.tags))

for i, tag := range testCase.tags {
if len(tag.File) == 0 {
tag.File = testCase.filename
if len(tags) != len(testCase.tags) {
t.Fatalf(
"[%s] len(tags) == %d, want %d",
testCase.filename,
len(tags),
len(testCase.tags),
)
}
if tags[i].String() != tag.String() {
t.Errorf("[%s] tag(%d)\n is:%s\nwant:%s", testCase.filename, i, tags[i].String(), tag.String())

for i, tag := range testCase.tags {
if len(tag.File) == 0 {
tag.File = testCase.filename
}
if tags[i].String() != tag.String() {
t.Fatalf("[%s] tag(%d)\n is:%s\nwant:%s", testCase.filename, i, tags[i].String(), tag.String())
}

}
}
})
}
}

Expand Down
3 changes: 3 additions & 0 deletions testdata/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ func function6(v ...interface{}) {

func function7(s ...string) {
}

func function8[T any](input T) {
}
7 changes: 7 additions & 0 deletions testdata/struct.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,10 @@ func Dial2() (*Connection, *Struct2) {

func Dial3() (a, b *Connection) {
}

type WithGenerics[T any] struct {
Field1 T
}

func (w *WithGenerics[T]) Do(input T) {
}