| // Copyright 2011 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. |
| |
| // This file implements export filtering of an AST. |
| |
| package doc |
| |
| import "go/ast" |
| |
| func filterIdentList(list []*ast.Ident) []*ast.Ident { |
| j := 0 |
| for _, x := range list { |
| if ast.IsExported(x.Name) { |
| list[j] = x |
| j++ |
| } |
| } |
| return list[0:j] |
| } |
| |
| func baseName(x ast.Expr) *ast.Ident { |
| switch t := x.(type) { |
| case *ast.Ident: |
| return t |
| case *ast.SelectorExpr: |
| if _, ok := t.X.(*ast.Ident); ok { |
| return t.Sel |
| } |
| case *ast.StarExpr: |
| return baseName(t.X) |
| } |
| return nil |
| } |
| |
| func (doc *docReader) filterFieldList(tinfo *typeInfo, fields *ast.FieldList) (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 := baseName(f.Type) |
| if name != nil && name.IsExported() { |
| // we keep the field - in this case doc.addDecl |
| // will take care of adding the embedded type |
| keepField = true |
| } else if tinfo != nil { |
| // we don't keep the field - add it as an embedded |
| // type so we won't loose its methods, if any |
| if embedded := doc.lookupTypeInfo(name.Name); embedded != nil { |
| _, ptr := f.Type.(*ast.StarExpr) |
| tinfo.addEmbeddedType(embedded, ptr) |
| } |
| } |
| } else { |
| n := len(f.Names) |
| f.Names = filterIdentList(f.Names) |
| if len(f.Names) < n { |
| removedFields = true |
| } |
| keepField = len(f.Names) > 0 |
| } |
| if keepField { |
| doc.filterType(nil, f.Type) |
| list[j] = f |
| j++ |
| } |
| } |
| if j < len(list) { |
| removedFields = true |
| } |
| fields.List = list[0:j] |
| return |
| } |
| |
| func (doc *docReader) filterParamList(fields *ast.FieldList) bool { |
| if fields == nil { |
| return false |
| } |
| var b bool |
| for _, f := range fields.List { |
| if doc.filterType(nil, f.Type) { |
| b = true |
| } |
| } |
| return b |
| } |
| |
| func (doc *docReader) filterType(tinfo *typeInfo, typ ast.Expr) bool { |
| switch t := typ.(type) { |
| case *ast.Ident: |
| return ast.IsExported(t.Name) |
| case *ast.ParenExpr: |
| return doc.filterType(nil, t.X) |
| case *ast.ArrayType: |
| return doc.filterType(nil, t.Elt) |
| case *ast.StructType: |
| if doc.filterFieldList(tinfo, t.Fields) { |
| t.Incomplete = true |
| } |
| return len(t.Fields.List) > 0 |
| case *ast.FuncType: |
| b1 := doc.filterParamList(t.Params) |
| b2 := doc.filterParamList(t.Results) |
| return b1 || b2 |
| case *ast.InterfaceType: |
| if doc.filterFieldList(tinfo, t.Methods) { |
| t.Incomplete = true |
| } |
| return len(t.Methods.List) > 0 |
| case *ast.MapType: |
| b1 := doc.filterType(nil, t.Key) |
| b2 := doc.filterType(nil, t.Value) |
| return b1 || b2 |
| case *ast.ChanType: |
| return doc.filterType(nil, t.Value) |
| } |
| return false |
| } |
| |
| func (doc *docReader) filterSpec(spec ast.Spec) bool { |
| switch s := spec.(type) { |
| case *ast.ImportSpec: |
| // always keep imports so we can collect them |
| return true |
| case *ast.ValueSpec: |
| s.Names = filterIdentList(s.Names) |
| if len(s.Names) > 0 { |
| doc.filterType(nil, s.Type) |
| return true |
| } |
| case *ast.TypeSpec: |
| if ast.IsExported(s.Name.Name) { |
| doc.filterType(doc.lookupTypeInfo(s.Name.Name), s.Type) |
| return true |
| } |
| } |
| return false |
| } |
| |
| func (doc *docReader) filterSpecList(list []ast.Spec) []ast.Spec { |
| j := 0 |
| for _, s := range list { |
| if doc.filterSpec(s) { |
| list[j] = s |
| j++ |
| } |
| } |
| return list[0:j] |
| } |
| |
| func (doc *docReader) filterDecl(decl ast.Decl) bool { |
| switch d := decl.(type) { |
| case *ast.GenDecl: |
| d.Specs = doc.filterSpecList(d.Specs) |
| return len(d.Specs) > 0 |
| case *ast.FuncDecl: |
| return ast.IsExported(d.Name.Name) |
| } |
| return false |
| } |
| |
| // fileExports trims the AST for a Go file in place such that |
| // only exported nodes remain. fileExports returns true if |
| // there are exported declarations; otherwise it returns false. |
| // |
| func (doc *docReader) fileExports(src *ast.File) bool { |
| j := 0 |
| for _, d := range src.Decls { |
| if doc.filterDecl(d) { |
| src.Decls[j] = d |
| j++ |
| } |
| } |
| src.Decls = src.Decls[0:j] |
| return j > 0 |
| } |