Skip to content

Commit 80687e7

Browse files
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
1 parent 1d71fff commit 80687e7

File tree

6 files changed

+197
-2
lines changed

6 files changed

+197
-2
lines changed

docs/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ When releasing a new version:
2828

2929
### Bug fixes:
3030

31-
- Fixed non-deterministic generated code when querying graphql interfaces
31+
- Fixed non-deterministic generated code when querying graphql interfaces.
32+
- Fixed generated code when last component of package name is not a valid identifier (e.g. `"path/to/my-package"`).
3233

3334
## v0.5.0
3435

generate/generate_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ func TestGenerateWithConfig(t *testing.T) {
171171
Generated: "generated.go",
172172
ContextType: "github.com/Khan/genqlient/internal/testutil.MyContext",
173173
}},
174+
{"CustomContextWithAlias", "", nil, &Config{
175+
Generated: "generated.go",
176+
ContextType: "github.com/Khan/genqlient/internal/testutil/junk---fun.name.MyContext",
177+
}},
174178
{"StructReferences", "", nil, &Config{
175179
StructReferences: true,
176180
Generated: "generated-structrefs.go",

generate/imports.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,43 @@ package generate
22

33
import (
44
"fmt"
5+
"go/token"
56
"go/types"
67
"regexp"
78
"strconv"
89
"strings"
10+
"unicode"
911
)
1012

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+
1140
func (g *generator) addImportFor(pkgPath string) (alias string) {
12-
pkgName := pkgPath[strings.LastIndex(pkgPath, "/")+1:]
41+
pkgName := makeIdentifier(pkgPath[strings.LastIndex(pkgPath, "/")+1:])
1342
alias = pkgName
1443
suffix := 2
1544
for g.usedAliases[alias] {

generate/imports_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package generate
2+
3+
import (
4+
"go/token"
5+
"testing"
6+
)
7+
8+
func TestMakeIdentifier(t *testing.T) {
9+
tests := []struct {
10+
testName string
11+
input string
12+
expected string
13+
}{
14+
{"GoodIdentifier", "myIdent", "myIdent"},
15+
{"GoodIdentifierNumbers", "myIdent1234", "myIdent1234"},
16+
{"NumberPrefix", "1234myIdent", "myIdent"},
17+
{"OnlyNumbers", "1234", "alias"},
18+
{"Dashes", "my-ident", "myident"},
19+
// Note: most Go implementations won't actually allow
20+
// this package-path, but the spec is pretty vague
21+
// so make sure to handle it.
22+
{"JunkAnd", "my!!\\\\\nident", "myident"},
23+
{"JunkOnly", "!!\\\\\n", "alias"},
24+
{"Accents", "née", "née"},
25+
{"Kanji", "日本", "日本"},
26+
{"EmojiAnd", "ident👍", "ident"},
27+
{"EmojiOnly", "👍", "alias"},
28+
}
29+
30+
for _, test := range tests {
31+
test := test
32+
t.Run(test.testName, func(t *testing.T) {
33+
actual := makeIdentifier(test.input)
34+
if actual != test.expected {
35+
t.Errorf("mismatch:\ngot: %s\nwant: %s", actual, test.expected)
36+
}
37+
if !token.IsIdentifier(actual) {
38+
t.Errorf("not a valid identifier: %s", actual)
39+
}
40+
})
41+
}
42+
}

generate/testdata/snapshots/TestGenerateWithConfig-CustomContextWithAlias-testdata-queries-generated.go

Lines changed: 68 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package testutil
2+
3+
import (
4+
"context"
5+
"time"
6+
7+
"github.com/Khan/genqlient/graphql"
8+
)
9+
10+
type ID string
11+
12+
type Pokemon struct {
13+
Species string `json:"species"`
14+
Level int `json:"level"`
15+
}
16+
17+
func (p Pokemon) Battle(q Pokemon) bool {
18+
return p.Level > q.Level
19+
}
20+
21+
type MyContext interface {
22+
context.Context
23+
24+
MyMethod()
25+
}
26+
27+
func GetClientFromNowhere() (graphql.Client, error) { return nil, nil }
28+
func GetClientFromContext(ctx context.Context) (graphql.Client, error) { return nil, nil }
29+
func GetClientFromMyContext(ctx MyContext) (graphql.Client, error) { return nil, nil }
30+
31+
const dateFormat = "2006-01-02"
32+
33+
func MarshalDate(t *time.Time) ([]byte, error) {
34+
// nil should never happen but we might as well check. zero-time does
35+
// happen because omitempty doesn't consider it zero; we'd prefer to write
36+
// null than "0001-01-01".
37+
//
38+
// (I mean, we're tests. Who cares! But we may as well try to match what
39+
// prod code would want.)
40+
if t == nil || t.IsZero() {
41+
return []byte("null"), nil
42+
}
43+
return []byte(`"` + t.Format(dateFormat) + `"`), nil
44+
}
45+
46+
func UnmarshalDate(b []byte, t *time.Time) error {
47+
// (modified from time.Time.UnmarshalJSON)
48+
var err error
49+
*t, err = time.Parse(`"`+dateFormat+`"`, string(b))
50+
return err
51+
}

0 commit comments

Comments
 (0)