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.
9 // ----------------------------------------------------------------------------
12 // exportFilter is a special filter function to extract exported nodes.
13 func exportFilter(name string) bool {
14 return IsExported(name)
17 // FileExports trims the AST for a Go source file in place such that
18 // only exported nodes remain: all top-level identifiers which are not exported
19 // and their associated information (such as type, initial value, or function
20 // body) are removed. Non-exported fields and methods of exported types are
21 // stripped. The File.Comments list is not changed.
23 // FileExports returns true if there are exported declarationa;
24 // it returns false otherwise.
26 func FileExports(src *File) bool {
27 return FilterFile(src, exportFilter)
30 // PackageExports trims the AST for a Go package in place such that
31 // only exported nodes remain. The pkg.Files list is not changed, so that
32 // file names and top-level package comments don't get lost.
34 // PackageExports returns true if there are exported declarations;
35 // it returns false otherwise.
37 func PackageExports(pkg *Package) bool {
38 return FilterPackage(pkg, exportFilter)
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.
61 func fieldName(x Expr) *Ident {
62 switch t := x.(type) {
66 if _, ok := t.X.(*Ident); ok {
75 func filterFieldList(fields *FieldList, filter Filter) (removedFields bool) {
81 for _, f := range list {
83 if len(f.Names) == 0 {
85 name := fieldName(f.Type)
86 keepField = name != nil && filter(name.Name)
89 f.Names = filterIdentList(f.Names, filter)
93 keepField = len(f.Names) > 0
96 if filter == exportFilter {
97 filterType(f.Type, filter)
106 fields.List = list[0:j]
110 func filterParamList(fields *FieldList, filter Filter) bool {
115 for _, f := range fields.List {
116 if filterType(f.Type, filter) {
123 func filterType(typ Expr, f Filter) bool {
124 switch t := typ.(type) {
128 return filterType(t.X, f)
130 return filterType(t.Elt, f)
132 if filterFieldList(t.Fields, f) {
135 return len(t.Fields.List) > 0
137 b1 := filterParamList(t.Params, f)
138 b2 := filterParamList(t.Results, f)
141 if filterFieldList(t.Methods, f) {
144 return len(t.Methods.List) > 0
146 b1 := filterType(t.Key, f)
147 b2 := filterType(t.Value, f)
150 return filterType(t.Value, f)
155 func filterSpec(spec Spec, f Filter) bool {
156 switch s := spec.(type) {
158 s.Names = filterIdentList(s.Names, f)
159 if len(s.Names) > 0 {
160 if f == exportFilter {
161 filterType(s.Type, f)
167 if f == exportFilter {
168 filterType(s.Type, f)
172 if f != exportFilter {
173 // For general filtering (not just exports),
174 // filter type even if name is not filtered
176 // If the type contains filtered elements,
177 // keep the declaration.
178 return filterType(s.Type, f)
184 func filterSpecList(list []Spec, f Filter) []Spec {
186 for _, s := range list {
187 if filterSpec(s, f) {
195 // FilterDecl trims the AST for a Go declaration in place by removing
196 // all names (including struct field and interface method names, but
197 // not from parameter lists) that don't pass through the filter f.
199 // FilterDecl returns true if there are any declared names left after
200 // filtering; it returns false otherwise.
202 func FilterDecl(decl Decl, f Filter) bool {
203 switch d := decl.(type) {
205 d.Specs = filterSpecList(d.Specs, f)
206 return len(d.Specs) > 0
208 return f(d.Name.Name)
213 // FilterFile trims the AST for a Go file in place by removing all
214 // names from top-level declarations (including struct field and
215 // interface method names, but not from parameter lists) that don't
216 // pass through the filter f. If the declaration is empty afterwards,
217 // the declaration is removed from the AST. The File.Comments list
220 // FilterFile returns true if there are any top-level declarations
221 // left after filtering; it returns false otherwise.
223 func FilterFile(src *File, f Filter) bool {
225 for _, d := range src.Decls {
226 if FilterDecl(d, f) {
231 src.Decls = src.Decls[0:j]
235 // FilterPackage trims the AST for a Go package in place by removing
236 // all names from top-level declarations (including struct field and
237 // interface method names, but not from parameter lists) that don't
238 // pass through the filter f. If the declaration is empty afterwards,
239 // the declaration is removed from the AST. The pkg.Files list is not
240 // changed, so that file names and top-level package comments don't get
243 // FilterPackage returns true if there are any top-level declarations
244 // left after filtering; it returns false otherwise.
246 func FilterPackage(pkg *Package, f Filter) bool {
248 for _, src := range pkg.Files {
249 if FilterFile(src, f) {
256 // ----------------------------------------------------------------------------
257 // Merging of package files
259 // The MergeMode flags control the behavior of MergePackageFiles.
263 // If set, duplicate function declarations are excluded.
264 FilterFuncDuplicates MergeMode = 1 << iota
265 // If set, comments that are not associated with a specific
266 // AST node (as Doc or Comment) are excluded.
267 FilterUnassociatedComments
268 // If set, duplicate import declarations are excluded.
269 FilterImportDuplicates
272 // separator is an empty //-style comment that is interspersed between
273 // different comment groups when they are concatenated into a single group
275 var separator = &Comment{noPos, "//"}
277 // MergePackageFiles creates a file AST by merging the ASTs of the
278 // files belonging to a package. The mode flags control merging behavior.
280 func MergePackageFiles(pkg *Package, mode MergeMode) *File {
281 // Count the number of package docs, comments and declarations across
282 // all package files.
286 for _, f := range pkg.Files {
288 ndocs += len(f.Doc.List) + 1 // +1 for separator
290 ncomments += len(f.Comments)
291 ndecls += len(f.Decls)
294 // Collect package comments from all package files into a single
295 // CommentGroup - the collected package documentation. The order
296 // is unspecified. In general there should be only one file with
297 // a package comment; but it's better to collect extra comments
298 // than drop them on the floor.
299 var doc *CommentGroup
302 list := make([]*Comment, ndocs-1) // -1: no separator before first group
304 for _, f := range pkg.Files {
307 // not the first group - add separator
311 for _, c := range f.Doc.List {
316 // Keep the maximum package clause position as
317 // position for the package clause of the merged
323 doc = &CommentGroup{list}
326 // Collect declarations from all package files.
329 decls = make([]Decl, ndecls)
330 funcs := make(map[string]int) // map of global function name -> decls index
331 i := 0 // current index
332 n := 0 // number of filtered entries
333 for _, f := range pkg.Files {
334 for _, d := range f.Decls {
335 if mode&FilterFuncDuplicates != 0 {
336 // A language entity may be declared multiple
337 // times in different package files; only at
338 // build time declarations must be unique.
339 // For now, exclude multiple declarations of
340 // functions - keep the one with documentation.
342 // TODO(gri): Expand this filtering to other
343 // entities (const, type, vars) if
344 // multiple declarations are common.
345 if f, isFun := d.(*FuncDecl); isFun {
347 if j, exists := funcs[name]; exists {
348 // function declared already
349 if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
350 // existing declaration has no documentation;
351 // ignore the existing declaration
354 // ignore the new declaration
357 n++ // filtered an entry
368 // Eliminate nil entries from the decls list if entries were
369 // filtered. We do this using a 2nd pass in order to not disturb
370 // the original declaration order in the source (otherwise, this
371 // would also invalidate the monotonically increasing position
372 // info within a single file).
375 for _, d := range decls {
385 // Collect import specs from all package files.
386 var imports []*ImportSpec
387 if mode&FilterImportDuplicates != 0 {
388 seen := make(map[string]bool)
389 for _, f := range pkg.Files {
390 for _, imp := range f.Imports {
391 if path := imp.Path.Value; !seen[path] {
392 // TODO: consider handling cases where:
393 // - 2 imports exist with the same import path but
394 // have different local names (one should probably
395 // keep both of them)
396 // - 2 imports exist but only one has a comment
397 // - 2 imports exist and they both have (possibly
398 // different) comments
399 imports = append(imports, imp)
405 for _, f := range pkg.Files {
406 imports = append(imports, f.Imports...)
410 // Collect comments from all package files.
411 var comments []*CommentGroup
412 if mode&FilterUnassociatedComments == 0 {
413 comments = make([]*CommentGroup, ncomments)
415 for _, f := range pkg.Files {
416 i += copy(comments[i:], f.Comments)
420 // TODO(gri) need to compute unresolved identifiers!
421 return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments}