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 }