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