@@ -512,6 +512,179 @@ func (c *GoToTSCompiler) WriteStructTypeSpec(a *ast.TypeSpec, t *ast.StructType)
512512 return nil
513513}
514514
515+ // WriteNamedStructTypeSpec generates a TypeScript class for a named type whose
516+ // underlying type is a struct defined elsewhere.
517+ func (c * GoToTSCompiler ) WriteNamedStructTypeSpec (a * ast.TypeSpec , named * types.Named ) error {
518+ isInsideFunction := false
519+ if nodeInfo := c .analysis .NodeData [a ]; nodeInfo != nil {
520+ isInsideFunction = nodeInfo .IsInsideFunction
521+ }
522+ if ! isInsideFunction {
523+ c .tsw .WriteLiterally ("export " )
524+ }
525+ c .tsw .WriteLiterally ("class " )
526+ if err := c .WriteValueExpr (a .Name ); err != nil {
527+ return err
528+ }
529+
530+ if a .TypeParams != nil {
531+ c .WriteTypeParameters (a .TypeParams )
532+ }
533+
534+ c .tsw .WriteLiterally (" extends " )
535+ c .WriteTypeExpr (a .Type )
536+ c .tsw .WriteLiterally (" " )
537+ c .tsw .WriteLine ("{" )
538+ c .tsw .Indent (1 )
539+
540+ className := sanitizeIdentifier (a .Name .Name )
541+ underlyingStruct , ok := named .Underlying ().(* types.Struct )
542+ if ! ok {
543+ return fmt .Errorf ("underlying type of %s is not a struct" , a .Name .Name )
544+ }
545+
546+ c .tsw .WriteLine ("constructor(init?: any) {" )
547+ c .tsw .Indent (1 )
548+ c .tsw .WriteLine ("super(init)" )
549+ c .tsw .Indent (- 1 )
550+ c .tsw .WriteLine ("}" )
551+ c .tsw .WriteLine ("" )
552+
553+ c .tsw .WriteLinef ("public clone(): %s {" , className )
554+ c .tsw .Indent (1 )
555+ c .tsw .WriteLinef ("const cloned = new %s()" , className )
556+ c .tsw .WriteLine ("cloned._fields = {" )
557+ c .tsw .Indent (1 )
558+
559+ firstFieldWritten := false
560+ for i := 0 ; i < underlyingStruct .NumFields (); i ++ {
561+ field := underlyingStruct .Field (i )
562+ fieldType := field .Type ()
563+ var fieldKeyName string
564+ if field .Anonymous () {
565+ fieldKeyName = c .getEmbeddedFieldKeyName (field .Type ())
566+ } else {
567+ fieldKeyName = field .Name ()
568+ }
569+ if fieldKeyName == "_" {
570+ continue
571+ }
572+
573+ if firstFieldWritten {
574+ c .tsw .WriteLine ("," )
575+ }
576+ c .writeClonedFieldInitializer (fieldKeyName , fieldType , field .Anonymous ())
577+ firstFieldWritten = true
578+ }
579+ if firstFieldWritten {
580+ c .tsw .WriteLine ("" )
581+ }
582+
583+ c .tsw .Indent (- 1 )
584+ c .tsw .WriteLine ("}" )
585+ c .tsw .WriteLine ("return cloned" )
586+ c .tsw .Indent (- 1 )
587+ c .tsw .WriteLine ("}" )
588+
589+ for _ , fileSyntax := range c .pkg .Syntax {
590+ for _ , decl := range fileSyntax .Decls {
591+ funcDecl , isFunc := decl .(* ast.FuncDecl )
592+ if ! isFunc || funcDecl .Recv == nil || len (funcDecl .Recv .List ) == 0 {
593+ continue
594+ }
595+ recvType := funcDecl .Recv .List [0 ].Type
596+ if starExpr , ok := recvType .(* ast.StarExpr ); ok {
597+ recvType = starExpr .X
598+ }
599+
600+ var recvTypeName string
601+ if ident , ok := recvType .(* ast.Ident ); ok {
602+ recvTypeName = sanitizeIdentifier (ident .Name )
603+ } else if indexExpr , ok := recvType .(* ast.IndexExpr ); ok {
604+ if ident , ok := indexExpr .X .(* ast.Ident ); ok {
605+ recvTypeName = sanitizeIdentifier (ident .Name )
606+ }
607+ }
608+
609+ if recvTypeName == className {
610+ c .tsw .WriteLine ("" )
611+ if err := c .WriteFuncDeclAsMethod (funcDecl ); err != nil {
612+ return err
613+ }
614+ }
615+ }
616+ }
617+
618+ c .tsw .WriteLine ("" )
619+ c .tsw .WriteLine ("// Register this type with the runtime type system" )
620+
621+ structName := className
622+ pkgPath := c .pkg .Types .Path ()
623+ pkgName := c .pkg .Types .Name ()
624+ if pkgPath != "" && pkgName != "main" {
625+ structName = pkgPath + "." + className
626+ } else if pkgName == "main" {
627+ structName = "main." + className
628+ }
629+
630+ c .tsw .WriteLine ("static __typeInfo = $.registerStructType(" )
631+ c .tsw .WriteLinef (" %q," , structName )
632+ c .tsw .WriteLinef (" new %s()," , className )
633+ c .tsw .WriteLiterally (" [" )
634+
635+ var structMethods []* types.Func
636+ for method := range named .Methods () {
637+ sig := method .Type ().(* types.Signature )
638+ recv := sig .Recv ().Type ()
639+ if ptr , ok := recv .(* types.Pointer ); ok {
640+ recv = ptr .Elem ()
641+ }
642+ if namedRecv , ok := recv .(* types.Named ); ok && namedRecv .Obj () == named .Obj () {
643+ structMethods = append (structMethods , method )
644+ }
645+ }
646+ c .writeMethodSignatures (structMethods )
647+ c .tsw .WriteLiterally ("]," )
648+ c .tsw .WriteLine ("" )
649+
650+ c .tsw .WriteLinef (" %s," , className )
651+ c .tsw .WriteLiterally (" {" )
652+ firstField := true
653+ for i := 0 ; i < underlyingStruct .NumFields (); i ++ {
654+ field := underlyingStruct .Field (i )
655+ var fieldKeyName string
656+ if field .Anonymous () {
657+ fieldKeyName = c .getEmbeddedFieldKeyName (field .Type ())
658+ } else {
659+ fieldKeyName = field .Name ()
660+ }
661+ if fieldKeyName == "_" {
662+ continue
663+ }
664+ if ! firstField {
665+ c .tsw .WriteLiterally (", " )
666+ }
667+ firstField = false
668+ c .tsw .WriteLiterallyf ("%q: " , fieldKeyName )
669+
670+ tag := underlyingStruct .Tag (i )
671+ if tag != "" {
672+ c .tsw .WriteLiterally ("{ type: " )
673+ c .writeTypeInfoObject (field .Type ())
674+ c .tsw .WriteLiterallyf (", tag: %q }" , tag )
675+ } else {
676+ c .writeTypeInfoObject (field .Type ())
677+ }
678+ }
679+ c .tsw .WriteLiterally ("}" )
680+ c .tsw .WriteLine ("" )
681+ c .tsw .WriteLine (");" )
682+
683+ c .tsw .Indent (- 1 )
684+ c .tsw .WriteLine ("}" )
685+ return nil
686+ }
687+
515688// generateFlattenedInitTypeString generates a TypeScript type string for the
516689// initialization object passed to a Go struct's constructor (`_init` method in TypeScript).
517690// The generated type is a `Partial`-like structure, `"{ Field1?: Type1, Field2?: Type2, ... }"`,
0 commit comments