mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-04 23:23:48 +08:00

* 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>
180 lines
4.7 KiB
Go
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()
|
|
}
|