Skip to content

Commit 0287f98

Browse files
authored
Merge pull request #52 from pdhammond42/main
Add ability to emit literal Typescript from with Go file
2 parents 1e537aa + ff1b695 commit 0287f98

File tree

8 files changed

+175
-2
lines changed

8 files changed

+175
-2
lines changed

README.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,60 @@ export interface Book {
197197

198198
You could use the `frontmatter` field in the config to inject `export type Genre = "novel" | "crime" | "fantasy"` at the top of the file, and use `tstype:"Genre"`. I personally prefer that as we may use the `Genre` type more than once.
199199

200+
**`tygo:emit` directive**
201+
202+
Another way to generate types that cannot be directly represented in Go is to use a `//tygo:emit` directive to
203+
directly emit literal TS code.
204+
The directive can be used in two ways. A `tygo:emit` directive on a struct will emit the remainder of the directive
205+
text before the struct.
206+
```golang
207+
// Golang input
208+
209+
//tygo:emit export type Genre = "novel" | "crime" | "fantasy"
210+
type Book struct {
211+
Title string `json:"title"`
212+
Genre string `json:"genre" tstype:"Genre"`
213+
}
214+
```
215+
216+
```typescript
217+
export type Genre = "novel" | "crime" | "fantasy"
218+
219+
export interface Book {
220+
title: string;
221+
genre: Genre;
222+
}
223+
```
224+
225+
A `//tygo:emit` directive on a string var will emit the contents of the var, useful for multi-line content.
226+
```golang
227+
//tygo:emit
228+
var _ = `export type StructAsTuple=[
229+
a:number,
230+
b:number,
231+
c:string,
232+
]
233+
`
234+
type CustomMarshalled struct {
235+
Content []StructAsTuple `json:"content"`
236+
}
237+
```
238+
239+
```typescript
240+
export type StructAsTuple=[
241+
a:number,
242+
b:number,
243+
c:string,
244+
]
245+
246+
export interface CustomMarshalled {
247+
content: StructAsTuple[];
248+
}
249+
250+
```
251+
252+
Generating types this way is particularly useful for tuple types, because a comma cannot be used in the `tstype` tag.
253+
200254
### Required fields
201255

202256
Pointer type fields usually become optional in the Typescript output, but sometimes you may want to require it regardless.

examples/emit/custom.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package emit
2+
3+
// emit directive on a string literal emits that value.
4+
//
5+
//tygo:emit
6+
var _ = `export type OtherStructAsTuple=[
7+
a:number,
8+
b:number,
9+
c:string,
10+
]
11+
`
12+
13+
//tygo:emit This has no effect, only strings.
14+
var _ = 12
15+
16+
// a non-string var is ignored. A var with no comment is ignored.
17+
18+
var foo = " "
19+
20+
// CustomMarshalled illustrates getting tygo to emit literal text
21+
// This solves the problem of a struct field being marshalled into a tuple.
22+
//
23+
// emit directive on a struct emits the remainder of the directive line
24+
//
25+
//tygo:emit export type StructAsTuple=[a:number, b:number, c:string]
26+
type CustomMarshalled struct {
27+
Content []StructAsTuple `json:"content"`
28+
}
29+
30+
//tygo:emit export type Genre = "novel" | "crime" | "fantasy"
31+
type Book struct {
32+
Title string `json:"title"`
33+
Genre string `json:"genre" tstype:"Genre"`
34+
}

examples/emit/excluded.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package emit
2+
3+
type StructAsTuple struct {
4+
A int
5+
B float64
6+
C string
7+
}

examples/emit/index.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Code generated by tygo. DO NOT EDIT.
2+
3+
//////////
4+
// source: custom.go
5+
6+
export type OtherStructAsTuple=[
7+
a:number,
8+
b:number,
9+
c:string,
10+
]
11+
12+
export type StructAsTuple=[a:number, b:number, c:string]
13+
/**
14+
* CustomMarshalled illustrates getting tygo to emit literal text
15+
* This solves the problem of a struct field being marshalled into a tuple.
16+
* emit directive on a struct emits the remainder of the directive line
17+
*/
18+
export interface CustomMarshalled {
19+
content: StructAsTuple[];
20+
}
21+
export type Genre = "novel" | "crime" | "fantasy"
22+
23+
export interface Book {
24+
title: string;
25+
genre: Genre;
26+
}

tygo.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,8 @@ packages:
5050
flavor: "yaml"
5151
- path: "github.com/gzuidhof/tygo/examples/interface"
5252
- path: "github.com/gzuidhof/tygo/examples/directive"
53+
- path: "github.com/gzuidhof/tygo/examples/emit"
54+
exclude_files:
55+
- "excluded.go"
5356

5457

tygo/package_generator.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,25 @@ func (g *PackageGenerator) Generate() (string, error) {
2626

2727
// GenDecl can be an import, type, var, or const expression
2828
case *ast.GenDecl:
29-
if x.Tok == token.VAR || x.Tok == token.IMPORT {
29+
if x.Tok == token.IMPORT {
3030
return false
3131
}
32+
isEmit := false
33+
if x.Tok == token.VAR {
34+
isEmit = g.isEmitVar(x)
35+
if !isEmit {
36+
return false
37+
}
38+
}
3239

3340
if first {
3441
g.writeFileSourceHeader(s, filepaths[i], file)
3542
first = false
3643
}
37-
44+
if isEmit {
45+
g.emitVar(s, x)
46+
return false
47+
}
3848
g.writeGroupDecl(s, x)
3949
return false
4050
}

tygo/write_comment.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,20 @@ func (g *PackageGenerator) writeCommentGroupIfNotNil(s *strings.Builder, f *ast.
1919
}
2020
}
2121

22+
func (c *PackageGenerator) writeDirective(s *strings.Builder, cg *ast.CommentGroup) {
23+
for _, cm := range cg.List {
24+
if strings.HasPrefix(cm.Text, "//tygo:emit") {
25+
// remove the separator whitespace but leave extra whitespace for indentation
26+
s.WriteString(strings.TrimPrefix(cm.Text, "//tygo:emit")[1:])
27+
s.WriteString("\n")
28+
}
29+
}
30+
}
31+
2232
func (g *PackageGenerator) writeCommentGroup(s *strings.Builder, cg *ast.CommentGroup, depth int) {
2333
docLines := strings.Split(cg.Text(), "\n")
2434

35+
g.writeDirective(s, cg)
2536
if len(cg.List) > 0 && cg.Text() == "" { // This is a directive comment like //go:embed
2637
s.WriteByte('\n')
2738
return

tygo/write_toplevel.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,34 @@ type groupContext struct {
1717
iotaValue int
1818
}
1919

20+
// isEmitVar returns true if dec is a string var with a tygo:emit directive.
21+
func (g *PackageGenerator) isEmitVar(dec *ast.GenDecl) bool {
22+
if dec.Tok != token.VAR || dec.Doc == nil {
23+
return false
24+
}
25+
26+
for _, c := range dec.Doc.List {
27+
if strings.HasPrefix(c.Text, "//tygo:emit") {
28+
// we know it's VAR so asserting *ast.ValueSpec is OK.
29+
v, ok := dec.Specs[0].(*ast.ValueSpec).Values[0].(*ast.BasicLit)
30+
if !ok {
31+
return false
32+
}
33+
return v.Kind == token.STRING
34+
}
35+
}
36+
return false
37+
}
38+
39+
// emitVar emits the text associated with dec, which is assumes to be a string var with a
40+
// tygo:emit directive, as tested by isEmitVar.
41+
func (g *PackageGenerator) emitVar(s *strings.Builder, dec *ast.GenDecl) {
42+
v := dec.Specs[0].(*ast.ValueSpec).Values[0].(*ast.BasicLit).Value
43+
if len(v) < 2 {
44+
return
45+
}
46+
s.WriteString(v[1:len(v)-1] + "\n")
47+
}
2048
func (g *PackageGenerator) writeGroupDecl(s *strings.Builder, decl *ast.GenDecl) {
2149
// This checks whether the declaration is a group declaration like:
2250
// const (

0 commit comments

Comments
 (0)