github.com/jmigpin/editor@v1.6.0/core/godebug/filestoannotate.go (about) 1 package godebug 2 3 import ( 4 "context" 5 "crypto/md5" 6 "encoding/base64" 7 "fmt" 8 "go/ast" 9 "go/token" 10 "math/rand" 11 "os" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/jmigpin/editor/util/goutil" 18 "github.com/jmigpin/editor/util/osutil" 19 "golang.org/x/tools/go/packages" 20 ) 21 22 type FilesToAnnotate struct { 23 cmd *Cmd 24 25 pathsPkgs map[string]*packages.Package // map[pkgPath] 26 filesPkgs map[string]*packages.Package // map[filename] 27 filesAsts map[string]*ast.File // map[filename] 28 29 toAnnotate map[string]AnnotationType // map[filename] 30 nodeAnnTypes map[ast.Node]AnnotationType // map[*ast.File and inner ast.Node's, check how a file is added for annotation] 31 32 main struct { 33 pkgs []*packages.Package 34 } 35 } 36 37 func NewFilesToAnnotate(cmd *Cmd) *FilesToAnnotate { 38 fa := &FilesToAnnotate{cmd: cmd} 39 fa.pathsPkgs = map[string]*packages.Package{} 40 fa.filesPkgs = map[string]*packages.Package{} 41 fa.filesAsts = map[string]*ast.File{} 42 fa.toAnnotate = map[string]AnnotationType{} 43 fa.nodeAnnTypes = map[ast.Node]AnnotationType{} 44 return fa 45 } 46 47 func (fa *FilesToAnnotate) find(ctx context.Context) error { 48 pkgs, err := fa.loadPackages(ctx) 49 if err != nil { 50 return fmt.Errorf("load packages: %w", err) 51 } 52 if err := fa.initMaps(pkgs); err != nil { 53 return err 54 } 55 if err := fa.addFromArgs(ctx); err != nil { 56 return err 57 } 58 if err := fa.addFromMain(ctx); err != nil { 59 return err 60 } 61 //if err := fa.addFromMainFuncDecl(ctx); err != nil { 62 // return err 63 //} 64 if err := fa.addFromSrcDirectives(ctx); err != nil { 65 return err 66 } 67 68 if fa.cmd.flags.verbose { 69 fa.cmd.printf("files to annotate:\n") 70 for k, v := range fa.toAnnotate { 71 fa.cmd.printf("\t%v: %v\n", k, v) 72 } 73 } 74 75 return nil 76 } 77 78 func (fa *FilesToAnnotate) initMaps(pkgs []*packages.Package) error { 79 fa.main.pkgs = pkgs 80 81 if fa.cmd.flags.verbose { 82 fa.cmd.printf("main pkgs: %v\n", len(pkgs)) 83 for _, pkg := range pkgs { 84 fa.cmd.printf("\t%v (%v)\n", pkg.PkgPath, pkg.ID) 85 for _, filename := range pkg.CompiledGoFiles { 86 fa.cmd.printf("\t\t%v\n", filename) 87 } 88 } 89 } 90 91 for _, pkg := range pkgs { 92 if err := fa.initMaps2(pkg); err != nil { 93 return err 94 } 95 } 96 return nil 97 } 98 func (fa *FilesToAnnotate) initMaps2(pkg *packages.Package) error { 99 // don't handle runtime pkg (ex: has a file that contains a "main()" func and gets caught only "sometimes" when findind for the main func decl) 100 //if pkg.PkgPath == "runtime" { 101 // return 102 //} 103 104 // map pkgpaths to pkgs 105 pkg0, ok := fa.pathsPkgs[pkg.PkgPath] 106 if ok { 107 if len(pkg0.Syntax) < len(pkg.Syntax) { 108 // ok, visit again and keep the new pkg 109 } else { 110 // DEBUG 111 //if pkg != pkg0 { 112 // fmt.Println("PKG0---") 113 // spew.Dump(pkg0) 114 // spew.Dump(len(pkg0.Syntax)) 115 // spew.Dump(pkg0.CompiledGoFiles) 116 // fmt.Println("PKG---") 117 // spew.Dump(pkg) 118 // spew.Dump(len(pkg.Syntax)) 119 // spew.Dump(pkg.CompiledGoFiles) 120 // fmt.Println("---") 121 //} 122 123 return nil // already visited 124 } 125 } 126 fa.pathsPkgs[pkg.PkgPath] = pkg 127 128 if fa.cmd.flags.verbose { 129 fa.cmd.printf("pkg: %v\n", pkg.PkgPath) 130 // for _, filename := range pkg.CompiledGoFiles { 131 // fa.cmd.printf("\tpkgfile: %v\n", filename) 132 // } 133 } 134 135 // map filenames to pkgs 136 for _, filename := range pkg.CompiledGoFiles { 137 fa.filesPkgs[filename] = pkg 138 } 139 140 // map filenames to asts 141 for _, astFile := range pkg.Syntax { 142 filename, err := nodeFilename(fa.cmd.fset, astFile) 143 if err != nil { 144 return err 145 } 146 fa.filesAsts[filename] = astFile 147 } 148 149 // visit imports recursively 150 for _, pkg2 := range pkg.Imports { 151 if err := fa.initMaps2(pkg2); err != nil { 152 return err 153 } 154 } 155 return nil 156 } 157 158 //---------- 159 160 func (fa *FilesToAnnotate) addFromArgs(ctx context.Context) error { 161 absFilePath := func(s string) string { 162 if !filepath.IsAbs(s) { 163 return filepath.Join(fa.cmd.Dir, s) 164 } 165 return s 166 } 167 168 // detect filenames in args (best effort) 169 for _, arg := range fa.cmd.flags.unnamedArgs { 170 if !strings.HasSuffix(arg, ".go") { 171 continue 172 } 173 filename := arg 174 filename = absFilePath(filename) 175 if _, ok := fa.filesPkgs[filename]; !ok { 176 continue 177 } 178 fa.addToAnnotate(filename, AnnotationTypeFile) 179 } 180 181 for _, path := range fa.cmd.flags.paths { 182 // early stop 183 if err := ctx.Err(); err != nil { 184 return err 185 } 186 187 // because full paths are needed to match in the map 188 path = absFilePath(path) 189 190 fi, err := os.Stat(path) 191 if err != nil { 192 return err 193 } 194 if fi.IsDir() { 195 dir := path 196 des, err := os.ReadDir(dir) 197 if err != nil { 198 return fmt.Errorf("read dir error: %w", err) 199 } 200 for _, de := range des { 201 filename := filepath.Join(dir, de.Name()) 202 if _, ok := fa.filesPkgs[filename]; !ok { 203 continue 204 } 205 fa.addToAnnotate(filename, AnnotationTypeFile) 206 } 207 } else { 208 filename := path 209 if _, ok := fa.filesPkgs[filename]; !ok { 210 return fmt.Errorf("file not loaded: %v", filename) 211 } 212 fa.addToAnnotate(filename, AnnotationTypeFile) 213 } 214 } 215 return nil 216 } 217 218 //---------- 219 220 func (fa *FilesToAnnotate) addFromMain(ctx context.Context) error { 221 for _, pkg := range fa.main.pkgs { 222 for _, filename := range pkg.CompiledGoFiles { 223 224 if fa.cmd.flags.mode.test { 225 // bypass files without .go ext (avoids the generated main() test file) 226 ext := filepath.Ext(filename) 227 if ext != ".go" { 228 continue 229 } 230 } 231 232 fa.addToAnnotate(filename, AnnotationTypeFile) 233 } 234 } 235 return nil 236 } 237 238 //func (fa *FilesToAnnotate) addFromMainFuncDecl(ctx context.Context) error { 239 // fd, filename, err := fa.getMainFuncDecl() 240 // if err != nil { 241 // return err 242 // } 243 // fa.addToAnnotate(filename, AnnotationTypeFile) 244 245 // fa.mainFunc.filename = filename 246 // fa.mainFunc.decl = fd 247 // fa.cmd.logf("mainfunc filename: %v\n", filename) 248 249 // return nil 250 //} 251 //func (fa *FilesToAnnotate) getMainFuncDecl() (*ast.FuncDecl, string, error) { 252 // fd, filename, err := fa.findMainFuncDecl() 253 // if err != nil { 254 // if fa.cmd.flags.mode.test { 255 // if err := fa.insertTestMains(); err != nil { 256 // return nil, "", err 257 // } 258 259 // //if err := fa.createTestMain(); err != nil { 260 // // return nil, "", err 261 // //} 262 263 // // try again 264 // fd, filename, err = fa.findMainFuncDecl() 265 // } 266 // } 267 // return fd, filename, err 268 //} 269 //func (fa *FilesToAnnotate) findMainFuncDecl() (*ast.FuncDecl, string, error) { 270 // name := mainFuncName(fa.cmd.flags.mode.test) 271 // for filename, astFile := range fa.filesAsts { 272 // fd, ok := findFuncDeclWithBody(astFile, name) 273 // if ok { 274 // return fd, filename, nil 275 // } 276 // } 277 // return nil, "", fmt.Errorf("main func decl not found") 278 //} 279 280 //---------- 281 282 //func (fa *FilesToAnnotate) insertTestMains() error { 283 // // insert testmain once per dir in *_test.go dirs 284 // seen := map[string]bool{} 285 // for filename, astFile := range fa.filesAsts { 286 // if !strings.HasSuffix(filename, "_test.go") { 287 // continue 288 // } 289 290 // dir := filepath.Dir(filename) 291 // if seen[dir] { 292 // continue 293 // } 294 // seen[dir] = true 295 296 // if err := fa.insertTestMain(astFile); err != nil { 297 // return err 298 // } 299 // } 300 301 // if len(seen) == 0 { 302 // return fmt.Errorf("missing *_test.go files") 303 // //return fmt.Errorf("testmain not inserted") 304 // } 305 // return nil 306 //} 307 308 //func (fa *FilesToAnnotate) insertTestMain(astFile *ast.File) error { 309 // // TODO: detect if used imports are already imported with another name (os,testing) 310 311 // // build ast to insert (easier to parse from text then to build the ast manually here. notice how "imports" are missing since it is just to get the ast of the funcdecl) 312 // src := ` 313 // package main 314 // func TestMain(m *testing.M) { 315 // os.Exit(m.Run()) 316 // } 317 // ` 318 // fset := token.NewFileSet() 319 // astFile2, err := parser.ParseFile(fset, "", src, 0) 320 // if err != nil { 321 // panic(err) 322 // } 323 // //goutil.PrintNode(fset, node) 324 325 // // insert imports first to avoid "crashing" with asutil when visiting a node that was inserted and might not have a position 326 // astutil.AddImport(fset, astFile, "os") 327 // astutil.AddImport(fset, astFile, "testing") 328 329 // // get func decl for insertion 330 // fd := (*ast.FuncDecl)(nil) 331 // ast.Inspect(astFile2, func(n ast.Node) bool { 332 // if n2, ok := n.(*ast.FuncDecl); ok { 333 // fd = n2 334 // return false 335 // } 336 // return true 337 // }) 338 // if fd == nil { 339 // err := fmt.Errorf("missing func decl") 340 // panic(err) 341 // } 342 343 // // insert in ast file 344 // astFile.Decls = append(astFile.Decls, fd) 345 346 // // DEBUG 347 // //goutil.PrintNode(fa.cmd.fset, astFile) 348 349 // return nil 350 //} 351 352 //---------- 353 354 //func (fa *FilesToAnnotate) createTestMain() error { 355 // // get info from a "_test.go" file 356 // found := false 357 // dir := "" 358 // pkgName := "" 359 // for filename, astFile := range fa.filesAsts { 360 // if strings.HasSuffix(filename, "_test.go") { 361 // found = true 362 // dir = filepath.Dir(filename) 363 // pkgName = astFile.Name.Name 364 // break 365 // } 366 // } 367 // if !found { 368 // return fmt.Errorf("missing *_test.go files") 369 // } 370 371 // fname := fmt.Sprintf("testmain%s_test.go", genDigitsStr(5)) 372 // filename := fsutil.JoinPath(dir, fname) 373 // astFile, src := fa.createTestMainAst(filename, pkgName) 374 375 // if err := writeFile(filename, src); err != nil { 376 // return err 377 // } 378 // fa.mainFunc.created = true 379 // fa.cmd.logf("mainfunc created: %v", filename) 380 // //fa.mainFunc.filename = filename 381 382 // // TODO: add to pathsPkgs? 383 // // TODO: file not supposed to be annotated, should just get the insert startserver/exitserver 384 385 // fa.filesAsts[filename] = astFile 386 387 // return nil 388 //} 389 390 //func (fa *FilesToAnnotate) createTestMainAst(filename, pkgName string) (*ast.File, []byte) { 391 // src := fa.testMainSrc(pkgName) 392 // astFile, err := parser.ParseFile(fa.cmd.fset, filename, src, parser.Mode(0)) 393 // if err != nil { 394 // panic(err) 395 // } 396 // return astFile, src 397 //} 398 //func (fa *FilesToAnnotate) testMainSrc(pkgName string) []byte { 399 // return []byte(` 400 //package ` + pkgName + ` 401 //import "os" 402 //import "testing" 403 //func TestMain(m *testing.M) { 404 // os.Exit(m.Run()) 405 //} 406 // `) 407 //} 408 409 //---------- 410 411 func (fa *FilesToAnnotate) addFromSrcDirectives(ctx context.Context) error { 412 for filename, astFile := range fa.filesAsts { 413 // early stop 414 if err := ctx.Err(); err != nil { 415 return err 416 } 417 418 if err := fa.addFromSrcDirectivesFile(filename, astFile); err != nil { 419 return err 420 } 421 } 422 return nil 423 } 424 func (fa *FilesToAnnotate) addFromSrcDirectivesFile(filename string, astFile *ast.File) error { 425 426 // get nodes with associated comments 427 cns := commentsWithNodes(fa.cmd.fset, astFile, astFile.Comments) 428 429 // find comments that have "//godebug" directives 430 opts := []*AnnotationOpt{} 431 for _, cns := range cns { 432 opt, ok, err := annOptInComment(cns.Comment, cns.Node) 433 if err != nil { 434 // improve error 435 err = positionError(fa.cmd.fset, cns.Comment.Pos(), err) 436 return err 437 } 438 if ok { 439 opts = append(opts, opt) 440 } 441 } 442 443 // keep node map for annotation phase 444 for _, opt := range opts { 445 fa.nodeAnnTypes[opt.Node] = opt.Type 446 } 447 // add filenames to annotate from annotations 448 for _, opt := range opts { 449 if err := fa.addFromAnnOpt(opt); err != nil { 450 // improve error 451 err = positionError(fa.cmd.fset, opt.Comment.Pos(), err) 452 return err 453 } 454 } 455 return nil 456 } 457 func (fa *FilesToAnnotate) addFromAnnOpt(opt *AnnotationOpt) error { 458 switch opt.Type { 459 case AnnotationTypeNone: 460 return nil 461 case AnnotationTypeOff: 462 return nil 463 case AnnotationTypeBlock: 464 return fa.addNodeFilename(opt.Node, opt.Type) 465 case AnnotationTypeFile: 466 if opt.Opt != "" { 467 filename := opt.Opt 468 469 // make it relative to current filename dir if not absolute 470 if !filepath.IsAbs(filename) { 471 u, err := nodeFilename(fa.cmd.fset, opt.Comment) 472 if err != nil { 473 return err 474 } 475 dir := filepath.Dir(u) 476 filename = filepath.Join(dir, filename) 477 } 478 479 return fa.addFilename(filename, opt.Type) 480 } 481 482 return fa.addNodeFilename(opt.Node, opt.Type) 483 case AnnotationTypeImport: 484 path, err := nodeImportPath(opt.Node) 485 if err != nil { 486 return err 487 } 488 // TODO: pkg==pkgpath always? 489 return fa.addPkgPath(path, opt.Type) 490 case AnnotationTypePackage: 491 if opt.Opt != "" { 492 pkgPath := opt.Opt 493 return fa.addPkgPath(pkgPath, opt.Type) 494 } 495 pkg, err := fa.nodePkg(opt.Node) 496 if err != nil { 497 return err 498 } 499 return fa.addPkg(pkg, opt.Type) 500 case AnnotationTypeModule: 501 if opt.Opt != "" { 502 pkgPath := opt.Opt 503 pkg, err := fa.pathPkg(pkgPath) 504 if err != nil { 505 return err 506 } 507 return fa.addModule(pkg, opt.Type) 508 } 509 pkg, err := fa.nodePkg(opt.Node) 510 if err != nil { 511 return err 512 } 513 return fa.addModule(pkg, opt.Type) 514 default: 515 return fmt.Errorf("todo: handleAnnOpt: %v", opt.Type) 516 } 517 } 518 func (fa *FilesToAnnotate) addNodeFilename(node ast.Node, typ AnnotationType) error { 519 filename, err := nodeFilename(fa.cmd.fset, node) 520 if err != nil { 521 return err 522 } 523 return fa.addFilename(filename, typ) 524 } 525 func (fa *FilesToAnnotate) addPkgPath(pkgPath string, typ AnnotationType) error { 526 pkg, err := fa.pathPkg(pkgPath) 527 if err != nil { 528 return err 529 } 530 return fa.addPkg(pkg, typ) 531 } 532 func (fa *FilesToAnnotate) addPkg(pkg *packages.Package, typ AnnotationType) error { 533 for _, filename := range pkg.CompiledGoFiles { 534 if err := fa.addFilename(filename, typ); err != nil { 535 return err 536 } 537 } 538 return nil 539 } 540 func (fa *FilesToAnnotate) addModule(pkg *packages.Package, typ AnnotationType) error { 541 if pkg.Module == nil { 542 return fmt.Errorf("missing module in pkg: %v", pkg.Name) 543 } 544 // add pkgs that belong to module 545 for _, pkg2 := range fa.filesPkgs { 546 if pkg2.Module == nil { 547 continue 548 } 549 // module pointers differ, must use path 550 if pkg2.Module.Path == pkg.Module.Path { 551 if err := fa.addPkg(pkg2, typ); err != nil { 552 return err 553 } 554 } 555 } 556 return nil 557 } 558 func (fa *FilesToAnnotate) addFilename(filename string, typ AnnotationType) error { 559 _, ok := fa.filesPkgs[filename] 560 if !ok { 561 return fmt.Errorf("file not found in loaded program: %v", filename) 562 } 563 fa.addToAnnotate(filename, typ) 564 return nil 565 } 566 567 //---------- 568 569 func (fa *FilesToAnnotate) addToAnnotate(filename string, typ AnnotationType) { 570 typ0, ok := fa.toAnnotate[filename] 571 add := !ok || typ > typ0 572 if add { 573 fa.toAnnotate[filename] = typ 574 575 // set astfile node as well for the annotator to know from the start what type of annotation type is in the file 576 if astFile, ok := fa.filesAsts[filename]; ok { 577 fa.nodeAnnTypes[astFile] = typ 578 } 579 } 580 } 581 582 //---------- 583 584 func (fa *FilesToAnnotate) loadPackages(ctx context.Context) ([]*packages.Package, error) { 585 586 loadMode := 0 | 587 packages.NeedName | // name and pkgpath 588 packages.NeedFiles | 589 packages.NeedCompiledGoFiles | 590 packages.NeedImports | 591 packages.NeedDeps | 592 //packages.NeedExportsFile | // TODO 593 packages.NeedTypes | 594 packages.NeedSyntax | // ast.File 595 packages.NeedTypesInfo | // access to pkg.TypesInfo.* 596 //packages.NeedTypesSizes | // TODO 597 packages.NeedModule | 598 0 599 600 // faster startup 601 env := fa.cmd.env 602 env = append(env, "GOPACKAGESDRIVER=off") 603 604 cfg := &packages.Config{ 605 Context: ctx, 606 Fset: fa.cmd.fset, 607 Tests: fa.cmd.flags.mode.test, 608 Dir: fa.cmd.Dir, 609 Mode: loadMode, 610 Env: env, 611 BuildFlags: fa.cmd.buildArgs(), 612 //ParseFile: parseFile, 613 //Logf: func(f string, args ...interface{}) { 614 //s := fmt.Sprintf(f, args...) 615 //fmt.Print(s) 616 //}, 617 } 618 619 // There is a distinction between passing a file directly, or with the "file=" query. Passing without the file will pass a file argument to the underlying build tool, that could actually fail to properly load pkg.module var in the case of a simple [main.go go.mod] project. Because "go build" and "go build main.go" have slightly different behaviours. Check testdata/basic_gomod.txt test where it fails if the "file=" patterns are commented. 620 p := []string{} 621 622 for _, f := range fa.cmd.flags.unnamedArgs { 623 p = append(p, "file="+f) 624 } 625 p = append(p, fa.cmd.flags.unnamedArgs...) 626 627 pkgs, err := packages.Load(cfg, p...) 628 if err != nil { 629 return nil, err 630 } 631 632 for _, pkg := range pkgs { 633 if len(pkg.Errors) > 0 { 634 return nil, pkg.Errors[0] 635 } 636 } 637 638 //me := iout.MultiError{} 639 //for _, pkg := range pkgs { 640 // for _, err := range pkg.Errors { 641 // me.Add(err) 642 // } 643 //} 644 //if err := me.Result(); err != nil { 645 // return err 646 //} 647 648 return pkgs, nil 649 } 650 651 //---------- 652 653 func (fa *FilesToAnnotate) GoModFilename() (string, bool) { 654 655 //for _, pkg := range fa.main.pkgs { // can fail to load // TODO: make test 656 for _, pkg := range fa.filesPkgs { 657 //mod := pkg.Module 658 mod := pkgMod(pkg) 659 if mod != nil && mod.GoMod != "" { 660 //fa.cmd.logf("gomod(nomain?)=%v", mod.GoMod) 661 if mod.Main { 662 fa.cmd.logf("gomod=%v", mod.GoMod) 663 return mod.GoMod, true 664 } 665 } 666 } 667 668 // try env 669 env := goutil.GoEnv(fa.cmd.Dir) 670 filename := osutil.GetEnv(env, "GOMOD") 671 if filename != "" && filename != os.DevNull { // can be "/dev/null"! 672 return filename, true 673 } 674 675 return "", false 676 } 677 678 //---------- 679 680 func (fa *FilesToAnnotate) nodePkg(node ast.Node) (*packages.Package, error) { 681 filename, err := nodeFilename(fa.cmd.fset, node) 682 if err != nil { 683 return nil, err 684 } 685 pkg, ok := fa.filesPkgs[filename] 686 if !ok { 687 return nil, fmt.Errorf("missing pkg for filename: %v", filename) 688 } 689 return pkg, nil 690 } 691 692 func (fa *FilesToAnnotate) pathPkg(path string) (*packages.Package, error) { 693 pkg, ok := fa.pathsPkgs[path] 694 if !ok { 695 return nil, fmt.Errorf("missing pkg for path: %v", path) 696 } 697 return pkg, nil 698 } 699 700 //---------- 701 //---------- 702 //---------- 703 704 // TODO: consider using "go list" instead of packages.load? 705 // - would need to use: 706 // - go/types types.Config{Importer} 707 // - conf.check()... 708 // go list -json -export -deps 709 710 //---------- 711 712 func nodeImportPath(node ast.Node) (string, error) { 713 // ex: direclty at *ast.ImportSpec 714 // import ( 715 // //godebug:annotateimport 716 // "pkg1" 717 // ) 718 719 // ex: at *ast.GenDecl 720 // //godebug:annotateimport 721 // import "pkg1" 722 // //godebug:annotateimport 723 // import ( 724 // "pkg1" 725 // ) 726 727 if gd, ok := node.(*ast.GenDecl); ok { 728 if len(gd.Specs) > 0 { 729 is, ok := gd.Specs[0].(*ast.ImportSpec) 730 if ok { 731 node = is 732 } 733 } 734 } 735 736 is, ok := node.(*ast.ImportSpec) 737 if !ok { 738 return "", fmt.Errorf("not at an import spec") 739 } 740 return strconv.Unquote(is.Path.Value) 741 } 742 743 func nodeFilename(fset *token.FileSet, node ast.Node) (string, error) { 744 if node == nil { 745 return "", fmt.Errorf("node is nil") 746 } 747 tokFile := fset.File(node.Pos()) 748 if tokFile == nil { 749 return "", fmt.Errorf("missing token file: %v", node.Pos()) 750 } 751 return tokFile.Name(), nil 752 } 753 754 //---------- 755 756 func mainFuncName(testMode bool) string { 757 if testMode { 758 // needs to be used because getting the generated file by packages.Load() that contains a "main" will not allow it to be compiled since it uses "testing/internal" packages. 759 return "TestMain" 760 } 761 return "main" 762 } 763 764 //---------- 765 766 var genDigitsRand = rand.New(rand.NewSource(time.Now().UnixNano())) 767 768 func genDigitsStr(n int) string { 769 sb := strings.Builder{} 770 sb.Grow(n) 771 for i := 0; i < n; i++ { 772 b := byte('0' + genDigitsRand.Intn(10)) 773 _ = sb.WriteByte(b) 774 } 775 return sb.String() 776 } 777 778 func hashStringN(s string, n int) string { 779 h := md5.New() 780 h.Write([]byte(s)) 781 v := h.Sum(nil) 782 s2 := base64.RawURLEncoding.EncodeToString(v) 783 if len(s2) < n { 784 n = len(s2) 785 } 786 return s2[:n] 787 } 788 789 //---------- 790 791 func positionError(fset *token.FileSet, pos token.Pos, err error) error { 792 p := fset.Position(pos) 793 return fmt.Errorf("%v: %w", p, err) 794 } 795 796 //---------- 797 798 // TODO: move to goutil? 799 func pkgMod(pkg *packages.Package) *packages.Module { 800 mod := pkg.Module 801 if mod != nil { 802 for mod.Replace != nil { 803 mod = mod.Replace 804 } 805 } 806 return mod 807 }