1 // Copyright 2009 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.
12 // ----------------------------------------------------------------------------
15 // exportFilter is a special filter function to extract exported nodes.
16 func exportFilter(name string) bool {
17 return IsExported(name)
20 // FileExports trims the AST for a Go source file in place such that
21 // only exported nodes remain: all top-level identifiers which are not exported
22 // and their associated information (such as type, initial value, or function
23 // body) are removed. Non-exported fields and methods of exported types are
24 // stripped. The File.Comments list is not changed.
26 // FileExports reports whether there are exported declarations.
27 func FileExports(src *File) bool {
28 return filterFile(src, exportFilter, true)
31 // PackageExports trims the AST for a Go package in place such that
32 // only exported nodes remain. The pkg.Files list is not changed, so that
33 // file names and top-level package comments don't get lost.
35 // PackageExports reports whether there are exported declarations;
36 // it returns false otherwise.
37 func PackageExports(pkg *Package) bool {
38 return filterPackage(pkg, exportFilter, true)
41 // ----------------------------------------------------------------------------
44 type Filter func(string) bool
46 func filterIdentList(list []*Ident, f Filter) []*Ident {
48 for _, x := range list {
57 // fieldName assumes that x is the type of an anonymous field and
58 // returns the corresponding field name. If x is not an acceptable
59 // anonymous field, the result is nil.
60 func fieldName(x Expr) *Ident {
61 switch t := x.(type) {
65 if _, ok := t.X.(*Ident); ok {
74 func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) {
80 for _, f := range list {
82 if len(f.Names) == 0 {
84 name := fieldName(f.Type)
85 keepField = name != nil && filter(name.Name)
88 f.Names = filterIdentList(f.Names, filter)
92 keepField = len(f.Names) > 0
96 filterType(f.Type, filter, export)
105 fields.List = list[0:j]
109 func filterCompositeLit(lit *CompositeLit, filter Filter, export bool) {
111 lit.Elts = filterExprList(lit.Elts, filter, export)
112 if len(lit.Elts) < n {
113 lit.Incomplete = true
117 func filterExprList(list []Expr, filter Filter, export bool) []Expr {
119 for _, exp := range list {
120 switch x := exp.(type) {
122 filterCompositeLit(x, filter, export)
124 if x, ok := x.Key.(*Ident); ok && !filter(x.Name) {
127 if x, ok := x.Value.(*CompositeLit); ok {
128 filterCompositeLit(x, filter, export)
137 func filterParamList(fields *FieldList, filter Filter, export bool) bool {
142 for _, f := range fields.List {
143 if filterType(f.Type, filter, export) {
150 func filterType(typ Expr, f Filter, export bool) bool {
151 switch t := typ.(type) {
155 return filterType(t.X, f, export)
157 return filterType(t.Elt, f, export)
159 if filterFieldList(t.Fields, f, export) {
162 return len(t.Fields.List) > 0
164 b1 := filterParamList(t.Params, f, export)
165 b2 := filterParamList(t.Results, f, export)
168 if filterFieldList(t.Methods, f, export) {
171 return len(t.Methods.List) > 0
173 b1 := filterType(t.Key, f, export)
174 b2 := filterType(t.Value, f, export)
177 return filterType(t.Value, f, export)
182 func filterSpec(spec Spec, f Filter, export bool) bool {
183 switch s := spec.(type) {
185 s.Names = filterIdentList(s.Names, f)
186 s.Values = filterExprList(s.Values, f, export)
187 if len(s.Names) > 0 {
189 filterType(s.Type, f, export)
196 filterType(s.Type, f, export)
201 // For general filtering (not just exports),
202 // filter type even if name is not filtered
204 // If the type contains filtered elements,
205 // keep the declaration.
206 return filterType(s.Type, f, export)
212 func filterSpecList(list []Spec, f Filter, export bool) []Spec {
214 for _, s := range list {
215 if filterSpec(s, f, export) {
223 // FilterDecl trims the AST for a Go declaration in place by removing
224 // all names (including struct field and interface method names, but
225 // not from parameter lists) that don't pass through the filter f.
227 // FilterDecl reports whether there are any declared names left after
229 func FilterDecl(decl Decl, f Filter) bool {
230 return filterDecl(decl, f, false)
233 func filterDecl(decl Decl, f Filter, export bool) bool {
234 switch d := decl.(type) {
236 d.Specs = filterSpecList(d.Specs, f, export)
237 return len(d.Specs) > 0
239 return f(d.Name.Name)
244 // FilterFile trims the AST for a Go file in place by removing all
245 // names from top-level declarations (including struct field and
246 // interface method names, but not from parameter lists) that don't
247 // pass through the filter f. If the declaration is empty afterwards,
248 // the declaration is removed from the AST. Import declarations are
249 // always removed. The File.Comments list is not changed.
251 // FilterFile reports whether there are any top-level declarations
252 // left after filtering.
253 func FilterFile(src *File, f Filter) bool {
254 return filterFile(src, f, false)
257 func filterFile(src *File, f Filter, export bool) bool {
259 for _, d := range src.Decls {
260 if filterDecl(d, f, export) {
265 src.Decls = src.Decls[0:j]
269 // FilterPackage trims the AST for a Go package in place by removing
270 // all names from top-level declarations (including struct field and
271 // interface method names, but not from parameter lists) that don't
272 // pass through the filter f. If the declaration is empty afterwards,
273 // the declaration is removed from the AST. The pkg.Files list is not
274 // changed, so that file names and top-level package comments don't get
277 // FilterPackage reports whether there are any top-level declarations
278 // left after filtering.
279 func FilterPackage(pkg *Package, f Filter) bool {
280 return filterPackage(pkg, f, false)
283 func filterPackage(pkg *Package, f Filter, export bool) bool {
285 for _, src := range pkg.Files {
286 if filterFile(src, f, export) {
293 // ----------------------------------------------------------------------------
294 // Merging of package files
296 // The MergeMode flags control the behavior of MergePackageFiles.
300 // If set, duplicate function declarations are excluded.
301 FilterFuncDuplicates MergeMode = 1 << iota
302 // If set, comments that are not associated with a specific
303 // AST node (as Doc or Comment) are excluded.
304 FilterUnassociatedComments
305 // If set, duplicate import declarations are excluded.
306 FilterImportDuplicates
309 // nameOf returns the function (foo) or method name (foo.bar) for
310 // the given function declaration. If the AST is incorrect for the
311 // receiver, it assumes a function instead.
312 func nameOf(f *FuncDecl) string {
313 if r := f.Recv; r != nil && len(r.List) == 1 {
314 // looks like a correct receiver declaration
316 // dereference pointer receiver types
317 if p, _ := t.(*StarExpr); p != nil {
320 // the receiver type must be a type name
321 if p, _ := t.(*Ident); p != nil {
322 return p.Name + "." + f.Name.Name
324 // otherwise assume a function instead
329 // separator is an empty //-style comment that is interspersed between
330 // different comment groups when they are concatenated into a single group
331 var separator = &Comment{token.NoPos, "//"}
333 // MergePackageFiles creates a file AST by merging the ASTs of the
334 // files belonging to a package. The mode flags control merging behavior.
335 func MergePackageFiles(pkg *Package, mode MergeMode) *File {
336 // Count the number of package docs, comments and declarations across
337 // all package files. Also, compute sorted list of filenames, so that
338 // subsequent iterations can always iterate in the same order.
342 filenames := make([]string, len(pkg.Files))
343 var minPos, maxPos token.Pos
345 for filename, f := range pkg.Files {
346 filenames[i] = filename
349 ndocs += len(f.Doc.List) + 1 // +1 for separator
351 ncomments += len(f.Comments)
352 ndecls += len(f.Decls)
353 if i == 0 || f.FileStart < minPos {
356 if i == 0 || f.FileEnd > maxPos {
360 sort.Strings(filenames)
362 // Collect package comments from all package files into a single
363 // CommentGroup - the collected package documentation. In general
364 // there should be only one file with a package comment; but it's
365 // better to collect extra comments than drop them on the floor.
366 var doc *CommentGroup
369 list := make([]*Comment, ndocs-1) // -1: no separator before first group
371 for _, filename := range filenames {
372 f := pkg.Files[filename]
375 // not the first group - add separator
379 for _, c := range f.Doc.List {
384 // Keep the maximum package clause position as
385 // position for the package clause of the merged
391 doc = &CommentGroup{list}
394 // Collect declarations from all package files.
397 decls = make([]Decl, ndecls)
398 funcs := make(map[string]int) // map of func name -> decls index
399 i := 0 // current index
400 n := 0 // number of filtered entries
401 for _, filename := range filenames {
402 f := pkg.Files[filename]
403 for _, d := range f.Decls {
404 if mode&FilterFuncDuplicates != 0 {
405 // A language entity may be declared multiple
406 // times in different package files; only at
407 // build time declarations must be unique.
408 // For now, exclude multiple declarations of
409 // functions - keep the one with documentation.
411 // TODO(gri): Expand this filtering to other
412 // entities (const, type, vars) if
413 // multiple declarations are common.
414 if f, isFun := d.(*FuncDecl); isFun {
416 if j, exists := funcs[name]; exists {
417 // function declared already
418 if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
419 // existing declaration has no documentation;
420 // ignore the existing declaration
423 // ignore the new declaration
426 n++ // filtered an entry
437 // Eliminate nil entries from the decls list if entries were
438 // filtered. We do this using a 2nd pass in order to not disturb
439 // the original declaration order in the source (otherwise, this
440 // would also invalidate the monotonically increasing position
441 // info within a single file).
444 for _, d := range decls {
454 // Collect import specs from all package files.
455 var imports []*ImportSpec
456 if mode&FilterImportDuplicates != 0 {
457 seen := make(map[string]bool)
458 for _, filename := range filenames {
459 f := pkg.Files[filename]
460 for _, imp := range f.Imports {
461 if path := imp.Path.Value; !seen[path] {
462 // TODO: consider handling cases where:
463 // - 2 imports exist with the same import path but
464 // have different local names (one should probably
465 // keep both of them)
466 // - 2 imports exist but only one has a comment
467 // - 2 imports exist and they both have (possibly
468 // different) comments
469 imports = append(imports, imp)
475 // Iterate over filenames for deterministic order.
476 for _, filename := range filenames {
477 f := pkg.Files[filename]
478 imports = append(imports, f.Imports...)
482 // Collect comments from all package files.
483 var comments []*CommentGroup
484 if mode&FilterUnassociatedComments == 0 {
485 comments = make([]*CommentGroup, ncomments)
487 for _, filename := range filenames {
488 f := pkg.Files[filename]
489 i += copy(comments[i:], f.Comments)
493 // TODO(gri) need to compute unresolved identifiers!
494 return &File{doc, pos, NewIdent(pkg.Name), decls, minPos, maxPos, pkg.Scope, imports, nil, comments}