diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index a1ae6ef..7dfed9b 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/setup-go@v2 with: - go-version: 1.13.x + go-version: 1.17 - uses: actions/checkout@v3 - name: Build run: go build . diff --git a/formatter.go b/formatter.go index 8e6969c..8cf1933 100644 --- a/formatter.go +++ b/formatter.go @@ -21,7 +21,7 @@ type formatter struct { // breaks and tabs. Object f responds to the "%v" formatting verb when both the // "#" and " " (space) flags are set, for example: // -// fmt.Sprintf("%# v", Formatter(x)) +// fmt.Sprintf("%# v", Formatter(x)) // // If one of these two flags is not set, or any other verb is used, f will // format x according to the usual rules of package fmt. @@ -69,8 +69,21 @@ type printer struct { depth int } +func (p *printer) clone() printer { + visited := make(map[visit]int, len(p.visited)) + for k, v := range p.visited { + visited[k] = v + } + return printer{ + Writer: p.Writer, + tw: p.tw, + visited: visited, + depth: p.depth, + } +} + func (p *printer) indent() *printer { - q := *p + q := p.clone() q.tw = tabwriter.NewWriter(p.Writer, 4, 4, 1, ' ', 0) q.Writer = text.NewIndentWriter(q.tw, []byte{'\t'}) return &q @@ -223,7 +236,7 @@ func (p *printer) printValue(v reflect.Value, showType, quote bool) { case e.Kind() == reflect.Invalid: io.WriteString(p, "nil") case e.IsValid(): - pp := *p + pp := p.clone() pp.depth++ pp.printValue(e, showType, true) default: @@ -270,7 +283,7 @@ func (p *printer) printValue(v reflect.Value, showType, quote bool) { io.WriteString(p, v.Type().String()) io.WriteString(p, ")(nil)") } else { - pp := *p + pp := p.clone() pp.depth++ writeByte(pp, '&') pp.printValue(e, true, true) diff --git a/formatter_test.go b/formatter_test.go index 6e27b27..69e5847 100644 --- a/formatter_test.go +++ b/formatter_test.go @@ -337,3 +337,62 @@ func TestCycle(t *testing.T) { *iv = *i t.Logf("Example long interface cycle:\n%# v", Formatter(i)) } + +type AValue struct { + ID int + Name string +} + +type ComplexValue struct { + AValues []*AValue + Values []interface{} + ByName map[string]interface{} +} + +func TestReuseVisitMap(t *testing.T) { + var a = &AValue{ID: 1, Name: "A"} + var c = ComplexValue{ + AValues: []*AValue{a}, + Values: []interface{}{a}, + ByName: map[string]interface{}{ + "A": a, + }, + } + + var s = Sprint(c) + if strings.Contains(s, "CYCLIC") { + t.Error("there should not cycle in ComplexValue ", s) + } +} + +type Tree struct { + Left *Tree + Value interface{} + Right *Tree +} + +func TestCycleRefer(t *testing.T) { + var tree = &Tree{ + Left: nil, + Value: 1, + Right: &Tree{ + Left: nil, + Value: 2, + Right: nil, + }, + } + var s = Sprint(tree) + if strings.Contains(s, "CYCLIC") { + t.Error("tree should have no cycle in Tree", s) + } + + tree.Right.Value = []interface{}{ + map[string]interface{}{ + "refer": tree, + }, + } + var s2 = Sprint(tree) + if !strings.Contains(s2, "CYCLIC") { + t.Error("tree should have no cycle in Tree", s2) + } +} diff --git a/go.mod b/go.mod index 98e6163..1cdb7d5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/kr/pretty -go 1.12 +go 1.17 require ( github.com/kr/text v0.2.0