...

Source file src/github.com/99designs/gqlgen/internal/code/imports.go

Documentation: github.com/99designs/gqlgen/internal/code

     1  package code
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"go/build"
     7  	"go/parser"
     8  	"go/token"
     9  	"os"
    10  	"path/filepath"
    11  	"regexp"
    12  	"strings"
    13  )
    14  
    15  var gopaths []string
    16  
    17  func init() {
    18  	gopaths = filepath.SplitList(build.Default.GOPATH)
    19  	for i, p := range gopaths {
    20  		gopaths[i] = filepath.ToSlash(filepath.Join(p, "src"))
    21  	}
    22  }
    23  
    24  // NameForDir manually looks for package stanzas in files located in the given directory. This can be
    25  // much faster than having to consult go list, because we already know exactly where to look.
    26  func NameForDir(dir string) string {
    27  	dir, err := filepath.Abs(dir)
    28  	if err != nil {
    29  		return SanitizePackageName(filepath.Base(dir))
    30  	}
    31  	files, err := os.ReadDir(dir)
    32  	if err != nil {
    33  		return SanitizePackageName(filepath.Base(dir))
    34  	}
    35  	fset := token.NewFileSet()
    36  	for _, file := range files {
    37  		if !strings.HasSuffix(strings.ToLower(file.Name()), ".go") {
    38  			continue
    39  		}
    40  
    41  		filename := filepath.Join(dir, file.Name())
    42  		if src, err := parser.ParseFile(fset, filename, nil, parser.PackageClauseOnly); err == nil {
    43  			return src.Name.Name
    44  		}
    45  	}
    46  
    47  	return SanitizePackageName(filepath.Base(dir))
    48  }
    49  
    50  type goModuleSearchResult struct {
    51  	path       string
    52  	goModPath  string
    53  	moduleName string
    54  }
    55  
    56  var goModuleRootCache = map[string]goModuleSearchResult{}
    57  
    58  // goModuleRoot returns the root of the current go module if there is a go.mod file in the directory tree
    59  // If not, it returns false
    60  func goModuleRoot(dir string) (string, bool) {
    61  	dir, err := filepath.Abs(dir)
    62  	if err != nil {
    63  		panic(err)
    64  	}
    65  	dir = filepath.ToSlash(dir)
    66  
    67  	dirs := []string{dir}
    68  	result := goModuleSearchResult{}
    69  
    70  	for {
    71  		modDir := dirs[len(dirs)-1]
    72  
    73  		if val, ok := goModuleRootCache[dir]; ok {
    74  			result = val
    75  			break
    76  		}
    77  
    78  		if content, err := os.ReadFile(filepath.Join(modDir, "go.mod")); err == nil {
    79  			moduleName := extractModuleName(content)
    80  			result = goModuleSearchResult{
    81  				path:       moduleName,
    82  				goModPath:  modDir,
    83  				moduleName: moduleName,
    84  			}
    85  			goModuleRootCache[modDir] = result
    86  			break
    87  		}
    88  
    89  		if modDir == "" || modDir == "." || modDir == "/" || strings.HasSuffix(modDir, "\\") {
    90  			// Reached the top of the file tree which means go.mod file is not found
    91  			// Set root folder with a sentinel cache value
    92  			goModuleRootCache[modDir] = result
    93  			break
    94  		}
    95  
    96  		dirs = append(dirs, filepath.Dir(modDir))
    97  	}
    98  
    99  	// create a cache for each path in a tree traversed, except the top one as it is already cached
   100  	for _, d := range dirs[:len(dirs)-1] {
   101  		if result.moduleName == "" {
   102  			// go.mod is not found in the tree, so the same sentinel value fits all the directories in a tree
   103  			goModuleRootCache[d] = result
   104  		} else {
   105  			if relPath, err := filepath.Rel(result.goModPath, d); err != nil {
   106  				panic(err)
   107  			} else {
   108  				path := result.moduleName
   109  				relPath := filepath.ToSlash(relPath)
   110  				if !strings.HasSuffix(relPath, "/") {
   111  					path += "/"
   112  				}
   113  				path += relPath
   114  
   115  				goModuleRootCache[d] = goModuleSearchResult{
   116  					path:       path,
   117  					goModPath:  result.goModPath,
   118  					moduleName: result.moduleName,
   119  				}
   120  			}
   121  		}
   122  	}
   123  
   124  	res := goModuleRootCache[dir]
   125  	if res.moduleName == "" {
   126  		return "", false
   127  	}
   128  	return res.path, true
   129  }
   130  
   131  func extractModuleName(content []byte) string {
   132  	for {
   133  		advance, tkn, err := bufio.ScanLines(content, false)
   134  		if err != nil {
   135  			panic(fmt.Errorf("error parsing mod file: %w", err))
   136  		}
   137  		if advance == 0 {
   138  			break
   139  		}
   140  		s := strings.Trim(string(tkn), " \t")
   141  		if s != "" && !strings.HasPrefix(s, "//") {
   142  			break
   143  		}
   144  		if advance <= len(content) {
   145  			content = content[advance:]
   146  		}
   147  	}
   148  	moduleName := string(modregex.FindSubmatch(content)[1])
   149  	return moduleName
   150  }
   151  
   152  // ImportPathForDir takes a path and returns a golang import path for the package
   153  func ImportPathForDir(dir string) (res string) {
   154  	dir, err := filepath.Abs(dir)
   155  	if err != nil {
   156  		panic(err)
   157  	}
   158  	dir = filepath.ToSlash(dir)
   159  
   160  	modDir, ok := goModuleRoot(dir)
   161  	if ok {
   162  		return modDir
   163  	}
   164  
   165  	for _, gopath := range gopaths {
   166  		if len(gopath) < len(dir) && strings.EqualFold(gopath, dir[0:len(gopath)]) {
   167  			return dir[len(gopath)+1:]
   168  		}
   169  	}
   170  
   171  	return ""
   172  }
   173  
   174  var modregex = regexp.MustCompile(`module (\S*)`)
   175  

View as plain text