Ctrl K
ComunidadGuías…

v2/codegen/decls.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package codegen

import (
	"fmt"
	"io"
	"slices"
	"strings"

	"github.com/dave/jennifer/jen"

	"encr.dev/pkg/fns"
	"encr.dev/pkg/paths"
	"encr.dev/v2/internals/pkginfo"
)

var importNames = map[string]string{
	"github.com/felixge/httpsnoop":        "httpsnoop",
	"github.com/json-iterator/go":         "jsoniter",
	"github.com/julienschmidt/httprouter": "httprouter",

	"encore.dev/appruntime/apisdk/api":      "__api",
	"encore.dev/appruntime/apisdk/app":      "__app",
	"encore.dev/appruntime/infrasdk/config": "__config",
	"encore.dev/appruntime/shared/etype":    "__etype",
	"encore.dev/appruntime/exported/model":  "__model",
	"encore.dev/appruntime/shared/serde":    "__serde",
	"encore.dev/appruntime/apisdk/service":  "__service",
	"encore.dev/beta/errs":                  "errs",
	"encore.dev/storage/sqldb":              "sqldb",
	"encore.dev/types/uuid":                 "uuid",
}

func newFile(pkg *pkginfo.Package, baseName, shortName string) *File {
	return newFileForPath(pkg.ImportPath, pkg.Name, pkg.FSPath, baseName, shortName)
}

func newFileForPath(pkgPath paths.Pkg, pkgName string, pkgDir paths.FS, baseName, shortName string) *File {
	jenFile := jen.NewFilePathName(pkgPath.String(), pkgName)

	for pkgPath, alias := range importNames {
		jenFile.ImportAlias(pkgPath, alias)
	}

	return &File{
		Jen:       jenFile,
		dir:       pkgDir,
		pkgPath:   pkgPath,
		baseName:  baseName,
		shortName: shortName,
	}
}

// File represents a generated file for a specific package.
type File struct {
	Jen *jen.File // the jen file we're generating

	// dir is the filesystem directory where the file should exist
	// within the application source tree. It need not match
	// any existing physical directory in the case of overlays.
	dir paths.FS

	pkgPath  paths.Pkg // the package the file belongs to
	baseName string    // the file's base name
	// shortName is the short version of the base name, for generated files.
	// For example if the base name is "encore_internal__metrics.go",
	// shortName is "metrics".
	shortName string

	decls []any // ordered list of Decl or jen.Code
}

// ImportAnon adds an anonymous ("_"-prefixed) import of the given packages.
func (f *File) ImportAnon(pkgs ...paths.Pkg) {
	f.Jen.Anon(fns.Map(pkgs, func(pkg paths.Pkg) string {
		return pkg.String()
	})...)
}

// name returns the computed file name.
func (f *File) name() string {
	return f.baseName
}

// Add adds a declaration to the file.
func (f *File) Add(code jen.Code) {
	if code != nil {
		f.decls = append(f.decls, code)
	}
}

// Render renders the file to the given writer.
func (f *File) Render(w io.Writer) error {
	for i, d := range f.decls {
		if i > 0 {
			f.Jen.Line()
		}

		switch d := d.(type) {
		case Decl:
			f.Jen.Add(d.Code())
		case jen.Code:
			f.Jen.Add(d)
		default:
			panic(fmt.Sprintf("internal error: unknown decl type: %T", d))
		}
	}
	return f.Jen.Render(w)
}

type Decl interface {
	Name() string
	Qual() *jen.Statement
	Code() *jen.Statement
}

func (f *File) HasDecl(nameParts ...string) bool {
	for _, decl := range f.decls {
		switch decl := decl.(type) {
		case *FuncDecl:
			if slices.Equal(decl.nameParts, nameParts) {
				return true
			}
		case *VarDecl:
			if slices.Equal(decl.nameParts, nameParts) {
				return true
			}
		}
	}
	return false
}

func (f *File) FuncDecl(nameParts ...string) *FuncDecl {
	if len(nameParts) == 0 {
		panic("gen.VarDecl: empty nameParts")
	}
	d := &FuncDecl{
		File:      f,
		nameParts: nameParts,
	}
	f.decls = append(f.decls, d)
	return d
}

func (f *File) VarDecl(nameParts ...string) *VarDecl {
	if len(nameParts) == 0 {
		panic("gen.VarDecl: empty nameParts")
	}
	d := &VarDecl{
		File:      f,
		nameParts: nameParts,
	}
	f.decls = append(f.decls, d)
	return d
}

// FuncDecl represents a generated declaration.
type FuncDecl struct {
	File *File // file the declaration belongs to.

	// nameParts are the suffix parts of the generated name.
	// For example, if the parts are ["foo", "bar"] and the
	// file shortName is "metrics", the generated declaration name
	// is "EncoreInternal_metrics_foo_bar".
	nameParts []string

	// typeParams are the type parameters of the generated function.
	typeParams []jen.Code

	// params are the parameters of the generated function.
	params []jen.Code

	// results are the results of the generated function.
	results []jen.Code

	// body is the body of the generated function.
	body *jen.Statement
}

// Name returns the package-level name of the declaration.
func (d *FuncDecl) Name() string {
	return "EncoreInternal_" + d.File.shortName + "_" + strings.Join(d.nameParts, "_")
}

// Qual returns the qualified name of the declaration.
func (d *FuncDecl) Qual() *jen.Statement {
	return jen.Qual(d.File.pkgPath.String(), d.Name())
}

// Code returns the generated code.
func (d *FuncDecl) Code() *jen.Statement {
	s := jen.Func().Id(d.Name())
	if len(d.typeParams) > 0 {
		s = s.Types(d.typeParams...)
	}
	if len(d.params) > 0 {
		s = s.Params(d.params...)
	}
	if len(d.results) > 0 {
		s = s.Params(d.results...)
	}
	if d.body != nil {
		s = s.Add(d.body)
	} else {
		s = s.Block()
	}
	return s
}

// TypeParams appends to the type parameters of the generated function.
func (d *FuncDecl) TypeParams(params ...jen.Code) *FuncDecl {
	d.typeParams = append(d.typeParams, params...)
	return d
}

// Params appends to the parameters of the generated function.
func (d *FuncDecl) Params(params ...jen.Code) *FuncDecl {
	d.params = append(d.params, params...)
	return d
}

// Results appends to the results of the generated function.
func (d *FuncDecl) Results(results ...jen.Code) *FuncDecl {
	d.results = append(d.results, results...)
	return d
}

// Body sets the body of the generated function.
func (d *FuncDecl) Body(code ...jen.Code) *FuncDecl {
	d.body = jen.Block(code...)
	return d
}

// BodyFunc computes the body of the generated function.
func (d *FuncDecl) BodyFunc(fn func(g *jen.Group)) *FuncDecl {
	d.body = jen.BlockFunc(fn)
	return d
}

type VarDecl struct {
	File *File // file the declaration belongs to.

	// nameParts are the suffix parts of the generated name.
	// For example, if the parts are ["foo", "bar"] and the
	// file shortName is "metrics", the generated declaration name
	// is "EncoreInternal_metrics_foo_bar".
	nameParts []string

	value *jen.Statement
}

func (d *VarDecl) Value(code ...jen.Code) *VarDecl {
	d.value = jen.Add(code...)
	return d
}

func (d *VarDecl) Code() *jen.Statement {
	return jen.Var().Id(d.Name()).Op("=").Add(d.value)
}

func (d *VarDecl) Name() string {
	return "EncoreInternal_" + d.File.shortName + "_" + strings.Join(d.nameParts, "_")
}

func (d *VarDecl) Qual() *jen.Statement {
	return jen.Qual(d.File.pkgPath.String(), d.Name())
}