5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-06 16:30:22 +08:00
wails/v3/internal/generator/render/default.go
Fabio Massaioli f01b4b9a21
[v3] Fix binding generator bugs (#4001)
* Add some clarifying comments

* Remove special handling of window parameters

* Improve internal method exclusion

* Add test for internal method exclusion

* Remove useless blank field from app options

This is a leftover from an older version of the static analyser. It should have been removed long ago.

* Remove redundant godebug setting

gotypesalias=1 is the default starting with go1.23

* Use new range for syntax to simplify code

* Remove generator dependency on github.com/samber/lo

* Ensure generator testing tasks do not use the test cache

* Rename cyclic types test

* Test for cyclic imports

* Fix import cycle between model files

* Sort class aliases after their aliased class

* Test class aliases

* Fix length of default value for array types

* Test array initialization

* Add changelog

* Update changelog

* Fix contrived marking technique in model sorting algorithm

* Update binding example

* Update test data

---------

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
2025-01-17 18:56:07 +11:00

182 lines
4.7 KiB
Go

package render
import (
"fmt"
"go/types"
"strings"
"text/template"
"github.com/wailsapp/wails/v3/internal/generator/collect"
)
// JSDefault renders the Javascript representation
// of the zero value of the given type,
// using the receiver's import map to resolve dependencies.
//
// JSDefault's output may be incorrect
// if imports.AddType has not been called for the given type.
func (m *module) JSDefault(typ types.Type, quoted bool) (result string) {
switch t := typ.(type) {
case *types.Alias, *types.Named:
result, ok := m.renderNamedDefault(t.(aliasOrNamed), quoted)
if ok {
return result
}
case *types.Array:
if t.Len() == 0 {
return "[]"
} else {
// Initialise array with expected number of elements
return fmt.Sprintf("Array.from({ length: %d }, () => %s)", t.Len(), m.JSDefault(t.Elem(), false))
}
case *types.Slice:
if types.Identical(typ, typeByteSlice) {
return `""`
} else {
return "[]"
}
case *types.Basic:
return m.renderBasicDefault(t, quoted)
case *types.Map:
return "{}"
case *types.Pointer:
return "null"
case *types.Struct:
return m.renderStructDefault(t)
}
// Fall back to null.
// encoding/json ignores null values so this is safe.
return "null"
}
// renderBasicDefault outputs the Javascript representation
// of the zero value for the given basic type.
func (*module) renderBasicDefault(typ *types.Basic, quoted bool) string {
switch {
case typ.Info()&types.IsBoolean != 0:
if quoted {
return `"false"`
} else {
return "false"
}
case typ.Info()&types.IsNumeric != 0 && typ.Info()&types.IsComplex == 0:
if quoted {
return `"0"`
} else {
return "0"
}
case typ.Info()&types.IsString != 0:
if quoted {
return `'""'`
} else {
return `""`
}
}
// Fall back to untyped mode.
if quoted {
return `""`
} else {
// encoding/json ignores null values so this is safe.
return "null"
}
}
// renderNamedDefault outputs the Javascript representation
// of the zero value for the given alias or named type.
// The result field named 'ok' is true when the resulting code is valid.
// If false, it must be discarded.
func (m *module) renderNamedDefault(typ aliasOrNamed, quoted bool) (result string, ok bool) {
if typ.Obj().Pkg() == nil {
// Builtin alias or named type: render underlying type.
return m.JSDefault(typ.Underlying(), quoted), true
}
if quoted {
// WARN: Do not test with IsString here!! We only want to catch marshalers.
if !collect.IsAny(typ) && !collect.MaybeTextMarshaler(typ) {
if basic, ok := typ.Underlying().(*types.Basic); ok {
// Quoted mode for basic alias/named type that is not a marshaler: delegate.
return m.renderBasicDefault(basic, quoted), true
}
// No need to handle typeparams: they are initialised to null anyways.
}
}
prefix := ""
if m.Imports.ImportModels {
prefix = "$models."
}
if collect.IsAny(typ) {
return "", false
} else if collect.MaybeTextMarshaler(typ) {
return `""`, true
} else if collect.IsClass(typ) {
if typ.Obj().Pkg().Path() == m.Imports.Self {
return fmt.Sprintf("(new %s%s())", prefix, jsid(typ.Obj().Name())), true
} else {
return fmt.Sprintf("(new %s.%s())", jsimport(m.Imports.External[typ.Obj().Pkg().Path()]), jsid(typ.Obj().Name())), true
}
} else if _, isAlias := typ.(*types.Alias); isAlias {
return m.JSDefault(types.Unalias(typ), quoted), true
} else {
// Inject a type assertion in case we are breaking an enum.
// Using the true Go zero value is preferrable to selecting an arbitrary enum value.
value := m.JSDefault(typ.Underlying(), quoted)
if typ.Obj().Pkg().Path() == m.Imports.Self {
if m.TS {
return fmt.Sprintf("(%s as %s%s)", value, prefix, jsid(typ.Obj().Name())), true
} else {
return fmt.Sprintf("(/** @type {%s%s} */(%s))", prefix, jsid(typ.Obj().Name()), value), true
}
} else {
if m.TS {
return fmt.Sprintf("(%s as %s.%s)", value, jsimport(m.Imports.External[typ.Obj().Pkg().Path()]), jsid(typ.Obj().Name())), true
} else {
return fmt.Sprintf("(/** @type {%s.%s} */(%s))", jsimport(m.Imports.External[typ.Obj().Pkg().Path()]), jsid(typ.Obj().Name()), value), true
}
}
}
}
// renderStructDefault outputs the Javascript representation
// of the zero value for the given struct type.
func (m *module) renderStructDefault(typ *types.Struct) string {
info := m.collector.Struct(typ)
info.Collect()
var builder strings.Builder
builder.WriteRune('{')
for i, field := range info.Fields {
if field.Optional {
continue
}
if i > 0 {
builder.WriteString(", ")
}
builder.WriteRune('"')
template.JSEscape(&builder, []byte(field.JsonName))
builder.WriteRune('"')
builder.WriteString(": ")
builder.WriteString(m.JSDefault(field.Type, field.Quoted))
}
builder.WriteRune('}')
return builder.String()
}