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  }