Source file src/pkg/go/ast/filter.go
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.
4
5 package ast
6
7 import (
8 "go/token"
9 "sort"
10 )
11
12 // ----------------------------------------------------------------------------
13 // Export filtering
14
15 // exportFilter is a special filter function to extract exported nodes.
16 func exportFilter(name string) bool {
17 return IsExported(name)
18 }
19
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.
25 //
26 // FileExports returns true if there are exported declarations;
27 // it returns false otherwise.
28 //
29 func FileExports(src *File) bool {
30 return filterFile(src, exportFilter, true)
31 }
32
33 // PackageExports trims the AST for a Go package in place such that
34 // only exported nodes remain. The pkg.Files list is not changed, so that
35 // file names and top-level package comments don't get lost.
36 //
37 // PackageExports returns true if there are exported declarations;
38 // it returns false otherwise.
39 //
40 func PackageExports(pkg *Package) bool {
41 return filterPackage(pkg, exportFilter, true)
42 }
43
44 // ----------------------------------------------------------------------------
45 // General filtering
46
47 type Filter func(string) bool
48
49 func filterIdentList(list []*Ident, f Filter) []*Ident {
50 j := 0
51 for _, x := range list {
52 if f(x.Name) {
53 list[j] = x
54 j++
55 }
56 }
57 return list[0:j]
58 }
59
60 // fieldName assumes that x is the type of an anonymous field and
61 // returns the corresponding field name. If x is not an acceptable
62 // anonymous field, the result is nil.
63 //
64 func fieldName(x Expr) *Ident {
65 switch t := x.(type) {
66 case *Ident:
67 return t
68 case *SelectorExpr:
69 if _, ok := t.X.(*Ident); ok {
70 return t.Sel
71 }
72 case *StarExpr:
73 return fieldName(t.X)
74 }
75 return nil
76 }
77
78 func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) {
79 if fields == nil {
80 return false
81 }
82 list := fields.List
83 j := 0
84 for _, f := range list {
85 keepField := false
86 if len(f.Names) == 0 {
87 // anonymous field
88 name := fieldName(f.Type)
89 keepField = name != nil && filter(name.Name)
90 } else {
91 n := len(f.Names)
92 f.Names = filterIdentList(f.Names, filter)
93 if len(f.Names) < n {
94 removedFields = true
95 }
96 keepField = len(f.Names) > 0
97 }
98 if keepField {
99 if export {
100 filterType(f.Type, filter, export)
101 }
102 list[j] = f
103 j++
104 }
105 }
106 if j < len(list) {
107 removedFields = true
108 }
109 fields.List = list[0:j]
110 return
111 }
112
113 func filterParamList(fields *FieldList, filter Filter, export bool) bool {
114 if fields == nil {
115 return false
116 }
117 var b bool
118 for _, f := range fields.List {
119 if filterType(f.Type, filter, export) {
120 b = true
121 }
122 }
123 return b
124 }
125
126 func filterType(typ Expr, f Filter, export bool) bool {
127 switch t := typ.(type) {
128 case *Ident:
129 return f(t.Name)
130 case *ParenExpr:
131 return filterType(t.X, f, export)
132 case *ArrayType:
133 return filterType(t.Elt, f, export)
134 case *StructType:
135 if filterFieldList(t.Fields, f, export) {
136 t.Incomplete = true
137 }
138 return len(t.Fields.List) > 0
139 case *FuncType:
140 b1 := filterParamList(t.Params, f, export)
141 b2 := filterParamList(t.Results, f, export)
142 return b1 || b2
143 case *InterfaceType:
144 if filterFieldList(t.Methods, f, export) {
145 t.Incomplete = true
146 }
147 return len(t.Methods.List) > 0
148 case *MapType:
149 b1 := filterType(t.Key, f, export)
150 b2 := filterType(t.Value, f, export)
151 return b1 || b2
152 case *ChanType:
153 return filterType(t.Value, f, export)
154 }
155 return false
156 }
157
158 func filterSpec(spec Spec, f Filter, export bool) bool {
159 switch s := spec.(type) {
160 case *ValueSpec:
161 s.Names = filterIdentList(s.Names, f)
162 if len(s.Names) > 0 {
163 if export {
164 filterType(s.Type, f, export)
165 }
166 return true
167 }
168 case *TypeSpec:
169 if f(s.Name.Name) {
170 if export {
171 filterType(s.Type, f, export)
172 }
173 return true
174 }
175 if !export {
176 // For general filtering (not just exports),
177 // filter type even if name is not filtered
178 // out.
179 // If the type contains filtered elements,
180 // keep the declaration.
181 return filterType(s.Type, f, export)
182 }
183 }
184 return false
185 }
186
187 func filterSpecList(list []Spec, f Filter, export bool) []Spec {
188 j := 0
189 for _, s := range list {
190 if filterSpec(s, f, export) {
191 list[j] = s
192 j++
193 }
194 }
195 return list[0:j]
196 }
197
198 // FilterDecl trims the AST for a Go declaration in place by removing
199 // all names (including struct field and interface method names, but
200 // not from parameter lists) that don't pass through the filter f.
201 //
202 // FilterDecl returns true if there are any declared names left after
203 // filtering; it returns false otherwise.
204 //
205 func FilterDecl(decl Decl, f Filter) bool {
206 return filterDecl(decl, f, false)
207 }
208
209 func filterDecl(decl Decl, f Filter, export bool) bool {
210 switch d := decl.(type) {
211 case *GenDecl:
212 d.Specs = filterSpecList(d.Specs, f, export)
213 return len(d.Specs) > 0
214 case *FuncDecl:
215 return f(d.Name.Name)
216 }
217 return false
218 }
219
220 // FilterFile trims the AST for a Go file in place by removing all
221 // names from top-level declarations (including struct field and
222 // interface method names, but not from parameter lists) that don't
223 // pass through the filter f. If the declaration is empty afterwards,
224 // the declaration is removed from the AST. The File.Comments list
225 // is not changed.
226 //
227 // FilterFile returns true if there are any top-level declarations
228 // left after filtering; it returns false otherwise.
229 //
230 func FilterFile(src *File, f Filter) bool {
231 return filterFile(src, f, false)
232 }
233
234 func filterFile(src *File, f Filter, export bool) bool {
235 j := 0
236 for _, d := range src.Decls {
237 if filterDecl(d, f, export) {
238 src.Decls[j] = d
239 j++
240 }
241 }
242 src.Decls = src.Decls[0:j]
243 return j > 0
244 }
245
246 // FilterPackage trims the AST for a Go package in place by removing
247 // all names from top-level declarations (including struct field and
248 // interface method names, but not from parameter lists) that don't
249 // pass through the filter f. If the declaration is empty afterwards,
250 // the declaration is removed from the AST. The pkg.Files list is not
251 // changed, so that file names and top-level package comments don't get
252 // lost.
253 //
254 // FilterPackage returns true if there are any top-level declarations
255 // left after filtering; it returns false otherwise.
256 //
257 func FilterPackage(pkg *Package, f Filter) bool {
258 return filterPackage(pkg, f, false)
259 }
260
261 func filterPackage(pkg *Package, f Filter, export bool) bool {
262 hasDecls := false
263 for _, src := range pkg.Files {
264 if filterFile(src, f, export) {
265 hasDecls = true
266 }
267 }
268 return hasDecls
269 }
270
271 // ----------------------------------------------------------------------------
272 // Merging of package files
273
274 // The MergeMode flags control the behavior of MergePackageFiles.
275 type MergeMode uint
276
277 const (
278 // If set, duplicate function declarations are excluded.
279 FilterFuncDuplicates MergeMode = 1 << iota
280 // If set, comments that are not associated with a specific
281 // AST node (as Doc or Comment) are excluded.
282 FilterUnassociatedComments
283 // If set, duplicate import declarations are excluded.
284 FilterImportDuplicates
285 )
286
287 // separator is an empty //-style comment that is interspersed between
288 // different comment groups when they are concatenated into a single group
289 //
290 var separator = &Comment{noPos, "//"}
291
292 // MergePackageFiles creates a file AST by merging the ASTs of the
293 // files belonging to a package. The mode flags control merging behavior.
294 //
295 func MergePackageFiles(pkg *Package, mode MergeMode) *File {
296 // Count the number of package docs, comments and declarations across
297 // all package files. Also, compute sorted list of filenames, so that
298 // subsequent iterations can always iterate in the same order.
299 ndocs := 0
300 ncomments := 0
301 ndecls := 0
302 filenames := make([]string, len(pkg.Files))
303 i := 0
304 for filename, f := range pkg.Files {
305 filenames[i] = filename
306 i++
307 if f.Doc != nil {
308 ndocs += len(f.Doc.List) + 1 // +1 for separator
309 }
310 ncomments += len(f.Comments)
311 ndecls += len(f.Decls)
312 }
313 sort.Strings(filenames)
314
315 // Collect package comments from all package files into a single
316 // CommentGroup - the collected package documentation. In general
317 // there should be only one file with a package comment; but it's
318 // better to collect extra comments than drop them on the floor.
319 var doc *CommentGroup
320 var pos token.Pos
321 if ndocs > 0 {
322 list := make([]*Comment, ndocs-1) // -1: no separator before first group
323 i := 0
324 for _, filename := range filenames {
325 f := pkg.Files[filename]
326 if f.Doc != nil {
327 if i > 0 {
328 // not the first group - add separator
329 list[i] = separator
330 i++
331 }
332 for _, c := range f.Doc.List {
333 list[i] = c
334 i++
335 }
336 if f.Package > pos {
337 // Keep the maximum package clause position as
338 // position for the package clause of the merged
339 // files.
340 pos = f.Package
341 }
342 }
343 }
344 doc = &CommentGroup{list}
345 }
346
347 // Collect declarations from all package files.
348 var decls []Decl
349 if ndecls > 0 {
350 decls = make([]Decl, ndecls)
351 funcs := make(map[string]int) // map of global function name -> decls index
352 i := 0 // current index
353 n := 0 // number of filtered entries
354 for _, filename := range filenames {
355 f := pkg.Files[filename]
356 for _, d := range f.Decls {
357 if mode&FilterFuncDuplicates != 0 {
358 // A language entity may be declared multiple
359 // times in different package files; only at
360 // build time declarations must be unique.
361 // For now, exclude multiple declarations of
362 // functions - keep the one with documentation.
363 //
364 // TODO(gri): Expand this filtering to other
365 // entities (const, type, vars) if
366 // multiple declarations are common.
367 if f, isFun := d.(*FuncDecl); isFun {
368 name := f.Name.Name
369 if j, exists := funcs[name]; exists {
370 // function declared already
371 if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
372 // existing declaration has no documentation;
373 // ignore the existing declaration
374 decls[j] = nil
375 } else {
376 // ignore the new declaration
377 d = nil
378 }
379 n++ // filtered an entry
380 } else {
381 funcs[name] = i
382 }
383 }
384 }
385 decls[i] = d
386 i++
387 }
388 }
389
390 // Eliminate nil entries from the decls list if entries were
391 // filtered. We do this using a 2nd pass in order to not disturb
392 // the original declaration order in the source (otherwise, this
393 // would also invalidate the monotonically increasing position
394 // info within a single file).
395 if n > 0 {
396 i = 0
397 for _, d := range decls {
398 if d != nil {
399 decls[i] = d
400 i++
401 }
402 }
403 decls = decls[0:i]
404 }
405 }
406
407 // Collect import specs from all package files.
408 var imports []*ImportSpec
409 if mode&FilterImportDuplicates != 0 {
410 seen := make(map[string]bool)
411 for _, filename := range filenames {
412 f := pkg.Files[filename]
413 for _, imp := range f.Imports {
414 if path := imp.Path.Value; !seen[path] {
415 // TODO: consider handling cases where:
416 // - 2 imports exist with the same import path but
417 // have different local names (one should probably
418 // keep both of them)
419 // - 2 imports exist but only one has a comment
420 // - 2 imports exist and they both have (possibly
421 // different) comments
422 imports = append(imports, imp)
423 seen[path] = true
424 }
425 }
426 }
427 } else {
428 for _, f := range pkg.Files {
429 imports = append(imports, f.Imports...)
430 }
431 }
432
433 // Collect comments from all package files.
434 var comments []*CommentGroup
435 if mode&FilterUnassociatedComments == 0 {
436 comments = make([]*CommentGroup, ncomments)
437 i := 0
438 for _, f := range pkg.Files {
439 i += copy(comments[i:], f.Comments)
440 }
441 }
442
443 // TODO(gri) need to compute unresolved identifiers!
444 return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments}
445 }