1717package main
1818
1919import (
20+ "bytes"
2021 "fmt"
2122 "go/ast"
2223 "go/doc"
24+ "go/format"
25+ "go/parser"
26+ "go/printer"
27+ "go/token"
2328 "log"
2429 "sort"
2530 "strings"
@@ -58,17 +63,23 @@ type syntax struct {
5863 Content string `yaml:"content,omitempty"`
5964}
6065
66+ type example struct {
67+ Content string `yaml:"content,omitempty"`
68+ Name string `yaml:"name,omitempty"`
69+ }
70+
6171// item represents a DocFX item.
6272type item struct {
63- UID string `yaml:"uid"`
64- Name string `yaml:"name,omitempty"`
65- ID string `yaml:"id,omitempty"`
66- Summary string `yaml:"summary,omitempty"`
67- Parent string `yaml:"parent,omitempty"`
68- Type string `yaml:"type,omitempty"`
69- Langs []string `yaml:"langs,omitempty"`
70- Syntax syntax `yaml:"syntax,omitempty"`
71- Children []child `yaml:"children,omitempty"`
73+ UID string `yaml:"uid"`
74+ Name string `yaml:"name,omitempty"`
75+ ID string `yaml:"id,omitempty"`
76+ Summary string `yaml:"summary,omitempty"`
77+ Parent string `yaml:"parent,omitempty"`
78+ Type string `yaml:"type,omitempty"`
79+ Langs []string `yaml:"langs,omitempty"`
80+ Syntax syntax `yaml:"syntax,omitempty"`
81+ Examples []example `yaml:"codeexamples,omitempty"`
82+ Children []child `yaml:"children,omitempty"`
7283}
7384
7485func (p * page ) addItem (i * item ) {
@@ -109,97 +120,116 @@ func parse(glob string) (map[string]*page, tableOfContents, *packages.Module, er
109120
110121 log .Printf ("Processing %s@%s" , module .Path , module .Version )
111122
123+ // First, collect all of the files grouped by package, including test
124+ // packages.
125+ pkgFiles := map [string ][]string {}
112126 for _ , pkg := range pkgs {
113- if pkg == nil || pkg .Module == nil {
127+ id := pkg .ID
128+ // See https://pkg.go.dev/golang.org/x/tools/go/packages#Config.
129+ // The uncompiled test package shows up as "foo_test [foo.test]".
130+ if strings .HasSuffix (id , ".test" ) ||
131+ strings .Contains (id , "internal" ) ||
132+ (strings .Contains (id , " [" ) && ! strings .Contains (id , "_test [" )) {
114133 continue
115134 }
116- if pkg .Module .Path != module .Path {
117- skippedModules [pkg .Module .Path ] = struct {}{}
118- continue
135+ if strings .Contains (id , "_test" ) {
136+ id = id [0 :strings .Index (id , "_test [" )]
137+ } else {
138+ // The test package doesn't have Module set.
139+ if pkg .Module .Path != module .Path {
140+ skippedModules [pkg .Module .Path ] = struct {}{}
141+ continue
142+ }
119143 }
120- // Don't generate docs for tests or internal.
121- switch {
122- case strings .HasSuffix (pkg .ID , ".test" ),
123- strings .HasSuffix (pkg .ID , ".test]" ),
124- strings .Contains (pkg .ID , "internal" ):
125- continue
144+ for _ , f := range pkg .Syntax {
145+ name := pkg .Fset .File (f .Pos ()).Name ()
146+ if strings .HasSuffix (name , ".go" ) {
147+ pkgFiles [id ] = append (pkgFiles [id ], name )
148+ }
126149 }
150+ }
127151
128- // Collect all .go files.
129- files := []* ast.File {}
130- for _ , f := range pkg .Syntax {
131- tf := pkg .Fset .File (f .Pos ())
132- if strings .HasSuffix (tf .Name (), ".go" ) {
133- files = append (files , f )
152+ // Once the files are grouped by package, process each package
153+ // independently.
154+ for pkgPath , files := range pkgFiles {
155+ parsedFiles := []* ast.File {}
156+ fset := token .NewFileSet ()
157+ for _ , f := range files {
158+ pf , err := parser .ParseFile (fset , f , nil , parser .ParseComments )
159+ if err != nil {
160+ return nil , nil , nil , fmt .Errorf ("ParseFile: %v" , err )
134161 }
162+ parsedFiles = append (parsedFiles , pf )
135163 }
136164
137165 // Parse out GoDoc.
138- docPkg , err := doc .NewFromFiles (pkg . Fset , files , pkg . PkgPath )
166+ docPkg , err := doc .NewFromFiles (fset , parsedFiles , pkgPath )
139167 if err != nil {
140168 return nil , nil , nil , fmt .Errorf ("doc.NewFromFiles: %v" , err )
141169 }
142170
143171 toc = append (toc , & tocItem {
144- UID : pkg . ID ,
145- Name : pkg . PkgPath ,
172+ UID : docPkg . ImportPath ,
173+ Name : docPkg . ImportPath ,
146174 })
147175
148176 pkgItem := & item {
149- UID : pkg .ID ,
150- Name : pkg .PkgPath ,
151- ID : pkg .Name ,
152- Summary : docPkg .Doc ,
153- Langs : onlyGo ,
154- Type : "package" ,
177+ UID : docPkg .ImportPath ,
178+ Name : docPkg .ImportPath ,
179+ ID : docPkg .Name ,
180+ Summary : docPkg .Doc ,
181+ Langs : onlyGo ,
182+ Type : "package" ,
183+ Examples : processExamples (docPkg .Examples , fset ),
155184 }
156185 pkgPage := & page {Items : []* item {pkgItem }}
157- pages [pkg . PkgPath ] = pkgPage
186+ pages [pkgPath ] = pkgPage
158187
159188 for _ , c := range docPkg .Consts {
160189 name := strings .Join (c .Names , ", " )
161190 id := strings .Join (c .Names , "," )
162- uid := pkg . PkgPath + "." + id
191+ uid := docPkg . ImportPath + "." + id
163192 pkgItem .addChild (child (uid ))
164193 pkgPage .addItem (& item {
165194 UID : uid ,
166195 Name : name ,
167196 ID : id ,
168- Parent : pkg . PkgPath ,
197+ Parent : docPkg . ImportPath ,
169198 Type : "const" ,
170199 Summary : c .Doc ,
171200 Langs : onlyGo ,
172- Syntax : syntax {Content : pkgsite .PrintType (pkg . Fset , c .Decl )},
201+ Syntax : syntax {Content : pkgsite .PrintType (fset , c .Decl )},
173202 })
174203 }
175204 for _ , v := range docPkg .Vars {
176205 name := strings .Join (v .Names , ", " )
177206 id := strings .Join (v .Names , "," )
178- uid := pkg . PkgPath + "." + id
207+ uid := docPkg . ImportPath + "." + id
179208 pkgItem .addChild (child (uid ))
180209 pkgPage .addItem (& item {
181210 UID : uid ,
182211 Name : name ,
183212 ID : id ,
184- Parent : pkg . PkgPath ,
213+ Parent : docPkg . ImportPath ,
185214 Type : "variable" ,
186215 Summary : v .Doc ,
187216 Langs : onlyGo ,
188- Syntax : syntax {Content : pkgsite .PrintType (pkg . Fset , v .Decl )},
217+ Syntax : syntax {Content : pkgsite .PrintType (fset , v .Decl )},
189218 })
190219 }
191220 for _ , t := range docPkg .Types {
192- uid := pkg . PkgPath + "." + t .Name
221+ uid := docPkg . ImportPath + "." + t .Name
193222 pkgItem .addChild (child (uid ))
194223 typeItem := & item {
195- UID : uid ,
196- Name : t .Name ,
197- ID : t .Name ,
198- Parent : pkg .PkgPath ,
199- Type : "type" ,
200- Summary : t .Doc ,
201- Langs : onlyGo ,
202- Syntax : syntax {Content : pkgsite .PrintType (pkg .Fset , t .Decl )},
224+ UID : uid ,
225+ Name : t .Name ,
226+ ID : t .Name ,
227+ Parent : docPkg .ImportPath ,
228+ Type : "type" ,
229+ Summary : t .Doc ,
230+ Langs : onlyGo ,
231+ Syntax : syntax {Content : pkgsite .PrintType (fset , t .Decl )},
232+ Examples : processExamples (t .Examples , fset ),
203233 }
204234 // TODO: items are added as page.Children, rather than
205235 // typeItem.Children, as a workaround for the DocFX template.
@@ -208,7 +238,7 @@ func parse(glob string) (map[string]*page, tableOfContents, *packages.Module, er
208238 for _ , c := range t .Consts {
209239 name := strings .Join (c .Names , ", " )
210240 id := strings .Join (c .Names , "," )
211- cUID := pkg . PkgPath + "." + id
241+ cUID := docPkg . ImportPath + "." + id
212242 pkgItem .addChild (child (cUID ))
213243 pkgPage .addItem (& item {
214244 UID : cUID ,
@@ -218,13 +248,13 @@ func parse(glob string) (map[string]*page, tableOfContents, *packages.Module, er
218248 Type : "const" ,
219249 Summary : c .Doc ,
220250 Langs : onlyGo ,
221- Syntax : syntax {Content : pkgsite .PrintType (pkg . Fset , c .Decl )},
251+ Syntax : syntax {Content : pkgsite .PrintType (fset , c .Decl )},
222252 })
223253 }
224254 for _ , v := range t .Vars {
225255 name := strings .Join (v .Names , ", " )
226256 id := strings .Join (v .Names , "," )
227- cUID := pkg . PkgPath + "." + id
257+ cUID := docPkg . ImportPath + "." + id
228258 pkgItem .addChild (child (cUID ))
229259 pkgPage .addItem (& item {
230260 UID : cUID ,
@@ -234,51 +264,54 @@ func parse(glob string) (map[string]*page, tableOfContents, *packages.Module, er
234264 Type : "variable" ,
235265 Summary : v .Doc ,
236266 Langs : onlyGo ,
237- Syntax : syntax {Content : pkgsite .PrintType (pkg . Fset , v .Decl )},
267+ Syntax : syntax {Content : pkgsite .PrintType (fset , v .Decl )},
238268 })
239269 }
240270
241271 for _ , fn := range t .Funcs {
242272 fnUID := uid + "." + fn .Name
243273 pkgItem .addChild (child (fnUID ))
244274 pkgPage .addItem (& item {
245- UID : fnUID ,
246- Name : fmt .Sprintf ("func %s\n " , fn .Name ),
247- ID : fn .Name ,
248- Parent : uid ,
249- Type : "function" ,
250- Summary : fn .Doc ,
251- Langs : onlyGo ,
252- Syntax : syntax {Content : pkgsite .Synopsis (pkg .Fset , fn .Decl )},
275+ UID : fnUID ,
276+ Name : fmt .Sprintf ("func %s\n " , fn .Name ),
277+ ID : fn .Name ,
278+ Parent : uid ,
279+ Type : "function" ,
280+ Summary : fn .Doc ,
281+ Langs : onlyGo ,
282+ Syntax : syntax {Content : pkgsite .Synopsis (fset , fn .Decl )},
283+ Examples : processExamples (fn .Examples , fset ),
253284 })
254285 }
255286 for _ , fn := range t .Methods {
256287 fnUID := uid + "." + fn .Name
257288 pkgItem .addChild (child (fnUID ))
258289 pkgPage .addItem (& item {
259- UID : fnUID ,
260- Name : fmt .Sprintf ("func (%s) %s\n " , fn .Recv , fn .Name ),
261- ID : fn .Name ,
262- Parent : uid ,
263- Type : "function" , // Note: this is actually a method.
264- Summary : fn .Doc ,
265- Langs : onlyGo ,
266- Syntax : syntax {Content : pkgsite .Synopsis (pkg .Fset , fn .Decl )},
290+ UID : fnUID ,
291+ Name : fmt .Sprintf ("func (%s) %s\n " , fn .Recv , fn .Name ),
292+ ID : fn .Name ,
293+ Parent : uid ,
294+ Type : "function" , // Note: this is actually a method.
295+ Summary : fn .Doc ,
296+ Langs : onlyGo ,
297+ Syntax : syntax {Content : pkgsite .Synopsis (fset , fn .Decl )},
298+ Examples : processExamples (fn .Examples , fset ),
267299 })
268300 }
269301 }
270302 for _ , fn := range docPkg .Funcs {
271- uid := pkg . PkgPath + "." + fn .Name
303+ uid := docPkg . ImportPath + "." + fn .Name
272304 pkgItem .addChild (child (uid ))
273305 pkgPage .addItem (& item {
274- UID : uid ,
275- Name : fmt .Sprintf ("func %s\n " , fn .Name ),
276- ID : fn .Name ,
277- Parent : pkg .PkgPath ,
278- Type : "function" ,
279- Summary : fn .Doc ,
280- Langs : onlyGo ,
281- Syntax : syntax {Content : pkgsite .Synopsis (pkg .Fset , fn .Decl )},
306+ UID : uid ,
307+ Name : fmt .Sprintf ("func %s\n " , fn .Name ),
308+ ID : fn .Name ,
309+ Parent : docPkg .ImportPath ,
310+ Type : "function" ,
311+ Summary : fn .Doc ,
312+ Langs : onlyGo ,
313+ Syntax : syntax {Content : pkgsite .Synopsis (fset , fn .Decl )},
314+ Examples : processExamples (fn .Examples , fset ),
282315 })
283316 }
284317 }
@@ -292,3 +325,38 @@ func parse(glob string) (map[string]*page, tableOfContents, *packages.Module, er
292325 }
293326 return pages , toc , module , nil
294327}
328+
329+ // processExamples converts the examples to []example.
330+ //
331+ // Surrounding braces and indentation is removed.
332+ func processExamples (exs []* doc.Example , fset * token.FileSet ) []example {
333+ result := []example {}
334+ for _ , ex := range exs {
335+ buf := & bytes.Buffer {}
336+ var node interface {} = & printer.CommentedNode {
337+ Node : ex .Code ,
338+ Comments : ex .Comments ,
339+ }
340+ if ex .Play != nil {
341+ node = ex .Play
342+ }
343+ if err := format .Node (buf , fset , node ); err != nil {
344+ log .Fatal (err )
345+ }
346+ s := buf .String ()
347+ if strings .HasPrefix (s , "{\n " ) && strings .HasSuffix (s , "\n }" ) {
348+ lines := strings .Split (s , "\n " )
349+ builder := strings.Builder {}
350+ for _ , line := range lines [1 : len (lines )- 1 ] {
351+ builder .WriteString (strings .TrimPrefix (line , "\t " ))
352+ builder .WriteString ("\n " )
353+ }
354+ s = builder .String ()
355+ }
356+ result = append (result , example {
357+ Content : s ,
358+ Name : ex .Suffix ,
359+ })
360+ }
361+ return result
362+ }
0 commit comments