| // 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 ( |
| "golang.org/x/website/internal/backport/go/ast" |
| "golang.org/x/website/internal/backport/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] |
| } |