...
Run Format

Source file src/go/ast/filter.go

Documentation: go/ast

  // Copyright 2009 The Go Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style
  // license that can be found in the LICENSE file.
  
  package ast
  
  import (
  	"go/token"
  	"sort"
  )
  
  // ----------------------------------------------------------------------------
  // Export filtering
  
  // exportFilter is a special filter function to extract exported nodes.
  func exportFilter(name string) bool {
  	return IsExported(name)
  }
  
  // FileExports trims the AST for a Go source file in place such that
  // only exported nodes remain: all top-level identifiers which are not exported
  // and their associated information (such as type, initial value, or function
  // body) are removed. Non-exported fields and methods of exported types are
  // stripped. The File.Comments list is not changed.
  //
  // FileExports reports whether there are exported declarations.
  //
  func FileExports(src *File) bool {
  	return filterFile(src, exportFilter, true)
  }
  
  // PackageExports trims the AST for a Go package in place such that
  // only exported nodes remain. The pkg.Files list is not changed, so that
  // file names and top-level package comments don't get lost.
  //
  // PackageExports reports whether there are exported declarations;
  // it returns false otherwise.
  //
  func PackageExports(pkg *Package) bool {
  	return filterPackage(pkg, exportFilter, true)
  }
  
  // ----------------------------------------------------------------------------
  // General filtering
  
  type Filter func(string) bool
  
  func filterIdentList(list []*Ident, f Filter) []*Ident {
  	j := 0
  	for _, x := range list {
  		if f(x.Name) {
  			list[j] = x
  			j++
  		}
  	}
  	return list[0:j]
  }
  
  // fieldName assumes that x is the type of an anonymous field and
  // returns the corresponding field name. If x is not an acceptable
  // anonymous field, the result is nil.
  //
  func fieldName(x Expr) *Ident {
  	switch t := x.(type) {
  	case *Ident:
  		return t
  	case *SelectorExpr:
  		if _, ok := t.X.(*Ident); ok {
  			return t.Sel
  		}
  	case *StarExpr:
  		return fieldName(t.X)
  	}
  	return nil
  }
  
  func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) {
  	if fields == nil {
  		return false
  	}
  	list := fields.List
  	j := 0
  	for _, f := range list {
  		keepField := false
  		if len(f.Names) == 0 {
  			// anonymous field
  			name := fieldName(f.Type)
  			keepField = name != nil && filter(name.Name)
  		} else {
  			n := len(f.Names)
  			f.Names = filterIdentList(f.Names, filter)
  			if len(f.Names) < n {
  				removedFields = true
  			}
  			keepField = len(f.Names) > 0
  		}
  		if keepField {
  			if export {
  				filterType(f.Type, filter, export)
  			}
  			list[j] = f
  			j++
  		}
  	}
  	if j < len(list) {
  		removedFields = true
  	}
  	fields.List = list[0:j]
  	return
  }
  
  func filterParamList(fields *FieldList, filter Filter, export bool) bool {
  	if fields == nil {
  		return false
  	}
  	var b bool
  	for _, f := range fields.List {
  		if filterType(f.Type, filter, export) {
  			b = true
  		}
  	}
  	return b
  }
  
  func filterType(typ Expr, f Filter, export bool) bool {
  	switch t := typ.(type) {
  	case *Ident:
  		return f(t.Name)
  	case *ParenExpr:
  		return filterType(t.X, f, export)
  	case *ArrayType:
  		return filterType(t.Elt, f, export)
  	case *StructType:
  		if filterFieldList(t.Fields, f, export) {
  			t.Incomplete = true
  		}
  		return len(t.Fields.List) > 0
  	case *FuncType:
  		b1 := filterParamList(t.Params, f, export)
  		b2 := filterParamList(t.Results, f, export)
  		return b1 || b2
  	case *InterfaceType:
  		if filterFieldList(t.Methods, f, export) {
  			t.Incomplete = true
  		}
  		return len(t.Methods.List) > 0
  	case *MapType:
  		b1 := filterType(t.Key, f, export)
  		b2 := filterType(t.Value, f, export)
  		return b1 || b2
  	case *ChanType:
  		return filterType(t.Value, f, export)
  	}
  	return false
  }
  
  func filterSpec(spec Spec, f Filter, export bool) bool {
  	switch s := spec.(type) {
  	case *ValueSpec:
  		s.Names = filterIdentList(s.Names, f)
  		if len(s.Names) > 0 {
  			if export {
  				filterType(s.Type, f, export)
  			}
  			return true
  		}
  	case *TypeSpec:
  		if f(s.Name.Name) {
  			if export {
  				filterType(s.Type, f, export)
  			}
  			return true
  		}
  		if !export {
  			// For general filtering (not just exports),
  			// filter type even if name is not filtered
  			// out.
  			// If the type contains filtered elements,
  			// keep the declaration.
  			return filterType(s.Type, f, export)
  		}
  	}
  	return false
  }
  
  func filterSpecList(list []Spec, f Filter, export bool) []Spec {
  	j := 0
  	for _, s := range list {
  		if filterSpec(s, f, export) {
  			list[j] = s
  			j++
  		}
  	}
  	return list[0:j]
  }
  
  // FilterDecl trims the AST for a Go declaration in place by removing
  // all names (including struct field and interface method names, but
  // not from parameter lists) that don't pass through the filter f.
  //
  // FilterDecl reports whether there are any declared names left after
  // filtering.
  //
  func FilterDecl(decl Decl, f Filter) bool {
  	return filterDecl(decl, f, false)
  }
  
  func filterDecl(decl Decl, f Filter, export bool) bool {
  	switch d := decl.(type) {
  	case *GenDecl:
  		d.Specs = filterSpecList(d.Specs, f, export)
  		return len(d.Specs) > 0
  	case *FuncDecl:
  		return f(d.Name.Name)
  	}
  	return false
  }
  
  // FilterFile trims the AST for a Go file in place by removing all
  // names from top-level declarations (including struct field and
  // interface method names, but not from parameter lists) that don't
  // pass through the filter f. If the declaration is empty afterwards,
  // the declaration is removed from the AST. Import declarations are
  // always removed. The File.Comments list is not changed.
  //
  // FilterFile reports whether there are any top-level declarations
  // left after filtering.
  //
  func FilterFile(src *File, f Filter) bool {
  	return filterFile(src, f, false)
  }
  
  func filterFile(src *File, f Filter, export bool) bool {
  	j := 0
  	for _, d := range src.Decls {
  		if filterDecl(d, f, export) {
  			src.Decls[j] = d
  			j++
  		}
  	}
  	src.Decls = src.Decls[0:j]
  	return j > 0
  }
  
  // FilterPackage trims the AST for a Go package in place by removing
  // all names from top-level declarations (including struct field and
  // interface method names, but not from parameter lists) that don't
  // pass through the filter f. If the declaration is empty afterwards,
  // the declaration is removed from the AST. The pkg.Files list is not
  // changed, so that file names and top-level package comments don't get
  // lost.
  //
  // FilterPackage reports whether there are any top-level declarations
  // left after filtering.
  //
  func FilterPackage(pkg *Package, f Filter) bool {
  	return filterPackage(pkg, f, false)
  }
  
  func filterPackage(pkg *Package, f Filter, export bool) bool {
  	hasDecls := false
  	for _, src := range pkg.Files {
  		if filterFile(src, f, export) {
  			hasDecls = true
  		}
  	}
  	return hasDecls
  }
  
  // ----------------------------------------------------------------------------
  // Merging of package files
  
  // The MergeMode flags control the behavior of MergePackageFiles.
  type MergeMode uint
  
  const (
  	// If set, duplicate function declarations are excluded.
  	FilterFuncDuplicates MergeMode = 1 << iota
  	// If set, comments that are not associated with a specific
  	// AST node (as Doc or Comment) are excluded.
  	FilterUnassociatedComments
  	// If set, duplicate import declarations are excluded.
  	FilterImportDuplicates
  )
  
  // nameOf returns the function (foo) or method name (foo.bar) for
  // the given function declaration. If the AST is incorrect for the
  // receiver, it assumes a function instead.
  //
  func nameOf(f *FuncDecl) string {
  	if r := f.Recv; r != nil && len(r.List) == 1 {
  		// looks like a correct receiver declaration
  		t := r.List[0].Type
  		// dereference pointer receiver types
  		if p, _ := t.(*StarExpr); p != nil {
  			t = p.X
  		}
  		// the receiver type must be a type name
  		if p, _ := t.(*Ident); p != nil {
  			return p.Name + "." + f.Name.Name
  		}
  		// otherwise assume a function instead
  	}
  	return f.Name.Name
  }
  
  // separator is an empty //-style comment that is interspersed between
  // different comment groups when they are concatenated into a single group
  //
  var separator = &Comment{token.NoPos, "//"}
  
  // MergePackageFiles creates a file AST by merging the ASTs of the
  // files belonging to a package. The mode flags control merging behavior.
  //
  func MergePackageFiles(pkg *Package, mode MergeMode) *File {
  	// Count the number of package docs, comments and declarations across
  	// all package files. Also, compute sorted list of filenames, so that
  	// subsequent iterations can always iterate in the same order.
  	ndocs := 0
  	ncomments := 0
  	ndecls := 0
  	filenames := make([]string, len(pkg.Files))
  	i := 0
  	for filename, f := range pkg.Files {
  		filenames[i] = filename
  		i++
  		if f.Doc != nil {
  			ndocs += len(f.Doc.List) + 1 // +1 for separator
  		}
  		ncomments += len(f.Comments)
  		ndecls += len(f.Decls)
  	}
  	sort.Strings(filenames)
  
  	// Collect package comments from all package files into a single
  	// CommentGroup - the collected package documentation. In general
  	// there should be only one file with a package comment; but it's
  	// better to collect extra comments than drop them on the floor.
  	var doc *CommentGroup
  	var pos token.Pos
  	if ndocs > 0 {
  		list := make([]*Comment, ndocs-1) // -1: no separator before first group
  		i := 0
  		for _, filename := range filenames {
  			f := pkg.Files[filename]
  			if f.Doc != nil {
  				if i > 0 {
  					// not the first group - add separator
  					list[i] = separator
  					i++
  				}
  				for _, c := range f.Doc.List {
  					list[i] = c
  					i++
  				}
  				if f.Package > pos {
  					// Keep the maximum package clause position as
  					// position for the package clause of the merged
  					// files.
  					pos = f.Package
  				}
  			}
  		}
  		doc = &CommentGroup{list}
  	}
  
  	// Collect declarations from all package files.
  	var decls []Decl
  	if ndecls > 0 {
  		decls = make([]Decl, ndecls)
  		funcs := make(map[string]int) // map of func name -> decls index
  		i := 0                        // current index
  		n := 0                        // number of filtered entries
  		for _, filename := range filenames {
  			f := pkg.Files[filename]
  			for _, d := range f.Decls {
  				if mode&FilterFuncDuplicates != 0 {
  					// A language entity may be declared multiple
  					// times in different package files; only at
  					// build time declarations must be unique.
  					// For now, exclude multiple declarations of
  					// functions - keep the one with documentation.
  					//
  					// TODO(gri): Expand this filtering to other
  					//            entities (const, type, vars) if
  					//            multiple declarations are common.
  					if f, isFun := d.(*FuncDecl); isFun {
  						name := nameOf(f)
  						if j, exists := funcs[name]; exists {
  							// function declared already
  							if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
  								// existing declaration has no documentation;
  								// ignore the existing declaration
  								decls[j] = nil
  							} else {
  								// ignore the new declaration
  								d = nil
  							}
  							n++ // filtered an entry
  						} else {
  							funcs[name] = i
  						}
  					}
  				}
  				decls[i] = d
  				i++
  			}
  		}
  
  		// Eliminate nil entries from the decls list if entries were
  		// filtered. We do this using a 2nd pass in order to not disturb
  		// the original declaration order in the source (otherwise, this
  		// would also invalidate the monotonically increasing position
  		// info within a single file).
  		if n > 0 {
  			i = 0
  			for _, d := range decls {
  				if d != nil {
  					decls[i] = d
  					i++
  				}
  			}
  			decls = decls[0:i]
  		}
  	}
  
  	// Collect import specs from all package files.
  	var imports []*ImportSpec
  	if mode&FilterImportDuplicates != 0 {
  		seen := make(map[string]bool)
  		for _, filename := range filenames {
  			f := pkg.Files[filename]
  			for _, imp := range f.Imports {
  				if path := imp.Path.Value; !seen[path] {
  					// TODO: consider handling cases where:
  					// - 2 imports exist with the same import path but
  					//   have different local names (one should probably
  					//   keep both of them)
  					// - 2 imports exist but only one has a comment
  					// - 2 imports exist and they both have (possibly
  					//   different) comments
  					imports = append(imports, imp)
  					seen[path] = true
  				}
  			}
  		}
  	} else {
  		for _, f := range pkg.Files {
  			imports = append(imports, f.Imports...)
  		}
  	}
  
  	// Collect comments from all package files.
  	var comments []*CommentGroup
  	if mode&FilterUnassociatedComments == 0 {
  		comments = make([]*CommentGroup, ncomments)
  		i := 0
  		for _, f := range pkg.Files {
  			i += copy(comments[i:], f.Comments)
  		}
  	}
  
  	// TODO(gri) need to compute unresolved identifiers!
  	return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments}
  }
  

View as plain text