// 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" "go/token" ) // filterIdentList removes unexported names from list in place // and returns the resulting list. func filterIdentList(list []*ast.Ident) []*ast.Ident { j := 0 for _, x := range list { if token.IsExported(x.Name) { list[j] = x j++ } } return list[0:j] } var underscore = ast.NewIdent("_") func filterCompositeLit(lit *ast.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 []ast.Expr, filter Filter, export bool) []ast.Expr { j := 0 for _, exp := range list { switch x := exp.(type) { case *ast.CompositeLit: filterCompositeLit(x, filter, export) case *ast.KeyValueExpr: if x, ok := x.Key.(*ast.Ident); ok && !filter(x.Name) { continue } if x, ok := x.Value.(*ast.CompositeLit); ok { filterCompositeLit(x, filter, export) } } list[j] = exp j++ } return list[0:j] } // updateIdentList replaces all unexported identifiers with underscore // and reports whether at least one exported name exists. func updateIdentList(list []*ast.Ident) (hasExported bool) { for i, x := range list { if token.IsExported(x.Name) { hasExported = true } else { list[i] = underscore } } return hasExported } // hasExportedName reports whether list contains any exported names. func hasExportedName(list []*ast.Ident) bool { for _, x := range list { if x.IsExported() { return true } } return false } // removeAnonymousField removes anonymous fields named name from an interface. func removeAnonymousField(name string, ityp *ast.InterfaceType) { list := ityp.Methods.List // we know that ityp.Methods != nil j := 0 for _, field := range list { keepField := true if n := len(field.Names); n == 0 { // anonymous field if fname, _ := baseTypeName(field.Type); fname == name { keepField = false } } if keepField { list[j] = field j++ } } if j < len(list) { ityp.Incomplete = true } ityp.Methods.List = list[0:j] } // filterFieldList removes unexported fields (field names) from the field list // in place and reports whether fields were removed. Anonymous fields are // recorded with the parent type. filterType is called with the types of // all remaining fields. func (r *reader) filterFieldList(parent *namedType, fields *ast.FieldList, ityp *ast.InterfaceType) (removedFields bool) { if fields == nil { return } list := fields.List j := 0 for _, field := range list { keepField := false if n := len(field.Names); n == 0 { // anonymous field or embedded type or union element fname := r.recordAnonymousField(parent, field.Type) if fname != "" { if token.IsExported(fname) { keepField = true } else if ityp != nil && predeclaredTypes[fname] { // possibly an embedded predeclared type; keep it for now but // remember this interface so that it can be fixed if name is also // defined locally keepField = true r.remember(fname, ityp) } } else { // If we're operating on an interface, assume that this is an embedded // type or union element. // // TODO(rfindley): consider traversing into approximation/unions // elements to see if they are entirely unexported. keepField = ityp != nil } } else { field.Names = filterIdentList(field.Names) if len(field.Names) < n { removedFields = true } if len(field.Names) > 0 { keepField = true } } if keepField { r.filterType(nil, field.Type) list[j] = field j++ } } if j < len(list) { removedFields = true } fields.List = list[0:j] return } // filterParamList applies filterType to each parameter type in fields. func (r *reader) filterParamList(fields *ast.FieldList) { if fields != nil { for _, f := range fields.List { r.filterType(nil, f.Type) } } } // filterType strips any unexported struct fields or method types from typ // in place. If fields (or methods) have been removed, the corresponding // struct or interface type has the Incomplete field set to true. func (r *reader) filterType(parent *namedType, typ ast.Expr) { switch t := typ.(type) { case *ast.Ident: // nothing to do case *ast.ParenExpr: r.filterType(nil, t.X) case *ast.StarExpr: // possibly an embedded type literal r.filterType(nil, t.X) case *ast.UnaryExpr: if t.Op == token.TILDE { // approximation element r.filterType(nil, t.X) } case *ast.BinaryExpr: if t.Op == token.OR { // union r.filterType(nil, t.X) r.filterType(nil, t.Y) } case *ast.ArrayType: r.filterType(nil, t.Elt) case *ast.StructType: if r.filterFieldList(parent, t.Fields, nil) { t.Incomplete = true } case *ast.FuncType: r.filterParamList(t.TypeParams) r.filterParamList(t.Params) r.filterParamList(t.Results) case *ast.InterfaceType: if r.filterFieldList(parent, t.Methods, t) { t.Incomplete = true } case *ast.MapType: r.filterType(nil, t.Key) r.filterType(nil, t.Value) case *ast.ChanType: r.filterType(nil, t.Value) } } func (r *reader) 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.Values = filterExprList(s.Values, token.IsExported, true) if len(s.Values) > 0 || s.Type == nil && len(s.Values) == 0 { // If there are values declared on RHS, just replace the unexported // identifiers on the LHS with underscore, so that it matches // the sequence of expression on the RHS. // // Similarly, if there are no type and values, then this expression // must be following an iota expression, where order matters. if updateIdentList(s.Names) { r.filterType(nil, s.Type) return true } } else { s.Names = filterIdentList(s.Names) if len(s.Names) > 0 { r.filterType(nil, s.Type) return true } } case *ast.TypeSpec: // Don't filter type parameters here, by analogy with function parameters // which are not filtered for top-level function declarations. if name := s.Name.Name; token.IsExported(name) { r.filterType(r.lookupType(s.Name.Name), s.Type) return true } else if IsPredeclared(name) { if r.shadowedPredecl == nil { r.shadowedPredecl = make(map[string]bool) } r.shadowedPredecl[name] = true } } return false } // copyConstType returns a copy of typ with position pos. // typ must be a valid constant type. // In practice, only (possibly qualified) identifiers are possible. func copyConstType(typ ast.Expr, pos token.Pos) ast.Expr { switch typ := typ.(type) { case *ast.Ident: return &ast.Ident{Name: typ.Name, NamePos: pos} case *ast.SelectorExpr: if id, ok := typ.X.(*ast.Ident); ok { // presumably a qualified identifier return &ast.SelectorExpr{ Sel: ast.NewIdent(typ.Sel.Name), X: &ast.Ident{Name: id.Name, NamePos: pos}, } } } return nil // shouldn't happen, but be conservative and don't panic } func (r *reader) filterSpecList(list []ast.Spec, tok token.Token) []ast.Spec { if tok == token.CONST { // Propagate any type information that would get lost otherwise // when unexported constants are filtered. var prevType ast.Expr for _, spec := range list { spec := spec.(*ast.ValueSpec) if spec.Type == nil && len(spec.Values) == 0 && prevType != nil { // provide current spec with an explicit type spec.Type = copyConstType(prevType, spec.Pos()) } if hasExportedName(spec.Names) { // exported names are preserved so there's no need to propagate the type prevType = nil } else { prevType = spec.Type } } } j := 0 for _, s := range list { if r.filterSpec(s) { list[j] = s j++ } } return list[0:j] } func (r *reader) filterDecl(decl ast.Decl) bool { switch d := decl.(type) { case *ast.GenDecl: d.Specs = r.filterSpecList(d.Specs, d.Tok) return len(d.Specs) > 0 case *ast.FuncDecl: // ok to filter these methods early because any // conflicting method will be filtered here, too - // thus, removing these methods early will not lead // to the false removal of possible conflicts return token.IsExported(d.Name.Name) } return false } // fileExports removes unexported declarations from src in place. func (r *reader) fileExports(src *ast.File) { j := 0 for _, d := range src.Decls { if r.filterDecl(d) { src.Decls[j] = d j++ } } src.Decls = src.Decls[0:j] }