| 1 | // Copyright 2018 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 packages |
| 6 | |
| 7 | import ( |
| 8 | "encoding/json" |
| 9 | "fmt" |
| 10 | "go/parser" |
| 11 | "go/token" |
| 12 | "os" |
| 13 | "path/filepath" |
| 14 | "regexp" |
| 15 | "sort" |
| 16 | "strconv" |
| 17 | "strings" |
| 18 | |
| 19 | "golang.org/x/tools/internal/gocommand" |
| 20 | ) |
| 21 | |
| 22 | // processGolistOverlay provides rudimentary support for adding |
| 23 | // files that don't exist on disk to an overlay. The results can be |
| 24 | // sometimes incorrect. |
| 25 | // TODO(matloob): Handle unsupported cases, including the following: |
| 26 | // - determining the correct package to add given a new import path |
| 27 | func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) { |
| 28 | havePkgs := make(map[string]string) // importPath -> non-test package ID |
| 29 | needPkgsSet := make(map[string]bool) |
| 30 | modifiedPkgsSet := make(map[string]bool) |
| 31 | |
| 32 | pkgOfDir := make(map[string][]*Package) |
| 33 | for _, pkg := range response.dr.Packages { |
| 34 | // This is an approximation of import path to id. This can be |
| 35 | // wrong for tests, vendored packages, and a number of other cases. |
| 36 | havePkgs[pkg.PkgPath] = pkg.ID |
| 37 | dir, err := commonDir(pkg.GoFiles) |
| 38 | if err != nil { |
| 39 | return nil, nil, err |
| 40 | } |
| 41 | if dir != "" { |
| 42 | pkgOfDir[dir] = append(pkgOfDir[dir], pkg) |
| 43 | } |
| 44 | } |
| 45 | |
| 46 | // If no new imports are added, it is safe to avoid loading any needPkgs. |
| 47 | // Otherwise, it's hard to tell which package is actually being loaded |
| 48 | // (due to vendoring) and whether any modified package will show up |
| 49 | // in the transitive set of dependencies (because new imports are added, |
| 50 | // potentially modifying the transitive set of dependencies). |
| 51 | var overlayAddsImports bool |
| 52 | |
| 53 | // If both a package and its test package are created by the overlay, we |
| 54 | // need the real package first. Process all non-test files before test |
| 55 | // files, and make the whole process deterministic while we're at it. |
| 56 | var overlayFiles []string |
| 57 | for opath := range state.cfg.Overlay { |
| 58 | overlayFiles = append(overlayFiles, opath) |
| 59 | } |
| 60 | sort.Slice(overlayFiles, func(i, j int) bool { |
| 61 | iTest := strings.HasSuffix(overlayFiles[i], "_test.go") |
| 62 | jTest := strings.HasSuffix(overlayFiles[j], "_test.go") |
| 63 | if iTest != jTest { |
| 64 | return !iTest // non-tests are before tests. |
| 65 | } |
| 66 | return overlayFiles[i] < overlayFiles[j] |
| 67 | }) |
| 68 | for _, opath := range overlayFiles { |
| 69 | contents := state.cfg.Overlay[opath] |
| 70 | base := filepath.Base(opath) |
| 71 | dir := filepath.Dir(opath) |
| 72 | var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant |
| 73 | var testVariantOf *Package // if opath is a test file, this is the package it is testing |
| 74 | var fileExists bool |
| 75 | isTestFile := strings.HasSuffix(opath, "_test.go") |
| 76 | pkgName, ok := extractPackageName(opath, contents) |
| 77 | if !ok { |
| 78 | // Don't bother adding a file that doesn't even have a parsable package statement |
| 79 | // to the overlay. |
| 80 | continue |
| 81 | } |
| 82 | // If all the overlay files belong to a different package, change the |
| 83 | // package name to that package. |
| 84 | maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir]) |
| 85 | nextPackage: |
| 86 | for _, p := range response.dr.Packages { |
| 87 | if pkgName != p.Name && p.ID != "command-line-arguments" { |
| 88 | continue |
| 89 | } |
| 90 | for _, f := range p.GoFiles { |
| 91 | if !sameFile(filepath.Dir(f), dir) { |
| 92 | continue |
| 93 | } |
| 94 | // Make sure to capture information on the package's test variant, if needed. |
| 95 | if isTestFile && !hasTestFiles(p) { |
| 96 | // TODO(matloob): Are there packages other than the 'production' variant |
| 97 | // of a package that this can match? This shouldn't match the test main package |
| 98 | // because the file is generated in another directory. |
| 99 | testVariantOf = p |
| 100 | continue nextPackage |
| 101 | } else if !isTestFile && hasTestFiles(p) { |
| 102 | // We're examining a test variant, but the overlaid file is |
| 103 | // a non-test file. Because the overlay implementation |
| 104 | // (currently) only adds a file to one package, skip this |
| 105 | // package, so that we can add the file to the production |
| 106 | // variant of the package. (https://golang.org/issue/36857 |
| 107 | // tracks handling overlays on both the production and test |
| 108 | // variant of a package). |
| 109 | continue nextPackage |
| 110 | } |
| 111 | if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath { |
| 112 | // We have already seen the production version of the |
| 113 | // for which p is a test variant. |
| 114 | if hasTestFiles(p) { |
| 115 | testVariantOf = pkg |
| 116 | } |
| 117 | } |
| 118 | pkg = p |
| 119 | if filepath.Base(f) == base { |
| 120 | fileExists = true |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | // The overlay could have included an entirely new package or an |
| 125 | // ad-hoc package. An ad-hoc package is one that we have manually |
| 126 | // constructed from inadequate `go list` results for a file= query. |
| 127 | // It will have the ID command-line-arguments. |
| 128 | if pkg == nil || pkg.ID == "command-line-arguments" { |
| 129 | // Try to find the module or gopath dir the file is contained in. |
| 130 | // Then for modules, add the module opath to the beginning. |
| 131 | pkgPath, ok, err := state.getPkgPath(dir) |
| 132 | if err != nil { |
| 133 | return nil, nil, err |
| 134 | } |
| 135 | if !ok { |
| 136 | break |
| 137 | } |
| 138 | var forTest string // only set for x tests |
| 139 | isXTest := strings.HasSuffix(pkgName, "_test") |
| 140 | if isXTest { |
| 141 | forTest = pkgPath |
| 142 | pkgPath += "_test" |
| 143 | } |
| 144 | id := pkgPath |
| 145 | if isTestFile { |
| 146 | if isXTest { |
| 147 | id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest) |
| 148 | } else { |
| 149 | id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath) |
| 150 | } |
| 151 | } |
| 152 | if pkg != nil { |
| 153 | // TODO(rstambler): We should change the package's path and ID |
| 154 | // here. The only issue is that this messes with the roots. |
| 155 | } else { |
| 156 | // Try to reclaim a package with the same ID, if it exists in the response. |
| 157 | for _, p := range response.dr.Packages { |
| 158 | if reclaimPackage(p, id, opath, contents) { |
| 159 | pkg = p |
| 160 | break |
| 161 | } |
| 162 | } |
| 163 | // Otherwise, create a new package. |
| 164 | if pkg == nil { |
| 165 | pkg = &Package{ |
| 166 | PkgPath: pkgPath, |
| 167 | ID: id, |
| 168 | Name: pkgName, |
| 169 | Imports: make(map[string]*Package), |
| 170 | } |
| 171 | response.addPackage(pkg) |
| 172 | havePkgs[pkg.PkgPath] = id |
| 173 | // Add the production package's sources for a test variant. |
| 174 | if isTestFile && !isXTest && testVariantOf != nil { |
| 175 | pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) |
| 176 | pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) |
| 177 | // Add the package under test and its imports to the test variant. |
| 178 | pkg.forTest = testVariantOf.PkgPath |
| 179 | for k, v := range testVariantOf.Imports { |
| 180 | pkg.Imports[k] = &Package{ID: v.ID} |
| 181 | } |
| 182 | } |
| 183 | if isXTest { |
| 184 | pkg.forTest = forTest |
| 185 | } |
| 186 | } |
| 187 | } |
| 188 | } |
| 189 | if !fileExists { |
| 190 | pkg.GoFiles = append(pkg.GoFiles, opath) |
| 191 | // TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior |
| 192 | // if the file will be ignored due to its build tags. |
| 193 | pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath) |
| 194 | modifiedPkgsSet[pkg.ID] = true |
| 195 | } |
| 196 | imports, err := extractImports(opath, contents) |
| 197 | if err != nil { |
| 198 | // Let the parser or type checker report errors later. |
| 199 | continue |
| 200 | } |
| 201 | for _, imp := range imports { |
| 202 | // TODO(rstambler): If the package is an x test and the import has |
| 203 | // a test variant, make sure to replace it. |
| 204 | if _, found := pkg.Imports[imp]; found { |
| 205 | continue |
| 206 | } |
| 207 | overlayAddsImports = true |
| 208 | id, ok := havePkgs[imp] |
| 209 | if !ok { |
| 210 | var err error |
| 211 | id, err = state.resolveImport(dir, imp) |
| 212 | if err != nil { |
| 213 | return nil, nil, err |
| 214 | } |
| 215 | } |
| 216 | pkg.Imports[imp] = &Package{ID: id} |
| 217 | // Add dependencies to the non-test variant version of this package as well. |
| 218 | if testVariantOf != nil { |
| 219 | testVariantOf.Imports[imp] = &Package{ID: id} |
| 220 | } |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | // toPkgPath guesses the package path given the id. |
| 225 | toPkgPath := func(sourceDir, id string) (string, error) { |
| 226 | if i := strings.IndexByte(id, ' '); i >= 0 { |
| 227 | return state.resolveImport(sourceDir, id[:i]) |
| 228 | } |
| 229 | return state.resolveImport(sourceDir, id) |
| 230 | } |
| 231 | |
| 232 | // Now that new packages have been created, do another pass to determine |
| 233 | // the new set of missing packages. |
| 234 | for _, pkg := range response.dr.Packages { |
| 235 | for _, imp := range pkg.Imports { |
| 236 | if len(pkg.GoFiles) == 0 { |
| 237 | return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath) |
| 238 | } |
| 239 | pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID) |
| 240 | if err != nil { |
| 241 | return nil, nil, err |
| 242 | } |
| 243 | if _, ok := havePkgs[pkgPath]; !ok { |
| 244 | needPkgsSet[pkgPath] = true |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | if overlayAddsImports { |
| 250 | needPkgs = make([]string, 0, len(needPkgsSet)) |
| 251 | for pkg := range needPkgsSet { |
| 252 | needPkgs = append(needPkgs, pkg) |
| 253 | } |
| 254 | } |
| 255 | modifiedPkgs = make([]string, 0, len(modifiedPkgsSet)) |
| 256 | for pkg := range modifiedPkgsSet { |
| 257 | modifiedPkgs = append(modifiedPkgs, pkg) |
| 258 | } |
| 259 | return modifiedPkgs, needPkgs, err |
| 260 | } |
| 261 | |
| 262 | // resolveImport finds the ID of a package given its import path. |
| 263 | // In particular, it will find the right vendored copy when in GOPATH mode. |
| 264 | func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) { |
| 265 | env, err := state.getEnv() |
| 266 | if err != nil { |
| 267 | return "", err |
| 268 | } |
| 269 | if env["GOMOD"] != "" { |
| 270 | return importPath, nil |
| 271 | } |
| 272 | |
| 273 | searchDir := sourceDir |
| 274 | for { |
| 275 | vendorDir := filepath.Join(searchDir, "vendor") |
| 276 | exists, ok := state.vendorDirs[vendorDir] |
| 277 | if !ok { |
| 278 | info, err := os.Stat(vendorDir) |
| 279 | exists = err == nil && info.IsDir() |
| 280 | state.vendorDirs[vendorDir] = exists |
| 281 | } |
| 282 | |
| 283 | if exists { |
| 284 | vendoredPath := filepath.Join(vendorDir, importPath) |
| 285 | if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() { |
| 286 | // We should probably check for .go files here, but shame on anyone who fools us. |
| 287 | path, ok, err := state.getPkgPath(vendoredPath) |
| 288 | if err != nil { |
| 289 | return "", err |
| 290 | } |
| 291 | if ok { |
| 292 | return path, nil |
| 293 | } |
| 294 | } |
| 295 | } |
| 296 | |
| 297 | // We know we've hit the top of the filesystem when we Dir / and get /, |
| 298 | // or C:\ and get C:\, etc. |
| 299 | next := filepath.Dir(searchDir) |
| 300 | if next == searchDir { |
| 301 | break |
| 302 | } |
| 303 | searchDir = next |
| 304 | } |
| 305 | return importPath, nil |
| 306 | } |
| 307 | |
| 308 | func hasTestFiles(p *Package) bool { |
| 309 | for _, f := range p.GoFiles { |
| 310 | if strings.HasSuffix(f, "_test.go") { |
| 311 | return true |
| 312 | } |
| 313 | } |
| 314 | return false |
| 315 | } |
| 316 | |
| 317 | // determineRootDirs returns a mapping from absolute directories that could |
| 318 | // contain code to their corresponding import path prefixes. |
| 319 | func (state *golistState) determineRootDirs() (map[string]string, error) { |
| 320 | env, err := state.getEnv() |
| 321 | if err != nil { |
| 322 | return nil, err |
| 323 | } |
| 324 | if env["GOMOD"] != "" { |
| 325 | state.rootsOnce.Do(func() { |
| 326 | state.rootDirs, state.rootDirsError = state.determineRootDirsModules() |
| 327 | }) |
| 328 | } else { |
| 329 | state.rootsOnce.Do(func() { |
| 330 | state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH() |
| 331 | }) |
| 332 | } |
| 333 | return state.rootDirs, state.rootDirsError |
| 334 | } |
| 335 | |
| 336 | func (state *golistState) determineRootDirsModules() (map[string]string, error) { |
| 337 | // List all of the modules--the first will be the directory for the main |
| 338 | // module. Any replaced modules will also need to be treated as roots. |
| 339 | // Editing files in the module cache isn't a great idea, so we don't |
| 340 | // plan to ever support that. |
| 341 | out, err := state.invokeGo("list", "-m", "-json", "all") |
| 342 | if err != nil { |
| 343 | // 'go list all' will fail if we're outside of a module and |
| 344 | // GO111MODULE=on. Try falling back without 'all'. |
| 345 | var innerErr error |
| 346 | out, innerErr = state.invokeGo("list", "-m", "-json") |
| 347 | if innerErr != nil { |
| 348 | return nil, err |
| 349 | } |
| 350 | } |
| 351 | roots := map[string]string{} |
| 352 | modules := map[string]string{} |
| 353 | var i int |
| 354 | for dec := json.NewDecoder(out); dec.More(); { |
| 355 | mod := new(gocommand.ModuleJSON) |
| 356 | if err := dec.Decode(mod); err != nil { |
| 357 | return nil, err |
| 358 | } |
| 359 | if mod.Dir != "" && mod.Path != "" { |
| 360 | // This is a valid module; add it to the map. |
| 361 | absDir, err := filepath.Abs(mod.Dir) |
| 362 | if err != nil { |
| 363 | return nil, err |
| 364 | } |
| 365 | modules[absDir] = mod.Path |
| 366 | // The first result is the main module. |
| 367 | if i == 0 || mod.Replace != nil && mod.Replace.Path != "" { |
| 368 | roots[absDir] = mod.Path |
| 369 | } |
| 370 | } |
| 371 | i++ |
| 372 | } |
| 373 | return roots, nil |
| 374 | } |
| 375 | |
| 376 | func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) { |
| 377 | m := map[string]string{} |
| 378 | for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) { |
| 379 | absDir, err := filepath.Abs(dir) |
| 380 | if err != nil { |
| 381 | return nil, err |
| 382 | } |
| 383 | m[filepath.Join(absDir, "src")] = "" |
| 384 | } |
| 385 | return m, nil |
| 386 | } |
| 387 | |
| 388 | func extractImports(filename string, contents []byte) ([]string, error) { |
| 389 | f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset? |
| 390 | if err != nil { |
| 391 | return nil, err |
| 392 | } |
| 393 | var res []string |
| 394 | for _, imp := range f.Imports { |
| 395 | quotedPath := imp.Path.Value |
| 396 | path, err := strconv.Unquote(quotedPath) |
| 397 | if err != nil { |
| 398 | return nil, err |
| 399 | } |
| 400 | res = append(res, path) |
| 401 | } |
| 402 | return res, nil |
| 403 | } |
| 404 | |
| 405 | // reclaimPackage attempts to reuse a package that failed to load in an overlay. |
| 406 | // |
| 407 | // If the package has errors and has no Name, GoFiles, or Imports, |
| 408 | // then it's possible that it doesn't yet exist on disk. |
| 409 | func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool { |
| 410 | // TODO(rstambler): Check the message of the actual error? |
| 411 | // It differs between $GOPATH and module mode. |
| 412 | if pkg.ID != id { |
| 413 | return false |
| 414 | } |
| 415 | if len(pkg.Errors) != 1 { |
| 416 | return false |
| 417 | } |
| 418 | if pkg.Name != "" || pkg.ExportFile != "" { |
| 419 | return false |
| 420 | } |
| 421 | if len(pkg.GoFiles) > 0 || len(pkg.CompiledGoFiles) > 0 || len(pkg.OtherFiles) > 0 { |
| 422 | return false |
| 423 | } |
| 424 | if len(pkg.Imports) > 0 { |
| 425 | return false |
| 426 | } |
| 427 | pkgName, ok := extractPackageName(filename, contents) |
| 428 | if !ok { |
| 429 | return false |
| 430 | } |
| 431 | pkg.Name = pkgName |
| 432 | pkg.Errors = nil |
| 433 | return true |
| 434 | } |
| 435 | |
| 436 | func extractPackageName(filename string, contents []byte) (string, bool) { |
| 437 | // TODO(rstambler): Check the message of the actual error? |
| 438 | // It differs between $GOPATH and module mode. |
| 439 | f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset? |
| 440 | if err != nil { |
| 441 | return "", false |
| 442 | } |
| 443 | return f.Name.Name, true |
| 444 | } |
| 445 | |
| 446 | // commonDir returns the directory that all files are in, "" if files is empty, |
| 447 | // or an error if they aren't in the same directory. |
| 448 | func commonDir(files []string) (string, error) { |
| 449 | seen := make(map[string]bool) |
| 450 | for _, f := range files { |
| 451 | seen[filepath.Dir(f)] = true |
| 452 | } |
| 453 | if len(seen) > 1 { |
| 454 | return "", fmt.Errorf("files (%v) are in more than one directory: %v", files, seen) |
| 455 | } |
| 456 | for k := range seen { |
| 457 | // seen has only one element; return it. |
| 458 | return k, nil |
| 459 | } |
| 460 | return "", nil // no files |
| 461 | } |
| 462 | |
| 463 | // It is possible that the files in the disk directory dir have a different package |
| 464 | // name from newName, which is deduced from the overlays. If they all have a different |
| 465 | // package name, and they all have the same package name, then that name becomes |
| 466 | // the package name. |
| 467 | // It returns true if it changes the package name, false otherwise. |
| 468 | func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) { |
| 469 | names := make(map[string]int) |
| 470 | for _, p := range pkgsOfDir { |
| 471 | names[p.Name]++ |
| 472 | } |
| 473 | if len(names) != 1 { |
| 474 | // some files are in different packages |
| 475 | return |
| 476 | } |
| 477 | var oldName string |
| 478 | for k := range names { |
| 479 | oldName = k |
| 480 | } |
| 481 | if newName == oldName { |
| 482 | return |
| 483 | } |
| 484 | // We might have a case where all of the package names in the directory are |
| 485 | // the same, but the overlay file is for an x test, which belongs to its |
| 486 | // own package. If the x test does not yet exist on disk, we may not yet |
| 487 | // have its package name on disk, but we should not rename the packages. |
| 488 | // |
| 489 | // We use a heuristic to determine if this file belongs to an x test: |
| 490 | // The test file should have a package name whose package name has a _test |
| 491 | // suffix or looks like "newName_test". |
| 492 | maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test") |
| 493 | if isTestFile && maybeXTest { |
| 494 | return |
| 495 | } |
| 496 | for _, p := range pkgsOfDir { |
| 497 | p.Name = newName |
| 498 | } |
| 499 | } |
| 500 | |
| 501 | // This function is copy-pasted from |
| 502 | // https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/cmd/go/internal/search/search.go#L360. |
| 503 | // It should be deleted when we remove support for overlays from go/packages. |
| 504 | // |
| 505 | // NOTE: This does not handle any ./... or ./ style queries, as this function |
| 506 | // doesn't know the working directory. |
| 507 | // |
| 508 | // matchPattern(pattern)(name) reports whether |
| 509 | // name matches pattern. Pattern is a limited glob |
| 510 | // pattern in which '...' means 'any string' and there |
| 511 | // is no other special syntax. |
| 512 | // Unfortunately, there are two special cases. Quoting "go help packages": |
| 513 | // |
| 514 | // First, /... at the end of the pattern can match an empty string, |
| 515 | // so that net/... matches both net and packages in its subdirectories, like net/http. |
| 516 | // Second, any slash-separated pattern element containing a wildcard never |
| 517 | // participates in a match of the "vendor" element in the path of a vendored |
| 518 | // package, so that ./... does not match packages in subdirectories of |
| 519 | // ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. |
| 520 | // Note, however, that a directory named vendor that itself contains code |
| 521 | // is not a vendored package: cmd/vendor would be a command named vendor, |
| 522 | // and the pattern cmd/... matches it. |
| 523 | func matchPattern(pattern string) func(name string) bool { |
| 524 | // Convert pattern to regular expression. |
| 525 | // The strategy for the trailing /... is to nest it in an explicit ? expression. |
| 526 | // The strategy for the vendor exclusion is to change the unmatchable |
| 527 | // vendor strings to a disallowed code point (vendorChar) and to use |
| 528 | // "(anything but that codepoint)*" as the implementation of the ... wildcard. |
| 529 | // This is a bit complicated but the obvious alternative, |
| 530 | // namely a hand-written search like in most shell glob matchers, |
| 531 | // is too easy to make accidentally exponential. |
| 532 | // Using package regexp guarantees linear-time matching. |
| 533 | |
| 534 | const vendorChar = "\x00" |
| 535 | |
| 536 | if strings.Contains(pattern, vendorChar) { |
| 537 | return func(name string) bool { return false } |
| 538 | } |
| 539 | |
| 540 | re := regexp.QuoteMeta(pattern) |
| 541 | re = replaceVendor(re, vendorChar) |
| 542 | switch { |
| 543 | case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): |
| 544 | re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` |
| 545 | case re == vendorChar+`/\.\.\.`: |
| 546 | re = `(/vendor|/` + vendorChar + `/\.\.\.)` |
| 547 | case strings.HasSuffix(re, `/\.\.\.`): |
| 548 | re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` |
| 549 | } |
| 550 | re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`) |
| 551 | |
| 552 | reg := regexp.MustCompile(`^` + re + `$`) |
| 553 | |
| 554 | return func(name string) bool { |
| 555 | if strings.Contains(name, vendorChar) { |
| 556 | return false |
| 557 | } |
| 558 | return reg.MatchString(replaceVendor(name, vendorChar)) |
| 559 | } |
| 560 | } |
| 561 | |
| 562 | // replaceVendor returns the result of replacing |
| 563 | // non-trailing vendor path elements in x with repl. |
| 564 | func replaceVendor(x, repl string) string { |
| 565 | if !strings.Contains(x, "vendor") { |
| 566 | return x |
| 567 | } |
| 568 | elem := strings.Split(x, "/") |
| 569 | for i := 0; i < len(elem)-1; i++ { |
| 570 | if elem[i] == "vendor" { |
| 571 | elem[i] = repl |
| 572 | } |
| 573 | } |
| 574 | return strings.Join(elem, "/") |
| 575 | } |
| 576 |
Members