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