Adding upstream version 0.0~git20250307.c2e6a77.
Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
parent
be99035e76
commit
b6e042e2af
64 changed files with 10976 additions and 0 deletions
264
tygoja.go
Normal file
264
tygoja.go
Normal file
|
@ -0,0 +1,264 @@
|
|||
package tygoja
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
// Tygoja is a generator for one or more input packages,
|
||||
// responsible for linking them together if necessary.
|
||||
type Tygoja struct {
|
||||
conf *Config
|
||||
|
||||
parent *Tygoja
|
||||
implicitPackages map[string][]string
|
||||
generatedTypes map[string][]string
|
||||
generatedPackageDocs map[string]struct{}
|
||||
}
|
||||
|
||||
// New initializes a new Tygoja generator from the specified config.
|
||||
func New(config Config) *Tygoja {
|
||||
config.InitDefaults()
|
||||
|
||||
return &Tygoja{
|
||||
conf: &config,
|
||||
implicitPackages: map[string][]string{},
|
||||
generatedTypes: map[string][]string{},
|
||||
generatedPackageDocs: map[string]struct{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Generate executes the generator and produces the related TS files.
|
||||
func (g *Tygoja) Generate() (string, error) {
|
||||
// extract config packages
|
||||
configPackages := make([]string, 0, len(g.conf.Packages))
|
||||
for p, types := range g.conf.Packages {
|
||||
if len(types) == 0 {
|
||||
continue // no typings
|
||||
}
|
||||
configPackages = append(configPackages, p)
|
||||
}
|
||||
|
||||
// load packages info
|
||||
pkgs, err := packages.Load(&packages.Config{
|
||||
Mode: packages.NeedSyntax | packages.NeedFiles | packages.NeedDeps | packages.NeedImports | packages.NeedTypes,
|
||||
}, configPackages...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var s strings.Builder
|
||||
|
||||
// Heading
|
||||
if g.parent == nil {
|
||||
s.WriteString("// GENERATED CODE - DO NOT MODIFY BY HAND\n")
|
||||
|
||||
if g.conf.Heading != "" {
|
||||
s.WriteString(g.conf.Heading)
|
||||
}
|
||||
|
||||
// write base types
|
||||
// ---
|
||||
s.WriteString("type ")
|
||||
s.WriteString(BaseTypeDict)
|
||||
s.WriteString(" = { [key:string | number | symbol]: any; }\n")
|
||||
|
||||
s.WriteString("type ")
|
||||
s.WriteString(BaseTypeAny)
|
||||
s.WriteString(" = any\n")
|
||||
// ---
|
||||
}
|
||||
|
||||
for i, pkg := range pkgs {
|
||||
if len(pkg.Errors) > 0 {
|
||||
return "", fmt.Errorf("%+v", pkg.Errors)
|
||||
}
|
||||
|
||||
if len(pkg.GoFiles) == 0 {
|
||||
return "", fmt.Errorf("no input go files for package index %d", i)
|
||||
}
|
||||
|
||||
if len(g.conf.Packages[pkg.ID]) == 0 {
|
||||
// ignore the package as it has no typings
|
||||
continue
|
||||
}
|
||||
|
||||
pkgGen := &PackageGenerator{
|
||||
conf: g.conf,
|
||||
pkg: pkg,
|
||||
types: g.conf.Packages[pkg.ID],
|
||||
withPkgDoc: !g.isPackageDocGenerated(pkg.ID),
|
||||
generatedTypes: map[string]struct{}{},
|
||||
unknownTypes: map[string]struct{}{},
|
||||
imports: map[string][]string{},
|
||||
}
|
||||
|
||||
code, err := pkgGen.Generate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
g.generatedPackageDocs[pkg.ID] = struct{}{}
|
||||
|
||||
for t := range pkgGen.generatedTypes {
|
||||
g.generatedTypes[pkg.ID] = append(g.generatedTypes[pkg.ID], t)
|
||||
}
|
||||
|
||||
for t := range pkgGen.unknownTypes {
|
||||
parts := strings.Split(t, ".")
|
||||
var tPkg string
|
||||
var tName string
|
||||
|
||||
if len(parts) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if len(parts) == 2 {
|
||||
// type from external package
|
||||
tPkg = parts[0]
|
||||
tName = parts[1]
|
||||
} else {
|
||||
// unexported type from the current package
|
||||
tName = parts[0]
|
||||
|
||||
// already mapped for export
|
||||
if pkgGen.isTypeAllowed(tName) {
|
||||
continue
|
||||
}
|
||||
|
||||
tPkg = packageNameFromPath(pkg.ID)
|
||||
|
||||
// add to self import later
|
||||
pkgGen.imports[pkg.ID] = []string{tPkg}
|
||||
}
|
||||
|
||||
for p, aliases := range pkgGen.imports {
|
||||
for _, alias := range aliases {
|
||||
if tName != "" && alias == tPkg && !g.isTypeGenerated(p, tName) && !exists(g.implicitPackages[p], tName) {
|
||||
if g.implicitPackages[p] == nil {
|
||||
g.implicitPackages[p] = []string{}
|
||||
}
|
||||
g.implicitPackages[p] = append(g.implicitPackages[p], tName)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.WriteString(code)
|
||||
}
|
||||
|
||||
// recursively try to generate the found unknown types
|
||||
if len(g.implicitPackages) > 0 {
|
||||
subConfig := *g.conf
|
||||
subConfig.Heading = ""
|
||||
if (subConfig.TypeMappings) == nil {
|
||||
subConfig.TypeMappings = map[string]string{}
|
||||
}
|
||||
|
||||
// extract the nonempty package definitions
|
||||
subConfig.Packages = make(map[string][]string, len(g.implicitPackages))
|
||||
for p, types := range g.implicitPackages {
|
||||
if len(types) == 0 {
|
||||
continue
|
||||
}
|
||||
subConfig.Packages[p] = types
|
||||
}
|
||||
|
||||
subGenerator := New(subConfig)
|
||||
subGenerator.parent = g
|
||||
subResult, err := subGenerator.Generate()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
s.WriteString(subResult)
|
||||
}
|
||||
|
||||
return s.String(), nil
|
||||
}
|
||||
|
||||
func (g *Tygoja) isPackageDocGenerated(pkgId string) bool {
|
||||
_, ok := g.generatedPackageDocs[pkgId]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if g.parent != nil {
|
||||
return g.parent.isPackageDocGenerated(pkgId)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *Tygoja) isTypeGenerated(pkg string, name string) bool {
|
||||
if g.parent != nil && g.parent.isTypeGenerated(pkg, name) {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(g.generatedTypes[pkg]) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, t := range g.generatedTypes[pkg] {
|
||||
if t == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// isTypeAllowed checks whether the provided type name is allowed by the generator "types".
|
||||
func (g *PackageGenerator) isTypeAllowed(name string) bool {
|
||||
name = strings.TrimSpace(name)
|
||||
|
||||
if name == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, t := range g.types {
|
||||
if t == name || t == "*" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (g *PackageGenerator) markTypeAsGenerated(t string) {
|
||||
g.generatedTypes[t] = struct{}{}
|
||||
}
|
||||
|
||||
var versionRegex = regexp.MustCompile(`^v\d+$`)
|
||||
|
||||
// packageNameFromPath extracts and normalizes the imported package identifier.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// "github.com/labstack/echo/v5" -> "echo"
|
||||
// "github.com/go-ozzo/ozzo-validation/v4" -> "ozzo_validation"
|
||||
func packageNameFromPath(path string) string {
|
||||
name := filepath.Base(strings.Trim(path, `"' `))
|
||||
|
||||
if versionRegex.MatchString(name) {
|
||||
name = filepath.Base(filepath.Dir(path))
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(name, "-", "_")
|
||||
}
|
||||
|
||||
// exists checks if search exists in list.
|
||||
func exists[T comparable](list []T, search T) bool {
|
||||
for _, v := range list {
|
||||
if v == search {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue