github.com/codykaup/genqlient@v0.6.2/generate/imports.go (about)

     1  package generate
     2  
     3  import (
     4  	"fmt"
     5  	"go/token"
     6  	"go/types"
     7  	"regexp"
     8  	"strconv"
     9  	"strings"
    10  	"unicode"
    11  )
    12  
    13  // makeIdentifier takes a string and returns a valid go identifier like it.
    14  //
    15  // If the string is an identifier, return the input. Otherwise, munge it to
    16  // make a valid identifier, which at worst (if the input is entirely emoji,
    17  // say) means coming up with one out of whole cloth. This identifier need not
    18  // be particularly unique; the caller may add a suffix.
    19  func makeIdentifier(candidateIdentifier string) string {
    20  	if token.IsIdentifier(candidateIdentifier) {
    21  		return candidateIdentifier
    22  	}
    23  
    24  	var goodChars strings.Builder
    25  	for _, c := range candidateIdentifier {
    26  		// modified from token.IsIdentifier
    27  		if unicode.IsLetter(c) || c == '_' ||
    28  			// digits only valid after first char
    29  			goodChars.Len() > 0 && unicode.IsDigit(c) {
    30  			goodChars.WriteRune(c)
    31  		}
    32  	}
    33  	if goodChars.Len() > 0 {
    34  		return goodChars.String()
    35  	}
    36  
    37  	return "alias"
    38  }
    39  
    40  func (g *generator) addImportFor(pkgPath string) (alias string) {
    41  	pkgName := makeIdentifier(pkgPath[strings.LastIndex(pkgPath, "/")+1:])
    42  	alias = pkgName
    43  	suffix := 2
    44  	for g.usedAliases[alias] {
    45  		alias = pkgName + strconv.Itoa(suffix)
    46  		suffix++
    47  	}
    48  
    49  	g.imports[pkgPath] = alias
    50  	g.usedAliases[alias] = true
    51  	return alias
    52  }
    53  
    54  var _sliceOrMapPrefixRegexp = regexp.MustCompile(`^(\*|\[\d*\]|map\[string\])*`)
    55  
    56  // ref takes a Go fully-qualified name, ensures that any necessary symbols are
    57  // imported, and returns an appropriate reference.
    58  //
    59  // Ideally, we want to allow a reference to basically an arbitrary symbol.
    60  // But that's very hard, because it might be quite complicated, like
    61  //
    62  //	struct{ F []map[mypkg.K]otherpkg.V }
    63  //
    64  // Now in practice, using an unnamed struct is not a great idea, but we do
    65  // want to allow as much as we can that encoding/json knows how to work
    66  // with, since you would reasonably expect us to accept, say,
    67  // map[string][]interface{}.  So we allow:
    68  //   - any named type (mypkg.T)
    69  //   - any predeclared basic type (string, int, etc.)
    70  //   - interface{}
    71  //   - for any allowed type T, *T, []T, [N]T, and map[string]T
    72  //
    73  // which effectively excludes:
    74  //   - unnamed struct types
    75  //   - map[K]V where K is a named type wrapping string
    76  //   - any nonstandard spelling of those (interface {/* hi */},
    77  //     map[  string      ]T)
    78  //
    79  // (This is documented in docs/genqlient.yaml)
    80  func (g *generator) ref(fullyQualifiedName string) (qualifiedName string, err error) {
    81  	errorMsg := `invalid type-name "%v" (%v); expected a builtin, ` +
    82  		`path/to/package.Name, interface{}, or a slice, map, or pointer of those`
    83  
    84  	if strings.Contains(fullyQualifiedName, " ") {
    85  		return "", errorf(nil, errorMsg, fullyQualifiedName, "contains spaces")
    86  	}
    87  
    88  	prefix := _sliceOrMapPrefixRegexp.FindString(fullyQualifiedName)
    89  	nameToImport := fullyQualifiedName[len(prefix):]
    90  
    91  	i := strings.LastIndex(nameToImport, ".")
    92  	if i == -1 {
    93  		if nameToImport != "interface{}" && types.Universe.Lookup(nameToImport) == nil {
    94  			return "", errorf(nil, errorMsg, fullyQualifiedName,
    95  				fmt.Sprintf(`unknown type-name "%v"`, nameToImport))
    96  		}
    97  		return fullyQualifiedName, nil
    98  	}
    99  
   100  	pkgPath := nameToImport[:i]
   101  	localName := nameToImport[i+1:]
   102  	alias, ok := g.imports[pkgPath]
   103  	if !ok {
   104  		if g.importsLocked {
   105  			return "", errorf(nil,
   106  				`genqlient internal error: imports locked but package "%v" has not been imported`, pkgPath)
   107  		}
   108  		alias = g.addImportFor(pkgPath)
   109  	}
   110  	return prefix + alias + "." + localName, nil
   111  }
   112  
   113  // Returns the import-clause to use in the generated code.
   114  func (g *generator) Imports() string {
   115  	g.importsLocked = true
   116  	if len(g.imports) == 0 {
   117  		return ""
   118  	}
   119  
   120  	var builder strings.Builder
   121  	builder.WriteString("import (\n")
   122  	for path, alias := range g.imports {
   123  		if path == alias || strings.HasSuffix(path, "/"+alias) {
   124  			builder.WriteString("\t" + strconv.Quote(path) + "\n")
   125  		} else {
   126  			builder.WriteString("\t" + alias + " " + strconv.Quote(path) + "\n")
   127  		}
   128  	}
   129  	builder.WriteString(")\n\n")
   130  	return builder.String()
   131  }