github.com/galaxyobe/gen@v0.0.0-20220910125335-392fa8f0990f/third_party/gengo/parser/parse.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 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 parser 18 19 import ( 20 "fmt" 21 "go/ast" 22 "go/build" 23 "go/constant" 24 "go/parser" 25 "go/token" 26 tc "go/types" 27 "io/ioutil" 28 "os" 29 "os/exec" 30 "path" 31 "path/filepath" 32 "regexp" 33 "sort" 34 "strings" 35 36 "k8s.io/gengo/types" 37 "k8s.io/klog/v2" 38 39 tptypes "github.com/galaxyobe/gen/third_party/gengo/types" 40 ) 41 42 // This clarifies when a pkg path has been canonicalized. 43 type importPathString string 44 45 // Builder lets you add all the go files in all the packages that you care 46 // about, then constructs the type source data. 47 type Builder struct { 48 context *build.Context 49 50 // If true, include *_test.go 51 IncludeTestFiles bool 52 53 // Map of package names to more canonical information about the package. 54 // This might hold the same value for multiple names, e.g. if someone 55 // referenced ./pkg/name or in the case of vendoring, which canonicalizes 56 // differently that what humans would type. 57 // 58 // This must only be accessed via getLoadedBuildPackage and setLoadedBuildPackage 59 buildPackages map[importPathString]*build.Package 60 61 fset *token.FileSet 62 // map of package path to list of parsed files 63 parsed map[importPathString][]parsedFile 64 // map of package path to absolute path (to prevent overlap) 65 absPaths map[importPathString]string 66 67 // Set by typeCheckPackage(), used by importPackage() and friends. 68 typeCheckedPackages map[importPathString]*tc.Package 69 70 // Map of package path to whether the user requested it or it was from 71 // an import. 72 userRequested map[importPathString]bool 73 74 // All comments from everywhere in every parsed file. 75 endLineToCommentGroup map[fileLine]*ast.CommentGroup 76 77 // map of package to list of packages it imports. 78 importGraph map[importPathString]map[string]struct{} 79 } 80 81 // parsedFile is for tracking files with name 82 type parsedFile struct { 83 name string 84 file *ast.File 85 } 86 87 // key type for finding comments. 88 type fileLine struct { 89 file string 90 line int 91 } 92 93 // New constructs a new builder. 94 func New() *Builder { 95 c := build.Default 96 if c.GOROOT == "" { 97 if p, err := exec.Command("which", "go").CombinedOutput(); err == nil { 98 // The returned string will have some/path/bin/go, so remove the last two elements. 99 c.GOROOT = filepath.Dir(filepath.Dir(strings.Trim(string(p), "\n"))) 100 } else { 101 klog.Warningf("Warning: $GOROOT not set, and unable to run `which go` to find it: %v\n", err) 102 } 103 } 104 // Force this to off, since we don't properly parse CGo. All symbols must 105 // have non-CGo equivalents. 106 c.CgoEnabled = false 107 return &Builder{ 108 context: &c, 109 buildPackages: map[importPathString]*build.Package{}, 110 typeCheckedPackages: map[importPathString]*tc.Package{}, 111 fset: token.NewFileSet(), 112 parsed: map[importPathString][]parsedFile{}, 113 absPaths: map[importPathString]string{}, 114 userRequested: map[importPathString]bool{}, 115 endLineToCommentGroup: map[fileLine]*ast.CommentGroup{}, 116 importGraph: map[importPathString]map[string]struct{}{}, 117 } 118 } 119 120 // AddBuildTags adds the specified build tags to the parse context. 121 func (b *Builder) AddBuildTags(tags ...string) { 122 b.context.BuildTags = append(b.context.BuildTags, tags...) 123 } 124 125 func (b *Builder) getLoadedBuildPackage(importPath string) (*build.Package, bool) { 126 canonicalized := canonicalizeImportPath(importPath) 127 if string(canonicalized) != importPath { 128 klog.V(5).Infof("getLoadedBuildPackage: %s normalized to %s", importPath, canonicalized) 129 } 130 buildPkg, ok := b.buildPackages[canonicalized] 131 return buildPkg, ok 132 } 133 func (b *Builder) setLoadedBuildPackage(importPath string, buildPkg *build.Package) { 134 canonicalizedImportPath := canonicalizeImportPath(importPath) 135 if string(canonicalizedImportPath) != importPath { 136 klog.V(5).Infof("setLoadedBuildPackage: importPath %s normalized to %s", importPath, canonicalizedImportPath) 137 } 138 139 canonicalizedBuildPkgImportPath := canonicalizeImportPath(buildPkg.ImportPath) 140 if string(canonicalizedBuildPkgImportPath) != buildPkg.ImportPath { 141 klog.V(5).Infof("setLoadedBuildPackage: buildPkg.ImportPath %s normalized to %s", buildPkg.ImportPath, canonicalizedBuildPkgImportPath) 142 } 143 144 if canonicalizedImportPath != canonicalizedBuildPkgImportPath { 145 klog.V(5).Infof("setLoadedBuildPackage: normalized importPath (%s) differs from buildPkg.ImportPath (%s)", canonicalizedImportPath, canonicalizedBuildPkgImportPath) 146 } 147 b.buildPackages[canonicalizedImportPath] = buildPkg 148 b.buildPackages[canonicalizedBuildPkgImportPath] = buildPkg 149 } 150 151 // Get package information from the go/build package. Automatically excludes 152 // e.g. test files and files for other platforms-- there is quite a bit of 153 // logic of that nature in the build package. 154 func (b *Builder) importBuildPackage(dir string) (*build.Package, error) { 155 if buildPkg, ok := b.getLoadedBuildPackage(dir); ok { 156 return buildPkg, nil 157 } 158 // This validates the `package foo // github.com/bar/foo` comments. 159 buildPkg, err := b.importWithMode(dir, build.ImportComment) 160 if err != nil { 161 if _, ok := err.(*build.NoGoError); !ok { 162 return nil, fmt.Errorf("unable to import %q: %v", dir, err) 163 } 164 } 165 if buildPkg == nil { 166 // Might be an empty directory. Try to just find the dir. 167 buildPkg, err = b.importWithMode(dir, build.FindOnly) 168 if err != nil { 169 return nil, err 170 } 171 } 172 173 // Remember it under the user-provided name. 174 klog.V(5).Infof("saving buildPackage %s", dir) 175 b.setLoadedBuildPackage(dir, buildPkg) 176 177 return buildPkg, nil 178 } 179 180 // AddFileForTest adds a file to the set, without verifying that the provided 181 // pkg actually exists on disk. The pkg must be of the form "canonical/pkg/path" 182 // and the path must be the absolute path to the file. Because this bypasses 183 // the normal recursive finding of package dependencies (on disk), test should 184 // sort their test files topologically first, so all deps are resolved by the 185 // time we need them. 186 func (b *Builder) AddFileForTest(pkg string, path string, src []byte) error { 187 if err := b.addFile(importPathString(pkg), path, src, true); err != nil { 188 return err 189 } 190 if _, err := b.typeCheckPackage(importPathString(pkg), true); err != nil { 191 return err 192 } 193 return nil 194 } 195 196 // addFile adds a file to the set. The pkgPath must be of the form 197 // "canonical/pkg/path" and the path must be the absolute path to the file. A 198 // flag indicates whether this file was user-requested or just from following 199 // the import graph. 200 func (b *Builder) addFile(pkgPath importPathString, path string, src []byte, userRequested bool) error { 201 for _, p := range b.parsed[pkgPath] { 202 if path == p.name { 203 klog.V(5).Infof("addFile %s %s already parsed, skipping", pkgPath, path) 204 return nil 205 } 206 } 207 klog.V(6).Infof("addFile %s %s", pkgPath, path) 208 p, err := parser.ParseFile(b.fset, path, src, parser.DeclarationErrors|parser.ParseComments) 209 if err != nil { 210 return err 211 } 212 213 // This is redundant with addDir, but some tests call AddFileForTest, which 214 // call into here without calling addDir. 215 b.userRequested[pkgPath] = userRequested || b.userRequested[pkgPath] 216 217 b.parsed[pkgPath] = append(b.parsed[pkgPath], parsedFile{path, p}) 218 for _, c := range p.Comments { 219 position := b.fset.Position(c.End()) 220 b.endLineToCommentGroup[fileLine{position.Filename, position.Line}] = c 221 } 222 223 // We have to get the packages from this specific file, in case the 224 // user added individual files instead of entire directories. 225 if b.importGraph[pkgPath] == nil { 226 b.importGraph[pkgPath] = map[string]struct{}{} 227 } 228 for _, im := range p.Imports { 229 importedPath := strings.Trim(im.Path.Value, `"`) 230 b.importGraph[pkgPath][importedPath] = struct{}{} 231 } 232 return nil 233 } 234 235 // AddDir adds an entire directory, scanning it for go files. 'dir' should have 236 // a single go package in it. GOPATH, GOROOT, and the location of your go 237 // binary (`which go`) will all be searched if dir doesn't literally resolve. 238 func (b *Builder) AddDir(dir string) error { 239 _, err := b.importPackage(dir, true) 240 return err 241 } 242 243 // AddDirRecursive is just like AddDir, but it also recursively adds 244 // subdirectories; it returns an error only if the path couldn't be resolved; 245 // any directories recursed into without go source are ignored. 246 func (b *Builder) AddDirRecursive(dir string) error { 247 // Add the root. 248 if _, err := b.importPackage(dir, true); err != nil { 249 klog.Warningf("Ignoring directory %v: %v", dir, err) 250 } 251 252 // filepath.Walk does not follow symlinks. We therefore evaluate symlinks and use that with 253 // filepath.Walk. 254 buildPkg, ok := b.getLoadedBuildPackage(dir) 255 if !ok { 256 return fmt.Errorf("no loaded build package for %s", dir) 257 } 258 realPath, err := filepath.EvalSymlinks(buildPkg.Dir) 259 if err != nil { 260 return err 261 } 262 263 fn := func(filePath string, info os.FileInfo, err error) error { 264 if info != nil && info.IsDir() { 265 rel := filepath.ToSlash(strings.TrimPrefix(filePath, realPath)) 266 if rel != "" { 267 // Make a pkg path. 268 buildPkg, ok := b.getLoadedBuildPackage(dir) 269 if !ok { 270 return fmt.Errorf("no loaded build package for %s", dir) 271 } 272 pkg := path.Join(string(canonicalizeImportPath(buildPkg.ImportPath)), rel) 273 274 // Add it. 275 if _, err := b.importPackage(pkg, true); err != nil { 276 klog.Warningf("Ignoring child directory %v: %v", pkg, err) 277 } 278 } 279 } 280 return nil 281 } 282 if err := filepath.Walk(realPath, fn); err != nil { 283 return err 284 } 285 return nil 286 } 287 288 // AddDirTo adds an entire directory to a given Universe. Unlike AddDir, this 289 // processes the package immediately, which makes it safe to use from within a 290 // generator (rather than just at init time. 'dir' must be a single go package. 291 // GOPATH, GOROOT, and the location of your go binary (`which go`) will all be 292 // searched if dir doesn't literally resolve. 293 // Deprecated. Please use AddDirectoryTo. 294 func (b *Builder) AddDirTo(dir string, u *types.Universe) error { 295 // We want all types from this package, as if they were directly added 296 // by the user. They WERE added by the user, in effect. 297 if _, err := b.importPackage(dir, true); err != nil { 298 return err 299 } 300 pkg, ok := b.getLoadedBuildPackage(dir) 301 if !ok { 302 return fmt.Errorf("no such package: %q", dir) 303 } 304 return b.findTypesIn(canonicalizeImportPath(pkg.ImportPath), u) 305 } 306 307 // AddDirectoryTo adds an entire directory to a given Universe. Unlike AddDir, 308 // this processes the package immediately, which makes it safe to use from 309 // within a generator (rather than just at init time. 'dir' must be a single go 310 // package. GOPATH, GOROOT, and the location of your go binary (`which go`) 311 // will all be searched if dir doesn't literally resolve. 312 func (b *Builder) AddDirectoryTo(dir string, u *types.Universe) (*types.Package, error) { 313 // We want all types from this package, as if they were directly added 314 // by the user. They WERE added by the user, in effect. 315 if _, err := b.importPackage(dir, true); err != nil { 316 return nil, err 317 } 318 pkg, ok := b.getLoadedBuildPackage(dir) 319 if !ok || pkg == nil { 320 return nil, fmt.Errorf("no such package: %q", dir) 321 } 322 path := canonicalizeImportPath(pkg.ImportPath) 323 if err := b.findTypesIn(path, u); err != nil { 324 return nil, err 325 } 326 return u.Package(string(path)), nil 327 } 328 329 // The implementation of AddDir. A flag indicates whether this directory was 330 // user-requested or just from following the import graph. 331 func (b *Builder) addDir(dir string, userRequested bool) error { 332 klog.V(5).Infof("addDir %s", dir) 333 buildPkg, err := b.importBuildPackage(dir) 334 if err != nil { 335 return err 336 } 337 canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath) 338 pkgPath := canonicalPackage 339 if dir != string(canonicalPackage) { 340 klog.V(5).Infof("addDir %s, canonical path is %s", dir, pkgPath) 341 } 342 343 // Sanity check the pkg dir has not changed. 344 if prev, found := b.absPaths[pkgPath]; found { 345 if buildPkg.Dir != prev { 346 return fmt.Errorf("package %q (%s) previously resolved to %s", pkgPath, buildPkg.Dir, prev) 347 } 348 } else { 349 b.absPaths[pkgPath] = buildPkg.Dir 350 } 351 352 files := []string{} 353 files = append(files, buildPkg.GoFiles...) 354 if b.IncludeTestFiles { 355 files = append(files, buildPkg.TestGoFiles...) 356 } 357 358 for _, file := range files { 359 if !strings.HasSuffix(file, ".go") { 360 continue 361 } 362 absPath := filepath.Join(buildPkg.Dir, file) 363 data, err := ioutil.ReadFile(absPath) 364 if err != nil { 365 return fmt.Errorf("while loading %q: %v", absPath, err) 366 } 367 err = b.addFile(pkgPath, absPath, data, userRequested) 368 if err != nil { 369 return fmt.Errorf("while parsing %q: %v", absPath, err) 370 } 371 } 372 return nil 373 } 374 375 // regexErrPackageNotFound helps test the expected error for not finding a package. 376 var regexErrPackageNotFound = regexp.MustCompile(`^unable to import ".*?":.*`) 377 378 func isErrPackageNotFound(err error) bool { 379 return regexErrPackageNotFound.MatchString(err.Error()) 380 } 381 382 // importPackage is a function that will be called by the type check package when it 383 // needs to import a go package. 'path' is the import path. 384 func (b *Builder) importPackage(dir string, userRequested bool) (*tc.Package, error) { 385 klog.V(5).Infof("importPackage %s", dir) 386 387 var pkgPath = importPathString(dir) 388 389 // Get the canonical path if we can. 390 if buildPkg, _ := b.getLoadedBuildPackage(dir); buildPkg != nil { 391 canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath) 392 klog.V(5).Infof("importPackage %s, canonical path is %s", dir, canonicalPackage) 393 pkgPath = canonicalPackage 394 } 395 396 // If we have not seen this before, process it now. 397 ignoreError := false 398 if _, found := b.parsed[pkgPath]; !found { 399 // Ignore errors in paths that we're importing solely because 400 // they're referenced by other packages. 401 ignoreError = true 402 403 // Add it. 404 if err := b.addDir(dir, userRequested); err != nil { 405 if isErrPackageNotFound(err) { 406 klog.V(6).Info(err) 407 return nil, nil 408 } 409 410 return nil, err 411 } 412 413 // Get the canonical path now that it has been added. 414 if buildPkg, _ := b.getLoadedBuildPackage(dir); buildPkg != nil { 415 canonicalPackage := canonicalizeImportPath(buildPkg.ImportPath) 416 klog.V(5).Infof("importPackage %s, canonical path is %s", dir, canonicalPackage) 417 pkgPath = canonicalPackage 418 } 419 } 420 421 // If it was previously known, just check that the user-requestedness hasn't 422 // changed. 423 b.userRequested[pkgPath] = userRequested || b.userRequested[pkgPath] 424 425 // Run the type checker. We may end up doing this to pkgs that are already 426 // done, or are in the queue to be done later, but it will short-circuit, 427 // and we can't miss pkgs that are only depended on. 428 pkg, err := b.typeCheckPackage(pkgPath, !ignoreError) 429 if err != nil { 430 switch { 431 case ignoreError && pkg != nil: 432 klog.V(4).Infof("type checking encountered some issues in %q, but ignoring.\n", pkgPath) 433 case !ignoreError && pkg != nil: 434 klog.V(3).Infof("type checking encountered some errors in %q\n", pkgPath) 435 return nil, err 436 default: 437 return nil, err 438 } 439 } 440 441 return pkg, nil 442 } 443 444 type importAdapter struct { 445 b *Builder 446 } 447 448 func (a importAdapter) Import(path string) (*tc.Package, error) { 449 return a.b.importPackage(path, false) 450 } 451 452 // typeCheckPackage will attempt to return the package even if there are some 453 // errors, so you may check whether the package is nil or not even if you get 454 // an error. 455 func (b *Builder) typeCheckPackage(pkgPath importPathString, logErr bool) (*tc.Package, error) { 456 klog.V(5).Infof("typeCheckPackage %s", pkgPath) 457 if pkg, ok := b.typeCheckedPackages[pkgPath]; ok { 458 if pkg != nil { 459 klog.V(6).Infof("typeCheckPackage %s already done", pkgPath) 460 return pkg, nil 461 } 462 // We store a nil right before starting work on a package. So 463 // if we get here and it's present and nil, that means there's 464 // another invocation of this function on the call stack 465 // already processing this package. 466 return nil, fmt.Errorf("circular dependency for %q", pkgPath) 467 } 468 parsedFiles, ok := b.parsed[pkgPath] 469 if !ok { 470 return nil, fmt.Errorf("No files for pkg %q", pkgPath) 471 } 472 files := make([]*ast.File, len(parsedFiles)) 473 for i := range parsedFiles { 474 files[i] = parsedFiles[i].file 475 } 476 b.typeCheckedPackages[pkgPath] = nil 477 c := tc.Config{ 478 IgnoreFuncBodies: true, 479 // Note that importAdapter can call b.importPackage which calls this 480 // method. So there can't be cycles in the import graph. 481 Importer: importAdapter{b}, 482 Error: func(err error) { 483 if logErr { 484 klog.V(2).Infof("type checker: %v\n", err) 485 } else { 486 klog.V(3).Infof("type checker: %v\n", err) 487 } 488 }, 489 } 490 pkg, err := c.Check(string(pkgPath), b.fset, files, nil) 491 b.typeCheckedPackages[pkgPath] = pkg // record the result whether or not there was an error 492 return pkg, err 493 } 494 495 // FindPackages fetches a list of the user-imported packages. 496 // Note that you need to call b.FindTypes() first. 497 func (b *Builder) FindPackages() []string { 498 // Iterate packages in a predictable order. 499 pkgPaths := []string{} 500 for k := range b.typeCheckedPackages { 501 pkgPaths = append(pkgPaths, string(k)) 502 } 503 sort.Strings(pkgPaths) 504 505 result := []string{} 506 for _, pkgPath := range pkgPaths { 507 if b.userRequested[importPathString(pkgPath)] { 508 // Since walkType is recursive, all types that are in packages that 509 // were directly mentioned will be included. We don't need to 510 // include all types in all transitive packages, though. 511 result = append(result, pkgPath) 512 } 513 } 514 return result 515 } 516 517 // FindTypes finalizes the package imports, and searches through all the 518 // packages for types. 519 func (b *Builder) FindTypes() (types.Universe, error) { 520 // Take a snapshot of pkgs to iterate, since this will recursively mutate 521 // b.parsed. Iterate in a predictable order. 522 pkgPaths := []string{} 523 for pkgPath := range b.parsed { 524 pkgPaths = append(pkgPaths, string(pkgPath)) 525 } 526 sort.Strings(pkgPaths) 527 528 u := types.Universe{} 529 for _, pkgPath := range pkgPaths { 530 if err := b.findTypesIn(importPathString(pkgPath), &u); err != nil { 531 return nil, err 532 } 533 } 534 return u, nil 535 } 536 537 // addCommentsToType takes any accumulated comment lines prior to obj and 538 // attaches them to the type t. 539 func (b *Builder) addCommentsToType(obj tc.Object, t *types.Type) { 540 c1 := b.priorCommentLines(obj.Pos(), 1) 541 // c1.Text() is safe if c1 is nil 542 t.CommentLines = splitLines(c1.Text()) 543 if c1 == nil { 544 t.SecondClosestCommentLines = splitLines(b.priorCommentLines(obj.Pos(), 2).Text()) 545 } else { 546 t.SecondClosestCommentLines = splitLines(b.priorCommentLines(c1.List[0].Slash, 2).Text()) 547 } 548 } 549 550 // findTypesIn finalizes the package import and searches through the package 551 // for types. 552 func (b *Builder) findTypesIn(pkgPath importPathString, u *types.Universe) error { 553 klog.V(5).Infof("findTypesIn %s", pkgPath) 554 pkg := b.typeCheckedPackages[pkgPath] 555 if pkg == nil { 556 return fmt.Errorf("findTypesIn(%s): package is not known", pkgPath) 557 } 558 if !b.userRequested[pkgPath] { 559 // Since walkType is recursive, all types that the 560 // packages they asked for depend on will be included. 561 // But we don't need to include all types in all 562 // *packages* they depend on. 563 klog.V(5).Infof("findTypesIn %s: package is not user requested", pkgPath) 564 return nil 565 } 566 567 // We're keeping this package. This call will create the record. 568 u.Package(string(pkgPath)).Name = pkg.Name() 569 u.Package(string(pkgPath)).Path = pkg.Path() 570 u.Package(string(pkgPath)).SourcePath = b.absPaths[pkgPath] 571 572 for _, f := range b.parsed[pkgPath] { 573 if _, fileName := filepath.Split(f.name); fileName == "doc.go" { 574 tp := u.Package(string(pkgPath)) 575 // findTypesIn might be called multiple times. Clean up tp.Comments 576 // to avoid repeatedly fill same comments to it. 577 tp.Comments = []string{} 578 for i := range f.file.Comments { 579 tp.Comments = append(tp.Comments, splitLines(f.file.Comments[i].Text())...) 580 } 581 if f.file.Doc != nil { 582 tp.DocComments = splitLines(f.file.Doc.Text()) 583 } 584 } 585 } 586 587 s := pkg.Scope() 588 for _, n := range s.Names() { 589 obj := s.Lookup(n) 590 tn, ok := obj.(*tc.TypeName) 591 if ok { 592 t := b.walkType(*u, nil, tn.Type()) 593 b.addCommentsToType(obj, t) 594 } 595 tf, ok := obj.(*tc.Func) 596 // We only care about functions, not concrete/abstract methods. 597 if ok && tf.Type() != nil && tf.Type().(*tc.Signature).Recv() == nil { 598 t := b.addFunction(*u, nil, tf) 599 b.addCommentsToType(obj, t) 600 } 601 tv, ok := obj.(*tc.Var) 602 if ok && !tv.IsField() { 603 t := b.addVariable(*u, nil, tv) 604 b.addCommentsToType(obj, t) 605 } 606 tconst, ok := obj.(*tc.Const) 607 if ok { 608 t := b.addConstant(*u, nil, tconst) 609 b.addCommentsToType(obj, t) 610 } 611 } 612 613 importedPkgs := []string{} 614 for k := range b.importGraph[pkgPath] { 615 importedPkgs = append(importedPkgs, string(k)) 616 } 617 sort.Strings(importedPkgs) 618 for _, p := range importedPkgs { 619 u.AddImports(string(pkgPath), p) 620 } 621 return nil 622 } 623 624 func (b *Builder) importWithMode(dir string, mode build.ImportMode) (*build.Package, error) { 625 // This is a bit of a hack. The srcDir argument to Import() should 626 // properly be the dir of the file which depends on the package to be 627 // imported, so that vendoring can work properly and local paths can 628 // resolve. We assume that there is only one level of vendoring, and that 629 // the CWD is inside the GOPATH, so this should be safe. Nobody should be 630 // using local (relative) paths except on the CLI, so CWD is also 631 // sufficient. 632 cwd, err := os.Getwd() 633 if err != nil { 634 return nil, fmt.Errorf("unable to get current directory: %v", err) 635 } 636 637 // normalize to drop /vendor/ if present 638 dir = string(canonicalizeImportPath(dir)) 639 640 buildPkg, err := b.context.Import(filepath.ToSlash(dir), cwd, mode) 641 if err != nil { 642 return nil, err 643 } 644 return buildPkg, nil 645 } 646 647 // if there's a comment on the line `lines` before pos, return its text, otherwise "". 648 func (b *Builder) priorCommentLines(pos token.Pos, lines int) *ast.CommentGroup { 649 position := b.fset.Position(pos) 650 key := fileLine{position.Filename, position.Line - lines} 651 return b.endLineToCommentGroup[key] 652 } 653 654 func splitLines(str string) []string { 655 return strings.Split(strings.TrimRight(str, "\n"), "\n") 656 } 657 658 func tcFuncNameToName(in string) types.Name { 659 name := strings.TrimPrefix(in, "func ") 660 nameParts := strings.Split(name, "(") 661 return tcNameToName(nameParts[0]) 662 } 663 664 func tcVarNameToName(in string) types.Name { 665 nameParts := strings.Split(in, " ") 666 // nameParts[0] is "var". 667 // nameParts[2:] is the type of the variable, we ignore it for now. 668 return tcNameToName(nameParts[1]) 669 } 670 671 func tcNameToName(in string) types.Name { 672 // Detect anonymous type names. (These may have '.' characters because 673 // embedded types may have packages, so we detect them specially.) 674 if strings.HasPrefix(in, "struct{") || 675 strings.HasPrefix(in, "<-chan") || 676 strings.HasPrefix(in, "chan<-") || 677 strings.HasPrefix(in, "chan ") || 678 strings.HasPrefix(in, "func(") || 679 strings.HasPrefix(in, "func (") || 680 strings.HasPrefix(in, "*") || 681 strings.HasPrefix(in, "map[") || 682 strings.HasPrefix(in, "[") { 683 return types.Name{Name: in} 684 } 685 686 // Otherwise, if there are '.' characters present, the name has a 687 // package path in front. 688 nameParts := strings.Split(in, ".") 689 name := types.Name{Name: in} 690 if n := len(nameParts); n >= 2 { 691 // The final "." is the name of the type--previous ones must 692 // have been in the package path. 693 name.Package, name.Name = strings.Join(nameParts[:n-1], "."), nameParts[n-1] 694 } 695 return name 696 } 697 698 func (b *Builder) convertSignature(u types.Universe, t *tc.Signature) *types.Signature { 699 signature := &types.Signature{} 700 for i := 0; i < t.Params().Len(); i++ { 701 signature.Parameters = append(signature.Parameters, b.walkType(u, nil, t.Params().At(i).Type())) 702 signature.ParameterNames = append(signature.ParameterNames, t.Params().At(i).Name()) 703 } 704 for i := 0; i < t.Results().Len(); i++ { 705 signature.Results = append(signature.Results, b.walkType(u, nil, t.Results().At(i).Type())) 706 signature.ResultNames = append(signature.ResultNames, t.Results().At(i).Name()) 707 } 708 if r := t.Recv(); r != nil { 709 signature.Receiver = b.walkType(u, nil, r.Type()) 710 } 711 signature.Variadic = t.Variadic() 712 return signature 713 } 714 715 // walkType adds the type, and any necessary child types. 716 func (b *Builder) walkType(u types.Universe, useName *types.Name, in tc.Type) *types.Type { 717 // Most of the cases are underlying types of the named type. 718 name := tcNameToName(in.String()) 719 if useName != nil { 720 name = *useName 721 } 722 723 switch t := in.(type) { 724 case *tc.Struct: 725 out := u.Type(name) 726 if out.Kind != types.Unknown { 727 return out 728 } 729 out.Kind = types.Struct 730 for i := 0; i < t.NumFields(); i++ { 731 f := t.Field(i) 732 m := types.Member{ 733 Name: f.Name(), 734 Embedded: f.Anonymous(), 735 Tags: t.Tag(i), 736 Type: b.walkType(u, nil, f.Type()), 737 CommentLines: splitLines(b.priorCommentLines(f.Pos(), 1).Text()), 738 } 739 out.Members = append(out.Members, m) 740 } 741 return out 742 case *tc.Map: 743 out := u.Type(name) 744 if out.Kind != types.Unknown { 745 return out 746 } 747 out.Kind = types.Map 748 out.Elem = b.walkType(u, nil, t.Elem()) 749 out.Key = b.walkType(u, nil, t.Key()) 750 return out 751 case *tc.Pointer: 752 out := u.Type(name) 753 if out.Kind != types.Unknown { 754 return out 755 } 756 out.Kind = types.Pointer 757 out.Elem = b.walkType(u, nil, t.Elem()) 758 return out 759 case *tc.Slice: 760 out := u.Type(name) 761 if out.Kind != types.Unknown { 762 return out 763 } 764 out.Kind = types.Slice 765 out.Elem = b.walkType(u, nil, t.Elem()) 766 return out 767 case *tc.Array: 768 out := u.Type(name) 769 if out.Kind != types.Unknown { 770 return out 771 } 772 out.Kind = types.Array 773 out.Elem = b.walkType(u, nil, t.Elem()) 774 out.Len = in.(*tc.Array).Len() 775 return out 776 case *tc.Chan: 777 out := u.Type(name) 778 if out.Kind != types.Unknown { 779 return out 780 } 781 out.Kind = types.Chan 782 out.Elem = b.walkType(u, nil, t.Elem()) 783 // TODO: need to store direction, otherwise raw type name 784 // cannot be properly written. 785 return out 786 case *tc.Basic: 787 out := u.Type(types.Name{ 788 Package: "", 789 Name: t.Name(), 790 }) 791 // modify basic types 792 out = tptypes.ModifyType(u, t.Name(), out) 793 if out.Kind != types.Unknown { 794 return out 795 } 796 out.Kind = types.Unsupported 797 return out 798 case *tc.Signature: 799 out := u.Type(name) 800 if out.Kind != types.Unknown { 801 return out 802 } 803 out.Kind = types.Func 804 out.Signature = b.convertSignature(u, t) 805 return out 806 case *tc.Interface: 807 out := u.Type(name) 808 if out.Kind != types.Unknown { 809 return out 810 } 811 out.Kind = types.Interface 812 t.Complete() 813 for i := 0; i < t.NumMethods(); i++ { 814 if out.Methods == nil { 815 out.Methods = map[string]*types.Type{} 816 } 817 method := t.Method(i) 818 name := tcNameToName(method.String()) 819 mt := b.walkType(u, &name, method.Type()) 820 mt.CommentLines = splitLines(b.priorCommentLines(method.Pos(), 1).Text()) 821 out.Methods[method.Name()] = mt 822 } 823 return out 824 case *tc.Named: 825 var out *types.Type 826 switch t.Underlying().(type) { 827 case *tc.Named, *tc.Basic, *tc.Map, *tc.Slice: 828 name := tcNameToName(t.String()) 829 out = u.Type(name) 830 if out.Kind != types.Unknown { 831 return out 832 } 833 out.Kind = types.Alias 834 out.Underlying = b.walkType(u, nil, t.Underlying()) 835 default: 836 // tc package makes everything "named" with an 837 // underlying anonymous type--we remove that annoying 838 // "feature" for users. This flattens those types 839 // together. 840 name := tcNameToName(t.String()) 841 if out := u.Type(name); out.Kind != types.Unknown { 842 return out // short circuit if we've already made this. 843 } 844 out = b.walkType(u, &name, t.Underlying()) 845 } 846 // If the underlying type didn't already add methods, add them. 847 // (Interface types will have already added methods.) 848 if len(out.Methods) == 0 { 849 for i := 0; i < t.NumMethods(); i++ { 850 if out.Methods == nil { 851 out.Methods = map[string]*types.Type{} 852 } 853 method := t.Method(i) 854 name := tcNameToName(method.String()) 855 mt := b.walkType(u, &name, method.Type()) 856 mt.CommentLines = splitLines(b.priorCommentLines(method.Pos(), 1).Text()) 857 out.Methods[method.Name()] = mt 858 } 859 } 860 return out 861 default: 862 out := u.Type(name) 863 if out.Kind != types.Unknown { 864 return out 865 } 866 out.Kind = types.Unsupported 867 klog.Warningf("Making unsupported type entry %q for: %#v\n", out, t) 868 return out 869 } 870 } 871 872 func (b *Builder) addFunction(u types.Universe, useName *types.Name, in *tc.Func) *types.Type { 873 name := tcFuncNameToName(in.String()) 874 if useName != nil { 875 name = *useName 876 } 877 out := u.Function(name) 878 out.Kind = types.DeclarationOf 879 out.Underlying = b.walkType(u, nil, in.Type()) 880 return out 881 } 882 883 func (b *Builder) addVariable(u types.Universe, useName *types.Name, in *tc.Var) *types.Type { 884 name := tcVarNameToName(in.String()) 885 if useName != nil { 886 name = *useName 887 } 888 out := u.Variable(name) 889 out.Kind = types.DeclarationOf 890 out.Underlying = b.walkType(u, nil, in.Type()) 891 return out 892 } 893 894 func (b *Builder) addConstant(u types.Universe, useName *types.Name, in *tc.Const) *types.Type { 895 name := tcVarNameToName(in.String()) 896 if useName != nil { 897 name = *useName 898 } 899 out := u.Constant(name) 900 out.Kind = types.DeclarationOf 901 out.Underlying = b.walkType(u, nil, in.Type()) 902 903 var constval string 904 905 // For strings, we use `StringVal()` to get the un-truncated, 906 // un-quoted string. For other values, `.String()` is preferable to 907 // get something relatively human readable (especially since for 908 // floating point types, `ExactString()` will generate numeric 909 // expressions using `big.(*Float).Text()`. 910 switch in.Val().Kind() { 911 case constant.String: 912 constval = constant.StringVal(in.Val()) 913 default: 914 constval = in.Val().String() 915 } 916 917 out.ConstValue = &constval 918 return out 919 } 920 921 // canonicalizeImportPath takes an import path and returns the actual package. 922 // It doesn't support nested vendoring. 923 func canonicalizeImportPath(importPath string) importPathString { 924 if !strings.Contains(importPath, "/vendor/") { 925 return importPathString(importPath) 926 } 927 928 return importPathString(importPath[strings.Index(importPath, "/vendor/")+len("/vendor/"):]) 929 }