mirror of
https://github.com/taigrr/wails.git
synced 2026-04-13 18:38:11 -07:00
Support json tags in module generation
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
2
v2/pkg/parser/globals.d.ts
vendored
2
v2/pkg/parser/globals.d.ts
vendored
@@ -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
|
||||
}
|
||||
@@ -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}}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user