| 1 | // Copyright 2018 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 | "go/doc" |
| 9 | "net/http" |
| 10 | "net/http/httptest" |
| 11 | "net/url" |
| 12 | "sort" |
| 13 | "strings" |
| 14 | "testing" |
| 15 | "text/template" |
| 16 | |
| 17 | "golang.org/x/tools/godoc/vfs/mapfs" |
| 18 | "golang.org/x/tools/internal/typeparams" |
| 19 | ) |
| 20 | |
| 21 | // TestIgnoredGoFiles tests the scenario where a folder has no .go or .c files, |
| 22 | // but has an ignored go file. |
| 23 | func TestIgnoredGoFiles(t *testing.T) { |
| 24 | packagePath := "github.com/package" |
| 25 | packageComment := "main is documented in an ignored .go file" |
| 26 | |
| 27 | c := NewCorpus(mapfs.New(map[string]string{ |
| 28 | "src/" + packagePath + "/ignored.go": `// +build ignore |
| 29 | |
| 30 | // ` + packageComment + ` |
| 31 | package main`})) |
| 32 | srv := &handlerServer{ |
| 33 | p: &Presentation{ |
| 34 | Corpus: c, |
| 35 | }, |
| 36 | c: c, |
| 37 | } |
| 38 | pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, NoFiltering, "linux", "amd64") |
| 39 | |
| 40 | if pInfo.PDoc == nil { |
| 41 | t.Error("pInfo.PDoc = nil; want non-nil.") |
| 42 | } else { |
| 43 | if got, want := pInfo.PDoc.Doc, packageComment+"\n"; got != want { |
| 44 | t.Errorf("pInfo.PDoc.Doc = %q; want %q.", got, want) |
| 45 | } |
| 46 | if got, want := pInfo.PDoc.Name, "main"; got != want { |
| 47 | t.Errorf("pInfo.PDoc.Name = %q; want %q.", got, want) |
| 48 | } |
| 49 | if got, want := pInfo.PDoc.ImportPath, packagePath; got != want { |
| 50 | t.Errorf("pInfo.PDoc.ImportPath = %q; want %q.", got, want) |
| 51 | } |
| 52 | } |
| 53 | if pInfo.FSet == nil { |
| 54 | t.Error("pInfo.FSet = nil; want non-nil.") |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | func TestIssue5247(t *testing.T) { |
| 59 | const packagePath = "example.com/p" |
| 60 | c := NewCorpus(mapfs.New(map[string]string{ |
| 61 | "src/" + packagePath + "/p.go": `package p |
| 62 | |
| 63 | //line notgen.go:3 |
| 64 | // F doc //line 1 should appear |
| 65 | // line 2 should appear |
| 66 | func F() |
| 67 | //line foo.go:100`})) // No newline at end to check corner cases. |
| 68 | |
| 69 | srv := &handlerServer{ |
| 70 | p: &Presentation{Corpus: c}, |
| 71 | c: c, |
| 72 | } |
| 73 | pInfo := srv.GetPageInfo("/src/"+packagePath, packagePath, 0, "linux", "amd64") |
| 74 | if got, want := pInfo.PDoc.Funcs[0].Doc, "F doc //line 1 should appear\nline 2 should appear\n"; got != want { |
| 75 | t.Errorf("pInfo.PDoc.Funcs[0].Doc = %q; want %q", got, want) |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | func testServeBody(t *testing.T, p *Presentation, path, body string) { |
| 80 | t.Helper() |
| 81 | r := &http.Request{URL: &url.URL{Path: path}} |
| 82 | rw := httptest.NewRecorder() |
| 83 | p.ServeFile(rw, r) |
| 84 | if rw.Code != 200 || !strings.Contains(rw.Body.String(), body) { |
| 85 | t.Fatalf("GET %s: expected 200 w/ %q: got %d w/ body:\n%s", |
| 86 | path, body, rw.Code, rw.Body) |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | func TestRedirectAndMetadata(t *testing.T) { |
| 91 | c := NewCorpus(mapfs.New(map[string]string{ |
| 92 | "doc/y/index.html": "Hello, y.", |
| 93 | "doc/x/index.html": `<!--{ |
| 94 | "Path": "/doc/x/" |
| 95 | }--> |
| 96 | |
| 97 | Hello, x. |
| 98 | `})) |
| 99 | c.updateMetadata() |
| 100 | p := &Presentation{ |
| 101 | Corpus: c, |
| 102 | GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)), |
| 103 | } |
| 104 | |
| 105 | // Test that redirect is sent back correctly. |
| 106 | // Used to panic. See golang.org/issue/40665. |
| 107 | for _, elem := range []string{"x", "y"} { |
| 108 | dir := "/doc/" + elem + "/" |
| 109 | |
| 110 | r := &http.Request{URL: &url.URL{Path: dir + "index.html"}} |
| 111 | rw := httptest.NewRecorder() |
| 112 | p.ServeFile(rw, r) |
| 113 | loc := rw.Result().Header.Get("Location") |
| 114 | if rw.Code != 301 || loc != dir { |
| 115 | t.Errorf("GET %s: expected 301 -> %q, got %d -> %q", r.URL.Path, dir, rw.Code, loc) |
| 116 | } |
| 117 | |
| 118 | testServeBody(t, p, dir, "Hello, "+elem) |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | func TestMarkdown(t *testing.T) { |
| 123 | p := &Presentation{ |
| 124 | Corpus: NewCorpus(mapfs.New(map[string]string{ |
| 125 | "doc/test.md": "**bold**", |
| 126 | "doc/test2.md": `{{"*template*"}}`, |
| 127 | })), |
| 128 | GodocHTML: template.Must(template.New("").Parse(`{{printf "%s" .Body}}`)), |
| 129 | } |
| 130 | |
| 131 | testServeBody(t, p, "/doc/test.html", "<strong>bold</strong>") |
| 132 | testServeBody(t, p, "/doc/test2.html", "<em>template</em>") |
| 133 | } |
| 134 | |
| 135 | func TestGenerics(t *testing.T) { |
| 136 | if !typeparams.Enabled { |
| 137 | t.Skip("type params are not enabled at this Go version") |
| 138 | } |
| 139 | |
| 140 | c := NewCorpus(mapfs.New(map[string]string{ |
| 141 | "blah/blah.go": `package blah |
| 142 | |
| 143 | var A AStruct[int] |
| 144 | |
| 145 | type AStruct[T any] struct { |
| 146 | A string |
| 147 | X T |
| 148 | } |
| 149 | |
| 150 | func (a *AStruct[T]) Method() T { |
| 151 | return a.X |
| 152 | } |
| 153 | |
| 154 | func (a AStruct[T]) NonPointerMethod() T { |
| 155 | return a.X |
| 156 | } |
| 157 | |
| 158 | func NewAStruct[T any](arg T) *AStruct[T] { |
| 159 | return &AStruct[T]{ X: arg } |
| 160 | } |
| 161 | |
| 162 | type NonGenericStruct struct { |
| 163 | B int |
| 164 | } |
| 165 | |
| 166 | func (b *NonGenericStruct) NonGenericMethod() int { |
| 167 | return b.B |
| 168 | } |
| 169 | |
| 170 | func NewNonGenericStruct(arg int) *NonGenericStruct { |
| 171 | return &NonGenericStruct{arg} |
| 172 | } |
| 173 | |
| 174 | type Pair[K, V any] struct { |
| 175 | K K |
| 176 | V V |
| 177 | } |
| 178 | |
| 179 | func (p Pair[K, V]) Apply(kf func(K) K, vf func(V) V) Pair[K, V] { |
| 180 | return &Pair{ K: kf(p.K), V: vf(p.V) } |
| 181 | } |
| 182 | |
| 183 | func (p *Pair[K, V]) Set(k K, v V) { |
| 184 | p.K = k |
| 185 | p.V = v |
| 186 | } |
| 187 | |
| 188 | func NewPair[K, V any](k K, v V) Pair[K, V] { |
| 189 | return Pair[K, V]{ k, v } |
| 190 | } |
| 191 | `})) |
| 192 | |
| 193 | srv := &handlerServer{ |
| 194 | p: &Presentation{ |
| 195 | Corpus: c, |
| 196 | }, |
| 197 | c: c, |
| 198 | } |
| 199 | pInfo := srv.GetPageInfo("/blah/", "", NoFiltering, "linux", "amd64") |
| 200 | t.Logf("%v\n", pInfo) |
| 201 | |
| 202 | findType := func(name string) *doc.Type { |
| 203 | for _, typ := range pInfo.PDoc.Types { |
| 204 | if typ.Name == name { |
| 205 | return typ |
| 206 | } |
| 207 | } |
| 208 | return nil |
| 209 | } |
| 210 | |
| 211 | assertFuncs := func(typ *doc.Type, typFuncs []*doc.Func, funcs ...string) { |
| 212 | typfuncs := make([]string, len(typFuncs)) |
| 213 | for i := range typFuncs { |
| 214 | typfuncs[i] = typFuncs[i].Name |
| 215 | } |
| 216 | sort.Strings(typfuncs) |
| 217 | sort.Strings(funcs) |
| 218 | if len(typfuncs) != len(funcs) { |
| 219 | t.Errorf("function mismatch for type %q, got: %q, want: %q", typ.Name, typfuncs, funcs) |
| 220 | return |
| 221 | } |
| 222 | for i := range funcs { |
| 223 | if funcs[i] != typfuncs[i] { |
| 224 | t.Errorf("function mismatch for type %q: got: %q, want: %q", typ.Name, typfuncs, funcs) |
| 225 | return |
| 226 | } |
| 227 | } |
| 228 | } |
| 229 | |
| 230 | aStructType := findType("AStruct") |
| 231 | assertFuncs(aStructType, aStructType.Funcs, "NewAStruct") |
| 232 | assertFuncs(aStructType, aStructType.Methods, "Method", "NonPointerMethod") |
| 233 | |
| 234 | nonGenericStructType := findType("NonGenericStruct") |
| 235 | assertFuncs(nonGenericStructType, nonGenericStructType.Funcs, "NewNonGenericStruct") |
| 236 | assertFuncs(nonGenericStructType, nonGenericStructType.Methods, "NonGenericMethod") |
| 237 | |
| 238 | pairType := findType("Pair") |
| 239 | assertFuncs(pairType, pairType.Funcs, "NewPair") |
| 240 | assertFuncs(pairType, pairType.Methods, "Apply", "Set") |
| 241 | |
| 242 | if len(pInfo.PDoc.Funcs) > 0 { |
| 243 | t.Errorf("unexpected functions in package documentation") |
| 244 | } |
| 245 | } |
| 246 |
Members