blob: 3dc87696a682c8894017517bd1cbcd944183631d [file] [log] [blame]
// 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"
func filterIdentList(list []*Ident) []*Ident {
j := 0
for _, x := range list {
if x.IsExported() {
list[j] = x
j++
}
}
return list[0:j]
}
// isExportedType assumes that typ is a correct type.
func isExportedType(typ Expr) bool {
switch t := typ.(type) {
case *Ident:
return t.IsExported()
case *ParenExpr:
return isExportedType(t.X)
case *SelectorExpr:
// assume t.X is a typename
return t.Sel.IsExported()
case *StarExpr:
return isExportedType(t.X)
}
return false
}
func filterFieldList(list []*Field, incomplete *bool) []*Field {
j := 0
for _, f := range list {
exported := false
if len(f.Names) == 0 {
// anonymous field
// (Note that a non-exported anonymous field
// may still refer to a type with exported
// fields, so this is not absolutely correct.
// However, this cannot be done w/o complete
// type information.)
exported = isExportedType(f.Type)
} else {
n := len(f.Names)
f.Names = filterIdentList(f.Names)
if len(f.Names) < n {
*incomplete = true
}
exported = len(f.Names) > 0
}
if exported {
filterType(f.Type)
list[j] = f
j++
}
}
if j < len(list) {
*incomplete = true
}
return list[0:j]
}
func filterParamList(list []*Field) {
for _, f := range list {
filterType(f.Type)
}
}
var noPos token.Position
func filterType(typ Expr) {
switch t := typ.(type) {
case *ArrayType:
filterType(t.Elt)
case *StructType:
t.Fields = filterFieldList(t.Fields, &t.Incomplete)
case *FuncType:
filterParamList(t.Params)
filterParamList(t.Results)
case *InterfaceType:
t.Methods = filterFieldList(t.Methods, &t.Incomplete)
case *MapType:
filterType(t.Key)
filterType(t.Value)
case *ChanType:
filterType(t.Value)
}
}
func filterSpec(spec Spec) bool {
switch s := spec.(type) {
case *ValueSpec:
s.Names = filterIdentList(s.Names)
if len(s.Names) > 0 {
filterType(s.Type)
return true
}
case *TypeSpec:
// TODO(gri) consider stripping forward declarations
// of structs, interfaces, functions, and methods
if s.Name.IsExported() {
filterType(s.Type)
return true
}
}
return false
}
func filterSpecList(list []Spec) []Spec {
j := 0
for _, s := range list {
if filterSpec(s) {
list[j] = s
j++
}
}
return list[0:j]
}
func filterDecl(decl Decl) bool {
switch d := decl.(type) {
case *GenDecl:
d.Specs = filterSpecList(d.Specs)
return len(d.Specs) > 0
case *FuncDecl:
// TODO consider removing function declaration altogether if
// forward declaration (i.e., if d.Body == nil) because
// in that case the actual declaration will come later.
d.Body = nil // strip body
return d.Name.IsExported()
}
return false
}
// 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, and the function bodies of exported functions are set to nil.
// The File.comments list is not changed.
//
// FileExports returns true if there is an exported declaration; it returns
// false otherwise.
//
func FileExports(src *File) bool {
j := 0
for _, d := range src.Decls {
if filterDecl(d) {
src.Decls[j] = d
j++
}
}
src.Decls = src.Decls[0:j]
return j > 0
}
// 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 returns true if there is an exported declaration; it
// returns false otherwise.
//
func PackageExports(pkg *Package) bool {
hasExports := false
for _, f := range pkg.Files {
if FileExports(f) {
hasExports = true
}
}
return hasExports
}
// separator is an empty //-style comment that is interspersed between
// different comment groups when they are concatenated into a single group
//
var separator = &Comment{noPos, []byte{'/', '/'}}
// MergePackageFiles creates a file AST by merging the ASTs of the
// files belonging to a package.
//
func MergePackageFiles(pkg *Package) *File {
// Count the number of package comments and declarations across
// all package files.
ncomments := 0
ndecls := 0
for _, f := range pkg.Files {
if f.Doc != nil {
ncomments += len(f.Doc.List) + 1 // +1 for separator
}
ndecls += len(f.Decls)
}
// Collect package comments from all package files into a single
// CommentGroup - the collected package documentation. The order
// is unspecified. 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
if ncomments > 0 {
list := make([]*Comment, ncomments-1) // -1: no separator before first group
i := 0
for _, f := range pkg.Files {
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++
}
}
}
doc = &CommentGroup{list, nil}
}
// Collect declarations from all package files.
var decls []Decl
if ndecls > 0 {
decls = make([]Decl, ndecls)
i := 0
for _, f := range pkg.Files {
for _, d := range f.Decls {
decls[i] = d
i++
}
}
}
// TODO(gri) Should collect comments as well. For that the comment
// list should be changed back into a []*CommentGroup,
// otherwise need to modify the existing linked list.
return &File{doc, noPos, NewIdent(pkg.Name), decls, nil}
}