mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix: generate typescript types for healthcheck pkg (#8846)
This commit is contained in:
+76
-33
@@ -15,6 +15,8 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/fatih/structtag"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
"golang.org/x/tools/go/packages"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -23,21 +25,25 @@ import (
|
||||
"github.com/coder/coder/coderd/util/slice"
|
||||
)
|
||||
|
||||
const (
|
||||
baseDir = "./codersdk"
|
||||
indent = " "
|
||||
var (
|
||||
baseDirs = [...]string{"./codersdk", "./coderd/healthcheck"}
|
||||
indent = " "
|
||||
)
|
||||
|
||||
func main() {
|
||||
ctx := context.Background()
|
||||
log := slog.Make(sloghuman.Sink(os.Stderr))
|
||||
output, err := Generate(baseDir)
|
||||
if err != nil {
|
||||
log.Fatal(ctx, err.Error())
|
||||
}
|
||||
_, _ = fmt.Print("// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.\n\n")
|
||||
for _, baseDir := range baseDirs {
|
||||
_, _ = fmt.Printf("// The code below is generated from %s.\n\n", strings.TrimPrefix(baseDir, "./"))
|
||||
output, err := Generate(baseDir)
|
||||
if err != nil {
|
||||
log.Fatal(ctx, err.Error())
|
||||
}
|
||||
|
||||
// Just cat the output to a file to capture it
|
||||
_, _ = fmt.Println(output)
|
||||
// Just cat the output to a file to capture it
|
||||
_, _ = fmt.Print(output, "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
func Generate(directory string) (string, error) {
|
||||
@@ -63,11 +69,6 @@ type TypescriptTypes struct {
|
||||
// String just combines all the codeblocks.
|
||||
func (t TypescriptTypes) String() string {
|
||||
var s strings.Builder
|
||||
const prelude = `
|
||||
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
|
||||
|
||||
`
|
||||
_, _ = s.WriteString(prelude)
|
||||
|
||||
sortedTypes := make([]string, 0, len(t.Types))
|
||||
sortedEnums := make([]string, 0, len(t.Enums))
|
||||
@@ -176,6 +177,7 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) {
|
||||
Enums: make(map[string]types.Object),
|
||||
EnumConsts: make(map[string][]*types.Const),
|
||||
IgnoredTypes: make(map[string]struct{}),
|
||||
AllowedTypes: make(map[string]struct{}),
|
||||
}
|
||||
|
||||
// Look for comments that indicate to ignore a type for typescript generation.
|
||||
@@ -196,6 +198,24 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// This allows opt-in generation, instead of opt-out.
|
||||
allowRegex := regexp.MustCompile("@typescript-generate[:]?(?P<allowed_types>.*)")
|
||||
for _, file := range g.pkg.Syntax {
|
||||
for _, comment := range file.Comments {
|
||||
for _, line := range comment.List {
|
||||
text := line.Text
|
||||
matches := allowRegex.FindStringSubmatch(text)
|
||||
allowed := allowRegex.SubexpIndex("allowed_types")
|
||||
if len(matches) >= allowed && matches[allowed] != "" {
|
||||
arr := strings.Split(matches[allowed], ",")
|
||||
for _, s := range arr {
|
||||
m.AllowedTypes[strings.TrimSpace(s)] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, n := range g.pkg.Types.Scope().Names() {
|
||||
obj := g.pkg.Types.Scope().Lookup(n)
|
||||
err := g.generateOne(m, obj)
|
||||
@@ -260,6 +280,15 @@ type Maps struct {
|
||||
Enums map[string]types.Object
|
||||
EnumConsts map[string][]*types.Const
|
||||
IgnoredTypes map[string]struct{}
|
||||
AllowedTypes map[string]struct{}
|
||||
}
|
||||
|
||||
// objName prepends the package name of a type if it is outside of codersdk.
|
||||
func objName(obj types.Object) string {
|
||||
if pkgName := obj.Pkg().Name(); pkgName != "codersdk" {
|
||||
return cases.Title(language.English).String(pkgName) + obj.Name()
|
||||
}
|
||||
return obj.Name()
|
||||
}
|
||||
|
||||
func (g *Generator) generateOne(m *Maps, obj types.Object) error {
|
||||
@@ -273,6 +302,13 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we have allowed types, only allow those to be generated.
|
||||
if _, ok := m.AllowedTypes[obj.Name()]; len(m.AllowedTypes) > 0 && !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
objName := objName(obj)
|
||||
|
||||
switch obj := obj.(type) {
|
||||
// All named types are type declarations
|
||||
case *types.TypeName:
|
||||
@@ -286,13 +322,13 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
|
||||
// Structs are obvious.
|
||||
codeBlock, err := g.buildStruct(obj, underNamed)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("generate %q: %w", obj.Name(), err)
|
||||
return xerrors.Errorf("generate %q: %w", objName, err)
|
||||
}
|
||||
m.Structs[obj.Name()] = codeBlock
|
||||
m.Structs[objName] = codeBlock
|
||||
case *types.Basic:
|
||||
// type <Name> string
|
||||
// These are enums. Store to expand later.
|
||||
m.Enums[obj.Name()] = obj
|
||||
m.Enums[objName] = obj
|
||||
case *types.Map, *types.Array, *types.Slice:
|
||||
// Declared maps that are not structs are still valid codersdk objects.
|
||||
// Handle them custom by calling 'typescriptType' directly instead of
|
||||
@@ -301,7 +337,7 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
|
||||
// These are **NOT** enums, as a map in Go would never be used for an enum.
|
||||
ts, err := g.typescriptType(obj.Type().Underlying())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("(map) generate %q: %w", obj.Name(), err)
|
||||
return xerrors.Errorf("(map) generate %q: %w", objName, err)
|
||||
}
|
||||
|
||||
var str strings.Builder
|
||||
@@ -311,8 +347,8 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
|
||||
_, _ = str.WriteRune('\n')
|
||||
}
|
||||
// Use similar output syntax to enums.
|
||||
_, _ = str.WriteString(fmt.Sprintf("export type %s = %s\n", obj.Name(), ts.ValueType))
|
||||
m.Structs[obj.Name()] = str.String()
|
||||
_, _ = str.WriteString(fmt.Sprintf("export type %s = %s\n", objName, ts.ValueType))
|
||||
m.Structs[objName] = str.String()
|
||||
case *types.Interface:
|
||||
// Interfaces are used as generics. Non-generic interfaces are
|
||||
// not supported.
|
||||
@@ -330,9 +366,9 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
|
||||
|
||||
block, err := g.buildUnion(obj, union)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("generate union %q: %w", obj.Name(), err)
|
||||
return xerrors.Errorf("generate union %q: %w", objName, err)
|
||||
}
|
||||
m.Generics[obj.Name()] = block
|
||||
m.Generics[objName] = block
|
||||
}
|
||||
case *types.Signature:
|
||||
// Ignore named functions.
|
||||
@@ -353,7 +389,7 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
|
||||
case *types.Func:
|
||||
// Noop
|
||||
default:
|
||||
_, _ = fmt.Println(obj.Name())
|
||||
_, _ = fmt.Println(objName)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -361,7 +397,7 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
|
||||
func (g *Generator) posLine(obj types.Object) string {
|
||||
file := g.pkg.Fset.File(obj.Pos())
|
||||
// Do not use filepath, as that changes behavior based on OS
|
||||
return fmt.Sprintf("// From %s\n", path.Join("codersdk", filepath.Base(file.Name())))
|
||||
return fmt.Sprintf("// From %s\n", path.Join(obj.Pkg().Name(), filepath.Base(file.Name())))
|
||||
}
|
||||
|
||||
// buildStruct just prints the typescript def for a type.
|
||||
@@ -387,7 +423,7 @@ func (g *Generator) buildUnion(obj types.Object, st *types.Union) (string, error
|
||||
|
||||
allTypes = slice.Unique(allTypes)
|
||||
|
||||
_, _ = s.WriteString(fmt.Sprintf("export type %s = %s\n", obj.Name(), strings.Join(allTypes, " | ")))
|
||||
_, _ = s.WriteString(fmt.Sprintf("export type %s = %s\n", objName(obj), strings.Join(allTypes, " | ")))
|
||||
|
||||
return s.String(), nil
|
||||
}
|
||||
@@ -421,7 +457,7 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
|
||||
}
|
||||
|
||||
state.PosLine = g.posLine(obj)
|
||||
state.Name = obj.Name()
|
||||
state.Name = objName(obj)
|
||||
|
||||
// Handle named embedded structs in the codersdk package via extension.
|
||||
var extends []string
|
||||
@@ -453,6 +489,10 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
|
||||
panic("invalid struct tags on type " + obj.String())
|
||||
}
|
||||
|
||||
if !field.Exported() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use the json name if present
|
||||
jsonTag, err := tags.Get("json")
|
||||
var (
|
||||
@@ -729,6 +769,8 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
|
||||
case "time.Time":
|
||||
// We really should come up with a standard for time.
|
||||
return TypescriptType{ValueType: "string"}, nil
|
||||
case "time.Duration":
|
||||
return TypescriptType{ValueType: "number"}, nil
|
||||
case "database/sql.NullTime":
|
||||
return TypescriptType{ValueType: "string", Optional: true}, nil
|
||||
case "github.com/coder/coder/codersdk.NullTime":
|
||||
@@ -744,12 +786,13 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
|
||||
}
|
||||
|
||||
// Then see if the type is defined elsewhere. If it is, we can just
|
||||
// put the name as it will be defined in the typescript codeblock
|
||||
// put the objName as it will be defined in the typescript codeblock
|
||||
// we generate.
|
||||
name := n.Obj().Name()
|
||||
objName := objName(n.Obj())
|
||||
genericName := ""
|
||||
genericTypes := make(map[string]string)
|
||||
if obj := g.pkg.Types.Scope().Lookup(name); obj != nil {
|
||||
pkgName := n.Obj().Pkg().Name()
|
||||
if obj := g.pkg.Types.Scope().Lookup(n.Obj().Name()); g.pkg.Name == pkgName && obj != nil {
|
||||
// Sweet! Using other typescript types as fields. This could be an
|
||||
// enum or another struct
|
||||
if args := n.TypeArgs(); args != nil && args.Len() > 0 {
|
||||
@@ -758,7 +801,7 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
|
||||
for i := 0; i < args.Len(); i++ {
|
||||
genType, err := g.typescriptType(args.At(i))
|
||||
if err != nil {
|
||||
return TypescriptType{}, xerrors.Errorf("generic field %q<%q>: %w", name, args.At(i).String(), err)
|
||||
return TypescriptType{}, xerrors.Errorf("generic field %q<%q>: %w", objName, args.At(i).String(), err)
|
||||
}
|
||||
|
||||
if param, ok := args.At(i).(*types.TypeParam); ok {
|
||||
@@ -773,13 +816,13 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
|
||||
|
||||
genericConstraints = append(genericConstraints, genType.ValueType)
|
||||
}
|
||||
genericName = name + fmt.Sprintf("<%s>", strings.Join(genericNames, ", "))
|
||||
name += fmt.Sprintf("<%s>", strings.Join(genericConstraints, ", "))
|
||||
genericName = objName + fmt.Sprintf("<%s>", strings.Join(genericNames, ", "))
|
||||
objName += fmt.Sprintf("<%s>", strings.Join(genericConstraints, ", "))
|
||||
}
|
||||
return TypescriptType{
|
||||
GenericTypes: genericTypes,
|
||||
GenericValue: genericName,
|
||||
ValueType: name,
|
||||
ValueType: objName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package enums
|
||||
package codersdk
|
||||
|
||||
type (
|
||||
Enum string
|
||||
|
||||
-2
@@ -1,5 +1,3 @@
|
||||
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
|
||||
|
||||
// From codersdk/enums.go
|
||||
export type Enums = Enum[]
|
||||
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package genericmap
|
||||
package codersdk
|
||||
|
||||
type Foo struct {
|
||||
Bar string `json:"bar"`
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
|
||||
|
||||
// From codersdk/genericmap.go
|
||||
export interface Buzz {
|
||||
readonly foo: Foo
|
||||
|
||||
+17
-17
@@ -1,4 +1,4 @@
|
||||
package generics
|
||||
package codersdk
|
||||
|
||||
import "time"
|
||||
|
||||
@@ -10,26 +10,26 @@ type Custom interface {
|
||||
string | bool | int | time.Duration | []string | *int
|
||||
}
|
||||
|
||||
// StaticGeneric has all generic fields defined in the field
|
||||
type StaticGeneric struct {
|
||||
Static GenericFields[string, int, time.Duration, string] `json:"static"`
|
||||
// Static has all generic fields defined in the field
|
||||
type Static struct {
|
||||
Static Fields[string, int, time.Duration, string] `json:"static"`
|
||||
}
|
||||
|
||||
// DynamicGeneric can has some dynamic fields
|
||||
type DynamicGeneric[A any, S Single] struct {
|
||||
Dynamic GenericFields[bool, A, string, S] `json:"dynamic"`
|
||||
Comparable bool `json:"comparable"`
|
||||
// Dynamic has some dynamic fields.
|
||||
type Dynamic[A any, S Single] struct {
|
||||
Dynamic Fields[bool, A, string, S] `json:"dynamic"`
|
||||
Comparable bool `json:"comparable"`
|
||||
}
|
||||
|
||||
type ComplexGeneric[C comparable, S Single, T Custom] struct {
|
||||
Dynamic GenericFields[C, bool, string, S] `json:"dynamic"`
|
||||
Order GenericFieldsDiffOrder[C, string, S, T] `json:"order"`
|
||||
Comparable C `json:"comparable"`
|
||||
Single S `json:"single"`
|
||||
Static StaticGeneric `json:"static"`
|
||||
type Complex[C comparable, S Single, T Custom] struct {
|
||||
Dynamic Fields[C, bool, string, S] `json:"dynamic"`
|
||||
Order FieldsDiffOrder[C, string, S, T] `json:"order"`
|
||||
Comparable C `json:"comparable"`
|
||||
Single S `json:"single"`
|
||||
Static Static `json:"static"`
|
||||
}
|
||||
|
||||
type GenericFields[C comparable, A any, T Custom, S Single] struct {
|
||||
type Fields[C comparable, A any, T Custom, S Single] struct {
|
||||
Comparable C `json:"comparable"`
|
||||
Any A `json:"any"`
|
||||
|
||||
@@ -38,6 +38,6 @@ type GenericFields[C comparable, A any, T Custom, S Single] struct {
|
||||
SingleConstraint S `json:"single_constraint"`
|
||||
}
|
||||
|
||||
type GenericFieldsDiffOrder[A any, C comparable, S Single, T Custom] struct {
|
||||
GenericFields[C, A, T, S]
|
||||
type FieldsDiffOrder[A any, C comparable, S Single, T Custom] struct {
|
||||
Fields Fields[C, A, T, S]
|
||||
}
|
||||
|
||||
+11
-13
@@ -1,22 +1,20 @@
|
||||
// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface ComplexGeneric<C extends comparable, S extends Single, T extends Custom> {
|
||||
readonly dynamic: GenericFields<C, boolean, string, S>
|
||||
readonly order: GenericFieldsDiffOrder<C, string, S, T>
|
||||
export interface Complex<C extends comparable, S extends Single, T extends Custom> {
|
||||
readonly dynamic: Fields<C, boolean, string, S>
|
||||
readonly order: FieldsDiffOrder<C, string, S, T>
|
||||
readonly comparable: C
|
||||
readonly single: S
|
||||
readonly static: StaticGeneric
|
||||
readonly static: Static
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface DynamicGeneric<A extends any, S extends Single> {
|
||||
readonly dynamic: GenericFields<boolean, A, string, S>
|
||||
export interface Dynamic<A extends any, S extends Single> {
|
||||
readonly dynamic: Fields<boolean, A, string, S>
|
||||
readonly comparable: boolean
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface GenericFields<C extends comparable, A extends any, T extends Custom, S extends Single> {
|
||||
export interface Fields<C extends comparable, A extends any, T extends Custom, S extends Single> {
|
||||
readonly comparable: C
|
||||
readonly any: A
|
||||
readonly custom: T
|
||||
@@ -25,13 +23,13 @@ export interface GenericFields<C extends comparable, A extends any, T extends Cu
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface GenericFieldsDiffOrder<A extends any, C extends comparable, S extends Single, T extends Custom> {
|
||||
readonly GenericFields: GenericFields<C, A, T, S>
|
||||
export interface FieldsDiffOrder<A extends any, C extends comparable, S extends Single, T extends Custom> {
|
||||
readonly Fields: Fields<C, A, T, S>
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
export interface StaticGeneric {
|
||||
readonly static: GenericFields<string, number, number, string>
|
||||
export interface Static {
|
||||
readonly static: Fields<string, number, number, string>
|
||||
}
|
||||
|
||||
// From codersdk/generics.go
|
||||
|
||||
Reference in New Issue
Block a user