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  	"slices"
    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  func FileExports(src *File) bool {
    28  	return filterFile(src, exportFilter, true)
    29  }
    30  
    31  // PackageExports trims the AST for a Go package in place such that
    32  // only exported nodes remain. The pkg.Files list is not changed, so that
    33  // file names and top-level package comments don't get lost.
    34  //
    35  // PackageExports reports whether there are exported declarations;
    36  // it returns false otherwise.
    37  //
    38  // Deprecated: use the type checker [go/types] instead of [Package];
    39  // see [Object]. Alternatively, use [FileExports].
    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  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  func FilterDecl(decl Decl, f Filter) bool {
   233  	return filterDecl(decl, f, false)
   234  }
   235  
   236  func filterDecl(decl Decl, f Filter, export bool) bool {
   237  	switch d := decl.(type) {
   238  	case *GenDecl:
   239  		d.Specs = filterSpecList(d.Specs, f, export)
   240  		return len(d.Specs) > 0
   241  	case *FuncDecl:
   242  		return f(d.Name.Name)
   243  	}
   244  	return false
   245  }
   246  
   247  // FilterFile trims the AST for a Go file in place by removing all
   248  // names from top-level declarations (including struct field and
   249  // interface method names, but not from parameter lists) that don't
   250  // pass through the filter f. If the declaration is empty afterwards,
   251  // the declaration is removed from the AST. Import declarations are
   252  // always removed. The [File.Comments] list is not changed.
   253  //
   254  // FilterFile reports whether there are any top-level declarations
   255  // left after filtering.
   256  func FilterFile(src *File, f Filter) bool {
   257  	return filterFile(src, f, false)
   258  }
   259  
   260  func filterFile(src *File, f Filter, export bool) bool {
   261  	j := 0
   262  	for _, d := range src.Decls {
   263  		if filterDecl(d, f, export) {
   264  			src.Decls[j] = d
   265  			j++
   266  		}
   267  	}
   268  	src.Decls = src.Decls[0:j]
   269  	return j > 0
   270  }
   271  
   272  // FilterPackage trims the AST for a Go package in place by removing
   273  // all names from top-level declarations (including struct field and
   274  // interface method names, but not from parameter lists) that don't
   275  // pass through the filter f. If the declaration is empty afterwards,
   276  // the declaration is removed from the AST. The pkg.Files list is not
   277  // changed, so that file names and top-level package comments don't get
   278  // lost.
   279  //
   280  // FilterPackage reports whether there are any top-level declarations
   281  // left after filtering.
   282  //
   283  // Deprecated: use the type checker [go/types] instead of [Package];
   284  // see [Object]. Alternatively, use [FilterFile].
   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  //
   304  // Deprecated: use the type checker [go/types] instead of [Package];
   305  // see [Object].
   306  type MergeMode uint
   307  
   308  // Deprecated: use the type checker [go/types] instead of [Package];
   309  // see [Object].
   310  const (
   311  	// If set, duplicate function declarations are excluded.
   312  	FilterFuncDuplicates MergeMode = 1 << iota
   313  	// If set, comments that are not associated with a specific
   314  	// AST node (as Doc or Comment) are excluded.
   315  	FilterUnassociatedComments
   316  	// If set, duplicate import declarations are excluded.
   317  	FilterImportDuplicates
   318  )
   319  
   320  // nameOf returns the function (foo) or method name (foo.bar) for
   321  // the given function declaration. If the AST is incorrect for the
   322  // receiver, it assumes a function instead.
   323  func nameOf(f *FuncDecl) string {
   324  	if r := f.Recv; r != nil && len(r.List) == 1 {
   325  		// looks like a correct receiver declaration
   326  		t := r.List[0].Type
   327  		// dereference pointer receiver types
   328  		if p, _ := t.(*StarExpr); p != nil {
   329  			t = p.X
   330  		}
   331  		// the receiver type must be a type name
   332  		if p, _ := t.(*Ident); p != nil {
   333  			return p.Name + "." + f.Name.Name
   334  		}
   335  		// otherwise assume a function instead
   336  	}
   337  	return f.Name.Name
   338  }
   339  
   340  // separator is an empty //-style comment that is interspersed between
   341  // different comment groups when they are concatenated into a single group
   342  var separator = &Comment{token.NoPos, "//"}
   343  
   344  // MergePackageFiles creates a file AST by merging the ASTs of the
   345  // files belonging to a package. The mode flags control merging behavior.
   346  //
   347  // Deprecated: this function is poorly specified and has unfixable
   348  // bugs; also [Package] is deprecated.
   349  func MergePackageFiles(pkg *Package, mode MergeMode) *File {
   350  	// Count the number of package docs, comments and declarations across
   351  	// all package files. Also, compute sorted list of filenames, so that
   352  	// subsequent iterations can always iterate in the same order.
   353  	ndocs := 0
   354  	ncomments := 0
   355  	ndecls := 0
   356  	filenames := make([]string, len(pkg.Files))
   357  	var minPos, maxPos token.Pos
   358  	i := 0
   359  	for filename, f := range pkg.Files {
   360  		filenames[i] = filename
   361  		i++
   362  		if f.Doc != nil {
   363  			ndocs += len(f.Doc.List) + 1 // +1 for separator
   364  		}
   365  		ncomments += len(f.Comments)
   366  		ndecls += len(f.Decls)
   367  		if i == 0 || f.FileStart < minPos {
   368  			minPos = f.FileStart
   369  		}
   370  		if i == 0 || f.FileEnd > maxPos {
   371  			maxPos = f.FileEnd
   372  		}
   373  	}
   374  	slices.Sort(filenames)
   375  
   376  	// Collect package comments from all package files into a single
   377  	// CommentGroup - the collected package documentation. In general
   378  	// there should be only one file with a package comment; but it's
   379  	// better to collect extra comments than drop them on the floor.
   380  	var doc *CommentGroup
   381  	var pos token.Pos
   382  	if ndocs > 0 {
   383  		list := make([]*Comment, ndocs-1) // -1: no separator before first group
   384  		i := 0
   385  		for _, filename := range filenames {
   386  			f := pkg.Files[filename]
   387  			if f.Doc != nil {
   388  				if i > 0 {
   389  					// not the first group - add separator
   390  					list[i] = separator
   391  					i++
   392  				}
   393  				for _, c := range f.Doc.List {
   394  					list[i] = c
   395  					i++
   396  				}
   397  				if f.Package > pos {
   398  					// Keep the maximum package clause position as
   399  					// position for the package clause of the merged
   400  					// files.
   401  					pos = f.Package
   402  				}
   403  			}
   404  		}
   405  		doc = &CommentGroup{list}
   406  	}
   407  
   408  	// Collect declarations from all package files.
   409  	var decls []Decl
   410  	if ndecls > 0 {
   411  		decls = make([]Decl, ndecls)
   412  		funcs := make(map[string]int) // map of func name -> decls index
   413  		i := 0                        // current index
   414  		n := 0                        // number of filtered entries
   415  		for _, filename := range filenames {
   416  			f := pkg.Files[filename]
   417  			for _, d := range f.Decls {
   418  				if mode&FilterFuncDuplicates != 0 {
   419  					// A language entity may be declared multiple
   420  					// times in different package files; only at
   421  					// build time declarations must be unique.
   422  					// For now, exclude multiple declarations of
   423  					// functions - keep the one with documentation.
   424  					//
   425  					// TODO(gri): Expand this filtering to other
   426  					//            entities (const, type, vars) if
   427  					//            multiple declarations are common.
   428  					if f, isFun := d.(*FuncDecl); isFun {
   429  						name := nameOf(f)
   430  						if j, exists := funcs[name]; exists {
   431  							// function declared already
   432  							if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil {
   433  								// existing declaration has no documentation;
   434  								// ignore the existing declaration
   435  								decls[j] = nil
   436  							} else {
   437  								// ignore the new declaration
   438  								d = nil
   439  							}
   440  							n++ // filtered an entry
   441  						} else {
   442  							funcs[name] = i
   443  						}
   444  					}
   445  				}
   446  				decls[i] = d
   447  				i++
   448  			}
   449  		}
   450  
   451  		// Eliminate nil entries from the decls list if entries were
   452  		// filtered. We do this using a 2nd pass in order to not disturb
   453  		// the original declaration order in the source (otherwise, this
   454  		// would also invalidate the monotonically increasing position
   455  		// info within a single file).
   456  		if n > 0 {
   457  			i = 0
   458  			for _, d := range decls {
   459  				if d != nil {
   460  					decls[i] = d
   461  					i++
   462  				}
   463  			}
   464  			decls = decls[0:i]
   465  		}
   466  	}
   467  
   468  	// Collect import specs from all package files.
   469  	var imports []*ImportSpec
   470  	if mode&FilterImportDuplicates != 0 {
   471  		seen := make(map[string]bool)
   472  		for _, filename := range filenames {
   473  			f := pkg.Files[filename]
   474  			for _, imp := range f.Imports {
   475  				if path := imp.Path.Value; !seen[path] {
   476  					// TODO: consider handling cases where:
   477  					// - 2 imports exist with the same import path but
   478  					//   have different local names (one should probably
   479  					//   keep both of them)
   480  					// - 2 imports exist but only one has a comment
   481  					// - 2 imports exist and they both have (possibly
   482  					//   different) comments
   483  					imports = append(imports, imp)
   484  					seen[path] = true
   485  				}
   486  			}
   487  		}
   488  	} else {
   489  		// Iterate over filenames for deterministic order.
   490  		for _, filename := range filenames {
   491  			f := pkg.Files[filename]
   492  			imports = append(imports, f.Imports...)
   493  		}
   494  	}
   495  
   496  	// Collect comments from all package files.
   497  	var comments []*CommentGroup
   498  	if mode&FilterUnassociatedComments == 0 {
   499  		comments = make([]*CommentGroup, ncomments)
   500  		i := 0
   501  		for _, filename := range filenames {
   502  			f := pkg.Files[filename]
   503  			i += copy(comments[i:], f.Comments)
   504  		}
   505  	}
   506  
   507  	// TODO(gri) need to compute unresolved identifiers!
   508  	return &File{doc, pos, NewIdent(pkg.Name), decls, minPos, maxPos, pkg.Scope, imports, nil, comments, ""}
   509  }
   510  

View as plain text