...

Source file src/go/ast/filter.go

Documentation: go/ast

		 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  		// 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  

View as plain text