github.com/opiuman/genqlient@v1.0.0/generate/imports.go (about)

     1  package generate
     2  
     3  import (
     4  	"fmt"
     5  	"go/types"
     6  	"regexp"
     7  	"strconv"
     8  	"strings"
     9  )
    10  
    11  func (g *generator) addImportFor(pkgPath string) (alias string) {
    12  	pkgName := pkgPath[strings.LastIndex(pkgPath, "/")+1:]
    13  	alias = pkgName
    14  	suffix := 2
    15  	for g.usedAliases[alias] {
    16  		alias = pkgName + strconv.Itoa(suffix)
    17  		suffix++
    18  	}
    19  
    20  	g.imports[pkgPath] = alias
    21  	g.usedAliases[alias] = true
    22  	return alias
    23  }
    24  
    25  var _sliceOrMapPrefixRegexp = regexp.MustCompile(`^(\*|\[\d*\]|map\[string\])*`)
    26  
    27  // ref takes a Go fully-qualified name, ensures that any necessary symbols are
    28  // imported, and returns an appropriate reference.
    29  //
    30  // Ideally, we want to allow a reference to basically an arbitrary symbol.
    31  // But that's very hard, because it might be quite complicated, like
    32  //	struct{ F []map[mypkg.K]otherpkg.V }
    33  // Now in practice, using an unnamed struct is not a great idea, but we do
    34  // want to allow as much as we can that encoding/json knows how to work
    35  // with, since you would reasonably expect us to accept, say,
    36  // map[string][]interface{}.  So we allow:
    37  // - any named type (mypkg.T)
    38  // - any predeclared basic type (string, int, etc.)
    39  // - interface{}
    40  // - for any allowed type T, *T, []T, [N]T, and map[string]T
    41  // which effectively excludes:
    42  // - unnamed struct types
    43  // - map[K]V where K is a named type wrapping string
    44  // - any nonstandard spelling of those (interface {/* hi */},
    45  //	 map[  string      ]T)
    46  // (This is documented in docs/genqlient.yaml)
    47  func (g *generator) ref(fullyQualifiedName string) (qualifiedName string, err error) {
    48  	errorMsg := `invalid type-name "%v" (%v); expected a builtin, ` +
    49  		`path/to/package.Name, interface{}, or a slice, map, or pointer of those`
    50  
    51  	if strings.Contains(fullyQualifiedName, " ") {
    52  		return "", errorf(nil, errorMsg, fullyQualifiedName, "contains spaces")
    53  	}
    54  
    55  	prefix := _sliceOrMapPrefixRegexp.FindString(fullyQualifiedName)
    56  	nameToImport := fullyQualifiedName[len(prefix):]
    57  
    58  	i := strings.LastIndex(nameToImport, ".")
    59  	if i == -1 {
    60  		if nameToImport != "interface{}" && types.Universe.Lookup(nameToImport) == nil {
    61  			return "", errorf(nil, errorMsg, fullyQualifiedName,
    62  				fmt.Sprintf(`unknown type-name "%v"`, nameToImport))
    63  		}
    64  		return fullyQualifiedName, nil
    65  	}
    66  
    67  	pkgPath := nameToImport[:i]
    68  	localName := nameToImport[i+1:]
    69  	alias, ok := g.imports[pkgPath]
    70  	if !ok {
    71  		if g.importsLocked {
    72  			return "", errorf(nil,
    73  				`genqlient internal error: imports locked but package "%v" has not been imported`, pkgPath)
    74  		}
    75  		alias = g.addImportFor(pkgPath)
    76  	}
    77  	return prefix + alias + "." + localName, nil
    78  }
    79  
    80  // Returns the import-clause to use in the generated code.
    81  func (g *generator) Imports() string {
    82  	g.importsLocked = true
    83  	if len(g.imports) == 0 {
    84  		return ""
    85  	}
    86  
    87  	var builder strings.Builder
    88  	builder.WriteString("import (\n")
    89  	for path, alias := range g.imports {
    90  		if path == alias || strings.HasSuffix(path, "/"+alias) {
    91  			builder.WriteString("\t" + strconv.Quote(path) + "\n")
    92  		} else {
    93  			builder.WriteString("\t" + alias + " " + strconv.Quote(path) + "\n")
    94  		}
    95  	}
    96  	builder.WriteString(")\n\n")
    97  	return builder.String()
    98  }