stash/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go
SmallCoccinelle 45f700d6ea
Support Go 1.18: Upgrade gqlgen to v0.17.2 (#2443)
* Upgrade gqlgen to v0.17.2

This enables builds on Go 1.18. github.com/vektah/gqlparser is upgraded
to the newest version too.

Getting this to work is a bit of a hazzle. I had to first remove
vendoring from the repository, perform the upgrade and then re-introduce
the vendor directory. I think gqlgens analysis went wrong for some
reason on the upgrade. It would seem a clean-room installation fixed it.

* Bump project to 1.18

* Update all packages, address gqlgenc breaking changes

* Let `go mod tidy` handle the go.mod file

* Upgrade linter to 1.45.2

* Introduce v1.45.2 of the linter

The linter now correctly warns on `strings.Title` because it isn't
unicode-aware. Fix this by using the suggested fix from x/text/cases
to produce unicode-aware strings.

The mapping isn't entirely 1-1 as this new approach has a larger iface:
it spans all of unicode rather than just ASCII. It coincides for ASCII
however, so things should be largely the same.

* Ready ourselves for errchkjson and contextcheck.

* Revert dockerfile golang version changes for now

Co-authored-by: Kermie <kermie@isinthe.house>
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
2022-04-02 18:08:14 +11:00

212 lines
5.6 KiB
Go

package resolvergen
import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
"github.com/99designs/gqlgen/codegen"
"github.com/99designs/gqlgen/codegen/config"
"github.com/99designs/gqlgen/codegen/templates"
"github.com/99designs/gqlgen/internal/rewrite"
"github.com/99designs/gqlgen/plugin"
)
func New() plugin.Plugin {
return &Plugin{}
}
type Plugin struct{}
var _ plugin.CodeGenerator = &Plugin{}
func (m *Plugin) Name() string {
return "resolvergen"
}
func (m *Plugin) GenerateCode(data *codegen.Data) error {
if !data.Config.Resolver.IsDefined() {
return nil
}
switch data.Config.Resolver.Layout {
case config.LayoutSingleFile:
return m.generateSingleFile(data)
case config.LayoutFollowSchema:
return m.generatePerSchema(data)
}
return nil
}
func (m *Plugin) generateSingleFile(data *codegen.Data) error {
file := File{}
if _, err := os.Stat(data.Config.Resolver.Filename); err == nil {
// file already exists and we dont support updating resolvers with layout = single so just return
return nil
}
for _, o := range data.Objects {
if o.HasResolvers() {
file.Objects = append(file.Objects, o)
}
for _, f := range o.Fields {
if !f.IsResolver {
continue
}
resolver := Resolver{o, f, `panic("not implemented")`}
file.Resolvers = append(file.Resolvers, &resolver)
}
}
resolverBuild := &ResolverBuild{
File: &file,
PackageName: data.Config.Resolver.Package,
ResolverType: data.Config.Resolver.Type,
HasRoot: true,
}
return templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package,
FileNotice: `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.`,
Filename: data.Config.Resolver.Filename,
Data: resolverBuild,
Packages: data.Config.Packages,
})
}
func (m *Plugin) generatePerSchema(data *codegen.Data) error {
rewriter, err := rewrite.New(data.Config.Resolver.Dir())
if err != nil {
return err
}
files := map[string]*File{}
objects := make(codegen.Objects, len(data.Objects)+len(data.Inputs))
copy(objects, data.Objects)
copy(objects[len(data.Objects):], data.Inputs)
for _, o := range objects {
if o.HasResolvers() {
fn := gqlToResolverName(data.Config.Resolver.Dir(), o.Position.Src.Name, data.Config.Resolver.FilenameTemplate)
if files[fn] == nil {
files[fn] = &File{}
}
rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type))
rewriter.GetMethodBody(data.Config.Resolver.Type, strings.Title(o.Name))
files[fn].Objects = append(files[fn].Objects, o)
}
for _, f := range o.Fields {
if !f.IsResolver {
continue
}
structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)
implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName))
if implementation == "" {
implementation = `panic(fmt.Errorf("not implemented"))`
}
resolver := Resolver{o, f, implementation}
fn := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate)
if files[fn] == nil {
files[fn] = &File{}
}
files[fn].Resolvers = append(files[fn].Resolvers, &resolver)
}
}
for filename, file := range files {
file.imports = rewriter.ExistingImports(filename)
file.RemainingSource = rewriter.RemainingSource(filename)
}
for filename, file := range files {
resolverBuild := &ResolverBuild{
File: file,
PackageName: data.Config.Resolver.Package,
ResolverType: data.Config.Resolver.Type,
}
err := templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package,
FileNotice: `
// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.`,
Filename: filename,
Data: resolverBuild,
Packages: data.Config.Packages,
})
if err != nil {
return err
}
}
if _, err := os.Stat(data.Config.Resolver.Filename); errors.Is(err, fs.ErrNotExist) {
err := templates.Render(templates.Options{
PackageName: data.Config.Resolver.Package,
FileNotice: `
// This file will not be regenerated automatically.
//
// It serves as dependency injection for your app, add any dependencies you require here.`,
Template: `type {{.}} struct {}`,
Filename: data.Config.Resolver.Filename,
Data: data.Config.Resolver.Type,
Packages: data.Config.Packages,
})
if err != nil {
return err
}
}
return nil
}
type ResolverBuild struct {
*File
HasRoot bool
PackageName string
ResolverType string
}
type File struct {
// These are separated because the type definition of the resolver object may live in a different file from the
// resolver method implementations, for example when extending a type in a different graphql schema file
Objects []*codegen.Object
Resolvers []*Resolver
imports []rewrite.Import
RemainingSource string
}
func (f *File) Imports() string {
for _, imp := range f.imports {
if imp.Alias == "" {
_, _ = templates.CurrentImports.Reserve(imp.ImportPath)
} else {
_, _ = templates.CurrentImports.Reserve(imp.ImportPath, imp.Alias)
}
}
return ""
}
type Resolver struct {
Object *codegen.Object
Field *codegen.Field
Implementation string
}
func gqlToResolverName(base string, gqlname, filenameTmpl string) string {
gqlname = filepath.Base(gqlname)
ext := filepath.Ext(gqlname)
if filenameTmpl == "" {
filenameTmpl = "{name}.resolvers.go"
}
filename := strings.ReplaceAll(filenameTmpl, "{name}", strings.TrimSuffix(gqlname, ext))
return filepath.Join(base, filename)
}