1// Copyright 2013 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 godoc
6
7import (
8	"bytes"
9	"go/parser"
10	"go/token"
11	"strings"
12	"testing"
13)
14
15func TestPkgLinkFunc(t *testing.T) {
16	for _, tc := range []struct {
17		path string
18		want string
19	}{
20		{"/src/fmt", "pkg/fmt"},
21		{"src/fmt", "pkg/fmt"},
22		{"/fmt", "pkg/fmt"},
23		{"fmt", "pkg/fmt"},
24	} {
25		if got := pkgLinkFunc(tc.path); got != tc.want {
26			t.Errorf("pkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
27		}
28	}
29}
30
31func TestSrcPosLinkFunc(t *testing.T) {
32	for _, tc := range []struct {
33		src  string
34		line int
35		low  int
36		high int
37		want string
38	}{
39		{"/src/fmt/print.go", 42, 30, 50, "/src/fmt/print.go?s=30:50#L32"},
40		{"/src/fmt/print.go", 2, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
41		{"/src/fmt/print.go", 2, 0, 0, "/src/fmt/print.go#L2"},
42		{"/src/fmt/print.go", 0, 0, 0, "/src/fmt/print.go"},
43		{"/src/fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
44		{"fmt/print.go", 0, 0, 0, "/src/fmt/print.go"},
45		{"fmt/print.go", 0, 1, 5, "/src/fmt/print.go?s=1:5#L1"},
46	} {
47		if got := srcPosLinkFunc(tc.src, tc.line, tc.low, tc.high); got != tc.want {
48			t.Errorf("srcLinkFunc(%v, %v, %v, %v) = %v; want %v", tc.src, tc.line, tc.low, tc.high, got, tc.want)
49		}
50	}
51}
52
53func TestSrcLinkFunc(t *testing.T) {
54	for _, tc := range []struct {
55		src  string
56		want string
57	}{
58		{"/src/fmt/print.go", "/src/fmt/print.go"},
59		{"src/fmt/print.go", "/src/fmt/print.go"},
60		{"/fmt/print.go", "/src/fmt/print.go"},
61		{"fmt/print.go", "/src/fmt/print.go"},
62	} {
63		if got := srcLinkFunc(tc.src); got != tc.want {
64			t.Errorf("srcLinkFunc(%v) = %v; want %v", tc.src, got, tc.want)
65		}
66	}
67}
68
69func TestQueryLinkFunc(t *testing.T) {
70	for _, tc := range []struct {
71		src   string
72		query string
73		line  int
74		want  string
75	}{
76		{"/src/fmt/print.go", "Sprintf", 33, "/src/fmt/print.go?h=Sprintf#L33"},
77		{"/src/fmt/print.go", "Sprintf", 0, "/src/fmt/print.go?h=Sprintf"},
78		{"src/fmt/print.go", "EOF", 33, "/src/fmt/print.go?h=EOF#L33"},
79		{"src/fmt/print.go", "a%3f+%26b", 1, "/src/fmt/print.go?h=a%3f+%26b#L1"},
80	} {
81		if got := queryLinkFunc(tc.src, tc.query, tc.line); got != tc.want {
82			t.Errorf("queryLinkFunc(%v, %v, %v) = %v; want %v", tc.src, tc.query, tc.line, got, tc.want)
83		}
84	}
85}
86
87func TestDocLinkFunc(t *testing.T) {
88	for _, tc := range []struct {
89		src   string
90		ident string
91		want  string
92	}{
93		{"fmt", "Sprintf", "/pkg/fmt/#Sprintf"},
94		{"fmt", "EOF", "/pkg/fmt/#EOF"},
95	} {
96		if got := docLinkFunc(tc.src, tc.ident); got != tc.want {
97			t.Errorf("docLinkFunc(%v, %v) = %v; want %v", tc.src, tc.ident, got, tc.want)
98		}
99	}
100}
101
102func TestSanitizeFunc(t *testing.T) {
103	for _, tc := range []struct {
104		src  string
105		want string
106	}{
107		{},
108		{"foo", "foo"},
109		{"func   f()", "func f()"},
110		{"func f(a int,)", "func f(a int)"},
111		{"func f(a int,\n)", "func f(a int)"},
112		{"func f(\n\ta int,\n\tb int,\n\tc int,\n)", "func f(a int, b int, c int)"},
113		{"  (   a,   b,  c  )  ", "(a, b, c)"},
114		{"(  a,  b, c    int, foo   bar  ,  )", "(a, b, c int, foo bar)"},
115		{"{   a,   b}", "{a, b}"},
116		{"[   a,   b]", "[a, b]"},
117	} {
118		if got := sanitizeFunc(tc.src); got != tc.want {
119			t.Errorf("sanitizeFunc(%v) = %v; want %v", tc.src, got, tc.want)
120		}
121	}
122}
123
124// Test that we add <span id="StructName.FieldName"> elements
125// to the HTML of struct fields.
126func TestStructFieldsIDAttributes(t *testing.T) {
127	got := linkifySource(t, []byte(`
128package foo
129
130type T struct {
131	NoDoc string
132
133	// Doc has a comment.
134	Doc string
135
136	// Opt, if non-nil, is an option.
137	Opt *int
138
139	// Опция - другое поле.
140	Опция bool
141}
142`))
143	want := `type T struct {
144<span id="T.NoDoc"></span>NoDoc <a href="/pkg/builtin/#string">string</a>
145
146<span id="T.Doc"></span><span class="comment">// Doc has a comment.</span>
147Doc <a href="/pkg/builtin/#string">string</a>
148
149<span id="T.Opt"></span><span class="comment">// Opt, if non-nil, is an option.</span>
150Opt *<a href="/pkg/builtin/#int">int</a>
151
152<span id="T.Опция"></span><span class="comment">// Опция - другое поле.</span>
153Опция <a href="/pkg/builtin/#bool">bool</a>
154}`
155	if got != want {
156		t.Errorf("got: %s\n\nwant: %s\n", got, want)
157	}
158}
159
160// Test that we add <span id="ConstName"> elements to the HTML
161// of definitions in const and var specs.
162func TestValueSpecIDAttributes(t *testing.T) {
163	got := linkifySource(t, []byte(`
164package foo
165
166const (
167	NoDoc string = "NoDoc"
168
169	// Doc has a comment
170	Doc = "Doc"
171
172	NoVal
173)`))
174	want := `const (
175<span id="NoDoc">NoDoc</span> <a href="/pkg/builtin/#string">string</a> = &#34;NoDoc&#34;
176
177<span class="comment">// Doc has a comment</span>
178<span id="Doc">Doc</span> = &#34;Doc&#34;
179
180<span id="NoVal">NoVal</span>
181)`
182	if got != want {
183		t.Errorf("got: %s\n\nwant: %s\n", got, want)
184	}
185}
186
187func TestCompositeLitLinkFields(t *testing.T) {
188	got := linkifySource(t, []byte(`
189package foo
190
191type T struct {
192	X int
193}
194
195var S T = T{X: 12}`))
196	want := `type T struct {
197<span id="T.X"></span>X <a href="/pkg/builtin/#int">int</a>
198}
199var <span id="S">S</span> <a href="#T">T</a> = <a href="#T">T</a>{<a href="#T.X">X</a>: 12}`
200	if got != want {
201		t.Errorf("got: %s\n\nwant: %s\n", got, want)
202	}
203}
204
205func TestFuncDeclNotLink(t *testing.T) {
206	// Function.
207	got := linkifySource(t, []byte(`
208package http
209
210func Get(url string) (resp *Response, err error)`))
211	want := `func Get(url <a href="/pkg/builtin/#string">string</a>) (resp *<a href="#Response">Response</a>, err <a href="/pkg/builtin/#error">error</a>)`
212	if got != want {
213		t.Errorf("got: %s\n\nwant: %s\n", got, want)
214	}
215
216	// Method.
217	got = linkifySource(t, []byte(`
218package http
219
220func (h Header) Get(key string) string`))
221	want = `func (h <a href="#Header">Header</a>) Get(key <a href="/pkg/builtin/#string">string</a>) <a href="/pkg/builtin/#string">string</a>`
222	if got != want {
223		t.Errorf("got: %s\n\nwant: %s\n", got, want)
224	}
225}
226
227func linkifySource(t *testing.T, src []byte) string {
228	p := &Presentation{
229		DeclLinks: true,
230	}
231	fset := token.NewFileSet()
232	af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments)
233	if err != nil {
234		t.Fatal(err)
235	}
236	var buf bytes.Buffer
237	pi := &PageInfo{
238		FSet: fset,
239	}
240	sep := ""
241	for _, decl := range af.Decls {
242		buf.WriteString(sep)
243		sep = "\n"
244		buf.WriteString(p.node_htmlFunc(pi, decl, true))
245	}
246	return buf.String()
247}
248
249func TestScanIdentifier(t *testing.T) {
250	tests := []struct {
251		in, want string
252	}{
253		{"foo bar", "foo"},
254		{"foo/bar", "foo"},
255		{" foo", ""},
256		{"фоо", "фоо"},
257		{"f123", "f123"},
258		{"123f", ""},
259	}
260	for _, tt := range tests {
261		got := scanIdentifier([]byte(tt.in))
262		if string(got) != tt.want {
263			t.Errorf("scanIdentifier(%q) = %q; want %q", tt.in, got, tt.want)
264		}
265	}
266}
267
268func TestReplaceLeadingIndentation(t *testing.T) {
269	oldIndent := strings.Repeat(" ", 2)
270	newIndent := strings.Repeat(" ", 4)
271	tests := []struct {
272		src, want string
273	}{
274		{"  foo\n    bar\n  baz", "    foo\n      bar\n    baz"},
275		{"  '`'\n  '`'\n", "    '`'\n    '`'\n"},
276		{"  '\\''\n  '`'\n", "    '\\''\n    '`'\n"},
277		{"  \"`\"\n  \"`\"\n", "    \"`\"\n    \"`\"\n"},
278		{"  `foo\n  bar`", "    `foo\n      bar`"},
279		{"  `foo\\`\n  bar", "    `foo\\`\n    bar"},
280		{"  '\\`'`foo\n  bar", "    '\\`'`foo\n      bar"},
281		{
282			"  if true {\n    foo := `One\n    \tTwo\nThree`\n  }\n",
283			"    if true {\n      foo := `One\n        \tTwo\n    Three`\n    }\n",
284		},
285	}
286	for _, tc := range tests {
287		if got := replaceLeadingIndentation(tc.src, oldIndent, newIndent); got != tc.want {
288			t.Errorf("replaceLeadingIndentation:\n%v\n---\nhave:\n%v\n---\nwant:\n%v\n",
289				tc.src, got, tc.want)
290		}
291	}
292}
293
294func TestSrcBreadcrumbFunc(t *testing.T) {
295	for _, tc := range []struct {
296		path string
297		want string
298	}{
299		{"src/", `<span class="text-muted">src/</span>`},
300		{"src/fmt/", `<a href="/src">src</a>/<span class="text-muted">fmt/</span>`},
301		{"src/fmt/print.go", `<a href="/src">src</a>/<a href="/src/fmt">fmt</a>/<span class="text-muted">print.go</span>`},
302	} {
303		if got := srcBreadcrumbFunc(tc.path); got != tc.want {
304			t.Errorf("srcBreadcrumbFunc(%v) = %v; want %v", tc.path, got, tc.want)
305		}
306	}
307}
308
309func TestSrcToPkgLinkFunc(t *testing.T) {
310	for _, tc := range []struct {
311		path string
312		want string
313	}{
314		{"src/", `<a href="/pkg">Index</a>`},
315		{"src/fmt/", `<a href="/pkg/fmt">fmt</a>`},
316		{"pkg/", `<a href="/pkg">Index</a>`},
317		{"pkg/LICENSE", `<a href="/pkg">Index</a>`},
318	} {
319		if got := srcToPkgLinkFunc(tc.path); got != tc.want {
320			t.Errorf("srcToPkgLinkFunc(%v) = %v; want %v", tc.path, got, tc.want)
321		}
322	}
323}
324
325func TestFilterOutBuildAnnotations(t *testing.T) {
326	// TODO: simplify this by using a multiline string once we stop
327	// using go vet from 1.10 on the build dashboard.
328	// https://golang.org/issue/26627
329	src := []byte("// +build !foo\n" +
330		"// +build !anothertag\n" +
331		"\n" +
332		"// non-tag comment\n" +
333		"\n" +
334		"package foo\n" +
335		"\n" +
336		"func bar() int {\n" +
337		"	return 42\n" +
338		"}\n")
339
340	fset := token.NewFileSet()
341	af, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments)
342	if err != nil {
343		t.Fatal(err)
344	}
345
346	var found bool
347	for _, cg := range af.Comments {
348		if strings.HasPrefix(cg.Text(), "+build ") {
349			found = true
350			break
351		}
352	}
353	if !found {
354		t.Errorf("TestFilterOutBuildAnnotations is broken: missing build tag in test input")
355	}
356
357	found = false
358	for _, cg := range filterOutBuildAnnotations(af.Comments) {
359		if strings.HasPrefix(cg.Text(), "+build ") {
360			t.Errorf("filterOutBuildAnnotations failed to filter build tag")
361		}
362
363		if strings.Contains(cg.Text(), "non-tag comment") {
364			found = true
365		}
366	}
367	if !found {
368		t.Errorf("filterOutBuildAnnotations should not remove non-build tag comment")
369	}
370}
371