1// Copyright 2019 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package cache
6
7import (
8	"bytes"
9	"go/ast"
10	"go/format"
11	"go/parser"
12	"go/token"
13	"go/types"
14	"reflect"
15	"sort"
16	"testing"
17
18	"golang.org/x/tools/go/packages"
19)
20
21func TestArrayLength(t *testing.T) {
22	tests := []struct {
23		expr   string
24		length int
25	}{
26		{`[...]int{0,1,2,3,4,5,6,7,8,9}`, 10},
27		{`[...]int{9:0}`, 10},
28		{`[...]int{19-10:0}`, 10},
29		{`[...]int{19-10:0, 17-10:0, 18-10:0}`, 10},
30	}
31
32	for _, tt := range tests {
33		expr, err := parser.ParseExpr(tt.expr)
34		if err != nil {
35			t.Fatal(err)
36		}
37		l, ok := arrayLength(expr.(*ast.CompositeLit))
38		if !ok {
39			t.Errorf("arrayLength did not recognize expression %#v", expr)
40		}
41		if l != tt.length {
42			t.Errorf("arrayLength(%#v) = %v, want %v", expr, l, tt.length)
43		}
44	}
45}
46
47func TestTrim(t *testing.T) {
48	tests := []struct {
49		name string
50		file string
51		kept []string
52	}{
53		{
54			name: "delete_unused",
55			file: `
56type x struct{}
57func y()
58var z int
59`,
60			kept: []string{},
61		},
62		{
63			// From the common type in testing.
64			name: "unexported_embedded",
65			file: `
66type x struct {}
67type Exported struct { x }
68`,
69			kept: []string{"Exported", "x"},
70		},
71		{
72			// From the d type in unicode.
73			name: "exported_field_unexported_type",
74			file: `
75type x struct {}
76type Exported struct {
77	X x
78}
79`,
80			kept: []string{"Exported", "x"},
81		},
82		{
83			// From errNotExist in io/fs.
84			name: "exported_var_function_call",
85			file: `
86func x() int { return 0 }
87var Exported = x()
88`,
89			kept: []string{"Exported", "x"},
90		},
91		{
92			// From DefaultServeMux in net/http.
93			name: "exported_pointer_to_unexported_var",
94			file: `
95var Exported = &x
96var x int
97`,
98			kept: []string{"Exported", "x"},
99		},
100		{
101			// From DefaultWriter in goldmark/renderer/html.
102			name: "exported_pointer_to_composite_lit",
103			file: `
104var Exported = &x{}
105type x struct{}
106`,
107			kept: []string{"Exported", "x"},
108		},
109		{
110			// From SelectDir in reflect.
111			name: "leave_constants",
112			file: `
113type Enum int
114const (
115	_             Enum = iota
116	EnumOne
117)
118`,
119			kept: []string{"Enum", "EnumOne"},
120		},
121		{
122			name: "constant_conversion",
123			file: `
124type x int
125const (
126	foo x = 0
127)
128`,
129			kept: []string{"x", "foo"},
130		},
131		{
132			name: "unexported_return",
133			file: `
134type x int
135func Exported() x {}
136type y int
137type Interface interface {
138	Exported() y
139}
140`,
141			kept: []string{"Exported", "Interface", "x", "y"},
142		},
143		{
144			name: "drop_composite_literals",
145			file: `
146type x int
147type Exported struct {
148	foo x
149}
150var Var = Exported{foo:1}
151`,
152			kept: []string{"Exported", "Var"},
153		},
154		{
155			name: "drop_function_literals",
156			file: `
157type x int
158var Exported = func() { return x(0) }
159`,
160			kept: []string{"Exported"},
161		},
162		{
163			name: "missing_receiver_panic",
164			file: `
165			func() foo() {}
166`,
167			kept: []string{},
168		},
169	}
170
171	for _, tt := range tests {
172		t.Run(tt.name, func(t *testing.T) {
173			fset := token.NewFileSet()
174			file, err := parser.ParseFile(fset, "main.go", "package main\n\n"+tt.file, parser.AllErrors)
175			if err != nil {
176				t.Fatal(err)
177			}
178			filter := &unexportedFilter{uses: map[string]bool{}}
179			filter.Filter([]*ast.File{file})
180			pkg := types.NewPackage("main", "main")
181			checker := types.NewChecker(&types.Config{
182				DisableUnusedImportCheck: true,
183			}, fset, pkg, nil)
184			if err := checker.Files([]*ast.File{file}); err != nil {
185				t.Error(err)
186			}
187			names := pkg.Scope().Names()
188			sort.Strings(names)
189			sort.Strings(tt.kept)
190			if !reflect.DeepEqual(names, tt.kept) {
191				t.Errorf("package contains names %v, wanted %v", names, tt.kept)
192			}
193		})
194	}
195}
196
197func TestPkg(t *testing.T) {
198	t.Skip("for manual debugging")
199	fset := token.NewFileSet()
200	pkgs, err := packages.Load(&packages.Config{
201		Mode: packages.NeedSyntax | packages.NeedFiles,
202		Fset: fset,
203	}, "io")
204	if err != nil {
205		t.Fatal(err)
206	}
207	if len(pkgs[0].Errors) != 0 {
208		t.Fatal(pkgs[0].Errors)
209	}
210	filter := &unexportedFilter{uses: map[string]bool{}}
211	filter.Filter(pkgs[0].Syntax)
212	for _, file := range pkgs[0].Syntax {
213		buf := &bytes.Buffer{}
214		format.Node(buf, fset, file)
215		t.Log(buf.String())
216	}
217}
218