...

Source file src/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 reports whether there are exported declarations.
    27	//
    28	func 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	//
    39	func PackageExports(pkg *Package) bool {
    40		return filterPackage(pkg, exportFilter, true)
    41	}
    42	
    43	// ----------------------------------------------------------------------------
    44	// General filtering
    45	
    46	type Filter func(string) bool
    47	
    48	func 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	//
    63	func 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	
    77	func 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	
   112	func 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	
   120	func 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	
   140	func 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	
   153	func 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	
   185	func 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	
   215	func 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	//
   233	func FilterDecl(decl Decl, f Filter) bool {
   234		return filterDecl(decl, f, false)
   235	}
   236	
   237	func 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	//
   258	func FilterFile(src *File, f Filter) bool {
   259		return filterFile(src, f, false)
   260	}
   261	
   262	func 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	//
   285	func FilterPackage(pkg *Package, f Filter) bool {
   286		return filterPackage(pkg, f, false)
   287	}
   288	
   289	func 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.
   303	type MergeMode uint
   304	
   305	const (
   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	//
   319	func 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	//
   339	var 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	//
   344	func 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			for _, f := range pkg.Files {
   478				imports = append(imports, f.Imports...)
   479			}
   480		}
   481	
   482		// Collect comments from all package files.
   483		var comments []*CommentGroup
   484		if mode&FilterUnassociatedComments == 0 {
   485			comments = make([]*CommentGroup, ncomments)
   486			i := 0
   487			for _, f := range pkg.Files {
   488				i += copy(comments[i:], f.Comments)
   489			}
   490		}
   491	
   492		// TODO(gri) need to compute unresolved identifiers!
   493		return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments}
   494	}
   495	

View as plain text