go filter 源码

2022-07-15 浏览 (844)

golang filter 代码

文件路径:/src/go/ast/filter.go

/ 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 filterCompositeLit(lit *CompositeLit, filter Filter, export bool) {
	n := len(lit.Elts)
	lit.Elts = filterExprList(lit.Elts, filter, export)
	if len(lit.Elts) < n {
		lit.Incomplete = true
	}
}

func filterExprList(list []Expr, filter Filter, export bool) []Expr {
	j := 0
	for _, exp := range list {
		switch x := exp.(type) {
		case *CompositeLit:
			filterCompositeLit(x, filter, export)
		case *KeyValueExpr:
			if x, ok := x.Key.(*Ident); ok && !filter(x.Name) {
				continue
			}
			if x, ok := x.Value.(*CompositeLit); ok {
				filterCompositeLit(x, filter, export)
			}
		}
		list[j] = exp
		j++
	}
	return list[0:j]
}

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)
		s.Values = filterExprList(s.Values, f, export)
		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 {
		/ Iterate over filenames for deterministic order.
		for _, filename := range filenames {
			f := pkg.Files[filename]
			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 _, filename := range filenames {
			f := pkg.Files[filename]
			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}
}

相关信息

go 源码目录

相关文章

go ast 源码

go ast_test 源码

go commentmap 源码

go commentmap_test 源码

go example_test 源码

go filter_test 源码

go import 源码

go issues_test 源码

go print 源码

go print_test 源码

^