blob: 772407400d46254237382abd6c1bc0a16e692c52 [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, &Ident{noPos, pkg.Name}, decls, nil};
}