[v3] Typed Events, revisited (#4633)

* Add strong event typings

* Make `EmitEvent` take one data argument only

* Add event registration logic

* Report event cancellation to the emitter

* Prevent registration of system events

* Add support for typed event data initialisation

* Binding generation for events

* Tests for event bindings

* Add vite plugin for typed events

* Fix dev command execution order

Co-authored-by: Fabio Massaioli <fabio.massaioli@gmail.com>

* Propagate module path to templates

* Update templates

Co-authored-by: Ian VanSchooten <ian.vanschooten@gmail.com>

* Go mod tidy for examples

* Switch to tsconfig.json for jetbrains IDE support

* Replace jsconfig in example

* Convert vite plugin to typescript

* Downgrade vite for now

The templates all use 5.x

* Remove root plugins dir from npm files

It's now '/dist/plugins'

* Include types for Create

But keep out of the docs

* Assign a type for cancelAll results

* Restore variadic argument in EmitEvent methods

* Support registered events with void data

* Test cases for void alias support

* Support strict mode

* Support custom event hooks

* Update docs

* Update changelog

* Testdata for typed events

* Test data for void alias support

* fix webview_window emit event

* Update changelog.mdx

* Update events

* Fix generator test path normalization for cross-platform compatibility

The generator tests were failing on CI because they compared absolute file paths
in warning messages. These paths differ between development machines and CI environments.

Changes:
- Normalize file paths in warnings to be relative to testcases/ directory
- Handle both Unix and Windows path separators
- Use Unix line endings consistently in test output
- Update all test expectation files to use normalized paths

This ensures tests pass consistently across different environments including
Windows, macOS, Linux, and CI systems.

* Remove stale comment

* Handle errors returned from validation

* Restore variadic argument to Emit (fix bad rebase)

* Event emitters return a boolean

* Don't use `EmitEvent` in docs

Supposedly it's for internal use, according to comment

* Fix event docs (from rebase)

* Ensure all templates specify @wailsio/runtime: "latest"

* Fix Windows test failure due to CRLF line endings

The test was failing on Windows because:
1. Hardcoded "\n" was being used instead of render.Newline when writing
   warning logs, causing CRLF vs LF mismatch
2. The render package import was missing
3. .got.log files weren't being skipped when building expected file list

Changes:
- Add render package import
- Use render.Newline instead of hardcoded "\n" for cross-platform compatibility
- Skip .got.log files in test file walker

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix template tests by using local runtime package

The template tests were failing because they were installing @wailsio/runtime@latest from npm, which doesn't have the new vite plugin yet. This change packs the local runtime and uses it in template tests instead.

Changes:
- Pack the runtime to a tarball in test_js job
- Upload the runtime package as an artifact
- Download and install the local runtime in template tests before building
- Update cleanup job to delete the runtime package artifact

* Apply suggestion from @leaanthony

* Fix: Install local runtime in frontend directory with correct path

The previous fix wasn't working because:
1. npm install was run in the project root, not in frontend/
2. wails3 build runs npm install again, which would reinstall from npm

Fixed by:
- Using npm pkg set to modify package.json to use file:// protocol
- This ensures subsequent npm install calls use the local tarball

* Fix Vue template syntax conflicts with Go template delimiters

The Vue templates were converted to .tmpl files to support dynamic module
paths, but Vue's template syntax {{ }} conflicts with Go's template syntax.

Fixed by escaping Vue template braces:
- {{ becomes {{"{{"}}
- }} becomes {{"}}"}}

This allows the Go template engine to output the literal {{ }} for Vue to process.

* Fix Vue template escaping and Windows shell compatibility

Two issues fixed:

1. Vue template escaping: Changed from {{"{{"}} to {{ "{{" }}
   - The previous syntax caused "missing value for command" error
   - Correct Go template syntax uses spaces between delimiters and strings

2. Windows PowerShell compatibility: Added 'shell: bash' to template generation step
   - The bash syntax (ls, head, $()) doesn't work in PowerShell
   - Git Bash is available on all GitHub runners including Windows

* Fix: test_templates depends on test_js for runtime package artifact

The runtime-package artifact is created in test_js job, not test_go.
Added test_js to the needs array so the artifact is available for download.

* Fix Windows path compatibility for runtime package artifact

Changed from absolute Unix path '/tmp/wails-runtime' to relative path
'wails-runtime-temp' which works cross-platform. Using realpath to
convert to absolute path for file:// URL in npm pkg set command.

* Fix realpath issue on Windows for runtime package

realpath on Windows Git Bash was producing malformed paths with duplicate
drive letters (D:\d\a\...). Replaced with portable solution using pwd
that works correctly across all platforms.

* Use pwd -W on Windows to get native Windows paths

Git Bash's pwd returns Unix-style paths (/d/a/wails/wails) which npm
then incorrectly resolves as D:/d/a/wails/wails. Using pwd -W returns
native Windows paths (D:\a\wails\wails) that npm can handle correctly.

This is the root cause of all the Windows path issues.

* Improve typechecking for Events.Emit()

* [docs] Clarify where `Events` is imported from in each example

* Add docs for runtime Events.Emit()

* Revert to v2-style Events.Emit (name, data)

* Update changelog

---------

Co-authored-by: Fabio Massaioli <fabio.massaioli@gmail.com>
Co-authored-by: Atterpac <Capretta.Michael@gmail.com>
Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Ian VanSchooten 2025-11-11 04:25:57 -05:00 committed by GitHub
commit bbd5d99667
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
232 changed files with 4734 additions and 1646 deletions

View file

@ -79,6 +79,10 @@ jobs:
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: npm run build
- name: Pack runtime for template tests
working-directory: v3/internal/runtime/desktop/@wailsio/runtime
run: npm pack
- name: Store runtime build artifacts
uses: actions/upload-artifact@v4
with:
@ -88,6 +92,12 @@ jobs:
v3/internal/runtime/desktop/@wailsio/runtime/types/
v3/internal/runtime/desktop/@wailsio/runtime/tsconfig.tsbuildinfo
- name: Store runtime package
uses: actions/upload-artifact@v4
with:
name: runtime-package
path: v3/internal/runtime/desktop/@wailsio/runtime/*.tgz
test_go:
name: Run Go Tests v3
needs: [check_approval, test_js]
@ -165,17 +175,19 @@ jobs:
cleanup:
name: Cleanup build artifacts
if: always()
needs: [test_js, test_go]
needs: [test_js, test_go, test_templates]
runs-on: ubuntu-latest
steps:
- uses: geekyeggo/delete-artifact@v5
with:
name: runtime-build-artifacts
name: |
runtime-build-artifacts
runtime-package
failOnError: false
test_templates:
name: Test Templates
needs: test_go
needs: [test_js, test_go]
runs-on: ${{ matrix.os }}
if: github.base_ref == 'v3-alpha'
strategy:
@ -226,12 +238,28 @@ jobs:
task install
wails3 doctor
- name: Download runtime package
uses: actions/download-artifact@v4
with:
name: runtime-package
path: wails-runtime-temp
- name: Generate template '${{ matrix.template }}'
shell: bash
run: |
# Get absolute path - use pwd -W on Windows for native paths, pwd elsewhere
if [[ "$RUNNER_OS" == "Windows" ]]; then
RUNTIME_TGZ="$(cd wails-runtime-temp && pwd -W)/$(ls wails-runtime-temp/*.tgz | xargs basename)"
else
RUNTIME_TGZ="$(cd wails-runtime-temp && pwd)/$(ls wails-runtime-temp/*.tgz | xargs basename)"
fi
mkdir -p ./test-${{ matrix.template }}
cd ./test-${{ matrix.template }}
wails3 init -n ${{ matrix.template }} -t ${{ matrix.template }}
cd ${{ matrix.template }}
cd ${{ matrix.template }}/frontend
# Replace @wailsio/runtime version with local tarball
npm pkg set dependencies.@wailsio/runtime="file://$RUNTIME_TGZ"
cd ..
wails3 build
build_results:

File diff suppressed because it is too large Load diff

View file

@ -77,21 +77,38 @@ func (s *Service) UpdateData() {
// Notify the frontend
app := application.Get()
app.Event.EmitEvent(&application.CustomEvent{
Name: "my-app:data-updated",
Data: map[string]interface{}{
app.Event.Emit("my-app:data-updated",
map[string]interface{}{
"timestamp": time.Now(),
"count": 42,
},
})
)
}
```
### Emitting Events (Frontend)
While not as commonly used, you can also emit events from your frontend that your Go code can listen to:
```javascript
import { Events } from '@wailsio/runtime';
// Event without data
Events.Emit('myapp:close-window')
// Event with data
Events.Emit('myapp:disconnect-requested', 'id-123')
```
If you are using typescript in your frontend and [application.RegisterEvent](/learn/events#custom-event-registration) in your Go code, you will get event name autocomplete / checking and data type checking.
### Removing Event Listeners
Always clean up your event listeners when they're no longer needed:
```javascript
import { Events } from '@wailsio/runtime';
// Store the handler reference
const focusHandler = () => {
console.log('Window focused');
@ -114,6 +131,8 @@ Events.Off('common:WindowFocus');
Many applications need to pause certain activities when the window loses focus:
```javascript
import { Events } from '@wailsio/runtime';
let animationRunning = true;
Events.On('common:WindowLostFocus', () => {
@ -132,6 +151,8 @@ Events.On('common:WindowFocus', () => {
Keep your app in sync with the system theme:
```javascript
import { Events } from '@wailsio/runtime';
Events.On('common:ThemeChanged', (event) => {
const isDarkMode = event.data.isDark;
@ -150,6 +171,8 @@ Events.On('common:ThemeChanged', (event) => {
Make your app accept dragged files:
```javascript
import { Events } from '@wailsio/runtime';
Events.On('common:WindowFilesDropped', (event) => {
const files = event.data.files;
@ -166,6 +189,8 @@ Events.On('common:WindowFilesDropped', (event) => {
Respond to window state changes:
```javascript
import { Events } from '@wailsio/runtime';
Events.On('common:WindowClosing', () => {
// Save user data before closing
saveApplicationState();
@ -190,6 +215,8 @@ Events.On('common:WindowRestore', () => {
Handle platform-specific events when needed:
```javascript
import { Events } from '@wailsio/runtime';
// Windows-specific power management
Events.On('windows:APMSuspend', () => {
console.log('System is going to sleep');
@ -210,7 +237,7 @@ Events.On('mac:ApplicationWillTerminate', () => {
## Creating Custom Events
You can create your own events for application-specific needs:
You can create your own events for application-specific needs. See [application.RegisterEvent](/learn/events#custom-event-registration) to learn how to register these events and enable type checking of event data.
### Backend (Go)
@ -222,14 +249,13 @@ func (s *Service) ProcessUserData(userData UserData) error {
app := application.Get()
// Notify all listeners
app.Event.EmitEvent(&application.CustomEvent{
Name: "user:data-processed",
Data: map[string]interface{}{
app.Event.Emit("user:data-processed",
map[string]interface{}{
"userId": userData.ID,
"status": "completed",
"timestamp": time.Now(),
},
})
)
return nil
}
@ -250,6 +276,8 @@ func (s *Service) StartMonitoring() {
### Frontend (JavaScript)
```javascript
import { Events } from '@wailsio/runtime';
// Listen for your custom events
Events.On('user:data-processed', (event) => {
const { userId, status, timestamp } = event.data;
@ -341,6 +369,8 @@ Events.Emit('update');
Always remove event listeners when components unmount:
```javascript
import { Events } from '@wailsio/runtime';
// React example
useEffect(() => {
const handler = (event) => {
@ -361,7 +391,7 @@ useEffect(() => {
Check platform availability when using platform-specific events:
```javascript
import { Platform } from '@wailsio/runtime';
import { Platform, Events } from '@wailsio/runtime';
if (Platform.isWindows) {
Events.On('windows:APMSuspend', handleSuspend);
@ -382,6 +412,8 @@ While events are powerful, don't use them for everything:
To debug event issues:
```javascript
import { Events } from '@wailsio/runtime';
// Log all events (development only)
if (isDevelopment) {
const originalOn = Events.On;

View file

@ -127,13 +127,12 @@ func (s *GinService) ServiceStartup(ctx context.Context, options application.Ser
s.app.Logger.Info("Received event from frontend", "data", event.Data)
// Emit an event back to the frontend
s.app.Event.EmitEvent(&application.CustomEvent{
Name: "gin-api-response",
Data: map[string]interface{}{
s.app.Event.Emit("gin-api-response",
map[string]interface{}{
"message": "Response from Gin API Service",
"time": time.Now().Format(time.RFC3339),
},
})
)
})
return nil
@ -316,13 +315,12 @@ s.app.Event.On("gin-api-event", func(event *application.CustomEvent) {
s.app.Logger.Info("Received event from frontend", "data", event.Data)
// Emit an event back to the frontend
s.app.Event.EmitEvent(&application.CustomEvent{
Name: "gin-api-response",
Data: map[string]interface{}{
s.app.Event.Emit("gin-api-response",
map[string]interface{}{
"message": "Response from Gin API Service",
"time": time.Now().Format(time.RFC3339),
},
})
)
})
```
@ -349,10 +347,7 @@ Then use it in your code:
import * as wails from '@wailsio/runtime';
// Event emission
wails.Events.Emit({
name: 'gin-api-event',
data: eventData,
});
wails.Events.Emit('gin-api-event', eventData);
```
Here's an example of how to set up frontend integration:
@ -528,7 +523,7 @@ Here's an example of how to set up frontend integration:
message: "Hello from the frontend!",
timestamp: new Date().toISOString()
};
wails.Events.Emit({name: 'gin-api-event', data: eventData});
wails.Events.Emit('gin-api-event', eventData);
});
// Set up event listener for responses from the backend

View file

@ -313,7 +313,7 @@ win.OnWindowEvent(events.Common.WindowDropZoneFilesDropped, func(event *applicat
"dropY": details.Y,
"attributes": details.Attributes,
}
application.Get().EmitEvent("frontend:FileDropInfo", payload) // Emits globally
application.Get().Event.Emit("frontend:FileDropInfo", payload) // Emits globally
// or win.EmitEvent("frontend:FileDropInfoForWindow", payload) // Emits to this specific window
} else {
log.Println("Drop occurred, but DropZoneDetails were nil.")
@ -565,6 +565,9 @@ You can emit custom events from anywhere in your application:
// NEW: Using the Event Manager (recommended)
app.Event.Emit("myevent", "hello")
// Emit an event without data
app.Event.Emit("datalessevent")
// Emit from a specific window
window.EmitEvent("windowevent", "window specific data")
```
@ -574,8 +577,7 @@ window.EmitEvent("windowevent", "window specific data")
// Traditional API (no longer works)
app.EmitEvent("myevent", "hello")
// Emit from a specific window
window.EmitEvent("windowevent", "window specific data")
```
</TabItem>
</Tabs>
@ -633,6 +635,90 @@ app.OnMultipleEvent("myevent", func(e *application.CustomEvent) {
</TabItem>
</Tabs>
You can also register a hook that will run synchronously when the event is emitted.
Hooks are useful [to cancel custom events and stop their propagation early](#event-cancellation):
```go
app.Event.RegisterHook("myevent", func(e *application.CustomEvent) {
// Do something.
})
```
### Custom Event Registration
You can call the `application.RegisterEvent` function at init time to register custom event names:
```go
type MyEventData struct {
Num int
Text string
}
func init() {
application.RegisterEvent[MyEventData]("myevent")
}
```
:::caution
Because this function is meant to be called at init time, it will panic when the arguments are not valid
or the same event name is registered twice with two distinct data types.
:::
:::note
It is safe to register the same event multiple times as long as the data type is always the same.
This might be useful to ensure a certain event is registered as soon as any one of many packages is loaded.
:::
Once the event is registered, data arguments passed to the `Event.Emit` method will be checked against the specified type.
In case of a mismatch, an error will be emitted and either logged or passed on to the registered error handled, if any.
The offending event will not be propagated. Thanks to this mechanism, it is safe to assume that the data field
of registered custom events will always be assignable to the declared type.
:::tip[Strict mode]
If you use the `strictevents` build tag, using unregistered events will trigger a warning message in development mode.
The runtime emits at most one warning per event name, to avoid spamming the log.
:::
### Binding generator support
The binding generator outputs TypeScript definitions and internal glue code
to provide transparent support for registered events on the frontend.
The specified data type will be rendered to TypeScript
and the data field will be typed by the corresponding model:
```js
import { Events } from "@wailsio/runtime";
Events.On("myevent", (ev) => {
ev.data; // Has type MyEventData
ev.data.Text; // Has type string
})
```
:::caution
Static typing of event data on the frontend will only be available when the event name
is specified by a constant expression, as shown above.
:::
When using the `Events.On*/Events.Off*` family of runtime methods or the `WailsEvent` constructor,
IDEs with TypeScript support will offer autocompletion for registered event names.
You can look at built-in application templates
for an example of how to configure your project for typed events.
### Events without data
The `application.Void` interface can be used to register events without associated data:
```go
func init() {
application.RegisterEvent[application.Void]("dataless")
}
```
On the Go side, the runtime will check that the data field for the `"dataless"` event is always `nil`.
On the frontend, the binding generator will translate `application.Void` as the TypeScript `void` type.
## Event Cancellation
Events can be cancelled to prevent their default behaviour or stop propagation to other listeners. This is particularly useful for hooks that need to control window closing, menu actions, or other system events.
@ -646,8 +732,17 @@ window.RegisterHook(events.Common.WindowClosing, func(e *application.WindowEvent
// Prevent the window from closing
e.Cancel()
})
// For custom events
app.RegisterHook("myevent", func(e *application.CustomEvent) {
e.Cancel()
})
```
:::tip[Pro Tip]
Remember that event cancellation in hooks affects all subsequent hooks and listeners, whilst cancellation in standard listeners only affects listeners that haven't yet been called.
:::
### Checking Event Cancellation
You can check if an event has been cancelled using the `IsCancelled()` method:
@ -671,6 +766,13 @@ app.Event.On("myevent", func(e *application.CustomEvent) {
})
```
:::tip[Pro Tip]
Remember that event cancellation in hooks affects all subsequent hooks and listeners, whilst cancellation in standard listeners only affects listeners that haven't yet been called.
:::
When emitting a custom event, you can also check the value returned by the `Event.Emit` method (for the app and windows):
```go
if app.Event.Emit("myevent") {
app.Logger.Info("Event was cancelled")
return
}
```
The returned value will take into account all hooks, but may or may not take into account cancellations that happen asynchronously in listeners.

View file

@ -17,9 +17,11 @@ After processing, the content will be moved to the main changelog and this file
## Added
<!-- New features, capabilities, or enhancements -->
- Typed Events by @fbbdev and @ianvs in [#4633](https://github.com/wailsapp/wails/pull/4633)
## Changed
<!-- Changes in existing functionality -->
- When emitting a custom event with zero or one data argument, the data value will be assigned directly to the Data field without wrapping it in a slice by [@fbbdev](https://github.com/fbbdev) in [#4633](https://github.com/wailsapp/wails/pull/4633)
## Fixed
<!-- Bug fixes -->

View file

@ -43,14 +43,11 @@ removeButton.addEventListener('click', () => {
setButtonUsingGo.addEventListener('click', () => {
let label = (labelElement as HTMLInputElement).value
void Events.Emit({
name: "set:badge",
data: label,
})
void Events.Emit("set:badge", label);
})
removeButtonUsingGo.addEventListener('click', () => {
void Events.Emit({name:"remove:badge", data: null})
void Events.Emit("remove:badge");
})
Events.On('time', (time: {data: any}) => {

View file

@ -19,14 +19,11 @@ removeButton.addEventListener('click', () => {
setButtonUsingGo.addEventListener('click', () => {
let label = (labelElement as HTMLInputElement).value
void Events.Emit({
name: "set:badge",
data: label,
})
void Events.Emit("set:badge", label)
})
removeButtonUsingGo.addEventListener('click', () => {
void Events.Emit({name:"remove:badge", data: null})
void Events.Emit("remove:badge")
})
Events.On('time', (time: {data: any}) => {

View file

@ -12,6 +12,6 @@
"vite": "^6.3.5"
},
"dependencies": {
"@wailsio/runtime": "^3.0.0-alpha.66"
"@wailsio/runtime": "latest"
}
}

View file

@ -1,5 +1,6 @@
{
"compilerOptions": {
"allowJs": true,
"moduleResolution": "Node",
"target": "ESNext",
"module": "ESNext",
@ -29,5 +30,5 @@
* Use global.d.ts instead of compilerOptions.types
* to avoid limiting type declarations.
*/
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"]
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte", "bindings"]
}

View file

@ -223,7 +223,7 @@
message: "Hello from the frontend!",
timestamp: new Date().toISOString()
};
wails.Events.Emit({name: 'gin-api-event', data: eventData});
wails.Events.Emit('gin-api-event', eventData);
});
// Set up event listener for responses from the backend

View file

@ -62,12 +62,13 @@ func GenerateBindings(options *flags.GenerateBindingsOptions, patterns []string)
// Stop spinner and print summary.
term.StopSpinner(spinner)
term.Infof(
"Processed: %s, %s, %s, %s, %s in %s.",
"Processed: %s, %s, %s, %s, %s, %s in %s.",
pluralise(stats.NumPackages, "Package"),
pluralise(stats.NumServices, "Service"),
pluralise(stats.NumMethods, "Method"),
pluralise(stats.NumEnums, "Enum"),
pluralise(stats.NumModels, "Model"),
pluralise(stats.NumEvents, "Event"),
stats.Elapsed().String(),
)

View file

@ -14,7 +14,7 @@ tasks:
- package.json
- package-lock.json
generates:
- node_modules/*
- node_modules
preconditions:
- sh: npm version
msg: "Looks like npm isn't installed. Npm is part of the Node installer: https://nodejs.org/en/download/"

View file

@ -30,16 +30,14 @@ dev_mode:
- .gitkeep
watched_extension:
- "*.go"
- "*.js" # Watch for changes to JS/TS files included using the //wails:include directive.
- "*.ts" # The frontend directory will be excluded entirely by the setting above.
git_ignore: true
executes:
- cmd: wails3 task common:install:frontend:deps
type: once
- cmd: wails3 task common:dev:frontend
type: background
- cmd: go mod tidy
type: blocking
- cmd: wails3 task build
type: blocking
- cmd: wails3 task common:dev:frontend
type: background
- cmd: wails3 task run
type: primary

View file

@ -1,14 +1,17 @@
package commands
import (
"errors"
"fmt"
"github.com/go-git/go-git/v5/config"
"github.com/wailsapp/wails/v3/internal/term"
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/go-git/go-git/v5/config"
"github.com/wailsapp/wails/v3/internal/term"
"github.com/go-git/go-git/v5"
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v3/internal/flags"
@ -17,36 +20,52 @@ import (
var DisableFooter bool
// GitURLToModuleName converts a git URL to a Go module name by removing common prefixes
// See https://github.com/git/git/blob/master/Documentation/urls.adoc
var (
gitProtocolFormat = regexp.MustCompile(`^(?:ssh|git|https?|ftps?|file)://`)
gitScpLikeGuard = regexp.MustCompile(`^[^/:]+:`)
gitScpLikeFormat = regexp.MustCompile(`^(?:([^@/:]+)@)?([^@/:]+):([^\\].*)$`)
)
// gitURLToModulePath converts a git URL to a Go module name by removing common prefixes
// and suffixes. It handles HTTPS, SSH, Git protocol, and filesystem URLs.
func GitURLToModuleName(gitURL string) string {
moduleName := gitURL
if strings.HasSuffix(moduleName, ".git") {
moduleName = moduleName[:len(moduleName)-4]
}
// Handle various URL schemes
for _, prefix := range []string{
"https://",
"http://",
"git://",
"ssh://",
"file://",
} {
if strings.HasPrefix(moduleName, prefix) {
moduleName = moduleName[len(prefix):]
break
func gitURLToModulePath(gitURL string) string {
var path string
if gitProtocolFormat.MatchString(gitURL) {
// Standard URL
parsed, err := url.Parse(gitURL)
if err != nil {
term.Warningf("invalid Git repository URL: %s; module path will default to 'changeme'", err)
return "changeme"
}
path = parsed.Host + parsed.Path
} else if gitScpLikeGuard.MatchString(gitURL) {
// SCP-like URL
match := gitScpLikeFormat.FindStringSubmatch(gitURL)
if match != nil {
sep := ""
if !strings.HasPrefix(match[3], "/") {
// Add slash between host and path if missing
sep = "/"
}
path = match[2] + sep + match[3]
}
}
// Handle SSH URLs (git@github.com:username/project.git)
if strings.HasPrefix(moduleName, "git@") {
// Remove the 'git@' prefix
moduleName = moduleName[4:]
// Replace ':' with '/' for proper module path
moduleName = strings.Replace(moduleName, ":", "/", 1)
if path == "" {
// Filesystem path
path = gitURL
}
if strings.HasSuffix(path, ".git") {
path = path[:len(path)-4]
}
// Remove leading forward slash for file system paths
moduleName = strings.TrimPrefix(moduleName, "/")
return moduleName
return strings.TrimPrefix(path, "/")
}
func initGitRepository(projectDir string, gitURL string) error {
@ -65,28 +84,6 @@ func initGitRepository(projectDir string, gitURL string) error {
return fmt.Errorf("failed to create git remote: %w", err)
}
// Update go.mod with the module name
moduleName := GitURLToModuleName(gitURL)
goModPath := filepath.Join(projectDir, "go.mod")
content, err := os.ReadFile(goModPath)
if err != nil {
return fmt.Errorf("failed to read go.mod: %w", err)
}
// Replace module name
lines := strings.Split(string(content), "\n")
if len(lines) == 0 {
return fmt.Errorf("go.mod is empty")
}
lines[0] = fmt.Sprintf("module %s", moduleName)
newContent := strings.Join(lines, "\n")
err = os.WriteFile(goModPath, []byte(newContent), 0644)
if err != nil {
return fmt.Errorf("failed to write go.mod: %w", err)
}
// Stage all files
worktree, err := repo.Worktree()
if err != nil {
@ -102,7 +99,6 @@ func initGitRepository(projectDir string, gitURL string) error {
}
func Init(options *flags.Init) error {
if options.List {
term.Header("Available templates")
return printTemplates()
@ -120,11 +116,19 @@ func Init(options *flags.Init) error {
}
if options.ProjectName == "" {
return fmt.Errorf("please use the -n flag to specify a project name")
return errors.New("please use the -n flag to specify a project name")
}
options.ProjectName = sanitizeFileName(options.ProjectName)
if options.ModulePath == "" {
if options.Git == "" {
options.ModulePath = "changeme"
} else {
options.ModulePath = gitURLToModulePath(options.Git)
}
}
err := templates.Install(options)
if err != nil {
return err

View file

@ -1,8 +1,10 @@
package commands
import "testing"
import (
"testing"
)
func Test_GitURLToModuleName(t *testing.T) {
func TestGitURLToModulePath(t *testing.T) {
tests := []struct {
name string
gitURL string
@ -106,8 +108,8 @@ func Test_GitURLToModuleName(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := GitURLToModuleName(tt.gitURL); got != tt.want {
t.Errorf("GitURLToModuleName() = %v, want %v", got, tt.want)
if got := gitURLToModulePath(tt.gitURL); got != tt.want {
t.Errorf("gitURLToModulePath() = %v, want %v", got, tt.want)
}
})
}

View file

@ -16,6 +16,7 @@ type GenerateBindingsOptions struct {
UseInterfaces bool `name:"i" description:"Generate Typescript interfaces instead of classes"`
UseBundledRuntime bool `name:"b" description:"Use the bundled runtime instead of importing the npm package"`
UseNames bool `name:"names" description:"Use names instead of IDs for the binding calls"`
NoEvents bool `name:"noevents" description:"Do not generate types for registered custom events"`
NoIndex bool `name:"noindex" description:"Do not generate JS/TS index files"`
DryRun bool `name:"dry" description:"Do not write output files"`
Silent bool `name:"silent" description:"Silent mode"`

View file

@ -11,6 +11,7 @@ type Init struct {
List bool `name:"l" description:"List templates"`
SkipGoModTidy bool `name:"skipgomodtidy" description:"Skip running go mod tidy"`
Git string `name:"git" description:"Git repository URL to initialize (e.g. github.com/username/project)"`
ModulePath string `name:"mod" description:"The Go module path for the project. Will be computed from the Git URL if unspecified."`
ProductName string `description:"The name of the product" default:"My Product"`
ProductDescription string `description:"The description of the product" default:"My Product Description"`
ProductVersion string `description:"The version of the product" default:"0.1.0"`

View file

@ -15,12 +15,10 @@ import (
//
// Whenever one is found and the type of its unique argument
// is a valid service type, the corresponding named type object
// is passed to yield.
// is fed into the returned iterator.
//
// Results are deduplicated, i.e. yield is called at most once per object.
//
// If yield returns false, FindBoundTypes returns immediately.
func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, logger config.Logger) (iter.Seq[*types.TypeName], error) {
// Results are deduplicated, i.e. the iterator yields any given object at most once.
func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, logger config.Logger) (iter.Seq[*types.TypeName], types.Object, error) {
type instanceInfo struct {
args *types.TypeList
pos token.Position
@ -47,6 +45,9 @@ func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, log
// for deduplication.
scheduled := make(map[target]bool)
// registerEvent holds the `application.RegisterEvent` function if found.
var registerEvent types.Object
// next lists type parameter objects that have yet to be analysed.
var next []targetInfo
@ -102,29 +103,46 @@ func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, log
}
}
if len(next) > 0 {
// application.NewService has been found already.
continue
}
// Detect application.RegisterEvent
if registerEvent == nil && obj.Name() == "RegisterEvent" && obj.Pkg().Path() == systemPaths.ApplicationPackage {
fn, ok := obj.(*types.Func)
if !ok {
return nil, nil, ErrBadApplicationPackage
}
fn, ok := obj.(*types.Func)
if !ok {
signature := fn.Type().(*types.Signature)
if signature.Params().Len() != 1 || signature.Results().Len() != 0 || signature.TypeParams().Len() != 1 {
logger.Warningf("application.RegisterService params: %d, results: %d, typeparams: %d", signature.Params().Len(), signature.Results().Len(), signature.TypeParams().Len())
return nil, nil, ErrBadApplicationPackage
}
if !types.Identical(signature.Params().At(0).Type(), types.Universe.Lookup("string").Type()) {
logger.Warningf("application.RegisterService parameter type: %v", signature.Params().At(0).Type())
return nil, nil, ErrBadApplicationPackage
}
registerEvent = obj
continue
}
// Detect application.NewService
if fn.Name() == "NewService" && fn.Pkg().Path() == systemPaths.ApplicationPackage {
// Check signature.
if len(next) == 0 && obj.Name() == "NewService" && obj.Pkg().Path() == systemPaths.ApplicationPackage {
fn, ok := obj.(*types.Func)
if !ok {
return nil, nil, ErrBadApplicationPackage
}
signature := fn.Type().(*types.Signature)
if signature.Params().Len() > 2 || signature.Results().Len() != 1 || tp.Len() != 1 || tp.At(0).Obj() == nil {
logger.Warningf("Param Len: %d, Results Len: %d, tp.Len: %d, tp.At(0).Obj(): %v", signature.Params().Len(), signature.Results().Len(), tp.Len(), tp.At(0).Obj())
return nil, ErrBadApplicationPackage
if signature.Params().Len() != 1 || signature.Results().Len() != 1 || tp.Len() != 1 {
logger.Warningf("application.NewService params: %d, results: %d, typeparams: %d", signature.Params().Len(), signature.Results().Len(), tp.Len())
return nil, nil, ErrBadApplicationPackage
}
// Schedule unique type param for analysis.
tgt := target{obj, 0}
scheduled[tgt] = true
next = append(next, targetInfo{target: tgt})
continue
}
}
}
@ -185,7 +203,7 @@ func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, log
logger.Warningf("%s: ignoring interface service type %s%s", instance.pos, named, indirectMsg)
continue
} else if named.TypeParams() != nil {
logger.Warningf("%s: ignoring generic service type %s", instance.pos, named, indirectMsg)
logger.Warningf("%s: ignoring generic service type %s%s", instance.pos, named, indirectMsg)
continue
}
@ -198,5 +216,5 @@ func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, log
}
}
}
}, nil
}, registerEvent, nil
}

View file

@ -17,8 +17,9 @@ import (
func TestAnalyser(t *testing.T) {
type testParams struct {
name string
want []string
name string
want []string
events bool
}
// Gather tests from cases directory.
@ -57,6 +58,19 @@ func TestAnalyser(t *testing.T) {
}
slices.Sort(test.want)
events, err := os.Open(filepath.Join("testcases", entry.Name(), "events.json"))
if err != nil {
if !errors.Is(err, os.ErrNotExist) {
t.Fatal(err)
}
} else {
err = json.NewDecoder(events).Decode(&test.events)
events.Close()
if err != nil {
t.Fatal(err)
}
}
tests = append(tests, test)
}
@ -68,6 +82,7 @@ func TestAnalyser(t *testing.T) {
for _, test := range tests {
all.want = append(all.want, test.want...)
all.events = all.events || test.events
}
slices.Sort(all.want)
@ -97,7 +112,7 @@ func TestAnalyser(t *testing.T) {
got := make([]string, 0)
services, err := FindServices(pkgs, systemPaths, config.DefaultPtermLogger(nil))
services, registerService, err := FindServices(pkgs, systemPaths, config.DefaultPtermLogger(nil))
if err != nil {
t.Error(err)
}
@ -111,6 +126,10 @@ func TestAnalyser(t *testing.T) {
if diff := cmp.Diff(test.want, got); diff != "" {
t.Errorf("Found services mismatch (-want +got):\n%s", diff)
}
if test.events != (registerService != nil) {
t.Errorf("Found events mismatch: wanted=%t, got=%t", test.events, registerService != nil)
}
})
}
}

View file

@ -40,6 +40,9 @@ type Collector struct {
// and declaration groups. Elements are [Info] instances.
cache sync.Map
// events holds collected information about registered custom events.
events *EventMap
systemPaths *config.SystemPaths
options *flags.GenerateBindingsOptions
scheduler Scheduler
@ -47,7 +50,7 @@ type Collector struct {
}
// NewCollector initialises a new Collector instance for the given package set.
func NewCollector(pkgs []*packages.Package, systemPaths *config.SystemPaths, options *flags.GenerateBindingsOptions, scheduler Scheduler, logger config.Logger) *Collector {
func NewCollector(pkgs []*packages.Package, registerEvent types.Object, systemPaths *config.SystemPaths, options *flags.GenerateBindingsOptions, scheduler Scheduler, logger config.Logger) *Collector {
collector := &Collector{
pkgs: make(map[*types.Package]*PackageInfo, len(pkgs)),
@ -62,6 +65,11 @@ func NewCollector(pkgs []*packages.Package, systemPaths *config.SystemPaths, opt
collector.pkgs[pkg.Types] = newPackageInfo(pkg, collector)
}
// Initialise event map.
if !options.NoEvents {
collector.events = newEventMap(collector, registerEvent)
}
return collector
}

View file

@ -39,25 +39,11 @@ func (collector *Collector) findDeclaration(obj types.Object) (path []ast.Node)
return nil
}
// Perform a binary search to find the file enclosing the node.
// We can't use findEnclosingNode here because it is less accurate and less efficient with files.
fileIndex, exact := slices.BinarySearchFunc(pkg.Files, obj.Pos(), func(f *ast.File, p token.Pos) int {
return cmp.Compare(f.FileStart, p)
})
// If exact is true, pkg.Files[fileIndex] is the file we are looking for;
// otherwise, it is the first file whose start position is _after_ obj.Pos().
if !exact {
fileIndex--
}
// When exact is false, the position might lie within an empty segment in between two files.
if fileIndex < 0 || pkg.Files[fileIndex].FileEnd <= obj.Pos() {
file := findEnclosingFile(pkg.Files, obj.Pos())
if file == nil {
return nil
}
file := pkg.Files[fileIndex]
// Find enclosing declaration.
decl := findEnclosingNode(file.Decls, obj.Pos())
if decl == nil {
@ -212,6 +198,28 @@ func (collector *Collector) findDeclaration(obj types.Object) (path []ast.Node)
}
}
// findEnclosingFile finds the unique file in files, if any, that encloses the given position.
func findEnclosingFile(files []*ast.File, pos token.Pos) *ast.File {
// Perform a binary search to find the file enclosing the node.
// We can't use findEnclosingNode here because it is less accurate and less efficient with files.
fileIndex, exact := slices.BinarySearchFunc(files, pos, func(f *ast.File, p token.Pos) int {
return cmp.Compare(f.FileStart, p)
})
// If exact is true, pkg.Files[fileIndex] is the file we are looking for;
// otherwise, it is the first file whose start position is _after_ obj.Pos().
if !exact {
fileIndex--
}
// When exact is false, the position might lie within an empty segment in between two files.
if fileIndex < 0 || files[fileIndex].FileEnd <= pos {
return nil
}
return files[fileIndex]
}
// findEnclosingNode finds the unique node in nodes, if any,
// that encloses the given position.
//

View file

@ -0,0 +1,283 @@
package collect
import (
_ "embed"
"go/ast"
"go/constant"
"go/token"
"go/types"
"slices"
"strings"
"sync"
"golang.org/x/tools/go/ast/astutil"
)
type (
// EventMap holds information about a set of custom events
// and their associated data types.
//
// Read accesses to any public field are only safe
// if a call to [EventMap.Collect] has completed before the access,
// for example by calling it in the accessing goroutine
// or before spawning the accessing goroutine.
EventMap struct {
Imports *ImportMap
Defs []*EventInfo
registerEvent types.Object
collector *Collector
once sync.Once
}
// EventInfo holds information about a single event definition.
EventInfo struct {
// Name is the name of the event.
Name string
// Data is the data type the event has been registered with.
// It may be nil in case of conflicting definitions.
Data types.Type
// Pos records the position
// of the first discovered definition for this event.
Pos token.Position
}
)
func newEventMap(collector *Collector, registerEvent types.Object) *EventMap {
return &EventMap{
registerEvent: registerEvent,
collector: collector,
}
}
// EventMap returns the unique event map associated with the given collector,
// or nil if event collection is disabled.
func (collector *Collector) EventMap() *EventMap {
return collector.events
}
// Stats returns statistics for this event map.
// It is an error to call stats before a call to [EventMap.Collect] has completed.
func (em *EventMap) Stats() *Stats {
return &Stats{
NumEvents: len(em.Defs),
}
}
// Collect gathers information for the event map described by its receiver.
// It can be called concurrently by multiple goroutines;
// the computation will be performed just once.
//
// Collect returns the receiver for chaining.
// It is safe to call Collect with nil receiver.
//
// After Collect returns, the calling goroutine and all goroutines
// it might spawn afterwards are free to access
// the receiver's fields indefinitely.
func (em *EventMap) Collect() *EventMap {
if em == nil {
return nil
}
em.once.Do(func() {
// XXX: initialise the import map with a fake package.
// At present this works fine; let's hope it doesn't come back and haunt us later.
em.Imports = NewImportMap(&PackageInfo{
Path: em.collector.systemPaths.InternalPackage,
collector: em.collector,
})
if em.registerEvent == nil {
return
}
var (
wg sync.WaitGroup
defs sync.Map
)
for pkg := range em.collector.Iterate {
if !pkg.IsOrImportsApp {
// Packages that are not, and do not import, the Wails application package
// cannot define any events.
continue
}
wg.Add(1)
// Process packages in parallel.
em.collector.scheduler.Schedule(func() {
em.collectEventsInPackage(pkg, &defs)
wg.Done()
})
}
wg.Wait()
// Collect valid events.
em.Defs = slices.Collect(func(yield func(*EventInfo) bool) {
for _, v := range defs.Range {
event := v.(*EventInfo)
if event.Data != nil && !IsParametric(event.Data) {
if !yield(event) {
break
}
}
}
})
// Sort by name, ascending.
slices.SortFunc(em.Defs, func(a, b *EventInfo) int {
return strings.Compare(a.Name, b.Name)
})
// Record required types.
// This must be done at the end because:
// - [ImportMap.AddType] does not support concurrent calls, and
// - we only know the set of valid events after inspecting all definitions.
for _, def := range em.Defs {
em.Imports.AddType(def.Data)
}
})
return em
}
func (em *EventMap) collectEventsInPackage(pkg *PackageInfo, defs *sync.Map) {
for ident, inst := range pkg.TypesInfo.Instances {
if pkg.TypesInfo.Uses[ident] != em.registerEvent {
continue
}
file := findEnclosingFile(pkg.Collect().Files, ident.Pos())
if file == nil {
em.collector.logger.Warningf(
"package %s: found event declaration with no associated source file",
pkg.Path,
)
continue
}
path, _ := astutil.PathEnclosingInterval(file, ident.Pos(), ident.End())
if path[0] != ident {
em.collector.logger.Warningf(
"%v: event declaration not found in source file",
pkg.Fset.Position(ident.Pos()),
)
continue
}
// Walk up the path: *ast.Ident -> *ast.SelectorExpr? -> (*ast.IndexExpr | *ast.IndexListExpr)? -> *ast.CallExpr?
path = path[1:]
if _, ok := path[0].(*ast.SelectorExpr); ok {
path = path[1:]
}
if _, ok := path[0].(*ast.IndexExpr); ok {
path = path[1:]
} else if _, ok := path[0].(*ast.IndexListExpr); ok {
path = path[1:]
}
call, ok := path[0].(*ast.CallExpr)
if !ok {
em.collector.logger.Warningf(
"%v: `application.RegisterEvent` is instantiated here but not called",
pkg.Fset.Position(path[0].Pos()),
)
em.collector.logger.Warningf("events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly")
continue
}
if len(call.Args) == 0 {
// Invalid calls result in compile-time failures and can be ignored safely.
continue
}
eventName, ok := pkg.TypesInfo.Types[call.Args[0]]
if !ok || !types.AssignableTo(eventName.Type, types.Universe.Lookup("string").Type()) {
// Mistyped calls result in compile-time failures and can be ignored safely.
continue
}
if eventName.Value == nil {
em.collector.logger.Warningf(
"%v: `application.RegisterEvent` called here with non-constant event name",
pkg.Fset.Position(call.Pos()),
)
em.collector.logger.Warningf("dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only")
continue
}
event := &EventInfo{
Data: inst.TypeArgs.At(0),
Pos: pkg.Fset.Position(call.Pos()),
}
if eventName.Value.Kind() == constant.String {
event.Name = constant.StringVal(eventName.Value)
} else {
event.Name = eventName.Value.ExactString()
}
if IsKnownEvent(event.Name) {
em.collector.logger.Warningf(
"%v: event '%s' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic",
event.Pos,
event.Name,
)
continue
}
if v, ok := defs.LoadOrStore(event.Name, event); ok {
prev := v.(*EventInfo)
if prev.Data != nil && !types.Identical(event.Data, prev.Data) {
next := &EventInfo{
Name: prev.Name,
Pos: prev.Pos,
}
if defs.CompareAndSwap(prev.Name, prev, next) {
em.collector.logger.Warningf("event '%s' has multiple conflicting definitions and will be ignored", event.Name)
em.collector.logger.Warningf(
"%v: event '%s' has one of multiple definitions here with data type %s",
prev.Pos,
prev.Name,
prev.Data,
)
}
prev = next
}
if prev.Data == nil {
em.collector.logger.Warningf(
"%v: event '%s' has one of multiple definitions here with data type %s",
event.Pos,
event.Name,
event.Data,
)
}
continue
}
// Emit unsupported type warnings only for first definition
if IsParametric(event.Data) {
em.collector.logger.Warningf(
"%v: data type %s for event '%s' contains unresolved type parameters and will be ignored`",
event.Pos,
event.Data,
event.Name,
)
em.collector.logger.Warningf("generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only")
} else if types.IsInterface(event.Data) && !types.Identical(event.Data.Underlying(), typeAny) && !em.collector.IsVoidAlias(event.Data) {
em.collector.logger.Warningf(
"%v: data type %s for event '%s' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors",
event.Pos,
event.Data,
event.Name,
)
}
}
}

View file

@ -147,6 +147,11 @@ func (imports *ImportMap) addTypeImpl(typ types.Type, visited *typeutil.Map) {
return
}
// Special case: application.Void will render as TS void hence no dependencies and no model
if collector.IsVoidAlias(obj) {
return
}
if obj.Pkg().Path() == imports.Self {
imports.ImportModels = true
}
@ -260,7 +265,7 @@ func (imports *ImportMap) addTypeImpl(typ types.Type, visited *typeutil.Map) {
return
default:
collector.logger.Warningf("package %s: unknown type %s: please report this to Wails maintainers", imports.Self, typ)
collector.logger.Warningf("package %s: unknown or unexpected type %s: please report this to Wails maintainers", imports.Self, typ)
return
}
}

View file

@ -0,0 +1,220 @@
package collect
func IsKnownEvent(name string) bool {
_, ok := knownEvents[name]
return ok
}
var knownEvents = map[string]struct{}{
"common:ApplicationOpenedWithFile": {},
"common:ApplicationStarted": {},
"common:ApplicationLaunchedWithUrl": {},
"common:ThemeChanged": {},
"common:WindowClosing": {},
"common:WindowDidMove": {},
"common:WindowDidResize": {},
"common:WindowDPIChanged": {},
"common:WindowFilesDropped": {},
"common:WindowFocus": {},
"common:WindowFullscreen": {},
"common:WindowHide": {},
"common:WindowLostFocus": {},
"common:WindowMaximise": {},
"common:WindowMinimise": {},
"common:WindowToggleFrameless": {},
"common:WindowRestore": {},
"common:WindowRuntimeReady": {},
"common:WindowShow": {},
"common:WindowUnFullscreen": {},
"common:WindowUnMaximise": {},
"common:WindowUnMinimise": {},
"common:WindowZoom": {},
"common:WindowZoomIn": {},
"common:WindowZoomOut": {},
"common:WindowZoomReset": {},
"common:WindowDropZoneFilesDropped": {},
"linux:ApplicationStartup": {},
"linux:SystemThemeChanged": {},
"linux:WindowDeleteEvent": {},
"linux:WindowDidMove": {},
"linux:WindowDidResize": {},
"linux:WindowFocusIn": {},
"linux:WindowFocusOut": {},
"linux:WindowLoadChanged": {},
"mac:ApplicationDidBecomeActive": {},
"mac:ApplicationDidChangeBackingProperties": {},
"mac:ApplicationDidChangeEffectiveAppearance": {},
"mac:ApplicationDidChangeIcon": {},
"mac:ApplicationDidChangeOcclusionState": {},
"mac:ApplicationDidChangeScreenParameters": {},
"mac:ApplicationDidChangeStatusBarFrame": {},
"mac:ApplicationDidChangeStatusBarOrientation": {},
"mac:ApplicationDidChangeTheme!": {},
"mac:ApplicationDidFinishLaunching": {},
"mac:ApplicationDidHide": {},
"mac:ApplicationDidResignActive": {},
"mac:ApplicationDidUnhide": {},
"mac:ApplicationDidUpdate": {},
"mac:ApplicationShouldHandleReopen!": {},
"mac:ApplicationWillBecomeActive": {},
"mac:ApplicationWillFinishLaunching": {},
"mac:ApplicationWillHide": {},
"mac:ApplicationWillResignActive": {},
"mac:ApplicationWillTerminate": {},
"mac:ApplicationWillUnhide": {},
"mac:ApplicationWillUpdate": {},
"mac:MenuDidAddItem": {},
"mac:MenuDidBeginTracking": {},
"mac:MenuDidClose": {},
"mac:MenuDidDisplayItem": {},
"mac:MenuDidEndTracking": {},
"mac:MenuDidHighlightItem": {},
"mac:MenuDidOpen": {},
"mac:MenuDidPopUp": {},
"mac:MenuDidRemoveItem": {},
"mac:MenuDidSendAction": {},
"mac:MenuDidSendActionToItem": {},
"mac:MenuDidUpdate": {},
"mac:MenuWillAddItem": {},
"mac:MenuWillBeginTracking": {},
"mac:MenuWillDisplayItem": {},
"mac:MenuWillEndTracking": {},
"mac:MenuWillHighlightItem": {},
"mac:MenuWillOpen": {},
"mac:MenuWillPopUp": {},
"mac:MenuWillRemoveItem": {},
"mac:MenuWillSendAction": {},
"mac:MenuWillSendActionToItem": {},
"mac:MenuWillUpdate": {},
"mac:WebViewDidCommitNavigation": {},
"mac:WebViewDidFinishNavigation": {},
"mac:WebViewDidReceiveServerRedirectForProvisionalNavigation": {},
"mac:WebViewDidStartProvisionalNavigation": {},
"mac:WindowDidBecomeKey": {},
"mac:WindowDidBecomeMain": {},
"mac:WindowDidBeginSheet": {},
"mac:WindowDidChangeAlpha": {},
"mac:WindowDidChangeBackingLocation": {},
"mac:WindowDidChangeBackingProperties": {},
"mac:WindowDidChangeCollectionBehavior": {},
"mac:WindowDidChangeEffectiveAppearance": {},
"mac:WindowDidChangeOcclusionState": {},
"mac:WindowDidChangeOrderingMode": {},
"mac:WindowDidChangeScreen": {},
"mac:WindowDidChangeScreenParameters": {},
"mac:WindowDidChangeScreenProfile": {},
"mac:WindowDidChangeScreenSpace": {},
"mac:WindowDidChangeScreenSpaceProperties": {},
"mac:WindowDidChangeSharingType": {},
"mac:WindowDidChangeSpace": {},
"mac:WindowDidChangeSpaceOrderingMode": {},
"mac:WindowDidChangeTitle": {},
"mac:WindowDidChangeToolbar": {},
"mac:WindowDidDeminiaturize": {},
"mac:WindowDidEndSheet": {},
"mac:WindowDidEnterFullScreen": {},
"mac:WindowDidEnterVersionBrowser": {},
"mac:WindowDidExitFullScreen": {},
"mac:WindowDidExitVersionBrowser": {},
"mac:WindowDidExpose": {},
"mac:WindowDidFocus": {},
"mac:WindowDidMiniaturize": {},
"mac:WindowDidMove": {},
"mac:WindowDidOrderOffScreen": {},
"mac:WindowDidOrderOnScreen": {},
"mac:WindowDidResignKey": {},
"mac:WindowDidResignMain": {},
"mac:WindowDidResize": {},
"mac:WindowDidUpdate": {},
"mac:WindowDidUpdateAlpha": {},
"mac:WindowDidUpdateCollectionBehavior": {},
"mac:WindowDidUpdateCollectionProperties": {},
"mac:WindowDidUpdateShadow": {},
"mac:WindowDidUpdateTitle": {},
"mac:WindowDidUpdateToolbar": {},
"mac:WindowDidZoom!": {},
"mac:WindowFileDraggingEntered": {},
"mac:WindowFileDraggingExited": {},
"mac:WindowFileDraggingPerformed": {},
"mac:WindowHide": {},
"mac:WindowMaximise!": {},
"mac:WindowUnMaximise!": {},
"mac:WindowMinimise!": {},
"mac:WindowUnMinimise!": {},
"mac:WindowShouldClose!": {},
"mac:WindowShow": {},
"mac:WindowWillBecomeKey": {},
"mac:WindowWillBecomeMain": {},
"mac:WindowWillBeginSheet": {},
"mac:WindowWillChangeOrderingMode": {},
"mac:WindowWillClose": {},
"mac:WindowWillDeminiaturize": {},
"mac:WindowWillEnterFullScreen": {},
"mac:WindowWillEnterVersionBrowser": {},
"mac:WindowWillExitFullScreen": {},
"mac:WindowWillExitVersionBrowser": {},
"mac:WindowWillFocus": {},
"mac:WindowWillMiniaturize": {},
"mac:WindowWillMove": {},
"mac:WindowWillOrderOffScreen": {},
"mac:WindowWillOrderOnScreen": {},
"mac:WindowWillResignMain": {},
"mac:WindowWillResize": {},
"mac:WindowWillUnfocus": {},
"mac:WindowWillUpdate": {},
"mac:WindowWillUpdateAlpha": {},
"mac:WindowWillUpdateCollectionBehavior": {},
"mac:WindowWillUpdateCollectionProperties": {},
"mac:WindowWillUpdateShadow": {},
"mac:WindowWillUpdateTitle": {},
"mac:WindowWillUpdateToolbar": {},
"mac:WindowWillUpdateVisibility": {},
"mac:WindowWillUseStandardFrame": {},
"mac:WindowZoomIn!": {},
"mac:WindowZoomOut!": {},
"mac:WindowZoomReset!": {},
"windows:APMPowerSettingChange": {},
"windows:APMPowerStatusChange": {},
"windows:APMResumeAutomatic": {},
"windows:APMResumeSuspend": {},
"windows:APMSuspend": {},
"windows:ApplicationStarted": {},
"windows:SystemThemeChanged": {},
"windows:WebViewNavigationCompleted": {},
"windows:WindowActive": {},
"windows:WindowBackgroundErase": {},
"windows:WindowClickActive": {},
"windows:WindowClosing": {},
"windows:WindowDidMove": {},
"windows:WindowDidResize": {},
"windows:WindowDPIChanged": {},
"windows:WindowDragDrop": {},
"windows:WindowDragEnter": {},
"windows:WindowDragLeave": {},
"windows:WindowDragOver": {},
"windows:WindowEndMove": {},
"windows:WindowEndResize": {},
"windows:WindowFullscreen": {},
"windows:WindowHide": {},
"windows:WindowInactive": {},
"windows:WindowKeyDown": {},
"windows:WindowKeyUp": {},
"windows:WindowKillFocus": {},
"windows:WindowNonClientHit": {},
"windows:WindowNonClientMouseDown": {},
"windows:WindowNonClientMouseLeave": {},
"windows:WindowNonClientMouseMove": {},
"windows:WindowNonClientMouseUp": {},
"windows:WindowPaint": {},
"windows:WindowRestore": {},
"windows:WindowSetFocus": {},
"windows:WindowShow": {},
"windows:WindowStartMove": {},
"windows:WindowStartResize": {},
"windows:WindowUnFullscreen": {},
"windows:WindowZOrderChanged": {},
"windows:WindowMinimise": {},
"windows:WindowUnMinimise": {},
"windows:WindowMaximise": {},
"windows:WindowUnMaximise": {},
}

View file

@ -16,7 +16,7 @@ import (
// PackageInfo records information about a package.
//
// Read accesses to fields Path, Name, Types, TypesInfo, Fset
// Read accesses to fields Path, Name, IsOrImportsApp, Types, TypesInfo, Fset,
// are safe at any time without any synchronisation.
//
// Read accesses to all other fields are only safe
@ -32,6 +32,9 @@ type PackageInfo struct {
// Name holds the import name of the described package.
Name string
// IsOrImportsApp is true if this package is, or depends upon, the Wails application package.
IsOrImportsApp bool
// Types and TypesInfo hold type information for this package.
Types *types.Package
TypesInfo *types.Info
@ -73,10 +76,14 @@ type PackageInfo struct {
}
func newPackageInfo(pkg *packages.Package, collector *Collector) *PackageInfo {
_, importsApp := pkg.Imports[collector.systemPaths.ApplicationPackage]
return &PackageInfo{
Path: pkg.PkgPath,
Name: pkg.Name,
IsOrImportsApp: importsApp || pkg.PkgPath == collector.systemPaths.ApplicationPackage,
Types: pkg.Types,
TypesInfo: pkg.TypesInfo,
@ -87,7 +94,7 @@ func newPackageInfo(pkg *packages.Package, collector *Collector) *PackageInfo {
}
}
// Package retrieves the the unique [PackageInfo] instance, if any,
// Package retrieves the unique [PackageInfo] instance, if any,
// associated to the given package object within a Collector.
//
// Package is safe for concurrent use.

View file

@ -476,3 +476,123 @@ func IsAny(typ types.Type) bool {
return true
}
// IsParametric returns true if the given type
// contains unresolved type parameters.
func IsParametric(typ types.Type) bool {
for { // Avoid recursion where possible
switch t := typ.(type) {
case *types.Alias, *types.Named:
tp := t.(interface{ TypeParams() *types.TypeParamList }).TypeParams()
ta := t.(interface{ TypeArgs() *types.TypeList }).TypeArgs()
if tp.Len() == 0 {
// Not a generic alias/named type.
return false
}
if ta.Len() == 0 {
// Uninstantiated generic.
return true
}
for i := range ta.Len() - 1 {
if IsParametric(ta.At(i)) {
return true
}
}
typ = ta.At(ta.Len() - 1)
case *types.Basic:
return false
case *types.Array, *types.Pointer, *types.Slice, *types.Chan:
typ = typ.(interface{ Elem() types.Type }).Elem()
case *types.Map:
if IsParametric(t.Key()) {
return true
}
typ = t.Elem()
case *types.Signature:
if IsParametric(t.Params()) {
return true
}
typ = t.Results()
case *types.Struct:
if t.NumFields() == 0 {
// No more subtypes to check.
return false
}
for i := range t.NumFields() - 1 {
if IsParametric(t.Field(i).Type()) {
return true
}
}
typ = t.Field(t.NumFields() - 1).Type()
case *types.Interface:
for m := range t.ExplicitMethods() {
if IsParametric(m.Type()) {
return true
}
}
if t.NumEmbeddeds() == 0 {
// No more subtypes to check.
return false
}
for i := range t.NumEmbeddeds() - 1 {
if IsParametric(t.EmbeddedType(i)) {
return true
}
}
typ = t.EmbeddedType(t.NumEmbeddeds() - 1)
case *types.Tuple:
if t.Len() == 0 {
// No more subtypes to check.
return false
}
for i := range t.Len() - 1 {
if IsParametric(t.At(i).Type()) {
return true
}
}
typ = t.At(t.Len() - 1).Type()
case *types.TypeParam:
return true
case *types.Union:
if t.Len() == 0 {
// No more subtypes to check.
return false
}
for i := range t.Len() - 1 {
if IsParametric(t.Term(i).Type()) {
return true
}
}
typ = t.Term(t.Len() - 1).Type()
default:
// Unknown new type.
// This is wrong but [ImportMap.AddType] will take care of reporting it eventually.
return false
}
}
}

View file

@ -282,14 +282,14 @@ func (info *ServiceInfo) collectMethod(method *types.Func) *ServiceMethodInfo {
}
}
if types.IsInterface(param.Type()) && !types.Identical(param.Type(), typeAny) {
if types.IsInterface(param.Type()) && !types.Identical(param.Type().Underlying(), typeAny) {
paramName := param.Name()
if paramName == "" || paramName == "_" {
paramName = fmt.Sprintf("#%d", i+1)
}
collector.logger.Warningf(
"%s: parameter %s has non-empty interface type %s: this is not supported by encoding/json and will likely result in runtime errors",
"%s: parameter %s has non-empty interface type %s: passing values other than `null` is not supported by encoding/json and will likely result in runtime errors",
collector.Package(method.Pkg()).Fset.Position(param.Pos()),
paramName,
param.Type(),

View file

@ -8,6 +8,7 @@ type Stats struct {
NumMethods int
NumEnums int
NumModels int
NumEvents int
StartTime time.Time
EndTime time.Time
}
@ -31,4 +32,5 @@ func (stats *Stats) Add(other *Stats) {
stats.NumMethods += other.NumMethods
stats.NumEnums += other.NumEnums
stats.NumModels += other.NumModels
stats.NumEvents += other.NumEvents
}

View file

@ -0,0 +1,32 @@
package collect
import (
"go/types"
"sync/atomic"
)
// appVoidType caches the application.Void named type that stands in for the void TS type.
var appVoidType atomic.Value
// IsVoidAlias returns true when the given type or object is the application.Void named type that stands in for the void TS type.
func (collector *Collector) IsVoidAlias(typOrObj any) bool {
var obj types.Object
switch to := typOrObj.(type) {
case types.Object:
obj = to
case interface{ Obj() *types.TypeName }:
obj = to.Obj()
default:
return false
}
if vt := appVoidType.Load(); obj == vt {
return true
} else if vt == nil && obj.Name() == "Void" && obj.Pkg().Path() == collector.systemPaths.ApplicationPackage { // Check name before package to fail fast
// Cache void alias for faster checking
appVoidType.Store(obj)
return true
}
return false
}

View file

@ -3,8 +3,12 @@ package config
// WailsAppPkgPath is the official import path of Wails v3's application package.
const WailsAppPkgPath = "github.com/wailsapp/wails/v3/pkg/application"
// WailsInternalPkgPath is the official import path of Wails v3's internal package.
const WailsInternalPkgPath = "github.com/wailsapp/wails/v3/internal"
// SystemPaths holds resolved paths of required system packages.
type SystemPaths struct {
ContextPackage string
ApplicationPackage string
InternalPackage string
}

View file

@ -20,6 +20,11 @@ var ErrNoContextPackage = errors.New("standard context package not found at cano
// did not match any actual package.
var ErrNoApplicationPackage = errors.New("Wails application package not found at canonical import path ('" + config.WailsAppPkgPath + "'): is the Wails v3 module properly installed? ")
// ErrNoInternalPackage indicates that
// the canonical path for the Wails internal package
// did not match any actual package.
var ErrNoInternalPackage = errors.New("Wails internal package not found at canonical import path ('" + config.WailsInternalPkgPath + "'): is the Wails v3 module properly installed? ")
// ErrBadApplicationPackage indicates that
// the Wails application package has invalid content.
var ErrBadApplicationPackage = errors.New("package " + config.WailsAppPkgPath + ": function NewService has wrong signature: is the Wails v3 module properly installed? ")

View file

@ -0,0 +1,53 @@
package generator
import (
"path/filepath"
"github.com/wailsapp/wails/v3/internal/generator/collect"
)
func (generator *Generator) generateEvents(events *collect.EventMap) {
// Generate event data table.
generator.scheduler.Schedule(func() {
file, err := generator.creator.Create(filepath.Join(events.Imports.Self, generator.renderer.EventDataFile()))
if err != nil {
generator.logger.Errorf("%v", err)
generator.logger.Errorf("event data table generation failed")
return
}
defer func() {
if err := file.Close(); err != nil {
generator.logger.Errorf("%v", err)
generator.logger.Errorf("event data table generation failed")
}
}()
err = generator.renderer.EventData(file, events)
if err != nil {
generator.logger.Errorf("%v", err)
generator.logger.Errorf("event data table generation failed")
}
})
// Generate event creation code.
generator.scheduler.Schedule(func() {
file, err := generator.creator.Create(filepath.Join(events.Imports.Self, generator.renderer.EventCreateFile()))
if err != nil {
generator.logger.Errorf("%v", err)
generator.logger.Errorf("event creation code generation failed")
return
}
defer func() {
if err := file.Close(); err != nil {
generator.logger.Errorf("%v", err)
generator.logger.Errorf("event creation code generation failed")
}
}()
err = generator.renderer.EventCreate(file, events)
if err != nil {
generator.logger.Errorf("%v", err)
generator.logger.Errorf("event creation code generation failed")
}
})
}

View file

@ -132,16 +132,20 @@ func (generator *Generator) Generate(patterns ...string) (stats *collect.Stats,
panic("Generate() must not be called more than once on the same receiver")
}
// Initialise subcomponents.
generator.collector = collect.NewCollector(pkgs, systemPaths, generator.options, &generator.scheduler, generator.logger)
generator.renderer = render.NewRenderer(generator.options, generator.collector)
// Update status.
generator.logger.Statusf("Looking for services...")
serviceFound := sync.OnceFunc(func() { generator.logger.Statusf("Generating service bindings...") })
if generator.options.NoEvents {
generator.logger.Statusf("Looking for services...")
} else {
generator.logger.Statusf("Looking for services and events...")
}
serviceOrEventFound := sync.OnceFunc(func() { generator.logger.Statusf("Generating bindings...") })
// Run static analysis.
services, err := FindServices(pkgs, systemPaths, generator.logger)
services, registerEvent, err := FindServices(pkgs, systemPaths, generator.logger)
// Initialise subcomponents.
generator.collector = collect.NewCollector(pkgs, registerEvent, systemPaths, generator.options, &generator.scheduler, generator.logger)
generator.renderer = render.NewRenderer(generator.collector, generator.options)
// Check for analyser errors.
if err != nil {
@ -151,9 +155,23 @@ func (generator *Generator) Generate(patterns ...string) (stats *collect.Stats,
// Discard unneeded data.
pkgs = nil
// Schedule collection and code generation for event data types.
if !generator.options.NoEvents {
generator.scheduler.Schedule(func() {
events := generator.collector.EventMap().Collect()
if len(events.Defs) > 0 {
serviceOrEventFound()
// Not a data race because we wait for this scheduled task
// to complete before accessing stats again.
stats.Add(events.Stats())
}
generator.generateEvents(events)
})
}
// Schedule code generation for each found service.
for obj := range services {
serviceFound()
serviceOrEventFound()
generator.scheduler.Schedule(func() {
generator.generateService(obj)
})
@ -175,9 +193,9 @@ func (generator *Generator) Generate(patterns ...string) (stats *collect.Stats,
}
// Schedule models, index and included files generation for each package.
for info := range generator.collector.Iterate {
for pkg := range generator.collector.Iterate {
generator.scheduler.Schedule(func() {
generator.generateModelsIndexIncludes(info)
generator.generateModelsIndexIncludes(pkg)
})
}
@ -201,15 +219,15 @@ func (generator *Generator) Generate(patterns ...string) (stats *collect.Stats,
// generateModelsIndexIncludes schedules generation of public/private model files,
// included files and, if allowed by the options,
// of an index file for the given package.
func (generator *Generator) generateModelsIndexIncludes(info *collect.PackageInfo) {
index := info.Index(generator.options.TS)
func (generator *Generator) generateModelsIndexIncludes(pkg *collect.PackageInfo) {
index := pkg.Index(generator.options.TS)
// info.Index implies info.Collect: goroutines spawned below
// can access package information freely.
if len(index.Models) > 0 {
generator.scheduler.Schedule(func() {
generator.generateModels(info, index.Models)
generator.generateModels(pkg, index.Models)
})
}

View file

@ -3,7 +3,6 @@ package generator
import (
"errors"
"fmt"
"github.com/wailsapp/wails/v3/internal/generator/render"
"io"
"io/fs"
"os"
@ -17,6 +16,7 @@ import (
"github.com/pterm/pterm"
"github.com/wailsapp/wails/v3/internal/flags"
"github.com/wailsapp/wails/v3/internal/generator/config"
"github.com/wailsapp/wails/v3/internal/generator/render"
)
const testcases = "github.com/wailsapp/wails/v3/internal/generator/testcases/..."
@ -73,7 +73,7 @@ func TestGenerator(t *testing.T) {
}
// Skip got files.
if strings.HasSuffix(d.Name(), ".got.js") || strings.HasSuffix(d.Name(), ".got.ts") {
if strings.HasSuffix(d.Name(), ".got.js") || strings.HasSuffix(d.Name(), ".got.ts") || strings.HasSuffix(d.Name(), ".got.log") {
return nil
}
@ -116,6 +116,35 @@ func TestGenerator(t *testing.T) {
warnings := report.Warnings()
slices.Sort(warnings)
// Normalize paths in warnings to be relative to the testcases directory
// This ensures consistent output across different development environments and CI
for i, msg := range warnings {
// Handle both Unix and Windows path separators
msg = strings.ReplaceAll(msg, "\\", "/")
// Check if this is a file path (contains line:column position)
// File paths look like: /path/to/file.go:123:45: message
// Package paths look like: package github.com/...: message
if strings.HasPrefix(msg, "package ") {
// Keep package warnings as-is
warnings[i] = msg
} else if idx := strings.Index(msg, "testcases/"); idx >= 0 {
// Check if it's a file path by looking for :line:column pattern after testcases/
testcasesEnd := idx + len("testcases/")
colonIdx := strings.Index(msg[testcasesEnd:], ":")
if colonIdx > 0 {
// This looks like a file path, normalize it
warnings[i] = "/testcases/" + msg[testcasesEnd:]
} else {
// Not a file path, keep as-is
warnings[i] = msg
}
} else {
// Keep other warnings as-is
warnings[i] = msg
}
}
for _, msg := range warnings {
fmt.Fprint(log, msg, render.Newline)
}

View file

@ -9,7 +9,7 @@ import (
"golang.org/x/tools/go/packages"
)
// ResolveSystemPaths resolves paths for the Wails system
// ResolveSystemPaths resolves paths for stdlib and Wails packages.
func ResolveSystemPaths(buildFlags []string) (paths *config.SystemPaths, err error) {
// Resolve context pkg path.
contextPkgPaths, err := ResolvePatterns(buildFlags, "context")
@ -35,9 +35,22 @@ func ResolveSystemPaths(buildFlags []string) (paths *config.SystemPaths, err err
panic("wails application package path matched multiple packages")
}
// Resolve wails internal pkg path.
wailsInternalPkgPaths, err := ResolvePatterns(buildFlags, config.WailsInternalPkgPath)
if err != nil {
return
} else if len(wailsInternalPkgPaths) < 1 {
err = ErrNoInternalPackage
return
} else if len(wailsInternalPkgPaths) > 1 {
// This should never happen...
panic("wails internal package path matched multiple packages")
}
paths = &config.SystemPaths{
ContextPackage: contextPkgPaths[0],
ApplicationPackage: wailsAppPkgPaths[0],
InternalPackage: wailsInternalPkgPaths[0],
}
return
}

View file

@ -30,6 +30,10 @@ func (m *module) NeedsCreate(typ types.Type) bool {
func (m *module) needsCreateImpl(typ types.Type, visited *typeutil.Map) bool {
switch t := typ.(type) {
case *types.Alias:
if m.collector.IsVoidAlias(t.Obj()) {
return false
}
return m.needsCreateImpl(types.Unalias(typ), visited)
case *types.Named:
@ -46,6 +50,10 @@ func (m *module) needsCreateImpl(typ types.Type, visited *typeutil.Map) bool {
return m.needsCreateImpl(t.Underlying(), visited)
}
if m.collector.IsVoidAlias(t.Obj()) {
return false
}
if collect.IsAny(typ) || collect.IsStringAlias(typ) {
break
} else if collect.IsClass(typ) {
@ -97,13 +105,17 @@ func (m *module) JSCreate(typ types.Type) string {
// JSCreateWithParams's output may be incorrect
// if m.Imports.AddType has not been called for the given type.
func (m *module) JSCreateWithParams(typ types.Type, params string) string {
if len(params) > 0 && !m.hasTypeParams(typ) {
if len(params) > 0 && !collect.IsParametric(typ) {
// Forget params for non-generic types.
params = ""
}
switch t := typ.(type) {
case *types.Alias:
if m.collector.IsVoidAlias(t.Obj()) {
return "$Create.Any"
}
return m.JSCreateWithParams(types.Unalias(typ), params)
case *types.Array, *types.Pointer:
@ -135,6 +147,10 @@ func (m *module) JSCreateWithParams(typ types.Type, params string) string {
return m.JSCreateWithParams(t.Underlying(), params)
}
if m.collector.IsVoidAlias(t.Obj()) {
return "$Create.Any"
}
if !m.NeedsCreate(typ) {
break
}
@ -341,51 +357,3 @@ type postponed struct {
index int
params string
}
// hasTypeParams returns true if the given type depends upon type parameters.
func (m *module) hasTypeParams(typ types.Type) bool {
switch t := typ.(type) {
case *types.Alias:
if t.Obj().Pkg() == nil {
// Builtin alias: these are never rendered as templates.
return false
}
return m.hasTypeParams(types.Unalias(typ))
case *types.Array, *types.Pointer, *types.Slice:
return m.hasTypeParams(typ.(interface{ Elem() types.Type }).Elem())
case *types.Map:
return m.hasTypeParams(t.Key()) || m.hasTypeParams(t.Elem())
case *types.Named:
if t.Obj().Pkg() == nil {
// Builtin named type: these are never rendered as templates.
return false
}
if targs := t.TypeArgs(); targs != nil {
for i := range targs.Len() {
if m.hasTypeParams(targs.At(i)) {
return true
}
}
}
case *types.Struct:
info := m.collector.Struct(t)
info.Collect()
for _, field := range info.Fields {
if m.hasTypeParams(field.Type) {
return true
}
}
case *types.TypeParam:
return true
}
return false
}

View file

@ -14,31 +14,31 @@ import (
// Renderer holds the template set for a given configuration.
// It provides methods for rendering various output modules.
type Renderer struct {
options *flags.GenerateBindingsOptions
collector *collect.Collector
options *flags.GenerateBindingsOptions
ext string
service *template.Template
typedefs *template.Template
service *template.Template
models *template.Template
}
// NewRenderer initialises a code renderer
// for the given configuration and data collector.
func NewRenderer(options *flags.GenerateBindingsOptions, collector *collect.Collector) *Renderer {
func NewRenderer(collector *collect.Collector, options *flags.GenerateBindingsOptions) *Renderer {
ext := ".js"
if options.TS {
ext = ".ts"
}
return &Renderer{
options: options,
collector: collector,
options: options,
ext: ext,
service: tmplService[tmplLanguage(options.TS)],
typedefs: tmplModels[tmplLanguage(options.TS)],
service: tmplService[tmplLanguage(options.TS)],
models: tmplModels[tmplLanguage(options.TS)],
}
}
@ -54,6 +54,18 @@ func (renderer *Renderer) ModelsFile() string {
return renderer.options.ModelsFilename + renderer.ext
}
// EventDataFile returns the standard name of the event data definitions file
// with the appropriate extension.
func (renderer *Renderer) EventDataFile() string {
return "eventdata.d.ts"
}
// EventCreateFile returns the standard name of the event data creation file
// with the appropriate extension.
func (renderer *Renderer) EventCreateFile() string {
return "eventcreate" + renderer.ext
}
// IndexFile returns the standard name of a package index file
// with the appropriate extension.
func (renderer *Renderer) IndexFile() string {
@ -75,7 +87,7 @@ func (renderer *Renderer) Service(w io.Writer, info *collect.ServiceInfo) error
})
}
// Typedefs renders type definitions for the given list of models.
// Models renders type definitions for the given list of models.
func (renderer *Renderer) Models(w io.Writer, imports *collect.ImportMap, models []*collect.ModelInfo) error {
if !renderer.options.UseInterfaces {
// Sort class aliases after the class they alias.
@ -114,7 +126,7 @@ func (renderer *Renderer) Models(w io.Writer, imports *collect.ImportMap, models
}
}
return renderer.typedefs.Execute(w, &struct {
return renderer.models.Execute(w, &struct {
module
Models []*collect.ModelInfo
}{
@ -127,6 +139,36 @@ func (renderer *Renderer) Models(w io.Writer, imports *collect.ImportMap, models
})
}
// EventData renders the given event map to w as an event data table.
func (renderer *Renderer) EventData(w io.Writer, events *collect.EventMap) error {
return tmplEventData.Execute(w, &struct {
module
Events *collect.EventMap
}{
module{
Renderer: renderer,
GenerateBindingsOptions: renderer.options,
Imports: events.Imports,
},
events,
})
}
// EventCreate renders the given event map to w as event data creation code.
func (renderer *Renderer) EventCreate(w io.Writer, events *collect.EventMap) error {
return tmplEventCreate.Execute(w, &struct {
module
Events *collect.EventMap
}{
module{
Renderer: renderer,
GenerateBindingsOptions: renderer.options,
Imports: events.Imports,
},
events,
})
}
// Index renders the given package index to w.
func (renderer *Renderer) Index(w io.Writer, index *collect.PackageIndex) error {
return tmplIndex.Execute(w, &struct {

View file

@ -23,6 +23,9 @@ var tmplModels = map[tmplLanguage]*template.Template{
tmplTS: template.Must(template.New("models.ts.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/models.ts.tmpl")),
}
var tmplEventData = template.Must(template.New("eventdata.d.ts.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/eventdata.d.ts.tmpl"))
var tmplEventCreate = template.Must(template.New("eventcreate.js.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/eventcreate.js.tmpl"))
var tmplIndex = template.Must(template.New("index.tmpl").Funcs(tmplFunctions).ParseFS(templates, "templates/index.tmpl"))
var Newline string

View file

@ -0,0 +1,47 @@
{{$module := .}}
{{- $runtime := $module.Runtime}}
{{- $models := (fixext $module.ModelsFile)}}
{{- $useInterfaces := $module.UseInterfaces}}
{{- $imports := $module.Imports}}
{{- with .Events -}}
//@ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "{{js $runtime}}";
{{if (and (not $useInterfaces) .Defs)}}
{{- range $imports.External}}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}";
{{- end}}{{if $imports.External}}
{{end}}
{{- if $imports.ImportModels}}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./{{js $models}}";
{{end}}
function configure() {
Object.freeze(Object.assign($Create.Events, {
{{- range .Defs}}
{{- $create := ($module.JSCreate .Data)}}
{{- if ne $create "$Create.Any"}}
"{{js .Name}}": {{$create}},
{{- end}}
{{- end}}
}));
}
{{$postponed := $module.PostponedCreates}}
{{- if $postponed}}
// Private type creation functions
{{- range $i, $create := $postponed}}
{{if and (ge (len $create) 54) (eq (slice $create 39 54) "function $$init")}}var {{else}}const {{end -}}
$$createType{{$i}} = {{$create}};
{{- end}}
{{end}}
configure();
{{else}}
Object.freeze($Create.Events);
{{end}}{{end -}}

View file

@ -0,0 +1,32 @@
{{$module := .}}
{{- $runtime := $module.Runtime}}
{{- $models := (fixext $module.ModelsFile)}}
{{- $imports := $module.Imports}}
{{- with .Events -}}
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
{{if .Defs}}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type { Events } from "{{js $runtime}}";
{{range $imports.External}}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as {{jsimport .}} from "{{js .RelPath}}/{{js $models}}";
{{- end}}{{if $imports.External}}
{{end}}
{{- if $imports.ImportModels}}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as $models from "./{{js $models}}";
{{end}}
declare module "{{js $runtime}}" {
namespace Events {
interface CustomEvents {
{{- range .Defs}}
"{{js .Name}}": {{$module.JSType .Data}};
{{- end}}
}
}
}
{{end}}{{end -}}

View file

@ -176,6 +176,11 @@ func (m *module) renderNamedType(typ aliasOrNamed, quoted bool) (result string,
return m.renderType(typ.Underlying(), quoted)
}
// Special case: application.Void renders as TS void
if m.collector.IsVoidAlias(typ.Obj()) {
return "void", false
}
if quoted {
switch a := types.Unalias(typ).(type) {
case *types.Basic:

View file

@ -0,0 +1 @@
true

View file

@ -122,3 +122,8 @@ func main() {
}
}
func init() {
application.RegisterEvent[map[string]int]("collision")
application.RegisterEvent[*struct{ Field []bool }]("overlap")
}

View file

@ -0,0 +1,26 @@
package events_only
import (
"fmt"
nobindingshere "github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here"
"github.com/wailsapp/wails/v3/pkg/application"
)
// SomeClass renders as a TS class.
type SomeClass struct {
Field string
Meadow nobindingshere.HowDifferent[rune]
}
func init() {
application.RegisterEvent[string]("events_only:string")
application.RegisterEvent[map[string][]int]("events_only:map")
application.RegisterEvent[SomeClass]("events_only:class")
application.RegisterEvent[int]("collision")
application.RegisterEvent[bool](fmt.Sprintf("events_only:%s%d", "dynamic", 3))
}
func init() {
application.RegisterEvent[application.Void]("events_only:nodata")
}

View file

@ -0,0 +1 @@
true

View file

@ -0,0 +1,26 @@
package events_only
import (
"github.com/wailsapp/wails/v3/internal/generator/testcases/no_bindings_here/more"
"github.com/wailsapp/wails/v3/pkg/application"
)
const eventPrefix = "events_only" + `:`
var registerStringEvent = application.RegisterEvent[string]
func registerIntEvent(name string) {
application.RegisterEvent[int](name)
}
func registerSliceEvent[T any]() {
application.RegisterEvent[[]T]("parametric")
}
func init() {
application.RegisterEvent[[]more.StringPtr](eventPrefix + "other")
application.RegisterEvent[string]("common:ApplicationStarted")
registerStringEvent("indirect_var")
registerIntEvent("indirect_fn")
registerSliceEvent[uintptr]()
}

View file

@ -0,0 +1 @@
true

View file

@ -207,3 +207,9 @@ func main() {
}
}
func init() {
application.RegisterEvent[*struct{ Field []bool }]("collision")
application.RegisterEvent[*struct{ Field []bool }]("overlap")
application.RegisterEvent[json.Marshaler]("interface")
}

View file

@ -0,0 +1,4 @@
package more
// StringPtr is a nullable string.
type StringPtr *string

View file

@ -0,0 +1,39 @@
//@ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
function configure() {
Object.freeze(Object.assign($Create.Events, {
"events_only:class": $$createType0,
"events_only:map": $$createType2,
"events_only:other": $$createType3,
"overlap": $$createType6,
}));
}
// Private type creation functions
const $$createType0 = events_only$0.SomeClass.createFrom;
const $$createType1 = $Create.Array($Create.Any);
const $$createType2 = $Create.Map($Create.Any, $$createType1);
const $$createType3 = $Create.Array($Create.Any);
const $$createType4 = $Create.Array($Create.Any);
const $$createType5 = $Create.Struct({
"Field": $$createType4,
});
const $$createType6 = $Create.Nullable($$createType5);
configure();

View file

@ -0,0 +1,30 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type { Events } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
declare module "/wails/runtime.js" {
namespace Events {
interface CustomEvents {
"events_only:class": events_only$0.SomeClass;
"events_only:map": { [_: string]: number[] };
"events_only:nodata": void;
"events_only:other": more$0.StringPtr[];
"events_only:string": string;
"interface": json$0.Marshaler;
"overlap": {"Field": boolean[]} | null;
}
}
}

View file

@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export {
SomeClass
} from "./models.js";

View file

@ -0,0 +1,56 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as nobindingshere$0 from "../no_bindings_here/models.js";
/**
* SomeClass renders as a TS class.
*/
export class SomeClass {
/**
* Creates a new SomeClass instance.
* @param {Partial<SomeClass>} [$$source = {}] - The source object to create the SomeClass.
*/
constructor($$source = {}) {
if (!("Field" in $$source)) {
/**
* @member
* @type {string}
*/
this["Field"] = "";
}
if (!("Meadow" in $$source)) {
/**
* @member
* @type {nobindingshere$0.HowDifferent<number>}
*/
this["Meadow"] = (new nobindingshere$0.HowDifferent());
}
Object.assign(this, $$source);
}
/**
* Creates a new SomeClass instance from a string or object.
* @param {any} [$$source = {}]
* @returns {SomeClass}
*/
static createFrom($$source = {}) {
const $$createField1_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("Meadow" in $$parsedSource) {
$$parsedSource["Meadow"] = $$createField1_0($$parsedSource["Meadow"]);
}
return new SomeClass(/** @type {Partial<SomeClass>} */($$parsedSource));
}
}
// Private type creation functions
const $$createType0 = nobindingshere$0.HowDifferent.createFrom($Create.Any);

View file

@ -0,0 +1,10 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* StringPtr is a nullable string.
* @typedef {$models.StringPtr} StringPtr
*/

View file

@ -0,0 +1,12 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
/**
* StringPtr is a nullable string.
* @typedef {string | null} StringPtr
*/

View file

@ -1,3 +1,16 @@
/testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int
/testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int
/testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called
/testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored`
/testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic
/testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool}
/testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only
event 'collision' has multiple conflicting definitions and will be ignored
events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly
generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors

View file

@ -0,0 +1,42 @@
//@ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../pkg/application/models.js";
function configure() {
Object.freeze(Object.assign($Create.Events, {
"events_only:class": $$createType0,
"events_only:map": $$createType2,
"events_only:other": $$createType3,
"overlap": $$createType6,
}));
}
// Private type creation functions
const $$createType0 = events_only$0.SomeClass.createFrom;
const $$createType1 = $Create.Array($Create.Any);
const $$createType2 = $Create.Map($Create.Any, $$createType1);
const $$createType3 = $Create.Array($Create.Any);
const $$createType4 = $Create.Array($Create.Any);
const $$createType5 = $Create.Struct({
"Field": $$createType4,
});
const $$createType6 = $Create.Nullable($$createType5);
configure();

View file

@ -0,0 +1,33 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type { Events } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as application$0 from "../pkg/application/models.js";
declare module "/wails/runtime.js" {
namespace Events {
interface CustomEvents {
"events_only:class": events_only$0.SomeClass;
"events_only:map": { [_: string]: number[] };
"events_only:nodata": application$0.Void;
"events_only:other": more$0.StringPtr[];
"events_only:string": string;
"interface": json$0.Marshaler;
"overlap": {"Field": boolean[]} | null;
}
}
}

View file

@ -0,0 +1,7 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export {
SomeClass
} from "./models.js";

View file

@ -0,0 +1,56 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as nobindingshere$0 from "../no_bindings_here/models.js";
/**
* SomeClass renders as a TS class.
*/
export class SomeClass {
/**
* Creates a new SomeClass instance.
* @param {Partial<SomeClass>} [$$source = {}] - The source object to create the SomeClass.
*/
constructor($$source = {}) {
if (!("Field" in $$source)) {
/**
* @member
* @type {string}
*/
this["Field"] = "";
}
if (!("Meadow" in $$source)) {
/**
* @member
* @type {nobindingshere$0.HowDifferent<number>}
*/
this["Meadow"] = (new nobindingshere$0.HowDifferent());
}
Object.assign(this, $$source);
}
/**
* Creates a new SomeClass instance from a string or object.
* @param {any} [$$source = {}]
* @returns {SomeClass}
*/
static createFrom($$source = {}) {
const $$createField1_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("Meadow" in $$parsedSource) {
$$parsedSource["Meadow"] = $$createField1_0($$parsedSource["Meadow"]);
}
return new SomeClass(/** @type {Partial<SomeClass>} */($$parsedSource));
}
}
// Private type creation functions
const $$createType0 = nobindingshere$0.HowDifferent.createFrom($Create.Any);

View file

@ -0,0 +1,10 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* StringPtr is a nullable string.
* @typedef {$models.StringPtr} StringPtr
*/

View file

@ -0,0 +1,12 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
/**
* StringPtr is a nullable string.
* @typedef {string | null} StringPtr
*/

View file

@ -0,0 +1,11 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* Void will be translated by the binding generator to the TypeScript type 'void'.
* It can be used as an event data type to register events that must not have any associated data.
* @typedef {$models.Void} Void
*/

View file

@ -0,0 +1,13 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
/**
* Void will be translated by the binding generator to the TypeScript type 'void'.
* It can be used as an event data type to register events that must not have any associated data.
* @typedef {any} Void
*/

View file

@ -1,3 +1,17 @@
/testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int
/testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int
/testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/events.go:25:2: data type github.com/wailsapp/wails/v3/pkg/application.Void for event 'events_only:nodata' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
/testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called
/testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored`
/testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic
/testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool}
/testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only
event 'collision' has multiple conflicting definitions and will be ignored
events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly
generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors

View file

@ -0,0 +1,9 @@
//@ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
Object.freeze($Create.Events);

View file

@ -0,0 +1,33 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type { Events } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as application$0 from "../pkg/application/models.js";
declare module "/wails/runtime.js" {
namespace Events {
interface CustomEvents {
"events_only:class": events_only$0.SomeClass;
"events_only:map": { [_: string]: number[] | null } | null;
"events_only:nodata": application$0.Void;
"events_only:other": more$0.StringPtr[] | null;
"events_only:string": string;
"interface": json$0.Marshaler;
"overlap": {"Field": boolean[] | null} | null;
}
}
}

View file

@ -0,0 +1,10 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* SomeClass renders as a TS class.
* @typedef {$models.SomeClass} SomeClass
*/

View file

@ -0,0 +1,18 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as nobindingshere$0 from "../no_bindings_here/models.js";
/**
* SomeClass renders as a TS class.
* @typedef {Object} SomeClass
* @property {string} Field
* @property {nobindingshere$0.HowDifferent<number>} Meadow
*/
// In interface mode, this file is likely to contain just comments.
// We add a dummy export statement to ensure it is recognised as an ES module.
export {};

View file

@ -0,0 +1,10 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* StringPtr is a nullable string.
* @typedef {$models.StringPtr} StringPtr
*/

View file

@ -0,0 +1,12 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* StringPtr is a nullable string.
* @typedef {string | null} StringPtr
*/
// In interface mode, this file is likely to contain just comments.
// We add a dummy export statement to ensure it is recognised as an ES module.
export {};

View file

@ -0,0 +1,11 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* Void will be translated by the binding generator to the TypeScript type 'void'.
* It can be used as an event data type to register events that must not have any associated data.
* @typedef {$models.Void} Void
*/

View file

@ -0,0 +1,13 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* Void will be translated by the binding generator to the TypeScript type 'void'.
* It can be used as an event data type to register events that must not have any associated data.
* @typedef {any} Void
*/
// In interface mode, this file is likely to contain just comments.
// We add a dummy export statement to ensure it is recognised as an ES module.
export {};

View file

@ -1,3 +1,17 @@
/testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int
/testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int
/testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/events.go:25:2: data type github.com/wailsapp/wails/v3/pkg/application.Void for event 'events_only:nodata' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
/testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called
/testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored`
/testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic
/testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool}
/testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only
event 'collision' has multiple conflicting definitions and will be ignored
events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly
generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors

View file

@ -0,0 +1,9 @@
//@ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
Object.freeze($Create.Events);

View file

@ -0,0 +1,33 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type { Events } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as application$0 from "../pkg/application/models.js";
declare module "/wails/runtime.js" {
namespace Events {
interface CustomEvents {
"events_only:class": events_only$0.SomeClass;
"events_only:map": { [_: string]: number[] | null } | null;
"events_only:nodata": application$0.Void;
"events_only:other": more$0.StringPtr[] | null;
"events_only:string": string;
"interface": json$0.Marshaler;
"overlap": {"Field": boolean[] | null} | null;
}
}
}

View file

@ -0,0 +1,10 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* SomeClass renders as a TS class.
* @typedef {$models.SomeClass} SomeClass
*/

View file

@ -0,0 +1,18 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as nobindingshere$0 from "../no_bindings_here/models.js";
/**
* SomeClass renders as a TS class.
* @typedef {Object} SomeClass
* @property {string} Field
* @property {nobindingshere$0.HowDifferent<number>} Meadow
*/
// In interface mode, this file is likely to contain just comments.
// We add a dummy export statement to ensure it is recognised as an ES module.
export {};

View file

@ -0,0 +1,10 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* StringPtr is a nullable string.
* @typedef {$models.StringPtr} StringPtr
*/

View file

@ -0,0 +1,12 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* StringPtr is a nullable string.
* @typedef {string | null} StringPtr
*/
// In interface mode, this file is likely to contain just comments.
// We add a dummy export statement to ensure it is recognised as an ES module.
export {};

View file

@ -0,0 +1,11 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as $models from "./models.js";
/**
* Void will be translated by the binding generator to the TypeScript type 'void'.
* It can be used as an event data type to register events that must not have any associated data.
* @typedef {$models.Void} Void
*/

View file

@ -0,0 +1,13 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
/**
* Void will be translated by the binding generator to the TypeScript type 'void'.
* It can be used as an event data type to register events that must not have any associated data.
* @typedef {any} Void
*/
// In interface mode, this file is likely to contain just comments.
// We add a dummy export statement to ensure it is recognised as an ES module.
export {};

View file

@ -1,3 +1,17 @@
/testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int
/testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int
/testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/events.go:25:2: data type github.com/wailsapp/wails/v3/pkg/application.Void for event 'events_only:nodata' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
/testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called
/testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored`
/testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic
/testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool}
/testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only
event 'collision' has multiple conflicting definitions and will be ignored
events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly
generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors

View file

@ -0,0 +1,42 @@
//@ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../pkg/application/models.js";
function configure() {
Object.freeze(Object.assign($Create.Events, {
"events_only:class": $$createType0,
"events_only:map": $$createType2,
"events_only:other": $$createType3,
"overlap": $$createType6,
}));
}
// Private type creation functions
const $$createType0 = events_only$0.SomeClass.createFrom;
const $$createType1 = $Create.Array($Create.Any);
const $$createType2 = $Create.Map($Create.Any, $$createType1);
const $$createType3 = $Create.Array($Create.Any);
const $$createType4 = $Create.Array($Create.Any);
const $$createType5 = $Create.Struct({
"Field": $$createType4,
});
const $$createType6 = $Create.Nullable($$createType5);
configure();

View file

@ -0,0 +1,33 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type { Events } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as application$0 from "../pkg/application/models.js";
declare module "/wails/runtime.js" {
namespace Events {
interface CustomEvents {
"events_only:class": events_only$0.SomeClass;
"events_only:map": { [_: string]: number[] };
"events_only:nodata": application$0.Void;
"events_only:other": more$0.StringPtr[];
"events_only:string": string;
"interface": json$0.Marshaler;
"overlap": {"Field": boolean[]} | null;
}
}
}

View file

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export {
SomeClass
} from "./models.js";

View file

@ -0,0 +1,45 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as nobindingshere$0 from "../no_bindings_here/models.js";
/**
* SomeClass renders as a TS class.
*/
export class SomeClass {
"Field": string;
"Meadow": nobindingshere$0.HowDifferent<number>;
/** Creates a new SomeClass instance. */
constructor($$source: Partial<SomeClass> = {}) {
if (!("Field" in $$source)) {
this["Field"] = "";
}
if (!("Meadow" in $$source)) {
this["Meadow"] = (new nobindingshere$0.HowDifferent());
}
Object.assign(this, $$source);
}
/**
* Creates a new SomeClass instance from a string or object.
*/
static createFrom($$source: any = {}): SomeClass {
const $$createField1_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("Meadow" in $$parsedSource) {
$$parsedSource["Meadow"] = $$createField1_0($$parsedSource["Meadow"]);
}
return new SomeClass($$parsedSource as Partial<SomeClass>);
}
}
// Private type creation functions
const $$createType0 = nobindingshere$0.HowDifferent.createFrom($Create.Any);

View file

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export type {
StringPtr
} from "./models.js";

View file

@ -0,0 +1,11 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
/**
* StringPtr is a nullable string.
*/
export type StringPtr = string | null;

View file

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export type {
Void
} from "./models.js";

View file

@ -0,0 +1,12 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
/**
* Void will be translated by the binding generator to the TypeScript type 'void'.
* It can be used as an event data type to register events that must not have any associated data.
*/
export type Void = any;

View file

@ -1,3 +1,17 @@
/testcases/complex_json/main.go:127:2: event 'collision' has one of multiple definitions here with data type map[string]int
/testcases/events_only/events.go:20:2: event 'collision' has one of multiple definitions here with data type int
/testcases/events_only/events.go:21:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/events.go:25:2: data type github.com/wailsapp/wails/v3/pkg/application.Void for event 'events_only:nodata' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
/testcases/events_only/other.go:10:5: `application.RegisterEvent` is instantiated here but not called
/testcases/events_only/other.go:13:2: `application.RegisterEvent` called here with non-constant event name
/testcases/events_only/other.go:17:2: data type []T for event 'parametric' contains unresolved type parameters and will be ignored`
/testcases/events_only/other.go:22:2: event 'common:ApplicationStarted' is a known system event and cannot be overridden; this call to `application.RegisterEvent` will panic
/testcases/marshalers/main.go:212:2: event 'collision' has one of multiple definitions here with data type *struct{Field []bool}
/testcases/marshalers/main.go:214:2: data type encoding/json.Marshaler for event 'interface' is a non-empty interface: emitting events from the frontend with data other than `null` is not supported by encoding/json and will likely result in runtime errors
dynamically registered event names are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` with constant arguments only
event 'collision' has multiple conflicting definitions and will be ignored
events registered through indirect calls are not discoverable by the binding generator: it is recommended to invoke `application.RegisterEvent` directly
generic wrappers for calls to `application.RegisterEvent` are not analysable by the binding generator: it is recommended to call `application.RegisterEvent` with concrete types only
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *R is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *S is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors
package github.com/wailsapp/wails/v3/internal/generator/testcases/map_keys: type *T is used as a map key, but some of its instantiations might not implement encoding.TextMarshaler: this might result in runtime errors

View file

@ -0,0 +1,42 @@
//@ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as application$0 from "../pkg/application/models.js";
function configure() {
Object.freeze(Object.assign($Create.Events, {
"events_only:class": $$createType0,
"events_only:map": $$createType2,
"events_only:other": $$createType3,
"overlap": $$createType6,
}));
}
// Private type creation functions
const $$createType0 = events_only$0.SomeClass.createFrom;
const $$createType1 = $Create.Array($Create.Any);
const $$createType2 = $Create.Map($Create.Any, $$createType1);
const $$createType3 = $Create.Array($Create.Any);
const $$createType4 = $Create.Array($Create.Any);
const $$createType5 = $Create.Struct({
"Field": $$createType4,
});
const $$createType6 = $Create.Nullable($$createType5);
configure();

View file

@ -0,0 +1,33 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type { Events } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as json$0 from "../../../../../encoding/json/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as events_only$0 from "./generator/testcases/events_only/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as more$0 from "./generator/testcases/no_bindings_here/more/models.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import type * as application$0 from "../pkg/application/models.js";
declare module "/wails/runtime.js" {
namespace Events {
interface CustomEvents {
"events_only:class": events_only$0.SomeClass;
"events_only:map": { [_: string]: number[] };
"events_only:nodata": application$0.Void;
"events_only:other": more$0.StringPtr[];
"events_only:string": string;
"interface": json$0.Marshaler;
"overlap": {"Field": boolean[]} | null;
}
}
}

View file

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export {
SomeClass
} from "./models.js";

View file

@ -0,0 +1,45 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as nobindingshere$0 from "../no_bindings_here/models.js";
/**
* SomeClass renders as a TS class.
*/
export class SomeClass {
"Field": string;
"Meadow": nobindingshere$0.HowDifferent<number>;
/** Creates a new SomeClass instance. */
constructor($$source: Partial<SomeClass> = {}) {
if (!("Field" in $$source)) {
this["Field"] = "";
}
if (!("Meadow" in $$source)) {
this["Meadow"] = (new nobindingshere$0.HowDifferent());
}
Object.assign(this, $$source);
}
/**
* Creates a new SomeClass instance from a string or object.
*/
static createFrom($$source: any = {}): SomeClass {
const $$createField1_0 = $$createType0;
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
if ("Meadow" in $$parsedSource) {
$$parsedSource["Meadow"] = $$createField1_0($$parsedSource["Meadow"]);
}
return new SomeClass($$parsedSource as Partial<SomeClass>);
}
}
// Private type creation functions
const $$createType0 = nobindingshere$0.HowDifferent.createFrom($Create.Any);

View file

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export type {
StringPtr
} from "./models.js";

View file

@ -0,0 +1,11 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import { Create as $Create } from "/wails/runtime.js";
/**
* StringPtr is a nullable string.
*/
export type StringPtr = string | null;

View file

@ -0,0 +1,6 @@
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export type {
Void
} from "./models.js";

Some files were not shown because too many files have changed in this diff Show more