@@ -26,36 +26,64 @@ import (
26
26
)
27
27
28
28
var (
29
+ // baseDirs are the directories to introspect for types to generate.
29
30
baseDirs = [... ]string {"./codersdk" , "./coderd/healthcheck" , "./coderd/healthcheck/derphealth" }
30
- indent = " "
31
+ // externalTypes are types that are not in the baseDirs, but we want to
32
+ // support. These are usually types that are used in the baseDirs.
33
+ // Do not include things like "Database", as that would break the idea
34
+ // of splitting db and api types.
35
+ // Only include dirs that are client facing packages.
36
+ externalTypeDirs = [... ]string {"./cli/clibase" }
37
+ indent = " "
31
38
)
32
39
33
40
func main () {
34
41
ctx := context .Background ()
35
42
log := slog .Make (sloghuman .Sink (os .Stderr ))
43
+
44
+ external := []* Generator {}
45
+ for _ , dir := range externalTypeDirs {
46
+ extGen , err := ParseDirectory (ctx , log , dir )
47
+ if err != nil {
48
+ log .Fatal (ctx , fmt .Sprintf ("parse external directory %s: %s" , dir , err .Error ()))
49
+ }
50
+ extGen .onlyOptIn = true
51
+ external = append (external , extGen )
52
+ }
53
+
36
54
_ , _ = fmt .Print ("// Code generated by 'make site/src/api/typesGenerated.ts'. DO NOT EDIT.\n \n " )
37
55
for _ , baseDir := range baseDirs {
38
56
_ , _ = fmt .Printf ("// The code below is generated from %s.\n \n " , strings .TrimPrefix (baseDir , "./" ))
39
- output , err := Generate (baseDir )
57
+ output , err := Generate (baseDir , external ... )
40
58
if err != nil {
41
59
log .Fatal (ctx , err .Error ())
42
60
}
43
61
44
62
// Just cat the output to a file to capture it
45
63
_ , _ = fmt .Print (output , "\n \n " )
46
64
}
65
+
66
+ for i , ext := range external {
67
+ ts , err := ext .generateAll ()
68
+ if err != nil {
69
+ log .Fatal (ctx , fmt .Sprintf ("generate external: %s" , err .Error ()))
70
+ }
71
+ dir := externalTypeDirs [i ]
72
+ _ , _ = fmt .Printf ("// The code below is generated from %s.\n \n " , strings .TrimPrefix (dir , "./" ))
73
+ _ , _ = fmt .Print (ts .String (), "\n \n " )
74
+ }
47
75
}
48
76
49
- func Generate (directory string ) (string , error ) {
77
+ func Generate (directory string , externals ... * Generator ) (string , error ) {
50
78
ctx := context .Background ()
51
79
log := slog .Make (sloghuman .Sink (os .Stderr ))
52
- codeBlocks , err := GenerateFromDirectory (ctx , log , directory )
80
+ gen , err := GenerateFromDirectory (ctx , log , directory , externals ... )
53
81
if err != nil {
54
82
return "" , err
55
83
}
56
84
57
85
// Just cat the output to a file to capture it
58
- return codeBlocks .String (), nil
86
+ return gen . cachedResult .String (), nil
59
87
}
60
88
61
89
// TypescriptTypes holds all the code blocks created.
@@ -109,30 +137,51 @@ func (t TypescriptTypes) String() string {
109
137
return strings .TrimRight (s .String (), "\n " )
110
138
}
111
139
112
- // GenerateFromDirectory will return all the typescript code blocks for a directory
113
- func GenerateFromDirectory ( ctx context. Context , log slog. Logger , directory string ) ( * TypescriptTypes , error ) {
114
- g := Generator {
115
- log : log ,
116
- builtins : make ( map [ string ] string ) ,
140
+ func ParseDirectory ( ctx context. Context , log slog. Logger , directory string , externals ... * Generator ) ( * Generator , error ) {
141
+ g := & Generator {
142
+ log : log ,
143
+ builtins : make ( map [ string ] string ) ,
144
+ externals : externals ,
117
145
}
118
146
err := g .parsePackage (ctx , directory )
119
147
if err != nil {
120
148
return nil , xerrors .Errorf ("parse package %q: %w" , directory , err )
121
149
}
122
150
151
+ return g , nil
152
+ }
153
+
154
+ // GenerateFromDirectory will return all the typescript code blocks for a directory
155
+ func GenerateFromDirectory (ctx context.Context , log slog.Logger , directory string , externals ... * Generator ) (* Generator , error ) {
156
+ g , err := ParseDirectory (ctx , log , directory , externals ... )
157
+ if err != nil {
158
+ return nil , err
159
+ }
160
+
123
161
codeBlocks , err := g .generateAll ()
124
162
if err != nil {
125
- return nil , xerrors .Errorf ("parse package %q: %w" , directory , err )
163
+ return nil , xerrors .Errorf ("generate package %q: %w" , directory , err )
126
164
}
165
+ g .cachedResult = codeBlocks
127
166
128
- return codeBlocks , nil
167
+ return g , nil
129
168
}
130
169
131
170
type Generator struct {
132
171
// Package we are scanning.
133
172
pkg * packages.Package
134
173
log slog.Logger
135
174
175
+ // allowList if set only generates types in the allow list.
176
+ // This is kinda a hack to get around the fact that external types
177
+ // only should generate referenced types, and multiple packages can
178
+ // reference the same external types.
179
+ onlyOptIn bool
180
+ allowList []string
181
+
182
+ // externals are other packages referenced. Optional
183
+ externals []* Generator
184
+
136
185
// builtins is kinda a hack to get around the fact that using builtin
137
186
// generic constraints is common. We want to support them even though
138
187
// they are external to our package.
@@ -141,6 +190,8 @@ type Generator struct {
141
190
// cannot be implemented in go. So they are a first class thing that we just
142
191
// have to make a static string for ¯\_(ツ)_/¯
143
192
builtins map [string ]string
193
+
194
+ cachedResult * TypescriptTypes
144
195
}
145
196
146
197
// parsePackage takes a list of patterns such as a directory, and parses them.
@@ -180,6 +231,10 @@ func (g *Generator) generateAll() (*TypescriptTypes, error) {
180
231
AllowedTypes : make (map [string ]struct {}),
181
232
}
182
233
234
+ for _ , a := range g .allowList {
235
+ m .AllowedTypes [strings .TrimSpace (a )] = struct {}{}
236
+ }
237
+
183
238
// Look for comments that indicate to ignore a type for typescript generation.
184
239
ignoreRegex := regexp .MustCompile ("@typescript-ignore[:]?(?P<ignored_types>.*)" )
185
240
for _ , file := range g .pkg .Syntax {
@@ -303,7 +358,7 @@ func (g *Generator) generateOne(m *Maps, obj types.Object) error {
303
358
}
304
359
305
360
// If we have allowed types, only allow those to be generated.
306
- if _ , ok := m .AllowedTypes [obj .Name ()]; len (m .AllowedTypes ) > 0 && ! ok {
361
+ if _ , ok := m .AllowedTypes [obj .Name ()]; ( len (m .AllowedTypes ) > 0 || g . onlyOptIn ) && ! ok {
307
362
return nil
308
363
}
309
364
@@ -789,8 +844,16 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
789
844
objName := objName (n .Obj ())
790
845
genericName := ""
791
846
genericTypes := make (map [string ]string )
792
- pkgName := n .Obj ().Pkg ().Name ()
793
- if obj := g .pkg .Types .Scope ().Lookup (n .Obj ().Name ()); g .pkg .Name == pkgName && obj != nil {
847
+
848
+ obj , objGen , local := g .lookupNamedReference (n )
849
+ if obj != nil {
850
+ if ! local {
851
+ objGen .allowList = append (objGen .allowList , objName )
852
+ g .log .Debug (context .Background (), "found external type" ,
853
+ "name" , objName ,
854
+ "ext_pkg" , objGen .pkg .String (),
855
+ )
856
+ }
794
857
// Sweet! Using other typescript types as fields. This could be an
795
858
// enum or another struct
796
859
if args := n .TypeArgs (); args != nil && args .Len () > 0 {
@@ -817,10 +880,16 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
817
880
genericName = objName + fmt .Sprintf ("<%s>" , strings .Join (genericNames , ", " ))
818
881
objName += fmt .Sprintf ("<%s>" , strings .Join (genericConstraints , ", " ))
819
882
}
883
+
884
+ cmt := ""
885
+ if ! local {
886
+ indentedComment ("external reference" )
887
+ }
820
888
return TypescriptType {
821
- GenericTypes : genericTypes ,
822
- GenericValue : genericName ,
823
- ValueType : objName ,
889
+ GenericTypes : genericTypes ,
890
+ GenericValue : genericName ,
891
+ ValueType : objName ,
892
+ AboveTypeLine : cmt ,
824
893
}, nil
825
894
}
826
895
@@ -928,6 +997,22 @@ func (g *Generator) typescriptType(ty types.Type) (TypescriptType, error) {
928
997
return TypescriptType {}, xerrors .Errorf ("unknown type: %s" , ty .String ())
929
998
}
930
999
1000
+ func (g * Generator ) lookupNamedReference (n * types.Named ) (obj types.Object , generator * Generator , local bool ) {
1001
+ pkgName := n .Obj ().Pkg ().Name ()
1002
+
1003
+ if obj := g .pkg .Types .Scope ().Lookup (n .Obj ().Name ()); g .pkg .Name == pkgName && obj != nil {
1004
+ return obj , g , true
1005
+ }
1006
+
1007
+ for _ , ext := range g .externals {
1008
+ if obj := ext .pkg .Types .Scope ().Lookup (n .Obj ().Name ()); ext .pkg .Name == pkgName && obj != nil {
1009
+ return obj , ext , false
1010
+ }
1011
+ }
1012
+
1013
+ return nil , nil , false
1014
+ }
1015
+
931
1016
// isBuiltIn returns the string for a builtin type that we want to support
932
1017
// if the name is a reserved builtin type. This is for types like 'comparable'.
933
1018
// These types are not implemented in golang, so we just have to hardcode it.
0 commit comments