From 62ace249b38bc5eda4bf1ee2e5f65c475c89da67 Mon Sep 17 00:00:00 2001 From: "Kirill Che." Date: Fri, 29 Mar 2024 11:40:20 +0400 Subject: [PATCH] fix: use field doc for embedded struct field type AST parser now interprets embedded struct type of field as common field without type reference. Fix: #13 --- _examples/embedded.go | 14 ++++++++++++++ _examples/embedded.md | 6 ++++++ _examples/typedef.go | 12 ++++++++++++ _examples/{typeonly.md => typedef.md} | 2 +- _examples/typeonly.go | 18 ------------------ ast.go | 21 ++++++++++++++++++--- inspector_test.go | 21 +++++++++++++++++++++ testdata/embedded.go | 13 +++++++++++++ testdata/typedef.go | 11 +++++++++++ 9 files changed, 96 insertions(+), 22 deletions(-) create mode 100644 _examples/embedded.go create mode 100644 _examples/embedded.md create mode 100644 _examples/typedef.go rename _examples/{typeonly.md => typedef.md} (56%) delete mode 100644 _examples/typeonly.go create mode 100644 testdata/embedded.go create mode 100644 testdata/typedef.go diff --git a/_examples/embedded.go b/_examples/embedded.go new file mode 100644 index 0000000..b00c96e --- /dev/null +++ b/_examples/embedded.go @@ -0,0 +1,14 @@ +package main + +import "time" + +//go:generate go run ../ -output embedded.md +type Config struct { + // Start date. + Start Date `env:"START,notEmpty"` +} + +// Date is a time.Time wrapper that uses the time.DateOnly layout. +type Date struct { + time.Time +} diff --git a/_examples/embedded.md b/_examples/embedded.md new file mode 100644 index 0000000..13a0c08 --- /dev/null +++ b/_examples/embedded.md @@ -0,0 +1,6 @@ +# Environment Variables + +## Config + + - `START` (**required**, non-empty) - Start date. + diff --git a/_examples/typedef.go b/_examples/typedef.go new file mode 100644 index 0000000..19050ab --- /dev/null +++ b/_examples/typedef.go @@ -0,0 +1,12 @@ +package main + +import "time" + +//go:generate go run ../ -output typedef.md +type Config struct { + // Start date. + Start Date `env:"START"` +} + +// Date is a time.Time wrapper that uses the time.DateOnly layout. +type Date time.Time diff --git a/_examples/typeonly.md b/_examples/typedef.md similarity index 56% rename from _examples/typeonly.md rename to _examples/typedef.md index 809de9c..52c41c0 100644 --- a/_examples/typeonly.md +++ b/_examples/typedef.md @@ -2,5 +2,5 @@ ## Config - - `SOME_TIME` - Some time. + - `START` - Start date. diff --git a/_examples/typeonly.go b/_examples/typeonly.go deleted file mode 100644 index e9b1796..0000000 --- a/_examples/typeonly.go +++ /dev/null @@ -1,18 +0,0 @@ -package main - -import "time" - -//go:generate go run ../ -output typeonly.md -type Config - -type Config struct { - // Some time. - SomeTime MyTime `env:"SOME_TIME"` -} - -type MyTime time.Time - -func (t *MyTime) UnmarshalText(text []byte) error { - tt, err := time.Parse("2006-01-02", string(text)) - *t = MyTime(tt) - return err -} diff --git a/ast.go b/ast.go index dca88ab..1234055 100644 --- a/ast.go +++ b/ast.go @@ -27,6 +27,7 @@ type visitorNode struct { names []string // it's possible that a field has multiple names doc string // field or type documentation or comment if doc is empty children []*visitorNode // optional children nodes for structs + parent *visitorNode // parent node typeRef *visitorNode // type reference if field is a struct tag string // field tag isArray bool // true if field is an array @@ -58,6 +59,7 @@ func newAstVisitor(commentsHandler astCommentsHandler, typeDocsResolver astTypeD } func (v *astVisitor) push(node *visitorNode, appendChild bool) *astVisitor { + node.parent = v.currentNode if appendChild { v.currentNode.children = append(v.currentNode.children, node) } @@ -89,7 +91,7 @@ func (v *astVisitor) Visit(n ast.Node) ast.Visitor { } return v case *ast.TypeSpec: - v.logger.Printf("ast(%d): visit type: %q", v.depth, t.Name.Name) + v.logger.Printf("ast(%d): visit type (%T): %q", v.depth, t.Type, t.Name.Name) doc := v.typeDocResolver(t) name := t.Name.Name if v.pendingType { @@ -105,7 +107,20 @@ func (v *astVisitor) Visit(n ast.Node) ast.Visitor { } return v.push(typeNode, true) case *ast.StructType: - v.logger.Printf("ast(%d): found struct", v.depth) + v.logger.Printf("ast(%d): found struct (`%T`, incomplete: %t, fields: %v)", v.depth, t, t.Incomplete, len(t.Fields.List)) + embedStruct := true + for i, f := range t.Fields.List { + if len(f.Names) > 0 { + embedStruct = false + break + } + v.logger.Printf("ast(%d): debug struct field [%d]%v ", v.depth, i, f.Names) + } + if embedStruct { + v.logger.Printf("ast(%d): struct is embedded", v.depth) + return v + } + switch v.currentNode.kind { case nodeType: v.currentNode.kind = nodeStruct @@ -122,7 +137,7 @@ func (v *astVisitor) Visit(n ast.Node) ast.Visitor { } case *ast.Field: names := fieldNamesToStr(t) - v.logger.Printf("ast(%d): visit field (%v)", v.depth, names) + v.logger.Printf("ast(%d): visit field ([%d]%v)", v.depth, len(names), names) doc := getFieldDoc(t) var ( tag string diff --git a/inspector_test.go b/inspector_test.go index 423c8b3..b68a92b 100644 --- a/inspector_test.go +++ b/inspector_test.go @@ -336,6 +336,27 @@ func TestInspector(t *testing.T) { }, }, }, + { + name: "embedded.go", + typeName: "Config", + expect: []*EnvDocItem{ + { + Name: "START", + Doc: "Start date.", + Opts: EnvVarOptions{Required: true, NonEmpty: true}, + }, + }, + }, + { + name: "typedef.go", + typeName: "Config", + expect: []*EnvDocItem{ + { + Name: "START", + Doc: "Start date.", + }, + }, + }, } { scopes := c.expectScopes if scopes == nil { diff --git a/testdata/embedded.go b/testdata/embedded.go new file mode 100644 index 0000000..a676c2c --- /dev/null +++ b/testdata/embedded.go @@ -0,0 +1,13 @@ +package testdata + +import "time" + +type Config struct { + // Start date. + Start Date `env:"START,notEmpty"` +} + +// Date is a time.Time wrapper that uses the time.DateOnly layout. +type Date struct { + time.Time +} diff --git a/testdata/typedef.go b/testdata/typedef.go new file mode 100644 index 0000000..586bb35 --- /dev/null +++ b/testdata/typedef.go @@ -0,0 +1,11 @@ +package testdata + +import "time" + +type Config struct { + // Start date. + Start Date `env:"START"` +} + +// Date is a time.Time wrapper that uses the time.DateOnly layout. +type Date time.Time