Source file src/pkg/go/ast/filter.go
1 // Copyright 2009 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ast 6 7 import ( 8 "go/token" 9 "sort" 10 ) 11 12 // ---------------------------------------------------------------------------- 13 // Export filtering 14 15 // exportFilter is a special filter function to extract exported nodes. 16 func exportFilter(name string) bool { 17 return IsExported(name) 18 } 19 20 // FileExports trims the AST for a Go source file in place such that 21 // only exported nodes remain: all top-level identifiers which are not exported 22 // and their associated information (such as type, initial value, or function 23 // body) are removed. Non-exported fields and methods of exported types are 24 // stripped. The File.Comments list is not changed. 25 // 26 // FileExports returns true if there are exported declarations; 27 // it returns false otherwise. 28 // 29 func FileExports(src *File) bool { 30 return filterFile(src, exportFilter, true) 31 } 32 33 // PackageExports trims the AST for a Go package in place such that 34 // only exported nodes remain. The pkg.Files list is not changed, so that 35 // file names and top-level package comments don't get lost. 36 // 37 // PackageExports returns true if there are exported declarations; 38 // it returns false otherwise. 39 // 40 func PackageExports(pkg *Package) bool { 41 return filterPackage(pkg, exportFilter, true) 42 } 43 44 // ---------------------------------------------------------------------------- 45 // General filtering 46 47 type Filter func(string) bool 48 49 func filterIdentList(list []*Ident, f Filter) []*Ident { 50 j := 0 51 for _, x := range list { 52 if f(x.Name) { 53 list[j] = x 54 j++ 55 } 56 } 57 return list[0:j] 58 } 59 60 // fieldName assumes that x is the type of an anonymous field and 61 // returns the corresponding field name. If x is not an acceptable 62 // anonymous field, the result is nil. 63 // 64 func fieldName(x Expr) *Ident { 65 switch t := x.(type) { 66 case *Ident: 67 return t 68 case *SelectorExpr: 69 if _, ok := t.X.(*Ident); ok { 70 return t.Sel 71 } 72 case *StarExpr: 73 return fieldName(t.X) 74 } 75 return nil 76 } 77 78 func filterFieldList(fields *FieldList, filter Filter, export bool) (removedFields bool) { 79 if fields == nil { 80 return false 81 } 82 list := fields.List 83 j := 0 84 for _, f := range list { 85 keepField := false 86 if len(f.Names) == 0 { 87 // anonymous field 88 name := fieldName(f.Type) 89 keepField = name != nil && filter(name.Name) 90 } else { 91 n := len(f.Names) 92 f.Names = filterIdentList(f.Names, filter) 93 if len(f.Names) < n { 94 removedFields = true 95 } 96 keepField = len(f.Names) > 0 97 } 98 if keepField { 99 if export { 100 filterType(f.Type, filter, export) 101 } 102 list[j] = f 103 j++ 104 } 105 } 106 if j < len(list) { 107 removedFields = true 108 } 109 fields.List = list[0:j] 110 return 111 } 112 113 func filterParamList(fields *FieldList, filter Filter, export bool) bool { 114 if fields == nil { 115 return false 116 } 117 var b bool 118 for _, f := range fields.List { 119 if filterType(f.Type, filter, export) { 120 b = true 121 } 122 } 123 return b 124 } 125 126 func filterType(typ Expr, f Filter, export bool) bool { 127 switch t := typ.(type) { 128 case *Ident: 129 return f(t.Name) 130 case *ParenExpr: 131 return filterType(t.X, f, export) 132 case *ArrayType: 133 return filterType(t.Elt, f, export) 134 case *StructType: 135 if filterFieldList(t.Fields, f, export) { 136 t.Incomplete = true 137 } 138 return len(t.Fields.List) > 0 139 case *FuncType: 140 b1 := filterParamList(t.Params, f, export) 141 b2 := filterParamList(t.Results, f, export) 142 return b1 || b2 143 case *InterfaceType: 144 if filterFieldList(t.Methods, f, export) { 145 t.Incomplete = true 146 } 147 return len(t.Methods.List) > 0 148 case *MapType: 149 b1 := filterType(t.Key, f, export) 150 b2 := filterType(t.Value, f, export) 151 return b1 || b2 152 case *ChanType: 153 return filterType(t.Value, f, export) 154 } 155 return false 156 } 157 158 func filterSpec(spec Spec, f Filter, export bool) bool { 159 switch s := spec.(type) { 160 case *ValueSpec: 161 s.Names = filterIdentList(s.Names, f) 162 if len(s.Names) > 0 { 163 if export { 164 filterType(s.Type, f, export) 165 } 166 return true 167 } 168 case *TypeSpec: 169 if f(s.Name.Name) { 170 if export { 171 filterType(s.Type, f, export) 172 } 173 return true 174 } 175 if !export { 176 // For general filtering (not just exports), 177 // filter type even if name is not filtered 178 // out. 179 // If the type contains filtered elements, 180 // keep the declaration. 181 return filterType(s.Type, f, export) 182 } 183 } 184 return false 185 } 186 187 func filterSpecList(list []Spec, f Filter, export bool) []Spec { 188 j := 0 189 for _, s := range list { 190 if filterSpec(s, f, export) { 191 list[j] = s 192 j++ 193 } 194 } 195 return list[0:j] 196 } 197 198 // FilterDecl trims the AST for a Go declaration in place by removing 199 // all names (including struct field and interface method names, but 200 // not from parameter lists) that don't pass through the filter f. 201 // 202 // FilterDecl returns true if there are any declared names left after 203 // filtering; it returns false otherwise. 204 // 205 func FilterDecl(decl Decl, f Filter) bool { 206 return filterDecl(decl, f, false) 207 } 208 209 func filterDecl(decl Decl, f Filter, export bool) bool { 210 switch d := decl.(type) { 211 case *GenDecl: 212 d.Specs = filterSpecList(d.Specs, f, export) 213 return len(d.Specs) > 0 214 case *FuncDecl: 215 return f(d.Name.Name) 216 } 217 return false 218 } 219 220 // FilterFile trims the AST for a Go file in place by removing all 221 // names from top-level declarations (including struct field and 222 // interface method names, but not from parameter lists) that don't 223 // pass through the filter f. If the declaration is empty afterwards, 224 // the declaration is removed from the AST. The File.Comments list 225 // is not changed. 226 // 227 // FilterFile returns true if there are any top-level declarations 228 // left after filtering; it returns false otherwise. 229 // 230 func FilterFile(src *File, f Filter) bool { 231 return filterFile(src, f, false) 232 } 233 234 func filterFile(src *File, f Filter, export bool) bool { 235 j := 0 236 for _, d := range src.Decls { 237 if filterDecl(d, f, export) { 238 src.Decls[j] = d 239 j++ 240 } 241 } 242 src.Decls = src.Decls[0:j] 243 return j > 0 244 } 245 246 // FilterPackage trims the AST for a Go package in place by removing 247 // all names from top-level declarations (including struct field and 248 // interface method names, but not from parameter lists) that don't 249 // pass through the filter f. If the declaration is empty afterwards, 250 // the declaration is removed from the AST. The pkg.Files list is not 251 // changed, so that file names and top-level package comments don't get 252 // lost. 253 // 254 // FilterPackage returns true if there are any top-level declarations 255 // left after filtering; it returns false otherwise. 256 // 257 func FilterPackage(pkg *Package, f Filter) bool { 258 return filterPackage(pkg, f, false) 259 } 260 261 func filterPackage(pkg *Package, f Filter, export bool) bool { 262 hasDecls := false 263 for _, src := range pkg.Files { 264 if filterFile(src, f, export) { 265 hasDecls = true 266 } 267 } 268 return hasDecls 269 } 270 271 // ---------------------------------------------------------------------------- 272 // Merging of package files 273 274 // The MergeMode flags control the behavior of MergePackageFiles. 275 type MergeMode uint 276 277 const ( 278 // If set, duplicate function declarations are excluded. 279 FilterFuncDuplicates MergeMode = 1 << iota 280 // If set, comments that are not associated with a specific 281 // AST node (as Doc or Comment) are excluded. 282 FilterUnassociatedComments 283 // If set, duplicate import declarations are excluded. 284 FilterImportDuplicates 285 ) 286 287 // separator is an empty //-style comment that is interspersed between 288 // different comment groups when they are concatenated into a single group 289 // 290 var separator = &Comment{noPos, "//"} 291 292 // MergePackageFiles creates a file AST by merging the ASTs of the 293 // files belonging to a package. The mode flags control merging behavior. 294 // 295 func MergePackageFiles(pkg *Package, mode MergeMode) *File { 296 // Count the number of package docs, comments and declarations across 297 // all package files. Also, compute sorted list of filenames, so that 298 // subsequent iterations can always iterate in the same order. 299 ndocs := 0 300 ncomments := 0 301 ndecls := 0 302 filenames := make([]string, len(pkg.Files)) 303 i := 0 304 for filename, f := range pkg.Files { 305 filenames[i] = filename 306 i++ 307 if f.Doc != nil { 308 ndocs += len(f.Doc.List) + 1 // +1 for separator 309 } 310 ncomments += len(f.Comments) 311 ndecls += len(f.Decls) 312 } 313 sort.Strings(filenames) 314 315 // Collect package comments from all package files into a single 316 // CommentGroup - the collected package documentation. In general 317 // there should be only one file with a package comment; but it's 318 // better to collect extra comments than drop them on the floor. 319 var doc *CommentGroup 320 var pos token.Pos 321 if ndocs > 0 { 322 list := make([]*Comment, ndocs-1) // -1: no separator before first group 323 i := 0 324 for _, filename := range filenames { 325 f := pkg.Files[filename] 326 if f.Doc != nil { 327 if i > 0 { 328 // not the first group - add separator 329 list[i] = separator 330 i++ 331 } 332 for _, c := range f.Doc.List { 333 list[i] = c 334 i++ 335 } 336 if f.Package > pos { 337 // Keep the maximum package clause position as 338 // position for the package clause of the merged 339 // files. 340 pos = f.Package 341 } 342 } 343 } 344 doc = &CommentGroup{list} 345 } 346 347 // Collect declarations from all package files. 348 var decls []Decl 349 if ndecls > 0 { 350 decls = make([]Decl, ndecls) 351 funcs := make(map[string]int) // map of global function name -> decls index 352 i := 0 // current index 353 n := 0 // number of filtered entries 354 for _, filename := range filenames { 355 f := pkg.Files[filename] 356 for _, d := range f.Decls { 357 if mode&FilterFuncDuplicates != 0 { 358 // A language entity may be declared multiple 359 // times in different package files; only at 360 // build time declarations must be unique. 361 // For now, exclude multiple declarations of 362 // functions - keep the one with documentation. 363 // 364 // TODO(gri): Expand this filtering to other 365 // entities (const, type, vars) if 366 // multiple declarations are common. 367 if f, isFun := d.(*FuncDecl); isFun { 368 name := f.Name.Name 369 if j, exists := funcs[name]; exists { 370 // function declared already 371 if decls[j] != nil && decls[j].(*FuncDecl).Doc == nil { 372 // existing declaration has no documentation; 373 // ignore the existing declaration 374 decls[j] = nil 375 } else { 376 // ignore the new declaration 377 d = nil 378 } 379 n++ // filtered an entry 380 } else { 381 funcs[name] = i 382 } 383 } 384 } 385 decls[i] = d 386 i++ 387 } 388 } 389 390 // Eliminate nil entries from the decls list if entries were 391 // filtered. We do this using a 2nd pass in order to not disturb 392 // the original declaration order in the source (otherwise, this 393 // would also invalidate the monotonically increasing position 394 // info within a single file). 395 if n > 0 { 396 i = 0 397 for _, d := range decls { 398 if d != nil { 399 decls[i] = d 400 i++ 401 } 402 } 403 decls = decls[0:i] 404 } 405 } 406 407 // Collect import specs from all package files. 408 var imports []*ImportSpec 409 if mode&FilterImportDuplicates != 0 { 410 seen := make(map[string]bool) 411 for _, filename := range filenames { 412 f := pkg.Files[filename] 413 for _, imp := range f.Imports { 414 if path := imp.Path.Value; !seen[path] { 415 // TODO: consider handling cases where: 416 // - 2 imports exist with the same import path but 417 // have different local names (one should probably 418 // keep both of them) 419 // - 2 imports exist but only one has a comment 420 // - 2 imports exist and they both have (possibly 421 // different) comments 422 imports = append(imports, imp) 423 seen[path] = true 424 } 425 } 426 } 427 } else { 428 for _, f := range pkg.Files { 429 imports = append(imports, f.Imports...) 430 } 431 } 432 433 // Collect comments from all package files. 434 var comments []*CommentGroup 435 if mode&FilterUnassociatedComments == 0 { 436 comments = make([]*CommentGroup, ncomments) 437 i := 0 438 for _, f := range pkg.Files { 439 i += copy(comments[i:], f.Comments) 440 } 441 } 442 443 // TODO(gri) need to compute unresolved identifiers! 444 return &File{doc, pos, NewIdent(pkg.Name), decls, pkg.Scope, imports, nil, comments} 445 }