1
1
package main
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
5
6
"fmt"
6
7
"go/types"
@@ -10,6 +11,7 @@ import (
10
11
"regexp"
11
12
"sort"
12
13
"strings"
14
+ "text/template"
13
15
14
16
"github.com/fatih/structtag"
15
17
"golang.org/x/tools/go/packages"
@@ -320,11 +322,33 @@ func (g *Generator) buildUnion(obj types.Object, st *types.Union) (string, error
320
322
return s .String (), nil
321
323
}
322
324
325
+ type structTemplateState struct {
326
+ PosLine string
327
+ Name string
328
+ Fields []string
329
+ Extends string
330
+ AboveLine string
331
+ }
332
+
333
+ const structTemplate = `{{ .PosLine -}}
334
+ {{ if .AboveLine }}{{ .AboveLine }}
335
+ {{ end }}export interface {{ .Name }}{{ if .Extends }} extends {{ .Extends }}{{ end }}{
336
+ {{- range .Fields }}
337
+ {{ . -}}
338
+ {{- end }}
339
+ }
340
+ `
341
+
323
342
// buildStruct just prints the typescript def for a type.
324
343
func (g * Generator ) buildStruct (obj types.Object , st * types.Struct ) (string , error ) {
325
- var s strings.Builder
326
- _ , _ = s .WriteString (g .posLine (obj ))
327
- _ , _ = s .WriteString (fmt .Sprintf ("export interface %s " , obj .Name ()))
344
+ state := structTemplateState {}
345
+ tpl , err := template .New ("struct" ).Parse (structTemplate )
346
+ if err != nil {
347
+ return "" , xerrors .Errorf ("parse struct template: %w" , err )
348
+ }
349
+
350
+ state .PosLine = g .posLine (obj )
351
+ state .Name = obj .Name ()
328
352
329
353
// Handle named embedded structs in the codersdk package via extension.
330
354
var extends []string
@@ -340,10 +364,9 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
340
364
}
341
365
}
342
366
if len (extends ) > 0 {
343
- _ , _ = s . WriteString ( fmt . Sprintf ( "extends %s " , strings .Join (extends , ", " )) )
367
+ state . Extends = strings .Join (extends , ", " )
344
368
}
345
369
346
- _ , _ = s .WriteString ("{\n " )
347
370
// For each field in the struct, we print 1 line of the typescript interface
348
371
for i := 0 ; i < st .NumFields (); i ++ {
349
372
if extendedFields [i ] {
@@ -403,21 +426,29 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
403
426
}
404
427
405
428
if tsType .AboveTypeLine != "" {
406
- _ , _ = s .WriteString (tsType .AboveTypeLine )
407
- _ , _ = s .WriteRune ('\n' )
429
+ state .AboveLine = tsType .AboveTypeLine
408
430
}
409
431
optional := ""
410
432
if jsonOptional || tsType .Optional {
411
433
optional = "?"
412
434
}
413
- _ , _ = s . WriteString ( fmt .Sprintf ("%sreadonly %s%s: %s\n " , indent , jsonName , optional , tsType .ValueType ))
435
+ state . Fields = append ( state . Fields , fmt .Sprintf ("%sreadonly %s%s: %s" , indent , jsonName , optional , tsType .ValueType ))
414
436
}
415
- _ , _ = s .WriteString ("}\n " )
416
- return s .String (), nil
437
+
438
+ data := bytes .NewBuffer (make ([]byte , 0 ))
439
+ err = tpl .Execute (data , state )
440
+ if err != nil {
441
+ return "" , xerrors .Errorf ("execute struct template: %w" , err )
442
+ }
443
+ return data .String (), nil
417
444
}
418
445
419
446
type TypescriptType struct {
420
- ValueType string
447
+ // GenericMapping gives a unique character for mapping the value type
448
+ // to a generic. This is only useful if you can use generic syntax.
449
+ // This is optional, as the ValueType will have the correct constraints.
450
+ GenericMapping string
451
+ ValueType string
421
452
// AboveTypeLine lets you put whatever text you want above the typescript
422
453
// type line.
423
454
AboveTypeLine string
@@ -566,6 +597,23 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
566
597
AboveTypeLine : indentedComment ("eslint-disable-next-line" )}, nil
567
598
}
568
599
return TypescriptType {}, xerrors .New ("only empty interface types are supported" )
600
+ case * types.TypeParam :
601
+ _ , ok := ty .Underlying ().(* types.Interface )
602
+ if ! ok {
603
+ // If it's not an interface, it is likely a usage of generics that
604
+ // we have not hit yet. Feel free to add support for it.
605
+ return TypescriptType {}, xerrors .New ("type param must be an interface" )
606
+ }
607
+
608
+ generic := ty .Constraint ()
609
+ // This is kinda a hack, but we want just the end of the name.
610
+ name := strings .TrimSuffix ("github.com/coder/coder/codersdk." , generic .String ())
611
+ return TypescriptType {
612
+ GenericMapping : ty .Obj ().Name (),
613
+ ValueType : name ,
614
+ AboveTypeLine : "" ,
615
+ Optional : false ,
616
+ }, nil
569
617
}
570
618
571
619
// These are all the other types we need to support.
0 commit comments