Support package-names with dashes in them (#232)
Support package-names with dashes in them We were smart about aliasing if you have name-collisions, but not if your package name is something that's not a valid identifier, like `"path/to/my-package"`, which Go for better or worse allows. Now we remove all the invalid characters (in practice mainly dashes, dots, and leading digits). Fixes #231. Test plan: make check
This commit is contained in:
+2
-1
@@ -28,7 +28,8 @@ When releasing a new version:
|
|||||||
|
|
||||||
### Bug fixes:
|
### Bug fixes:
|
||||||
|
|
||||||
- Fixed non-deterministic generated code when querying graphql interfaces
|
- Fixed non-deterministic generated code when querying graphql interfaces.
|
||||||
|
- Fixed generated code when last component of package name is not a valid identifier (e.g. `"path/to/my-package"`).
|
||||||
|
|
||||||
## v0.5.0
|
## v0.5.0
|
||||||
|
|
||||||
|
|||||||
@@ -171,6 +171,10 @@ func TestGenerateWithConfig(t *testing.T) {
|
|||||||
Generated: "generated.go",
|
Generated: "generated.go",
|
||||||
ContextType: "github.com/Khan/genqlient/internal/testutil.MyContext",
|
ContextType: "github.com/Khan/genqlient/internal/testutil.MyContext",
|
||||||
}},
|
}},
|
||||||
|
{"CustomContextWithAlias", "", nil, &Config{
|
||||||
|
Generated: "generated.go",
|
||||||
|
ContextType: "github.com/Khan/genqlient/internal/testutil/junk---fun.name.MyContext",
|
||||||
|
}},
|
||||||
{"StructReferences", "", nil, &Config{
|
{"StructReferences", "", nil, &Config{
|
||||||
StructReferences: true,
|
StructReferences: true,
|
||||||
Generated: "generated-structrefs.go",
|
Generated: "generated-structrefs.go",
|
||||||
|
|||||||
+30
-1
@@ -2,14 +2,43 @@ package generate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// makeIdentifier takes a string and returns a valid go identifier like it.
|
||||||
|
//
|
||||||
|
// If the string is an identifier, return the input. Otherwise, munge it to
|
||||||
|
// make a valid identifier, which at worst (if the input is entirely emoji,
|
||||||
|
// say) means coming up with one out of whole cloth. This identifier need not
|
||||||
|
// be particularly unique; the caller may add a suffix.
|
||||||
|
func makeIdentifier(candidateIdentifier string) string {
|
||||||
|
if token.IsIdentifier(candidateIdentifier) {
|
||||||
|
return candidateIdentifier
|
||||||
|
}
|
||||||
|
|
||||||
|
var goodChars strings.Builder
|
||||||
|
for _, c := range candidateIdentifier {
|
||||||
|
// modified from token.IsIdentifier
|
||||||
|
if unicode.IsLetter(c) || c == '_' ||
|
||||||
|
// digits only valid after first char
|
||||||
|
goodChars.Len() > 0 && unicode.IsDigit(c) {
|
||||||
|
goodChars.WriteRune(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if goodChars.Len() > 0 {
|
||||||
|
return goodChars.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
return "alias"
|
||||||
|
}
|
||||||
|
|
||||||
func (g *generator) addImportFor(pkgPath string) (alias string) {
|
func (g *generator) addImportFor(pkgPath string) (alias string) {
|
||||||
pkgName := pkgPath[strings.LastIndex(pkgPath, "/")+1:]
|
pkgName := makeIdentifier(pkgPath[strings.LastIndex(pkgPath, "/")+1:])
|
||||||
alias = pkgName
|
alias = pkgName
|
||||||
suffix := 2
|
suffix := 2
|
||||||
for g.usedAliases[alias] {
|
for g.usedAliases[alias] {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/token"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMakeIdentifier(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
testName string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"GoodIdentifier", "myIdent", "myIdent"},
|
||||||
|
{"GoodIdentifierNumbers", "myIdent1234", "myIdent1234"},
|
||||||
|
{"NumberPrefix", "1234myIdent", "myIdent"},
|
||||||
|
{"OnlyNumbers", "1234", "alias"},
|
||||||
|
{"Dashes", "my-ident", "myident"},
|
||||||
|
// Note: most Go implementations won't actually allow
|
||||||
|
// this package-path, but the spec is pretty vague
|
||||||
|
// so make sure to handle it.
|
||||||
|
{"JunkAnd", "my!!\\\\\nident", "myident"},
|
||||||
|
{"JunkOnly", "!!\\\\\n", "alias"},
|
||||||
|
{"Accents", "née", "née"},
|
||||||
|
{"Kanji", "日本", "日本"},
|
||||||
|
{"EmojiAnd", "ident👍", "ident"},
|
||||||
|
{"EmojiOnly", "👍", "alias"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
test := test
|
||||||
|
t.Run(test.testName, func(t *testing.T) {
|
||||||
|
actual := makeIdentifier(test.input)
|
||||||
|
if actual != test.expected {
|
||||||
|
t.Errorf("mismatch:\ngot: %s\nwant: %s", actual, test.expected)
|
||||||
|
}
|
||||||
|
if !token.IsIdentifier(actual) {
|
||||||
|
t.Errorf("not a valid identifier: %s", actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+68
@@ -0,0 +1,68 @@
|
|||||||
|
// Code generated by github.com/Khan/genqlient, DO NOT EDIT.
|
||||||
|
|
||||||
|
package queries
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/Khan/genqlient/graphql"
|
||||||
|
junkfunname "github.com/Khan/genqlient/internal/testutil/junk---fun.name"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check that context_type from genqlient.yaml implements context.Context.
|
||||||
|
var _ context.Context = (junkfunname.MyContext)(nil)
|
||||||
|
|
||||||
|
// SimpleQueryResponse is returned by SimpleQuery on success.
|
||||||
|
type SimpleQueryResponse struct {
|
||||||
|
// user looks up a user by some stuff.
|
||||||
|
//
|
||||||
|
// See UserQueryInput for what stuff is supported.
|
||||||
|
// If query is null, returns the current user.
|
||||||
|
User SimpleQueryUser `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUser returns SimpleQueryResponse.User, and is useful for accessing the field via an interface.
|
||||||
|
func (v *SimpleQueryResponse) GetUser() SimpleQueryUser { return v.User }
|
||||||
|
|
||||||
|
// SimpleQueryUser includes the requested fields of the GraphQL type User.
|
||||||
|
// The GraphQL type's documentation follows.
|
||||||
|
//
|
||||||
|
// A User is a user!
|
||||||
|
type SimpleQueryUser struct {
|
||||||
|
// id is the user's ID.
|
||||||
|
//
|
||||||
|
// It is stable, unique, and opaque, like all good IDs.
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetId returns SimpleQueryUser.Id, and is useful for accessing the field via an interface.
|
||||||
|
func (v *SimpleQueryUser) GetId() string { return v.Id }
|
||||||
|
|
||||||
|
func SimpleQuery(
|
||||||
|
ctx junkfunname.MyContext,
|
||||||
|
client graphql.Client,
|
||||||
|
) (*SimpleQueryResponse, error) {
|
||||||
|
req := &graphql.Request{
|
||||||
|
OpName: "SimpleQuery",
|
||||||
|
Query: `
|
||||||
|
query SimpleQuery {
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var data SimpleQueryResponse
|
||||||
|
resp := &graphql.Response{Data: &data}
|
||||||
|
|
||||||
|
err = client.MakeRequest(
|
||||||
|
ctx,
|
||||||
|
req,
|
||||||
|
resp,
|
||||||
|
)
|
||||||
|
|
||||||
|
return &data, err
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package testutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Khan/genqlient/graphql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ID string
|
||||||
|
|
||||||
|
type Pokemon struct {
|
||||||
|
Species string `json:"species"`
|
||||||
|
Level int `json:"level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p Pokemon) Battle(q Pokemon) bool {
|
||||||
|
return p.Level > q.Level
|
||||||
|
}
|
||||||
|
|
||||||
|
type MyContext interface {
|
||||||
|
context.Context
|
||||||
|
|
||||||
|
MyMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetClientFromNowhere() (graphql.Client, error) { return nil, nil }
|
||||||
|
func GetClientFromContext(ctx context.Context) (graphql.Client, error) { return nil, nil }
|
||||||
|
func GetClientFromMyContext(ctx MyContext) (graphql.Client, error) { return nil, nil }
|
||||||
|
|
||||||
|
const dateFormat = "2006-01-02"
|
||||||
|
|
||||||
|
func MarshalDate(t *time.Time) ([]byte, error) {
|
||||||
|
// nil should never happen but we might as well check. zero-time does
|
||||||
|
// happen because omitempty doesn't consider it zero; we'd prefer to write
|
||||||
|
// null than "0001-01-01".
|
||||||
|
//
|
||||||
|
// (I mean, we're tests. Who cares! But we may as well try to match what
|
||||||
|
// prod code would want.)
|
||||||
|
if t == nil || t.IsZero() {
|
||||||
|
return []byte("null"), nil
|
||||||
|
}
|
||||||
|
return []byte(`"` + t.Format(dateFormat) + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnmarshalDate(b []byte, t *time.Time) error {
|
||||||
|
// (modified from time.Time.UnmarshalJSON)
|
||||||
|
var err error
|
||||||
|
*t, err = time.Parse(`"`+dateFormat+`"`, string(b))
|
||||||
|
return err
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user