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
5package ast
6
7import (
8	"go/token"
9	"sort"
10)
11
12// ----------------------------------------------------------------------------
13// Export filtering
14
15// exportFilter is a special filter function to extract exported nodes.
16func 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//
29func 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//
40func PackageExports(pkg *Package) bool {
41	return filterPackage(pkg, exportFilter, true)
42}
43
44// ----------------------------------------------------------------------------
45// General filtering
46
47type Filter func(string) bool
48
49func 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//
64func 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
78func 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
113func 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
126func 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
158func 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
187func 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//
205func FilterDecl(decl Decl, f Filter) bool {
206	return filterDecl(decl, f, false)
207}
208
209func 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//
230func FilterFile(src *File, f Filter) bool {
231	return filterFile(src, f, false)
232}
233
234func 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//
257func FilterPackage(pkg *Package, f Filter) bool {
258	return filterPackage(pkg, f, false)
259}
260
261func 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.
275type MergeMode uint
276
277const (
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// nameOf returns the function (foo) or method name (foo.bar) for
288// the given function declaration. If the AST is incorrect for the
289// receiver, it assumes a function instead.
290//
291func nameOf(f *FuncDecl) string {
292	if r := f.Recv; r != nil && len(r.List) == 1 {
293		// looks like a correct receiver declaration
294		t := r.List[0].Type
295		// dereference pointer receiver types
296		if p, _ := t.(*StarExpr); p != nil {
297			t = p.X
298		}
299		// the receiver type must be a type name
300		if p, _ := t.(*Ident); p != nil {
301			return p.Name + "." + f.Name.Name
302		}
303		// otherwise assume a function instead
304	}
305	return f.Name.Name
306}
307
308// separator is an empty //-style comment that is interspersed between
309// different comment groups when they are concatenated into a single group
310//
311var separator = &Comment{token.NoPos, "//"}
312
313// MergePackageFiles creates a file AST by merging the ASTs of the
314// files belonging to a package. The mode flags control merging behavior.
315//
316func MergePackageFiles(pkg *Package, mode MergeMode) *File {
317	// Count the number of package docs, comments and declarations across
318	// all package files. Also, compute sorted list of filenames, so that
319	// subsequent iterations can always iterate in the same order.
320	ndocs := 0
321	ncomments := 0
322	ndecls := 0
323	filenames := make([]string, len(pkg.Files))
324	i := 0
325	for filename, f := range pkg.Files {
326		filenames[i] = filename
327		i++
328		if f.Doc != nil {
329			ndocs += len(f.Doc.List) + 1 // +1 for separator
330		}
331		ncomments += len(f.Comments)
332		ndecls += len(f.Decls)
333	}
334	sort.Strings(filenames)
335
336	// Collect package comments from all package files into a single
337	// CommentGroup - the collected package documentation. In general
338	// there should be only one file with a package comment; but it's
339	// better to collect extra comments than drop them on the floor.
340	var doc *CommentGroup
341	var pos token.Pos
342	if ndocs > 0 {
343		list := make([]*Comment, ndocs-1) // -1: no separator before first group
344		i := 0
345		for _, filename := range filenames {
346			f := pkg.Files[filename]
347			if f.Doc != nil {
348				if i > 0 {
349					// not the first group - add separator
350					list[i] = separator
351					i++
352				}
353				for _, c := range f.Doc.List {
354					list[i] = c
355					i++
356				}
357				if f.Package > pos {
358					// Keep the maximum package clause position as
359					// position for the package clause of the merged
360					// files.
361					pos = f.Package
362				}
363			}
364		}
365		doc = &CommentGroup{list}
366	}
367
368	// Collect declarations from all package files.
369	var decls []Decl
370	if ndecls > 0 {
371		decls = make([]Decl, ndecls)
372		funcs := make(map[string]int) // map of func name -> decls index
373		i := 0                        // current index
374		n := 0                        // number of filtered entries
375		for _, filename := range filenames {
376			f := pkg.Files[filename]
377			for _, d := range f.Decls {
378				if mode&FilterFuncDuplicates != 0 {
379					// A language entity may be declared multiple
380					// times in different package files; only at
381					// build time declarations must be unique.
382					// For now, exclude multiple declarations of
383					// functions - keep the one with documentation.
384					//
385					// TODO(gri): Expand this filtering to other
386					//            entities (const, type, vars) if
387					//            multiple declarations are common.
388					if f, isFun := d.(*FuncDecl); isFun {
389						name := nameOf(f)
390						if j, exists := funcs[name]; exists {
391							// function declared already
392							if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
393								// existing declaration has no documentation;
394								// ignore the existing declaration
395								decls[j] = nil
396							} else {
397								// ignore the new declaration
398								d = nil
399							}
400							n++ // filtered an entry
401						} else {
402							funcs[name] = i
403						}
404					}
405				}
406				decls[i] = d
407				i++
408			}
409		}
410
411		// Eliminate nil entries from the decls list if entries were
412		// filtered. We do this using a 2nd pass in order to not disturb
413		// the original declaration order in the source (otherwise, this
414		// would also invalidate the monotonically increasing position
415		// info within a single file).
416		if n > 0 {
417			i = 0
418			for _, d := range decls {
419				if d != nil {
420					decls[i] = d
421					i++
422				}
423			}
424			decls = decls[0:i]
425		}
426	}
427
428	// Collect import specs from all package files.
429	var imports []*ImportSpec
430	if mode&FilterImportDuplicates != 0 {
431		seen := make(map[string]bool)
432		for _, filename := range filenames {
433			f := pkg.Files[filename]
434			for _, imp := range f.Imports {
435				if path := imp.Path.Value; !seen[path] {
436					// TODO: consider handling cases where:
437					// - 2 imports exist with the same import path but
438					//   have different local names (one should probably
439					//   keep both of them)
440					// - 2 imports exist but only one has a comment
441					// - 2 imports exist and they both have (possibly
442					//   different) comments
443					imports = append(imports, imp)
444					seen[path] = true
445				}
446			}
447		}
448	} else {
449		for _, f := range pkg.Files {
450			imports = append(imports, f.Imports...)
451		}
452	}
453
454	// Collect comments from all package files.
455	var comments []*CommentGroup
456	if mode&FilterUnassociatedComments == 0 {
457		comments = make([]*CommentGroup, ncomments)
458		i := 0
459		for _, f := range pkg.Files {
460			i += copy(comments[i:], f.Comments)
461		}
462	}
463
464	// TODO(gri) need to compute unresolved identifiers!
465	return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments}
466}
467