github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/src/go/ast/filter.go (about)

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