LEFT | RIGHT |
1 ÿ// Copyright 2009 The Go Authors. All rights reserved.ÿ | 1 // Copyright 2009 The Go Authors. All rights reserved. |
2 ÿ// Use of this source code is governed by a BSD-styleÿ | 2 // Use of this source code is governed by a BSD-style |
3 ÿ// license that can be found in the LICENSE file.ÿ | 3 // license that can be found in the LICENSE file. |
4 | 4 |
5 ÿ// The doc package extracts source code documentation from a Go AST.ÿ | 5 // The doc package extracts source code documentation from a Go AST. |
6 package doc | 6 package doc |
7 | 7 |
8 import ( | 8 import ( |
9 "go/ast" | 9 "go/ast" |
10 "go/token" | 10 "go/token" |
11 "regexp" | 11 "regexp" |
12 "sort" | 12 "sort" |
13 ) | 13 ) |
14 | 14 |
15 | 15 |
16 ÿ// ----------------------------------------------------------------------------
ÿ | 16 // ---------------------------------------------------------------------------- |
17 | 17 |
18 type typeDoc struct { | 18 type typeDoc struct { |
19 » ÿ// len(decl.Specs) == 1, and the element type is *ast.TypeSpecÿ | 19 » // len(decl.Specs) == 1, and the element type is *ast.TypeSpec |
20 » ÿ// if the type declaration hasn't been seen yet, decl is nilÿ | 20 » // if the type declaration hasn't been seen yet, decl is nil |
21 decl *ast.GenDecl | 21 decl *ast.GenDecl |
22 » ÿ// values, factory functions, and methods associated with the typeÿ | 22 » // values, factory functions, and methods associated with the type |
23 » values []*ast.GenDecl ÿ// consts and varsÿ | 23 » values []*ast.GenDecl // consts and vars |
24 factories map[string]*ast.FuncDecl | 24 factories map[string]*ast.FuncDecl |
25 methods map[string]*ast.FuncDecl | 25 methods map[string]*ast.FuncDecl |
26 } | 26 } |
27 | 27 |
28 | 28 |
29 ÿ// docReader accumulates documentation for a single package.ÿ | 29 // docReader accumulates documentation for a single package. |
30 ÿ// It modifies the AST: Comments (declaration documentation)ÿ | 30 // It modifies the AST: Comments (declaration documentation) |
31 ÿ// that have been collected by the DocReader are set to nilÿ | 31 // that have been collected by the DocReader are set to nil |
32 ÿ// in the respective AST nodes so that they are not printedÿ | 32 // in the respective AST nodes so that they are not printed |
33 ÿ// twice (once when printing the documentation and once whenÿ | 33 // twice (once when printing the documentation and once when |
34 ÿ// printing the corresponding AST node).ÿ | 34 // printing the corresponding AST node). |
35 ÿ//ÿ | 35 // |
36 type docReader struct { | 36 type docReader struct { |
37 » doc *ast.CommentGroup ÿ// package documentation, if anyÿ | 37 » doc *ast.CommentGroup // package documentation, if any |
38 pkgName string | 38 pkgName string |
39 » values []*ast.GenDecl ÿ// consts and varsÿ | 39 » values []*ast.GenDecl // consts and vars |
40 types map[string]*typeDoc | 40 types map[string]*typeDoc |
41 funcs map[string]*ast.FuncDecl | 41 funcs map[string]*ast.FuncDecl |
42 bugs []*ast.CommentGroup | 42 bugs []*ast.CommentGroup |
43 } | 43 } |
44 | 44 |
45 | 45 |
46 func (doc *docReader) init(pkgName string) { | 46 func (doc *docReader) init(pkgName string) { |
47 doc.pkgName = pkgName | 47 doc.pkgName = pkgName |
48 doc.types = make(map[string]*typeDoc) | 48 doc.types = make(map[string]*typeDoc) |
49 doc.funcs = make(map[string]*ast.FuncDecl) | 49 doc.funcs = make(map[string]*ast.FuncDecl) |
50 } | 50 } |
51 | 51 |
52 | 52 |
53 func (doc *docReader) addDoc(comments *ast.CommentGroup) { | 53 func (doc *docReader) addDoc(comments *ast.CommentGroup) { |
54 if doc.doc == nil { | 54 if doc.doc == nil { |
55 » » ÿ// common case: just one package commentÿ | 55 » » // common case: just one package comment |
56 doc.doc = comments | 56 doc.doc = comments |
57 return | 57 return |
58 } | 58 } |
59 | 59 |
60 » ÿ// More than one package comment: Usually there will be onlyÿ | 60 » // More than one package comment: Usually there will be only |
61 » ÿ// one file with a package comment, but it's better to collectÿ | 61 » // one file with a package comment, but it's better to collect |
62 » ÿ// all comments than drop them on the floor.ÿ | 62 » // all comments than drop them on the floor. |
63 » ÿ// (This code isn't particularly clever - no amortized doubling isÿ | 63 » // (This code isn't particularly clever - no amortized doubling is |
64 » ÿ// used - but this situation occurs rarely and is not time-critical.)ÿ | 64 » // used - but this situation occurs rarely and is not time-critical.) |
65 n1 := len(doc.doc.List) | 65 n1 := len(doc.doc.List) |
66 n2 := len(comments.List) | 66 n2 := len(comments.List) |
67 » list := make([]*ast.Comment, n1+1+n2) ÿ// + 1 for separator lineÿ | 67 » list := make([]*ast.Comment, n1+1+n2) // + 1 for separator line |
68 copy(list, doc.doc.List) | 68 copy(list, doc.doc.List) |
69 » list[n1] = &ast.Comment{token.NoPos, "//"} ÿ// separator lineÿ | 69 » list[n1] = &ast.Comment{token.NoPos, "//"} // separator line |
70 copy(list[n1+1:], comments.List) | 70 copy(list[n1+1:], comments.List) |
71 doc.doc = &ast.CommentGroup{list} | 71 doc.doc = &ast.CommentGroup{list} |
72 } | 72 } |
73 | 73 |
74 | 74 |
75 func (doc *docReader) addType(decl *ast.GenDecl) { | 75 func (doc *docReader) addType(decl *ast.GenDecl) { |
76 spec := decl.Specs[0].(*ast.TypeSpec) | 76 spec := decl.Specs[0].(*ast.TypeSpec) |
77 typ := doc.lookupTypeDoc(spec.Name.Name) | 77 typ := doc.lookupTypeDoc(spec.Name.Name) |
78 » ÿ// typ should always be != nil since declared typesÿ | 78 » // typ should always be != nil since declared types |
79 » ÿ// are always named - be conservative and checkÿ | 79 » // are always named - be conservative and check |
80 if typ != nil { | 80 if typ != nil { |
81 » » ÿ// a type should be added at most once, so typ.declÿ | 81 » » // a type should be added at most once, so typ.decl |
82 » » ÿ// should be nil - if it isn't, simply overwrite itÿ | 82 » » // should be nil - if it isn't, simply overwrite it |
83 typ.decl = decl | 83 typ.decl = decl |
84 } | 84 } |
85 } | 85 } |
86 | 86 |
87 | 87 |
88 func (doc *docReader) lookupTypeDoc(name string) *typeDoc { | 88 func (doc *docReader) lookupTypeDoc(name string) *typeDoc { |
89 if name == "" { | 89 if name == "" { |
90 » » return nil ÿ// no type docs for anonymous typesÿ | 90 » » return nil // no type docs for anonymous types |
91 } | 91 } |
92 if tdoc, found := doc.types[name]; found { | 92 if tdoc, found := doc.types[name]; found { |
93 return tdoc | 93 return tdoc |
94 } | 94 } |
95 » ÿ// type wasn't found - add one without declarationÿ | 95 » // type wasn't found - add one without declaration |
96 tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[stri
ng]*ast.FuncDecl)} | 96 tdoc := &typeDoc{nil, nil, make(map[string]*ast.FuncDecl), make(map[stri
ng]*ast.FuncDecl)} |
97 doc.types[name] = tdoc | 97 doc.types[name] = tdoc |
98 return tdoc | 98 return tdoc |
99 } | 99 } |
100 | 100 |
101 | 101 |
102 func baseTypeName(typ ast.Expr) string { | 102 func baseTypeName(typ ast.Expr) string { |
103 switch t := typ.(type) { | 103 switch t := typ.(type) { |
104 case *ast.Ident: | 104 case *ast.Ident: |
105 » » ÿ// if the type is not exported, the effect toÿ | 105 » » // if the type is not exported, the effect to |
106 » » ÿ// a client is as if there were no type nameÿ | 106 » » // a client is as if there were no type name |
107 if t.IsExported() { | 107 if t.IsExported() { |
108 return t.Name | 108 return t.Name |
109 } | 109 } |
110 case *ast.StarExpr: | 110 case *ast.StarExpr: |
111 return baseTypeName(t.X) | 111 return baseTypeName(t.X) |
112 } | 112 } |
113 return "" | 113 return "" |
114 } | 114 } |
115 | 115 |
116 | 116 |
117 func (doc *docReader) addValue(decl *ast.GenDecl) { | 117 func (doc *docReader) addValue(decl *ast.GenDecl) { |
118 » ÿ// determine if decl should be associated with a typeÿ | 118 » // determine if decl should be associated with a type |
119 » ÿ// Heuristic: For each typed entry, determine the type name, if any.ÿ | 119 » // Heuristic: For each typed entry, determine the type name, if any. |
120 » ÿ// If there is exactly one type name that is sufficientlyÿ | 120 » // If there is exactly one type name that is sufficiently |
121 » ÿ// frequent, associate the decl with the respective type.ÿ | 121 » // frequent, associate the decl with the respective type. |
122 domName := "" | 122 domName := "" |
123 domFreq := 0 | 123 domFreq := 0 |
124 prev := "" | 124 prev := "" |
125 for _, s := range decl.Specs { | 125 for _, s := range decl.Specs { |
126 if v, ok := s.(*ast.ValueSpec); ok { | 126 if v, ok := s.(*ast.ValueSpec); ok { |
127 name := "" | 127 name := "" |
128 switch { | 128 switch { |
129 case v.Type != nil: | 129 case v.Type != nil: |
130 » » » » ÿ// a type is present; determine its nameÿ | 130 » » » » // a type is present; determine its name |
131 name = baseTypeName(v.Type) | 131 name = baseTypeName(v.Type) |
132 case decl.Tok == token.CONST: | 132 case decl.Tok == token.CONST: |
133 » » » » ÿ// no type is present but we have a constant de
claration;ÿ | 133 » » » » // no type is present but we have a constant dec
laration; |
134 » » » » ÿ// use the previous type name (w/o more type in
formationÿ | 134 » » » » // use the previous type name (w/o more type inf
ormation |
135 » » » » ÿ// we cannot handle the case of unnamed variabl
es withÿ | 135 » » » » // we cannot handle the case of unnamed variable
s with |
136 » » » » ÿ// initializer expressions except for some triv
ial cases)ÿ | 136 » » » » // initializer expressions except for some trivi
al cases) |
137 name = prev | 137 name = prev |
138 } | 138 } |
139 if name != "" { | 139 if name != "" { |
140 » » » » ÿ// entry has a named typeÿ | 140 » » » » // entry has a named type |
141 if domName != "" && domName != name { | 141 if domName != "" && domName != name { |
142 » » » » » ÿ// more than one type name - do not ass
ociateÿ | 142 » » » » » // more than one type name - do not asso
ciate |
143 » » » » » ÿ// with any typeÿ | 143 » » » » » // with any type |
144 domName = "" | 144 domName = "" |
145 break | 145 break |
146 } | 146 } |
147 domName = name | 147 domName = name |
148 domFreq++ | 148 domFreq++ |
149 } | 149 } |
150 prev = name | 150 prev = name |
151 } | 151 } |
152 } | 152 } |
153 | 153 |
154 » ÿ// determine values listÿ | 154 » // determine values list |
155 const threshold = 0.75 | 155 const threshold = 0.75 |
156 values := &doc.values | 156 values := &doc.values |
157 if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) { | 157 if domName != "" && domFreq >= int(float64(len(decl.Specs))*threshold) { |
158 » » ÿ// typed entries are sufficiently frequentÿ | 158 » » // typed entries are sufficiently frequent |
159 typ := doc.lookupTypeDoc(domName) | 159 typ := doc.lookupTypeDoc(domName) |
160 if typ != nil { | 160 if typ != nil { |
161 » » » values = &typ.values ÿ// associate with that typeÿ | 161 » » » values = &typ.values // associate with that type |
162 } | 162 } |
163 } | 163 } |
164 | 164 |
165 *values = append(*values, decl) | 165 *values = append(*values, decl) |
166 } | 166 } |
167 | 167 |
168 | 168 |
169 ÿ// Helper function to set the table entry for function f. Makes sure thatÿ | 169 // Helper function to set the table entry for function f. Makes sure that |
170 ÿ// at least one f with associated documentation is stored in table, if thereÿ | 170 // at least one f with associated documentation is stored in table, if there |
171 ÿ// are multiple f's with the same name.ÿ | 171 // are multiple f's with the same name. |
172 func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) { | 172 func setFunc(table map[string]*ast.FuncDecl, f *ast.FuncDecl) { |
173 name := f.Name.Name | 173 name := f.Name.Name |
174 if g, exists := table[name]; exists && g.Doc != nil { | 174 if g, exists := table[name]; exists && g.Doc != nil { |
175 » » ÿ// a function with the same name has already been registered;ÿ | 175 » » // a function with the same name has already been registered; |
176 » » ÿ// since it has documentation, assume f is simply anotherÿ | 176 » » // since it has documentation, assume f is simply another |
177 » » ÿ// implementation and ignore itÿ | 177 » » // implementation and ignore it |
178 » » ÿ// TODO(gri) consider collecting all functions, or at leastÿ | 178 » » // TODO(gri) consider collecting all functions, or at least |
179 » » ÿ// all commentsÿ | 179 » » // all comments |
180 return | 180 return |
181 } | 181 } |
182 » ÿ// function doesn't exist or has no documentation; use fÿ | 182 » // function doesn't exist or has no documentation; use f |
183 table[name] = f | 183 table[name] = f |
184 } | 184 } |
185 | 185 |
186 | 186 |
187 func (doc *docReader) addFunc(fun *ast.FuncDecl) { | 187 func (doc *docReader) addFunc(fun *ast.FuncDecl) { |
188 name := fun.Name.Name | 188 name := fun.Name.Name |
189 | 189 |
190 » ÿ// determine if it should be associated with a typeÿ | 190 » // determine if it should be associated with a type |
191 if fun.Recv != nil { | 191 if fun.Recv != nil { |
192 » » ÿ// methodÿ | 192 » » // method |
193 typ := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type)) | 193 typ := doc.lookupTypeDoc(baseTypeName(fun.Recv.List[0].Type)) |
194 if typ != nil { | 194 if typ != nil { |
195 » » » ÿ// exported receiver typeÿ | 195 » » » // exported receiver type |
196 setFunc(typ.methods, fun) | 196 setFunc(typ.methods, fun) |
197 } | 197 } |
198 » » ÿ// otherwise don't show the methodÿ | 198 » » // otherwise don't show the method |
199 » » ÿ// TODO(gri): There may be exported methods of non-exported typ
esÿ | 199 » » // TODO(gri): There may be exported methods of non-exported type
s |
200 » » ÿ// that can be called because of exported values (consts, vars,
orÿ | 200 » » // that can be called because of exported values (consts, vars,
or |
201 » » ÿ// function results) of that type. Could determine if that is t
heÿ | 201 » » // function results) of that type. Could determine if that is th
e |
202 » » ÿ// case and then show those methods in an appropriate section.ÿ | 202 » » // case and then show those methods in an appropriate section. |
203 return | 203 return |
204 } | 204 } |
205 | 205 |
206 » ÿ// perhaps a factory functionÿ | 206 » // perhaps a factory function |
207 » ÿ// determine result type, if anyÿ | 207 » // determine result type, if any |
208 if fun.Type.Results.NumFields() >= 1 { | 208 if fun.Type.Results.NumFields() >= 1 { |
209 res := fun.Type.Results.List[0] | 209 res := fun.Type.Results.List[0] |
210 if len(res.Names) <= 1 { | 210 if len(res.Names) <= 1 { |
211 » » » ÿ// exactly one (named or anonymous) result associatedÿ | 211 » » » // exactly one (named or anonymous) result associated |
212 » » » ÿ// with the first type in result signature (there mayÿ | 212 » » » // with the first type in result signature (there may |
213 » » » ÿ// be more than one result)ÿ | 213 » » » // be more than one result) |
214 tname := baseTypeName(res.Type) | 214 tname := baseTypeName(res.Type) |
215 typ := doc.lookupTypeDoc(tname) | 215 typ := doc.lookupTypeDoc(tname) |
216 if typ != nil { | 216 if typ != nil { |
217 » » » » ÿ// named and exported result typeÿ | 217 » » » » // named and exported result type |
218 | 218 |
219 » » » » ÿ// Work-around for failure of heuristic: In pac
kage osÿ | 219 » » » » // Work-around for failure of heuristic: In pack
age os |
220 » » » » ÿ// too many functions are considered factory fu
nctionsÿ | 220 » » » » // too many functions are considered factory fun
ctions |
221 » » » » ÿ// for the Error type. Eliminate manually for n
ow asÿ | 221 » » » » // for the Error type. Eliminate manually for no
w as |
222 » » » » ÿ// this appears to be the only important case i
n theÿ | 222 » » » » // this appears to be the only important case in
the |
223 » » » » ÿ// current library where the heuristic fails.ÿ | 223 » » » » // current library where the heuristic fails. |
224 if doc.pkgName == "os" && tname == "Error" && | 224 if doc.pkgName == "os" && tname == "Error" && |
225 name != "NewError" && name != "NewSyscal
lError" { | 225 name != "NewError" && name != "NewSyscal
lError" { |
226 » » » » » ÿ// not a factory function for os.Errorÿ | 226 » » » » » // not a factory function for os.Error |
227 » » » » » setFunc(doc.funcs, fun) ÿ// treat as ord
inary functionÿ | 227 » » » » » setFunc(doc.funcs, fun) // treat as ordi
nary function |
228 return | 228 return |
229 } | 229 } |
230 | 230 |
231 setFunc(typ.factories, fun) | 231 setFunc(typ.factories, fun) |
232 return | 232 return |
233 } | 233 } |
234 } | 234 } |
235 } | 235 } |
236 | 236 |
237 » ÿ// ordinary functionÿ | 237 » // ordinary function |
238 setFunc(doc.funcs, fun) | 238 setFunc(doc.funcs, fun) |
239 } | 239 } |
240 | 240 |
241 | 241 |
242 func (doc *docReader) addDecl(decl ast.Decl) { | 242 func (doc *docReader) addDecl(decl ast.Decl) { |
243 switch d := decl.(type) { | 243 switch d := decl.(type) { |
244 case *ast.GenDecl: | 244 case *ast.GenDecl: |
245 if len(d.Specs) > 0 { | 245 if len(d.Specs) > 0 { |
246 switch d.Tok { | 246 switch d.Tok { |
247 case token.CONST, token.VAR: | 247 case token.CONST, token.VAR: |
248 » » » » ÿ// constants and variables are always handled a
s a groupÿ | 248 » » » » // constants and variables are always handled as
a group |
249 doc.addValue(d) | 249 doc.addValue(d) |
250 case token.TYPE: | 250 case token.TYPE: |
251 » » » » ÿ// types are handled individuallyÿ | 251 » » » » // types are handled individually |
252 for _, spec := range d.Specs { | 252 for _, spec := range d.Specs { |
253 » » » » » ÿ// make a (fake) GenDecl node for this
TypeSpecÿ | 253 » » » » » // make a (fake) GenDecl node for this T
ypeSpec |
254 » » » » » ÿ// (we need to do this here - as oppose
d to justÿ | 254 » » » » » // (we need to do this here - as opposed
to just |
255 » » » » » ÿ// for printing - so we don't lose the
GenDeclÿ | 255 » » » » » // for printing - so we don't lose the G
enDecl |
256 » » » » » ÿ// documentation)ÿ | 256 » » » » » // documentation) |
257 » » » » » ÿ//ÿ | 257 » » » » » // |
258 » » » » » ÿ// TODO(gri): Consider just collecting
the TypeSpecÿ | 258 » » » » » // TODO(gri): Consider just collecting t
he TypeSpec |
259 » » » » » ÿ// node (and copy in the GenDecl.doc if
there is noÿ | 259 » » » » » // node (and copy in the GenDecl.doc if
there is no |
260 » » » » » ÿ// doc in the TypeSpec - this is curren
tly done inÿ | 260 » » » » » // doc in the TypeSpec - this is current
ly done in |
261 » » » » » ÿ// makeTypeDocs below). Simpler data st
ructures, butÿ | 261 » » » » » // makeTypeDocs below). Simpler data str
uctures, but |
262 » » » » » ÿ// would lose GenDecl documentation if
the TypeSpecÿ | 262 » » » » » // would lose GenDecl documentation if t
he TypeSpec |
263 » » » » » ÿ// has documentation as well.ÿ | 263 » » » » » // has documentation as well. |
264 doc.addType(&ast.GenDecl{d.Doc, d.Pos(),
token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos}) | 264 doc.addType(&ast.GenDecl{d.Doc, d.Pos(),
token.TYPE, token.NoPos, []ast.Spec{spec}, token.NoPos}) |
265 » » » » » ÿ// A new GenDecl node is created, no ne
ed to nil out d.Doc.ÿ | 265 » » » » » // A new GenDecl node is created, no nee
d to nil out d.Doc. |
266 } | 266 } |
267 } | 267 } |
268 } | 268 } |
269 case *ast.FuncDecl: | 269 case *ast.FuncDecl: |
270 doc.addFunc(d) | 270 doc.addFunc(d) |
271 } | 271 } |
272 } | 272 } |
273 | 273 |
274 | 274 |
275 func copyCommentList(list []*ast.Comment) []*ast.Comment { | 275 func copyCommentList(list []*ast.Comment) []*ast.Comment { |
276 return append([]*ast.Comment(nil), list...) | 276 return append([]*ast.Comment(nil), list...) |
277 } | 277 } |
278 | 278 |
279 var ( | 279 var ( |
280 » bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") ÿ// B
UG(uid):ÿ | 280 » bug_markers = regexp.MustCompile("^/[/*][ \t]*BUG\\(.*\\):[ \t]*") // BU
G(uid): |
281 » bug_content = regexp.MustCompile("[^ \n\r\t]+") ÿ// a
t least one non-whitespace charÿ | 281 » bug_content = regexp.MustCompile("[^ \n\r\t]+") // at
least one non-whitespace char |
282 ) | 282 ) |
283 | 283 |
284 | 284 |
285 ÿ// addFile adds the AST for a source file to the docReader.ÿ | 285 // addFile adds the AST for a source file to the docReader. |
286 ÿ// Adding the same AST multiple times is a no-op.ÿ | 286 // Adding the same AST multiple times is a no-op. |
287 ÿ//ÿ | 287 // |
288 func (doc *docReader) addFile(src *ast.File) { | 288 func (doc *docReader) addFile(src *ast.File) { |
289 » ÿ// add package documentationÿ | 289 » // add package documentation |
290 if src.Doc != nil { | 290 if src.Doc != nil { |
291 doc.addDoc(src.Doc) | 291 doc.addDoc(src.Doc) |
292 » » src.Doc = nil ÿ// doc consumed - remove from ast.File nodeÿ | 292 » » src.Doc = nil // doc consumed - remove from ast.File node |
293 » } | 293 » } |
294 | 294 |
295 » ÿ// add all declarationsÿ | 295 » // add all declarations |
296 for _, decl := range src.Decls { | 296 for _, decl := range src.Decls { |
297 doc.addDecl(decl) | 297 doc.addDecl(decl) |
298 } | 298 } |
299 | 299 |
300 » ÿ// collect BUG(...) commentsÿ | 300 » // collect BUG(...) comments |
301 for _, c := range src.Comments { | 301 for _, c := range src.Comments { |
302 text := c.List[0].Text | 302 text := c.List[0].Text |
303 if m := bug_markers.FindStringIndex(text); m != nil { | 303 if m := bug_markers.FindStringIndex(text); m != nil { |
304 » » » ÿ// found a BUG comment; maybe emptyÿ | 304 » » » // found a BUG comment; maybe empty |
305 if btxt := text[m[1]:]; bug_content.MatchString(btxt) { | 305 if btxt := text[m[1]:]; bug_content.MatchString(btxt) { |
306 » » » » ÿ// non-empty BUG comment; collect comment witho
ut BUG prefixÿ | 306 » » » » // non-empty BUG comment; collect comment withou
t BUG prefix |
307 list := copyCommentList(c.List) | 307 list := copyCommentList(c.List) |
308 list[0].Text = text[m[1]:] | 308 list[0].Text = text[m[1]:] |
309 doc.bugs = append(doc.bugs, &ast.CommentGroup{li
st}) | 309 doc.bugs = append(doc.bugs, &ast.CommentGroup{li
st}) |
310 } | 310 } |
311 } | 311 } |
312 } | 312 } |
313 » src.Comments = nil ÿ// consumed unassociated comments - remove from ast.
File nodeÿ | 313 » src.Comments = nil // consumed unassociated comments - remove from ast.F
ile node |
314 } | 314 } |
315 | 315 |
316 | 316 |
317 func NewFileDoc(file *ast.File) *PackageDoc { | 317 func NewFileDoc(file *ast.File) *PackageDoc { |
318 var r docReader | 318 var r docReader |
319 r.init(file.Name.Name) | 319 r.init(file.Name.Name) |
320 r.addFile(file) | 320 r.addFile(file) |
321 return r.newDoc("", nil) | 321 return r.newDoc("", nil) |
322 } | 322 } |
323 | 323 |
324 | 324 |
325 func NewPackageDoc(pkg *ast.Package, importpath string) *PackageDoc { | 325 func NewPackageDoc(pkg *ast.Package, importpath string) *PackageDoc { |
326 var r docReader | 326 var r docReader |
327 r.init(pkg.Name) | 327 r.init(pkg.Name) |
328 filenames := make([]string, len(pkg.Files)) | 328 filenames := make([]string, len(pkg.Files)) |
329 i := 0 | 329 i := 0 |
330 for filename, f := range pkg.Files { | 330 for filename, f := range pkg.Files { |
331 r.addFile(f) | 331 r.addFile(f) |
332 filenames[i] = filename | 332 filenames[i] = filename |
333 i++ | 333 i++ |
334 } | 334 } |
335 return r.newDoc(importpath, filenames) | 335 return r.newDoc(importpath, filenames) |
336 } | 336 } |
337 | 337 |
338 | 338 |
339 ÿ// ----------------------------------------------------------------------------
ÿ | 339 // ---------------------------------------------------------------------------- |
340 ÿ// Conversion to external representationÿ | 340 // Conversion to external representation |
341 | 341 |
342 ÿ// ValueDoc is the documentation for a group of declaredÿ | 342 // ValueDoc is the documentation for a group of declared |
343 ÿ// values, either vars or consts.ÿ | 343 // values, either vars or consts. |
344 ÿ//ÿ | 344 // |
345 type ValueDoc struct { | 345 type ValueDoc struct { |
346 Doc string | 346 Doc string |
347 Decl *ast.GenDecl | 347 Decl *ast.GenDecl |
348 order int | 348 order int |
349 } | 349 } |
350 | 350 |
351 type sortValueDoc []*ValueDoc | 351 type sortValueDoc []*ValueDoc |
352 | 352 |
353 func (p sortValueDoc) Len() int { return len(p) } | 353 func (p sortValueDoc) Len() int { return len(p) } |
354 func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } | 354 func (p sortValueDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
355 | 355 |
356 | 356 |
357 func declName(d *ast.GenDecl) string { | 357 func declName(d *ast.GenDecl) string { |
358 if len(d.Specs) != 1 { | 358 if len(d.Specs) != 1 { |
359 return "" | 359 return "" |
360 } | 360 } |
361 | 361 |
362 switch v := d.Specs[0].(type) { | 362 switch v := d.Specs[0].(type) { |
363 case *ast.ValueSpec: | 363 case *ast.ValueSpec: |
364 return v.Names[0].Name | 364 return v.Names[0].Name |
365 case *ast.TypeSpec: | 365 case *ast.TypeSpec: |
366 return v.Name.Name | 366 return v.Name.Name |
367 } | 367 } |
368 | 368 |
369 return "" | 369 return "" |
370 } | 370 } |
371 | 371 |
372 | 372 |
373 func (p sortValueDoc) Less(i, j int) bool { | 373 func (p sortValueDoc) Less(i, j int) bool { |
374 » ÿ// sort by nameÿ | 374 » // sort by name |
375 » ÿ// pull blocks (name = "") up to topÿ | 375 » // pull blocks (name = "") up to top |
376 » ÿ// in original orderÿ | 376 » // in original order |
377 if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj { | 377 if ni, nj := declName(p[i].Decl), declName(p[j].Decl); ni != nj { |
378 return ni < nj | 378 return ni < nj |
379 } | 379 } |
380 return p[i].order < p[j].order | 380 return p[i].order < p[j].order |
381 } | 381 } |
382 | 382 |
383 | 383 |
384 func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc { | 384 func makeValueDocs(list []*ast.GenDecl, tok token.Token) []*ValueDoc { |
385 » d := make([]*ValueDoc, len(list)) ÿ// big enough in any caseÿ | 385 » d := make([]*ValueDoc, len(list)) // big enough in any case |
386 n := 0 | 386 n := 0 |
387 for i, decl := range list { | 387 for i, decl := range list { |
388 if decl.Tok == tok { | 388 if decl.Tok == tok { |
389 d[n] = &ValueDoc{CommentText(decl.Doc), decl, i} | 389 d[n] = &ValueDoc{CommentText(decl.Doc), decl, i} |
390 n++ | 390 n++ |
391 » » » decl.Doc = nil ÿ// doc consumed - removed from ASTÿ | 391 » » » decl.Doc = nil // doc consumed - removed from AST |
392 } | 392 } |
393 } | 393 } |
394 d = d[0:n] | 394 d = d[0:n] |
395 sort.Sort(sortValueDoc(d)) | 395 sort.Sort(sortValueDoc(d)) |
396 return d | 396 return d |
397 } | 397 } |
398 | 398 |
399 | 399 |
400 ÿ// FuncDoc is the documentation for a func declaration,ÿ | 400 // FuncDoc is the documentation for a func declaration, |
401 ÿ// either a top-level function or a method function.ÿ | 401 // either a top-level function or a method function. |
402 ÿ//ÿ | 402 // |
403 type FuncDoc struct { | 403 type FuncDoc struct { |
404 Doc string | 404 Doc string |
405 » Recv ast.Expr ÿ// TODO(rsc): Would like string hereÿ | 405 » Recv ast.Expr // TODO(rsc): Would like string here |
406 Name string | 406 Name string |
407 Decl *ast.FuncDecl | 407 Decl *ast.FuncDecl |
408 } | 408 } |
409 | 409 |
410 type sortFuncDoc []*FuncDoc | 410 type sortFuncDoc []*FuncDoc |
411 | 411 |
412 func (p sortFuncDoc) Len() int { return len(p) } | 412 func (p sortFuncDoc) Len() int { return len(p) } |
413 func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } | 413 func (p sortFuncDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
414 func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name } | 414 func (p sortFuncDoc) Less(i, j int) bool { return p[i].Name < p[j].Name } |
415 | 415 |
416 | 416 |
417 func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc { | 417 func makeFuncDocs(m map[string]*ast.FuncDecl) []*FuncDoc { |
418 d := make([]*FuncDoc, len(m)) | 418 d := make([]*FuncDoc, len(m)) |
419 i := 0 | 419 i := 0 |
420 for _, f := range m { | 420 for _, f := range m { |
421 doc := new(FuncDoc) | 421 doc := new(FuncDoc) |
422 doc.Doc = CommentText(f.Doc) | 422 doc.Doc = CommentText(f.Doc) |
423 » » f.Doc = nil ÿ// doc consumed - remove from ast.FuncDecl nodeÿ | 423 » » f.Doc = nil // doc consumed - remove from ast.FuncDecl node |
424 if f.Recv != nil { | 424 if f.Recv != nil { |
425 doc.Recv = f.Recv.List[0].Type | 425 doc.Recv = f.Recv.List[0].Type |
426 } | 426 } |
427 doc.Name = f.Name.Name | 427 doc.Name = f.Name.Name |
428 doc.Decl = f | 428 doc.Decl = f |
429 d[i] = doc | 429 d[i] = doc |
430 i++ | 430 i++ |
431 } | 431 } |
432 sort.Sort(sortFuncDoc(d)) | 432 sort.Sort(sortFuncDoc(d)) |
433 return d | 433 return d |
434 } | 434 } |
435 | 435 |
436 | 436 |
437 ÿ// TypeDoc is the documentation for a declared type.ÿ | 437 // TypeDoc is the documentation for a declared type. |
438 ÿ// Consts and Vars are sorted lists of constants and variables of (mostly) that
type.ÿ | 438 // Consts and Vars are sorted lists of constants and variables of (mostly) that
type. |
439 ÿ// Factories is a sorted list of factory functions that return that type.ÿ | 439 // Factories is a sorted list of factory functions that return that type. |
440 ÿ// Methods is a sorted list of method functions on that type.ÿ | 440 // Methods is a sorted list of method functions on that type. |
441 type TypeDoc struct { | 441 type TypeDoc struct { |
442 Doc string | 442 Doc string |
443 Type *ast.TypeSpec | 443 Type *ast.TypeSpec |
444 Consts []*ValueDoc | 444 Consts []*ValueDoc |
445 Vars []*ValueDoc | 445 Vars []*ValueDoc |
446 Factories []*FuncDoc | 446 Factories []*FuncDoc |
447 Methods []*FuncDoc | 447 Methods []*FuncDoc |
448 Decl *ast.GenDecl | 448 Decl *ast.GenDecl |
449 order int | 449 order int |
450 } | 450 } |
451 | 451 |
452 type sortTypeDoc []*TypeDoc | 452 type sortTypeDoc []*TypeDoc |
453 | 453 |
454 func (p sortTypeDoc) Len() int { return len(p) } | 454 func (p sortTypeDoc) Len() int { return len(p) } |
455 func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } | 455 func (p sortTypeDoc) Swap(i, j int) { p[i], p[j] = p[j], p[i] } |
456 func (p sortTypeDoc) Less(i, j int) bool { | 456 func (p sortTypeDoc) Less(i, j int) bool { |
457 » ÿ// sort by nameÿ | 457 » // sort by name |
458 » ÿ// pull blocks (name = "") up to topÿ | 458 » // pull blocks (name = "") up to top |
459 » ÿ// in original orderÿ | 459 » // in original order |
460 if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj { | 460 if ni, nj := p[i].Type.Name.Name, p[j].Type.Name.Name; ni != nj { |
461 return ni < nj | 461 return ni < nj |
462 } | 462 } |
463 return p[i].order < p[j].order | 463 return p[i].order < p[j].order |
464 } | 464 } |
465 | 465 |
466 | 466 |
467 ÿ// NOTE(rsc): This would appear not to be correct for type ( )ÿ | 467 // NOTE(rsc): This would appear not to be correct for type ( ) |
468 ÿ// blocks, but the doc extractor above has split them intoÿ | 468 // blocks, but the doc extractor above has split them into |
469 ÿ// individual declarations.ÿ | 469 // individual declarations. |
470 func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc { | 470 func (doc *docReader) makeTypeDocs(m map[string]*typeDoc) []*TypeDoc { |
471 d := make([]*TypeDoc, len(m)) | 471 d := make([]*TypeDoc, len(m)) |
472 i := 0 | 472 i := 0 |
473 for _, old := range m { | 473 for _, old := range m { |
474 » » ÿ// all typeDocs should have a declaration associated withÿ | 474 » » // all typeDocs should have a declaration associated with |
475 » » ÿ// them after processing an entire package - be conservativeÿ | 475 » » // them after processing an entire package - be conservative |
476 » » ÿ// and checkÿ | 476 » » // and check |
477 if decl := old.decl; decl != nil { | 477 if decl := old.decl; decl != nil { |
478 typespec := decl.Specs[0].(*ast.TypeSpec) | 478 typespec := decl.Specs[0].(*ast.TypeSpec) |
479 t := new(TypeDoc) | 479 t := new(TypeDoc) |
480 doc := typespec.Doc | 480 doc := typespec.Doc |
481 » » » typespec.Doc = nil ÿ// doc consumed - remove from ast.Ty
peSpec nodeÿ | 481 » » » typespec.Doc = nil // doc consumed - remove from ast.Typ
eSpec node |
482 if doc == nil { | 482 if doc == nil { |
483 » » » » ÿ// no doc associated with the spec, use the dec
laration doc, if anyÿ | 483 » » » » // no doc associated with the spec, use the decl
aration doc, if any |
484 doc = decl.Doc | 484 doc = decl.Doc |
485 } | 485 } |
486 » » » decl.Doc = nil ÿ// doc consumed - remove from ast.Decl n
odeÿ | 486 » » » decl.Doc = nil // doc consumed - remove from ast.Decl no
de |
487 t.Doc = CommentText(doc) | 487 t.Doc = CommentText(doc) |
488 t.Type = typespec | 488 t.Type = typespec |
489 t.Consts = makeValueDocs(old.values, token.CONST) | 489 t.Consts = makeValueDocs(old.values, token.CONST) |
490 t.Vars = makeValueDocs(old.values, token.VAR) | 490 t.Vars = makeValueDocs(old.values, token.VAR) |
491 t.Factories = makeFuncDocs(old.factories) | 491 t.Factories = makeFuncDocs(old.factories) |
492 t.Methods = makeFuncDocs(old.methods) | 492 t.Methods = makeFuncDocs(old.methods) |
493 t.Decl = old.decl | 493 t.Decl = old.decl |
494 t.order = i | 494 t.order = i |
495 d[i] = t | 495 d[i] = t |
496 i++ | 496 i++ |
497 } else { | 497 } else { |
498 » » » ÿ// no corresponding type declaration found - move any a
ssociatedÿ | 498 » » » // no corresponding type declaration found - move any as
sociated |
499 » » » ÿ// values, factory functions, and methods back to the t
op-levelÿ | 499 » » » // values, factory functions, and methods back to the to
p-level |
500 » » » ÿ// so that they are not lost (this should only happen i
f a packageÿ | 500 » » » // so that they are not lost (this should only happen if
a package |
501 » » » ÿ// file containing the explicit type declaration is mis
sing or ifÿ | 501 » » » // file containing the explicit type declaration is miss
ing or if |
502 » » » ÿ// an unqualified type name was used after a "." import
)ÿ | 502 » » » // an unqualified type name was used after a "." import) |
503 » » » ÿ// 1) move valuesÿ | 503 » » » // 1) move values |
504 doc.values = append(doc.values, old.values...) | 504 doc.values = append(doc.values, old.values...) |
505 » » » ÿ// 2) move factory functionsÿ | 505 » » » // 2) move factory functions |
506 for name, f := range old.factories { | 506 for name, f := range old.factories { |
507 doc.funcs[name] = f | 507 doc.funcs[name] = f |
508 } | 508 } |
509 » » » ÿ// 3) move methodsÿ | 509 » » » // 3) move methods |
510 for name, f := range old.methods { | 510 for name, f := range old.methods { |
511 » » » » ÿ// don't overwrite functions with the same name
ÿ | 511 » » » » // don't overwrite functions with the same name |
512 if _, found := doc.funcs[name]; !found { | 512 if _, found := doc.funcs[name]; !found { |
513 doc.funcs[name] = f | 513 doc.funcs[name] = f |
514 } | 514 } |
515 } | 515 } |
516 } | 516 } |
517 } | 517 } |
518 » d = d[0:i] ÿ// some types may have been ignoredÿ | 518 » d = d[0:i] // some types may have been ignored |
519 sort.Sort(sortTypeDoc(d)) | 519 sort.Sort(sortTypeDoc(d)) |
520 return d | 520 return d |
521 } | 521 } |
522 | 522 |
523 | 523 |
524 func makeBugDocs(list []*ast.CommentGroup) []string { | 524 func makeBugDocs(list []*ast.CommentGroup) []string { |
525 d := make([]string, len(list)) | 525 d := make([]string, len(list)) |
526 for i, g := range list { | 526 for i, g := range list { |
527 d[i] = CommentText(g) | 527 d[i] = CommentText(g) |
528 } | 528 } |
529 return d | 529 return d |
530 } | 530 } |
531 | 531 |
532 | 532 |
533 ÿ// PackageDoc is the documentation for an entire package.ÿ | 533 // PackageDoc is the documentation for an entire package. |
534 ÿ//ÿ | 534 // |
535 type PackageDoc struct { | 535 type PackageDoc struct { |
536 PackageName string | 536 PackageName string |
537 ImportPath string | 537 ImportPath string |
538 Filenames []string | 538 Filenames []string |
539 Doc string | 539 Doc string |
540 Consts []*ValueDoc | 540 Consts []*ValueDoc |
541 Types []*TypeDoc | 541 Types []*TypeDoc |
542 Vars []*ValueDoc | 542 Vars []*ValueDoc |
543 Funcs []*FuncDoc | 543 Funcs []*FuncDoc |
544 Bugs []string | 544 Bugs []string |
545 } | 545 } |
546 | 546 |
547 | 547 |
548 ÿ// newDoc returns the accumulated documentation for the package.ÿ | 548 // newDoc returns the accumulated documentation for the package. |
549 ÿ//ÿ | 549 // |
550 func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc
{ | 550 func (doc *docReader) newDoc(importpath string, filenames []string) *PackageDoc
{ |
551 p := new(PackageDoc) | 551 p := new(PackageDoc) |
552 p.PackageName = doc.pkgName | 552 p.PackageName = doc.pkgName |
553 p.ImportPath = importpath | 553 p.ImportPath = importpath |
554 sort.SortStrings(filenames) | 554 sort.SortStrings(filenames) |
555 p.Filenames = filenames | 555 p.Filenames = filenames |
556 p.Doc = CommentText(doc.doc) | 556 p.Doc = CommentText(doc.doc) |
557 » ÿ// makeTypeDocs may extend the list of doc.values andÿ | 557 » // makeTypeDocs may extend the list of doc.values and |
558 » ÿ// doc.funcs and thus must be called before any otherÿ | 558 » // doc.funcs and thus must be called before any other |
559 » ÿ// function consuming those listsÿ | 559 » // function consuming those lists |
560 p.Types = doc.makeTypeDocs(doc.types) | 560 p.Types = doc.makeTypeDocs(doc.types) |
561 p.Consts = makeValueDocs(doc.values, token.CONST) | 561 p.Consts = makeValueDocs(doc.values, token.CONST) |
562 p.Vars = makeValueDocs(doc.values, token.VAR) | 562 p.Vars = makeValueDocs(doc.values, token.VAR) |
563 p.Funcs = makeFuncDocs(doc.funcs) | 563 p.Funcs = makeFuncDocs(doc.funcs) |
564 p.Bugs = makeBugDocs(doc.bugs) | 564 p.Bugs = makeBugDocs(doc.bugs) |
565 return p | 565 return p |
566 } | 566 } |
567 | 567 |
568 | 568 |
569 ÿ// ----------------------------------------------------------------------------
ÿ | 569 // ---------------------------------------------------------------------------- |
570 ÿ// Filtering by nameÿ | 570 // Filtering by name |
571 | 571 |
572 type Filter func(string) bool | 572 type Filter func(string) bool |
573 | 573 |
574 | 574 |
575 func matchDecl(d *ast.GenDecl, f Filter) bool { | 575 func matchDecl(d *ast.GenDecl, f Filter) bool { |
576 for _, d := range d.Specs { | 576 for _, d := range d.Specs { |
577 switch v := d.(type) { | 577 switch v := d.(type) { |
578 case *ast.ValueSpec: | 578 case *ast.ValueSpec: |
579 for _, name := range v.Names { | 579 for _, name := range v.Names { |
580 if f(name.Name) { | 580 if f(name.Name) { |
(...skipping 30 matching lines...) Expand all Loading... |
611 w++ | 611 w++ |
612 } | 612 } |
613 } | 613 } |
614 return a[0:w] | 614 return a[0:w] |
615 } | 615 } |
616 | 616 |
617 | 617 |
618 func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc { | 618 func filterTypeDocs(a []*TypeDoc, f Filter) []*TypeDoc { |
619 w := 0 | 619 w := 0 |
620 for _, td := range a { | 620 for _, td := range a { |
621 » » n := 0 ÿ// number of matchesÿ | 621 » » n := 0 // number of matches |
622 if matchDecl(td.Decl, f) { | 622 if matchDecl(td.Decl, f) { |
623 n = 1 | 623 n = 1 |
624 } else { | 624 } else { |
625 » » » ÿ// type name doesn't match, but we may have matching co
nsts, vars, factories or methodsÿ | 625 » » » // type name doesn't match, but we may have matching con
sts, vars, factories or methods |
626 td.Consts = filterValueDocs(td.Consts, f) | 626 td.Consts = filterValueDocs(td.Consts, f) |
627 td.Vars = filterValueDocs(td.Vars, f) | 627 td.Vars = filterValueDocs(td.Vars, f) |
628 td.Factories = filterFuncDocs(td.Factories, f) | 628 td.Factories = filterFuncDocs(td.Factories, f) |
629 td.Methods = filterFuncDocs(td.Methods, f) | 629 td.Methods = filterFuncDocs(td.Methods, f) |
630 n += len(td.Consts) + len(td.Vars) + len(td.Factories) +
len(td.Methods) | 630 n += len(td.Consts) + len(td.Vars) + len(td.Factories) +
len(td.Methods) |
631 } | 631 } |
632 if n > 0 { | 632 if n > 0 { |
633 a[w] = td | 633 a[w] = td |
634 w++ | 634 w++ |
635 } | 635 } |
636 } | 636 } |
637 return a[0:w] | 637 return a[0:w] |
638 } | 638 } |
639 | 639 |
640 | 640 |
641 ÿ// Filter eliminates documentation for names that don't pass through the filter
f.ÿ | 641 // Filter eliminates documentation for names that don't pass through the filter
f. |
642 ÿ// TODO: Recognize "Type.Method" as a name.ÿ | 642 // TODO: Recognize "Type.Method" as a name. |
643 ÿ//ÿ | 643 // |
644 func (p *PackageDoc) Filter(f Filter) { | 644 func (p *PackageDoc) Filter(f Filter) { |
645 p.Consts = filterValueDocs(p.Consts, f) | 645 p.Consts = filterValueDocs(p.Consts, f) |
646 p.Vars = filterValueDocs(p.Vars, f) | 646 p.Vars = filterValueDocs(p.Vars, f) |
647 p.Types = filterTypeDocs(p.Types, f) | 647 p.Types = filterTypeDocs(p.Types, f) |
648 p.Funcs = filterFuncDocs(p.Funcs, f) | 648 p.Funcs = filterFuncDocs(p.Funcs, f) |
649 » p.Doc = "" ÿ// don't show top-level package docÿ | 649 » p.Doc = "" // don't show top-level package doc |
650 } | 650 } |
LEFT | RIGHT |