5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 23:23:48 +08:00
wails/v3/internal/generator/render/default.go
Fabio Massaioli 37673eb24d
[v3] Fix binding generator bugs and prepare for Go 1.24 (#4045)
* Rename predicates source file

* Overhaul and document type predicates

* Fix model collection logic for named types

* Fix map key type rendering

* Fix map creation code

* Fix rendering of structs that implement marshaler interfaces

* Fix type cycle detection to take type args into account

* Fix enum and typeparam field initialisation

* Improve unsupported type warnings

* Remove internal models file

* Deduplicate template code

* Accept generic aliases in static analyser

* Support new `encoding/json` flag `omitzero`

* Handle special cases when rendering generic aliases

* Update npm test dependencies

* Test class aliases and implicit private dependencies

* Test marshaler combinations

* Test map key types

* Remove bad map keys from unrelated tests

* Test service discovery through generic aliases

* Test generic aliases

* Test warning messages

* Disable go1.24 tests

* Update changelog

* Restore rendering of injected lines in index file

* Test directives

* Add wails:ignore directive

* Fix typo

* Move injections to the bottom of service files

* Handle errors from closing files

* Do not emit messages when services define only lifecycle methods

* Add internal directive for services and models

* Update changelog

* Fix error in service templates

* Test internal directive on services/models

* Fix error in index template

* Base testdata updates

* Testdata for class aliases and implicit private dependencies

* Testdata for marshaler combinations

* Testdata for map key types

* Testdata for bad map key fixes

* Add weakly typed enums aka alias constants

* Testdata for enum and typeparam field fixes

* Testdata for generic aliases

* Testdata for warning messages

* Testdata for directives

* Testdata for weakly typed enums

* Update binding example

* Update services example

* Remove go1.24 testdata

* Update cli doc

* Fix analyser tests

* Fix windows tests... hopefully

* go mod tidy on examples

* Update bindings guide

---------

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
2025-02-09 09:44:34 +11:00

180 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.Struct:
return m.renderStructDefault(t)
case *types.TypeParam:
// Should be unreachable
panic("type parameters have no default value")
}
// 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 IsAny/IsStringAlias here!! We only want to catch marshalers.
if collect.MaybeJSONMarshaler(typ) == collect.NonMarshaler && collect.MaybeTextMarshaler(typ) == collect.NonMarshaler {
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) != collect.NonMarshaler {
return `""`, true
} else if collect.IsClass(typ) && !istpalias(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 if len(m.collector.Model(typ.Obj()).Collect().Values) > 0 {
if typ.Obj().Pkg().Path() == m.Imports.Self {
return fmt.Sprintf("%s%s.$zero", prefix, jsid(typ.Obj().Name())), true
} else {
return fmt.Sprintf("%s.%s.$zero", jsimport(m.Imports.External[typ.Obj().Pkg().Path()]), jsid(typ.Obj().Name())), true
}
} else {
return m.JSDefault(typ.Underlying(), quoted), true
}
}
// renderStructDefault outputs the Javascript representation
// of the zero value for the given struct type.
func (m *module) renderStructDefault(typ *types.Struct) string {
if collect.MaybeJSONMarshaler(typ) != collect.NonMarshaler {
return "null"
} else if collect.MaybeTextMarshaler(typ) != collect.NonMarshaler {
return `""`
}
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()
}