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