From bbc4e532c45e110853e9417a9946b335efce625d Mon Sep 17 00:00:00 2001 From: Lea Anthony Date: Sun, 8 Feb 2026 21:45:23 +1100 Subject: [PATCH] docs: document automatic enum generation in binding generator (#4973) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: document automatic enum generation in binding generator (#4972) Add dedicated Enums page covering string/integer/type-alias enums, $zero values, struct field typing, imported package enums, supported types, and limitations. Fix inaccurate enum section in Data Models page and add both Data Models and Enums to sidebar navigation. Co-Authored-By: Claude Opus 4.6 * docs: add enum generation changelog entry (#4972) Co-Authored-By: Claude Opus 4.6 * docs: address review feedback on enum documentation - Add bool → false to $zero value table - Fix title.String() → string(title) in imported package example - Clarify $zero defaults apply to class output, not interfaces - Improve iota limitation wording for clarity Co-Authored-By: Claude Opus 4.6 --------- Co-authored-by: Claude Opus 4.6 --- docs/astro.config.mjs | 2 + .../content/docs/features/bindings/enums.mdx | 529 ++++++++++++++++++ .../content/docs/features/bindings/models.mdx | 30 +- v3/UNRELEASED_CHANGELOG.md | 2 + 4 files changed, 545 insertions(+), 18 deletions(-) create mode 100644 docs/src/content/docs/features/bindings/enums.mdx diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index 352989727..03133e2f5 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -138,6 +138,8 @@ export default defineConfig({ items: [ { label: "Method Binding", link: "/features/bindings/methods" }, { label: "Services", link: "/features/bindings/services" }, + { label: "Data Models", link: "/features/bindings/models" }, + { label: "Enums", link: "/features/bindings/enums" }, { label: "Advanced Binding", link: "/features/bindings/advanced" }, { label: "Best Practices", link: "/features/bindings/best-practices" }, ], diff --git a/docs/src/content/docs/features/bindings/enums.mdx b/docs/src/content/docs/features/bindings/enums.mdx new file mode 100644 index 000000000..1e389273a --- /dev/null +++ b/docs/src/content/docs/features/bindings/enums.mdx @@ -0,0 +1,529 @@ +--- +title: Enums +description: Automatic enum generation from Go constants +sidebar: + order: 4 +--- + +import { Card, CardGrid } from "@astrojs/starlight/components"; + +## Enum Bindings + +The Wails v3 binding generator **automatically detects Go constant types and generates TypeScript enums or JavaScript const objects**. No registration, no configuration — just define your types and constants in Go, and the generator handles the rest. + +:::note +Unlike Wails v2, there is **no need to call `EnumBind`** or register enums manually. The generator discovers them automatically from your source code. +::: + +## Quick Start + +**Define a named type with constants in Go:** + +```go +type Status string + +const ( + StatusActive Status = "active" + StatusPending Status = "pending" + StatusClosed Status = "closed" +) +``` + +**Use the type in a struct or service method:** + +```go +type Ticket struct { + ID int `json:"id"` + Title string `json:"title"` + Status Status `json:"status"` +} +``` + +**Generate bindings:** + +```bash +wails3 generate bindings +``` + +The generator output will report enum counts alongside models: + +``` +3 Enums, 5 Models +``` + +**Use in your frontend:** + +```javascript +import { Ticket, Status } from './bindings/myapp/models' + +const ticket = new Ticket({ + id: 1, + title: "Bug report", + status: Status.StatusActive +}) +``` + +**That's it!** The enum type is enforced in both Go and JavaScript/TypeScript. + +## Defining Enums + +An enum in Wails is a **named type** with an underlying basic type, combined with **const declarations** of that type. + +### String Enums + +```go +// Title is a title +type Title string + +const ( + // Mister is a title + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" + Mrs Title = "Mrs" + Dr Title = "Dr" +) +``` + +**Generated TypeScript:** + +```typescript +/** + * Title is a title + */ +export enum Title { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = "", + + /** + * Mister is a title + */ + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", + Mrs = "Mrs", + Dr = "Dr", +} +``` + +**Generated JavaScript:** + +```javascript +/** + * Title is a title + * @readonly + * @enum {string} + */ +export const Title = { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero: "", + + /** + * Mister is a title + */ + Mister: "Mr", + Miss: "Miss", + Ms: "Ms", + Mrs: "Mrs", + Dr: "Dr", +}; +``` + +### Integer Enums + +```go +type Priority int + +const ( + PriorityLow Priority = 0 + PriorityMedium Priority = 1 + PriorityHigh Priority = 2 +) +``` + +**Generated TypeScript:** + +```typescript +export enum Priority { + /** + * The Go zero value for the underlying type of the enum. + */ + $zero = 0, + + PriorityLow = 0, + PriorityMedium = 1, + PriorityHigh = 2, +} +``` + +### Type Alias Enums + +Go type aliases (`=`) also work, but generate a slightly different output — a type definition plus a const object, rather than a native TypeScript `enum`: + +```go +// Age is an integer with some predefined values +type Age = int + +const ( + NewBorn Age = 0 + Teenager Age = 12 + YoungAdult Age = 18 + + // Oh no, some grey hair! + MiddleAged Age = 50 + Mathusalem Age = 1000 // Unbelievable! +) +``` + +**Generated TypeScript:** + +```typescript +/** + * Age is an integer with some predefined values + */ +export type Age = number; + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; +``` + +**Generated JavaScript:** + +```javascript +/** + * Age is an integer with some predefined values + * @typedef {number} Age + */ + +/** + * Predefined constants for type Age. + * @namespace + */ +export const Age = { + NewBorn: 0, + Teenager: 12, + YoungAdult: 18, + + /** + * Oh no, some grey hair! + */ + MiddleAged: 50, + + /** + * Unbelievable! + */ + Mathusalem: 1000, +}; +``` + +:::tip +**Named types** (`type Title string`) generate native TypeScript `enum` declarations with a `$zero` member. +**Type aliases** (`type Age = int`) generate a `type` + `const` namespace pair without `$zero`. +::: + +## The `$zero` Value + +Every named-type enum includes a special `$zero` member representing the **Go zero value** for the underlying type: + +| Underlying Type | `$zero` Value | +|----------------|---------------| +| `string` | `""` | +| `int`, `int8`, `int16`, `int32`, `int64` | `0` | +| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | `0` | +| `float32`, `float64` | `0` | +| `bool` | `false` | + +When a struct field uses an enum type and no value is provided, the constructor defaults to `$zero`: + +```typescript +export class Person { + "Title": Title; + + constructor($$source: Partial = {}) { + if (!("Title" in $$source)) { + this["Title"] = Title.$zero; // defaults to "" + } + Object.assign(this, $$source); + } +} +``` + +This ensures type-safe initialisation when generating classes — enum fields are never `undefined`. When generating TypeScript interfaces (using `-i`), there is no constructor and fields may be absent as usual. + +## Using Enums in Structs + +When a struct field has an enum type, the generated code **preserves that type** rather than falling back to the primitive: + +```go +type Person struct { + Title Title + Name string + Age Age +} +``` + +**Generated TypeScript:** + +```typescript +export class Person { + "Title": Title; + "Name": string; + "Age": Age; + + constructor($$source: Partial = {}) { + if (!("Title" in $$source)) { + this["Title"] = Title.$zero; + } + if (!("Name" in $$source)) { + this["Name"] = ""; + } + if (!("Age" in $$source)) { + this["Age"] = 0; + } + + Object.assign(this, $$source); + } +} +``` + +The `Title` field is typed as `Title`, not `string`. This gives your IDE full autocompletion and type checking on enum values. + +## Enums from Imported Packages + +Enums defined in separate packages are fully supported. They are generated into the corresponding package directory: + +```go +// services/types.go +package services + +type Title string + +const ( + Mister Title = "Mr" + Miss Title = "Miss" + Ms Title = "Ms" +) +``` + +```go +// main.go +package main + +import "myapp/services" + +func (*GreetService) Greet(name string, title services.Title) string { + return "Hello " + string(title) + " " + name +} +``` + +The `Title` enum is generated in the `services` models file, and import paths are resolved automatically: + +```typescript +// bindings/myapp/services/models.ts +export enum Title { + $zero = "", + Mister = "Mr", + Miss = "Miss", + Ms = "Ms", +} +``` + +## Enum Methods + +You can add methods to your enum types in Go. These don't affect binding generation but provide useful server-side functionality: + +```go +type Title string + +func (t Title) String() string { + return string(t) +} + +const ( + Mister Title = "Mr" + Miss Title = "Miss" +) +``` + +The generated enum is identical whether or not Go methods exist on the type. + +## Comments and Documentation + +The generator preserves Go comments as JSDoc in the generated output: + +- **Type comments** become the enum's doc comment +- **Const group comments** become section separators +- **Individual const comments** become member doc comments +- **Inline comments** are preserved where possible + +This means your IDE will show documentation for enum values on hover. + +## Supported Underlying Types + +The binding generator supports enums with the following Go underlying types: + +| Go Type | Works as Enum | +|---------|:---:| +| `string` | Yes | +| `int`, `int8`, `int16`, `int32`, `int64` | Yes | +| `uint`, `uint8`, `uint16`, `uint32`, `uint64` | Yes | +| `float32`, `float64` | Yes | +| `byte` (`uint8`) | Yes | +| `rune` (`int32`) | Yes | +| `bool` | Yes | +| `complex64`, `complex128` | No | + +## Limitations + +The following are **not** supported for enum generation: + +- **Generic types** — Type parameters prevent constant detection +- **Types with custom `json.Marshaler` or `encoding.TextMarshaler`** — Custom serialisation means the generated values may not match runtime behaviour, so the generator skips these +- **Constants whose values cannot be statically evaluated or represented** — Constants must have known, representable values in their underlying type. Standard `iota` patterns work fine since the compiler resolves them to concrete values +- **Complex number types** — `complex64` and `complex128` cannot be enum underlying types + +## Complete Example + +**Go:** + +```go +package main + +import ( + "github.com/wailsapp/wails/v3/pkg/application" +) + +// BackgroundType defines the type of background +type BackgroundType string + +const ( + BackgroundSolid BackgroundType = "solid" + BackgroundGradient BackgroundType = "gradient" + BackgroundImage BackgroundType = "image" +) + +type BackgroundConfig struct { + Type BackgroundType `json:"type"` + Value string `json:"value"` +} + +type ThemeService struct{} + +func (*ThemeService) GetBackground() BackgroundConfig { + return BackgroundConfig{ + Type: BackgroundSolid, + Value: "#ffffff", + } +} + +func (*ThemeService) SetBackground(config BackgroundConfig) error { + // Apply background + return nil +} + +func main() { + app := application.New(application.Options{ + Services: []application.Service{ + application.NewService(&ThemeService{}), + }, + }) + app.Window.New() + app.Run() +} +``` + +**Frontend (TypeScript):** + +```typescript +import { GetBackground, SetBackground } from './bindings/myapp/themeservice' +import { BackgroundConfig, BackgroundType } from './bindings/myapp/models' + +// Get current background +const bg = await GetBackground() + +// Check the type using enum values +if (bg.type === BackgroundType.BackgroundSolid) { + console.log("Solid background:", bg.value) +} + +// Set a new background +await SetBackground(new BackgroundConfig({ + type: BackgroundType.BackgroundGradient, + value: "linear-gradient(to right, #000, #fff)" +})) +``` + +**Frontend (JavaScript):** + +```javascript +import { GetBackground, SetBackground } from './bindings/myapp/themeservice' +import { BackgroundConfig, BackgroundType } from './bindings/myapp/models' + +// Use enum values for type-safe comparisons +const bg = await GetBackground() + +switch (bg.type) { + case BackgroundType.BackgroundSolid: + applySolid(bg.value) + break + case BackgroundType.BackgroundGradient: + applyGradient(bg.value) + break + case BackgroundType.BackgroundImage: + applyImage(bg.value) + break +} +``` + +## Next Steps + + + + Structs, type mapping, and model generation. + + [Learn More →](/features/bindings/models) + + + + Bind Go methods to the frontend. + + [Learn More →](/features/bindings/methods) + + + + Directives, code injection, and custom IDs. + + [Learn More →](/features/bindings/advanced) + + + + Binding design patterns. + + [Learn More →](/features/bindings/best-practices) + + + +--- + +**Questions?** Ask in [Discord](https://discord.gg/JDdSxwjhGf) or check the [binding examples](https://github.com/wailsapp/wails/tree/v3-alpha/v3/examples/binding). diff --git a/docs/src/content/docs/features/bindings/models.mdx b/docs/src/content/docs/features/bindings/models.mdx index f393eb694..5df312ea7 100644 --- a/docs/src/content/docs/features/bindings/models.mdx +++ b/docs/src/content/docs/features/bindings/models.mdx @@ -480,6 +480,8 @@ if (user.nickname) { ### Enums +The binding generator automatically detects Go named types with constants and generates TypeScript enums or JavaScript const objects — including a `$zero` member for Go's zero value and full JSDoc preservation. + ```go type UserRole string @@ -488,26 +490,16 @@ const ( RoleUser UserRole = "user" RoleGuest UserRole = "guest" ) - -type User struct { - ID int `json:"id"` - Name string `json:"name"` - Role UserRole `json:"role"` -} ``` -**Generated:** +**Generated TypeScript:** -```javascript -export const UserRole = { - Admin: "admin", - User: "user", - Guest: "guest" -} - -export class User { - /** @type {string} */ - role = UserRole.User +```typescript +export enum UserRole { + $zero = "", + RoleAdmin = "admin", + RoleUser = "user", + RoleGuest = "guest", } ``` @@ -518,10 +510,12 @@ import { User, UserRole } from './bindings/myapp/models' const admin = new User({ name: "Admin", - role: UserRole.Admin + role: UserRole.RoleAdmin }) ``` +For comprehensive coverage of string enums, integer enums, type aliases, imported package enums, and limitations, see the dedicated **[Enums](/features/bindings/enums)** page. + ### Validation ```javascript diff --git a/v3/UNRELEASED_CHANGELOG.md b/v3/UNRELEASED_CHANGELOG.md index 8e4648038..1710e1795 100644 --- a/v3/UNRELEASED_CHANGELOG.md +++ b/v3/UNRELEASED_CHANGELOG.md @@ -17,6 +17,8 @@ After processing, the content will be moved to the main changelog and this file ## Added +- Add `-tags` flag to `wails3 build` command for passing custom Go build tags (e.g., `wails3 build -tags gtk4`) (#4957) +- Add documentation for automatic enum generation in binding generator, including dedicated Enums page and sidebar navigation (#4972) ## Changed