src/pkg/go/ast/filter.go - The Go Programming Language

Golang

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	}