Support json tags in module generation

This commit is contained in:
Lea Anthony
2020-11-11 17:31:26 +11:00
parent 6d3f4c06f1
commit 75d1fa51a2
5 changed files with 179 additions and 19 deletions

View File

@@ -1,5 +1,12 @@
package parser
import (
"fmt"
"strings"
"github.com/leaanthony/slicer"
)
// JSType represents a javascript type
type JSType string
@@ -37,7 +44,7 @@ func goTypeToJS(input *Field) string {
case "struct":
return input.Struct.Name
default:
println("UNSUPPORTED: ", input)
fmt.Printf("Unsupported input to goTypeToJS: %+v", input)
return "*"
}
}
@@ -71,7 +78,7 @@ func goTypeToTS(input *Field, pkgName string) string {
// case reflect.Map, reflect.Interface:
// return string(JsObject)
default:
println("UNSUPPORTED: ", input)
fmt.Printf("Unsupported input to goTypeToTS: %+v", input)
return JsUnsupported
}
@@ -81,3 +88,32 @@ func goTypeToTS(input *Field, pkgName string) string {
return result
}
func isUnresolvedType(typeName string) bool {
switch typeName {
case "string":
return false
case "int", "int8", "int16", "int32", "int64", "uint", "uint8", "uint16", "uint32", "uint64":
return false
case "float32", "float64":
return false
case "bool":
return false
case "struct":
return false
default:
return true
}
}
var reservedJSWords []string = []string{"abstract", "arguments", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "eval", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", "yield", "Array", "Date", "eval", "function", "hasOwnProperty", "Infinity", "isFinite", "isNaN", "isPrototypeOf", "length", "Math", "NaN", "Number", "Object", "prototype", "String", "toString", "undefined", "valueOf"}
var jsReservedWords *slicer.StringSlicer = slicer.String(reservedJSWords)
func isJSReservedWord(input string) bool {
return jsReservedWords.Contains(input)
}
func startsWithLowerCaseLetter(input string) bool {
firstLetter := string(input[0])
return strings.ToLower(firstLetter) == firstLetter
}

View File

@@ -3,8 +3,10 @@ package parser
import (
"fmt"
"go/ast"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/fatih/structtag"
)
// Field defines a parsed struct field
@@ -25,6 +27,15 @@ type Field struct {
// Indicates if the Field is an array of type "Type"
IsArray bool
// JSON field name defined by a json tag
JSONOptions
}
type JSONOptions struct {
Name string
IsOptional bool
Ignored bool
}
// JSType returns the Javascript type for this field
@@ -32,6 +43,31 @@ func (f *Field) JSType() string {
return string(goTypeToJS(f))
}
// JSName returns the Javascript name for this field
func (f *Field) JSName() string {
if f.JSONOptions.Name != "" {
return f.JSONOptions.Name
}
return f.Name
}
// NameForPropertyDoc returns a formatted name for the jsdoc @property declaration
func (f *Field) NameForPropertyDoc() string {
if f.IsOptional {
return "[" + f.JSName() + "]"
}
return f.JSName()
}
// TypeForPropertyDoc returns a formatted name for the jsdoc @property declaration
func (f *Field) TypeForPropertyDoc() string {
result := goTypeToJS(f)
if f.IsArray {
result += "[]"
}
return result
}
// TypeAsTSType converts the Field type to something TS wants
func (f *Field) TypeAsTSType(pkgName string) string {
var result = ""
@@ -65,10 +101,36 @@ func (p *Parser) parseField(file *ast.File, field *ast.Field, pkg *Package) ([]*
var strct *Struct
var isArray bool
var jsonOptions JSONOptions
// Determine type
switch t := field.Type.(type) {
case *ast.Ident:
fieldType = t.Name
unresolved := isUnresolvedType(fieldType)
// Check if this type is actually a struct
if unresolved {
// Assume it is a struct
// Parse the struct
var err error
strct, err = p.parseStruct(pkg, t.Name)
if err != nil {
return nil, err
}
if strct == nil {
fieldName := "<anonymous>"
if len(field.Names) > 0 {
fieldName = field.Names[0].Name
}
return nil, fmt.Errorf("unresolved type in field %s: %s", fieldName, fieldType)
}
fieldType = "struct"
}
case *ast.StarExpr:
fieldType = "struct"
packageName, structName, err := parseStructNameFromStarExpr(t)
@@ -150,10 +212,24 @@ func (p *Parser) parseField(file *ast.File, field *ast.Field, pkg *Package) ([]*
return nil, fmt.Errorf("unsupported field found in struct: %+v", t)
}
// Parse json tag if available
if field.Tag != nil {
err := parseJSONOptions(field.Tag.Value, &jsonOptions)
if err != nil {
return nil, err
}
}
// Loop over names if we have
if len(field.Names) > 0 {
for _, name := range field.Names {
// TODO: Check field names are valid in JS
if isJSReservedWord(name.Name) {
return nil, fmt.Errorf("unable to use field name %s - reserved word in Javascript", name.Name)
}
// Create a field per name
thisField := &Field{
Comments: parseComments(field.Doc),
@@ -162,6 +238,7 @@ func (p *Parser) parseField(file *ast.File, field *ast.Field, pkg *Package) ([]*
thisField.Type = fieldType
thisField.Struct = strct
thisField.IsArray = isArray
thisField.JSONOptions = jsonOptions
result = append(result, thisField)
}
@@ -179,3 +256,39 @@ func (p *Parser) parseField(file *ast.File, field *ast.Field, pkg *Package) ([]*
return result, nil
}
func parseJSONOptions(fieldTag string, jsonOptions *JSONOptions) error {
// Remove backticks
fieldTag = strings.Trim(fieldTag, "`")
// Parse the tag
tags, err := structtag.Parse(fieldTag)
if err != nil {
return err
}
jsonTag, err := tags.Get("json")
if err != nil {
return err
}
if jsonTag == nil {
return nil
}
// Save the name
jsonOptions.Name = jsonTag.Name
// Check if this field is ignored
if jsonTag.Name == "-" {
jsonOptions.Ignored = true
}
// Check if this field is optional
if jsonTag.HasOption("omitempty") {
jsonOptions.IsOptional = true
}
return nil
}

View File

@@ -1,6 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
interface window {
interface Window {
backend: any
}

View File

@@ -1,24 +1,27 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
{{- if .DeclarationReferences }}
{{range .DeclarationReferences}}
const {{.}} = require('./_{{.}}');{{end}}{{- end}}
{{- range $struct := .Structs }}
{{- if .IsUsedAsData }}
/**
{{if .Comments }}{{range .Comments}} *{{ . }}{{end}}{{end}}
* @typedef {object} {{.Name}}
{{range .Fields}} * @property {{"{"}}{{.JSType}}{{"}"}} {{.Name}}
{{- if .Comments}} - {{- range .Comments}}{{ . }}{{- end}}{{- end}}
{{end}} *
*/
export const {{.Name}} = {
{{- range .Fields}}
{{.Name}},
{{- range .Fields}}{{- if not .JSONOptions.Ignored }}
* @property {{"{"}}{{.TypeForPropertyDoc}}{{"}"}} {{.NameForPropertyDoc}} {{- if .Comments}} - {{- range .Comments}}{{ . }}{{- end}}{{- end}}{{- end}}
{{- end}}
}
*/
export var {{.Name}};
{{- end}}
{{- if .IsBound }}
{{if .Methods }}
{{- if .Methods }}
{{if .Comments }}{{range .Comments}}// {{ . }}{{end}}{{end}}
export const {{.Name}} = {
{{range .Methods }}
@@ -35,9 +38,7 @@ export const {{.Name}} = {
},
{{end}}
}
{{end}}
{{- end}}
{{- end}}
{{- end}}

View File

@@ -1,6 +1,10 @@
package parser
import "go/ast"
import (
"go/ast"
"github.com/pkg/errors"
)
func (p *Parser) parseStructFields(fileAst *ast.File, structType *ast.StructType, boundStruct *Struct) error {
@@ -8,7 +12,7 @@ func (p *Parser) parseStructFields(fileAst *ast.File, structType *ast.StructType
for _, field := range structType.Fields.List {
fields, err := p.parseField(fileAst, field, boundStruct.Package)
if err != nil {
return err
return errors.Wrap(err, "error parsing struct "+boundStruct.Name)
}
// If this field was a struct, flag that it is used as data
@@ -18,7 +22,13 @@ func (p *Parser) parseStructFields(fileAst *ast.File, structType *ast.StructType
}
}
boundStruct.Fields = append(boundStruct.Fields, fields...)
// If this field name is lowercase, it won't be exported
for _, field := range fields {
if !startsWithLowerCaseLetter(field.Name) {
boundStruct.Fields = append(boundStruct.Fields, field)
}
}
}
return nil