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 ] {
@@ -399,21 +422,29 @@ func (g *Generator) buildStruct(obj types.Object, st *types.Struct) (string, err
399
422
}
400
423
401
424
if tsType .AboveTypeLine != "" {
402
- _ , _ = s .WriteString (tsType .AboveTypeLine )
403
- _ , _ = s .WriteRune ('\n' )
425
+ state .AboveLine = tsType .AboveTypeLine
404
426
}
405
427
optional := ""
406
428
if jsonOptional || tsType .Optional {
407
429
optional = "?"
408
430
}
409
- _ , _ = s . WriteString ( fmt .Sprintf ("%sreadonly %s%s: %s\n " , indent , jsonName , optional , tsType .ValueType ))
431
+ state . Fields = append ( state . Fields , fmt .Sprintf ("%sreadonly %s%s: %s" , indent , jsonName , optional , tsType .ValueType ))
410
432
}
411
- _ , _ = s .WriteString ("}\n " )
412
- return s .String (), nil
433
+
434
+ data := bytes .NewBuffer (make ([]byte , 0 ))
435
+ err = tpl .Execute (data , state )
436
+ if err != nil {
437
+ return "" , xerrors .Errorf ("execute struct template: %w" , err )
438
+ }
439
+ return data .String (), nil
413
440
}
414
441
415
442
type TypescriptType struct {
416
- ValueType string
443
+ // GenericMapping gives a unique character for mapping the value type
444
+ // to a generic. This is only useful if you can use generic syntax.
445
+ // This is optional, as the ValueType will have the correct constraints.
446
+ GenericMapping string
447
+ ValueType string
417
448
// AboveTypeLine lets you put whatever text you want above the typescript
418
449
// type line.
419
450
AboveTypeLine string
@@ -562,6 +593,23 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
562
593
AboveTypeLine : indentedComment ("eslint-disable-next-line" )}, nil
563
594
}
564
595
return TypescriptType {}, xerrors .New ("only empty interface types are supported" )
596
+ case * types.TypeParam :
597
+ _ , ok := ty .Underlying ().(* types.Interface )
598
+ if ! ok {
599
+ // If it's not an interface, it is likely a usage of generics that
600
+ // we have not hit yet. Feel free to add support for it.
601
+ return TypescriptType {}, xerrors .New ("type param must be an interface" )
602
+ }
603
+
604
+ generic := ty .Constraint ()
605
+ // This is kinda a hack, but we want just the end of the name.
606
+ name := strings .TrimSuffix ("github.com/coder/coder/codersdk." , generic .String ())
607
+ return TypescriptType {
608
+ GenericMapping : ty .Obj ().Name (),
609
+ ValueType : name ,
610
+ AboveTypeLine : "" ,
611
+ Optional : false ,
612
+ }, nil
565
613
}
566
614
567
615
// These are all the other types we need to support.
0 commit comments