Skip to content

feat(v3): add experimental migrate command to CLI - #5895

Open
leaanthony wants to merge 6 commits into
masterfrom
agent/experimental-v3-migrate
Open

feat(v3): add experimental migrate command to CLI#5895
leaanthony wants to merge 6 commits into
masterfrom
agent/experimental-v3-migrate

Conversation

@leaanthony

@leaanthony leaanthony commented Aug 4, 2026

Copy link
Copy Markdown
Member

Summary

  • add wails3 migrate to the released V3 CLI
  • keep the command explicitly experimental in CLI output and generated MIGRATION.md
  • migrate deterministic project structure, build assets, frontend setup, and configuration
  • leave V2 API call sites listed for deliberate manual porting
  • document the actual V3 build/config.yml and Taskfile.yml layout
  • document reproducible bug reports and Wails Enhancement Proposal feedback

Validation

  • go test ./internal/commands ./internal/migrate
  • go build ./cmd/wails3
  • wails3 migrate --help
  • migrated the V2 panic-recovery-test fixture into a fresh output directory
  • verified the experimental warning, MIGRATION.md, Taskfile.yml, and build/config.yml

The generated project remains intentionally non-zero-touch: users must review the report, port remaining V2 API calls, regenerate bindings, and test their application.

Summary by CodeRabbit

  • New Features

    • Added an experimental wails3 migrate command to help convert Wails v2 projects to v3.
    • Generates a separate v3 project, updates configuration and dependencies, migrates frontend assets, and preserves supported project settings.
    • Creates a MIGRATION.md checklist identifying API calls and steps requiring manual review.
  • Documentation

    • Added CLI reference, usage guidance, migration instructions, troubleshooting steps, and beta release notes for the migration assistant.

… migration

wails3 migrate -d ./myv2project -o ./myv3project converts a Wails v2
project into a v3 project:

- Parses wails.json and the declarative options.App literal passed to
  wails.Run (syntax-only, no module downloads needed) and generates an
  equivalent programmatic main.go via application.New() +
  app.Window.NewWithOptions(), preserving user code and comments through
  textual surgery on just the wails.Run statement and imports.
- Maps v2 options to v3 (window geometry, start state, background
  colour, platform-specific options, single instance, asset server),
  converts Bind entries into v3 services and bridges the
  OnStartup/OnDomReady/OnShutdown/OnBeforeClose lifecycle callbacks.
- Adds pkg/v2compat/runtime: the v2 runtime API (context-first
  functions) implemented on the v3 application API, so migrated code
  only needs its import path rewritten. Covers events, window control,
  dialogs, clipboard, browser, screens, logging and app control; every
  function documents its v3 replacement for incremental migration.
- Migrates the frontend: regenerates wailsjs/ as a compatibility layer
  over @wailsio/runtime (runtime shim + Call.ByName binding shims from
  the parsed bound-struct methods) and adds the npm dependency.
- Scaffolds the v3 build system via the init machinery: Taskfile,
  build/ assets and build/config.yml populated from the v2 metadata
  (product info, file associations, protocols, bundle id kept as
  com.wails.<name> so the app keeps its identity).
- Transforms go.mod (wails/v2 -> wails/v3, go directive raised),
  preserving all other requires.
- Writes MIGRATION.md documenting every mapped option and any manual
  steps (menus, custom loggers, EnumBind, ...).

Docs: the v2-to-v3 migration guide now leads with the automated path.
The migrate command now announces its experimental status in the CLI
output, the generated MIGRATION.md and the migration guide, with a
pointer to the issue tracker for reports and contributions.
The bridge is no longer a public package in the v3 module: its source
lives at internal/migrate/v2compat/runtime (compiled, vetted and tested
in-repo but not importable) and wails3 migrate copies it into the
output project as <module>/v2compat/runtime with a generated-code
header explaining it is temporary.

This means only migrated projects carry the v2-style API - nobody can
adopt it for new code - and there is no sunset obligation on the v3
module: each project deletes its own bridge functions as call sites are
ported to the v3 API, and removes the package when nothing imports it.

Import rewriting now targets the project-local path, and the bridge is
only emitted when the project actually needs it (v2 runtime imports or
lifecycle hooks).
…at layers

Half-migrated code helps nobody, and compatibility shims invite new code
onto the old API. The migrate command now draws a hard line:

Migrated fully (deterministic):
- project scaffold, Taskfile, build assets, config.yml from wails.json
- main.go rewritten around application.New()/NewWithOptions with the
  options mapped; lifecycle hooks wired natively (ApplicationStarted
  event, WindowRuntimeReady event, Options.OnShutdown, ShouldQuit)
- go.mod v2 -> v3; frontend copied with @wailsio/runtime added

Documented instead of migrated:
- every call into the v2 runtime package, listed in MIGRATION.md by
  file:line with its concrete v3 replacement; the sources are copied
  untouched, so the compiler points at exactly the listed locations
  until they are ported
- every frontend wailsjs import, with the @wailsio/runtime equivalent
  and the generate-bindings workflow; the generated wailsjs directory
  is not carried over (it is v2 build output and cannot work with v3)

Removed: the v2compat runtime bridge and the generated wailsjs shims.
go mod tidy is skipped (with a warning) while v2 call sites remain,
since tidying would re-add the v2 dependency and let old calls compile
only to fail at runtime; MIGRATION.md spells this out.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR adds an experimental wails3 migrate CLI command that converts Wails v2 projects to v3. It includes a new migrate package for parsing v2 projects, mapping options, generating v3 main.go, transforming go.mod, migrating frontend files, and producing a MIGRATION.md report. Documentation is updated to describe the new command.

Changes

Wails v2-to-v3 migration assistant

Layer / File(s) Summary
Core data models
v3/internal/migrate/migrate.go
Defines V2Project, MainInfo, BoundType, BoundMethod, Param, and the v2 runtime import constant.
v2 config loading
v3/internal/migrate/wailsjson.go
Loads and validates wails.json, defines V2Config, V2FileAssociation, V2Protocol, and detects the package manager.
v2 project parsing
v3/internal/migrate/parse.go
Parses Go source for wails.Run calls, resolves bound types and methods, maps imports and TypeScript types.
Option mapping
v3/internal/migrate/mapping.go
Converts v2 options.App literals into v3 application/window configuration across platforms.
Migration report
v3/internal/migrate/report.go
Tracks mapped options, manual steps, and call sites, and renders MIGRATION.md.
Runtime/frontend advisor
v3/internal/migrate/advisor.go
Scans Go and frontend code for v2 runtime/wailsjs usage and provides v3 migration advice.
Main.go generation
v3/internal/migrate/maingen.go
Generates v3 main.go by replacing wails.Run and imports, rendering application/window setup.
go.mod transformation
v3/internal/migrate/gomod.go
Removes the v2 dependency, adds v3, and raises the Go version directive.
File and frontend copying
v3/internal/migrate/copy.go, v3/internal/migrate/frontend.go
Copies project files excluding generated/dependency paths; migrates frontend and adds the v3 runtime dependency.
Command orchestration and CLI wiring
v3/internal/commands/migrate.go, v3/internal/flags/migrate.go, v3/cmd/wails3/main.go
Wires validation, parsing, safety checks, scaffolding, config generation, and reporting into the migrate subcommand.
Test suites
v3/internal/commands/migrate_test.go, v3/internal/migrate/migrate_test.go
End-to-end and unit tests covering parsing, mapping, generation, go.mod transformation, advisor, and frontend migration.
Documentation
docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md, docs/src/content/docs/guides/cli.mdx, docs/src/content/docs/migration/v2-to-v3.mdx, docs/src/content/docs/reference/cli.mdx
Documents the wails3 migrate command, its usage, output, and manual review requirements.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as wails3 migrate
  participant Parser as ParseV2Project
  participant Mapper as MapOptions
  participant Generator as GenerateMain
  participant Report as Report

  CLI->>Parser: parse v2 project source
  Parser->>Mapper: pass parsed V2Project
  Mapper->>Generator: pass V3Options
  Generator->>Report: report manual steps and mapped options
  Report->>CLI: render MIGRATION.md
Loading

Possibly related issues

Possibly related PRs

  • wailsapp/wails#4311: This PR updates Wails v3 documentation to use the wails3 command, matching the CLI documentation changes in this PR.

Suggested labels: Enhancement, go

Poem

A rabbit hops from v2 to v3,
Carrying configs, safe and free.
wails3 migrate clears the trail,
Leaves a checklist, does not fail.
🐇✨ Hop along, the code is new!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding the experimental migrate command to the V3 CLI.
Description check ✅ Passed The description clearly covers the feature, migration behavior, limitations, documentation, and validation, but omits the issue link, environment details, and checklist selections.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent/experimental-v3-migrate

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added Documentation Improvements or additions to documentation cli v3-alpha labels Aug 4, 2026
@leaanthony
leaanthony marked this pull request as ready for review August 4, 2026 12:13
Copilot AI lite review requested due to automatic review settings August 4, 2026 12:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an experimental wails3 migrate command to the v3 CLI that parses a Wails v2 project (wails.json, go.mod, and the wails.Run(&options.App{...}) site), scaffolds an equivalent v3 project into a separate output directory, and generates a MIGRATION.md checklist for manual follow-up work (notably remaining v2 runtime/wailsjs call sites).

Changes:

  • Introduces a new v3/internal/migrate engine (parse + map + generate + report) with unit and end-to-end tests.
  • Wires the new migrate command into the v3 CLI, adds flags, and implements project scaffolding/output behavior.
  • Updates docs (CLI reference, migration guide, blog post) to document the experimental migration workflow and reporting guidance.

Reviewed changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
v3/internal/migrate/wailsjson.go Parses v2 wails.json into a minimal config model with defaults used by the migrator.
v3/internal/migrate/report.go Builds MIGRATION.md summarizing automatic mappings and manual porting steps.
v3/internal/migrate/parse.go Syntax-parses a v2 module, locates wails.Run, discovers bound services, and enumerates call sites.
v3/internal/migrate/migrate.go Defines core migrator data structures (V2Project, MainInfo, bound types).
v3/internal/migrate/migrate_test.go Unit tests covering parsing, mapping, main generation, go.mod rewriting, and frontend migration behavior.
v3/internal/migrate/mapping.go Maps v2 options.App fields to v3 application.Options / WebviewWindowOptions equivalents and records manual steps.
v3/internal/migrate/maingen.go Rewrites main.go by replacing imports and the wails.Run(...) statement with v3 equivalents, then formats/prunes imports.
v3/internal/migrate/gomod.go Rewrites go.mod from v2 to v3, bumps the Go directive minimum, and removes v2-specific replaces.
v3/internal/migrate/frontend.go Copies frontend sources while excluding v2-generated wailsjs, ensures dist/ exists, and injects @wailsio/runtime.
v3/internal/migrate/copy.go Copies remaining project files (excluding frontend/build and regenerated files) into the output directory.
v3/internal/migrate/advisor.go Collects v2 runtime and wailsjs import call sites with concrete v3 migration advice.
v3/internal/flags/migrate.go Adds CLI flags for wails3 migrate.
v3/internal/commands/migrate.go Implements the CLI command orchestration (parsing, scaffolding, writing output, report generation, optional tidy).
v3/internal/commands/migrate_test.go End-to-end test ensuring expected migrated outputs are generated and v2 artifacts are excluded.
v3/cmd/wails3/main.go Registers the migrate subcommand in the CLI.
docs/src/content/docs/reference/cli.mdx Documents the new wails3 migrate command in the CLI reference table.
docs/src/content/docs/migration/v2-to-v3.mdx Adds an “Automated Migration (Experimental)” section and related guidance.
docs/src/content/docs/guides/cli.mdx Adds a CLI guide section documenting the migrate workflow and reporting expectations.
docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md Updates the beta announcement to mention the experimental migration assistant.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +280 to +285
name := ""
if imp.Name != nil {
name = imp.Name.Name
} else {
name = path[strings.LastIndex(path, "/")+1:]
}
Comment on lines +365 to +379
ast.Inspect(mainFile, func(n ast.Node) bool {
if typeName != "" {
return false
}
assign, ok := n.(*ast.AssignStmt)
if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
return true
}
lhs, ok := assign.Lhs[0].(*ast.Ident)
if !ok || lhs.Name != e.Name {
return true
}
typeName = resolveElementType(fset, files, mainFile, assign.Rhs[0])
return false
})
Comment on lines +89 to +96
if dependenciesRe.MatchString(content) {
content = dependenciesRe.ReplaceAllString(content, "$1\n \"@wailsio/runtime\": \"latest\",")
} else if idx := strings.Index(content, "{"); idx >= 0 {
content = content[:idx+1] + "\n \"dependencies\": {\n \"@wailsio/runtime\": \"latest\"\n }," + content[idx+1:]
} else {
proj.Report.Manual("frontend/package.json", "Could not add the `@wailsio/runtime` dependency automatically; add it and run your package manager's install.")
return nil
}
Comment on lines +106 to +107
// Frontend: copy, regenerate wailsjs as a compatibility layer, add the
// @wailsio/runtime dependency.
Comment on lines +79 to +81
for _, cs := range r.callSites {
sb.WriteString(cs + "\n")
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 17

🧹 Nitpick comments (10)
v3/internal/migrate/mapping.go (1)

336-341: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Record a manual step when Debug is not a literal.

If Debug is not a composite literal, or the literal has no OpenInspectorOnStartup key, the option is dropped silently. The user gets no entry in MIGRATION.md. Add an m.manual("Debug", ...) call in the non-literal case.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/mapping.go` around lines 336 - 341, Update the “Debug”
handling around compositeLit and litFields so a manual migration step is
recorded when Debug is not a composite literal or lacks OpenInspectorOnStartup.
Preserve the existing m.carry call when that key is present, and add
m.manual("Debug", ...) only for the unsupported or missing-key path.
v3/internal/migrate/maingen.go (1)

271-293: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Import pruning drops the spec but keeps its comment.

The removal span covers only imp.Pos() to imp.End(). A line comment or doc comment attached to that import stays in the file. The result is a dangling comment inside the import block. Extend the span to the spec's Doc and Comment positions, or rebuild the import block from the AST with printer instead of cutting byte ranges.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/maingen.go` around lines 271 - 293, Update the
import-pruning logic in the loop over gen.Specs to include comments attached to
each ast.ImportSpec when calculating the removal span. Extend the start and end
positions using imp.Doc and imp.Comment so associated comments are removed with
the unused import, while preserving the existing handling for used and
side-effect imports.
docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md (1)

137-141: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Match the version casing used in this file.

Lines 124-135 use v3 and v2. The new paragraph uses V3 and V2. Use the lowercase form for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md` around lines 137 -
141, Update the migration assistant paragraph to use lowercase v3 and v2
consistently, including the project description, checklist reference, and API
calls.
v3/internal/commands/migrate.go (3)

106-107: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the stale comment.

The comment says the frontend step regenerates wailsjs as a compatibility layer. The final behavior drops wailsjs and only copies the frontend plus adds the @wailsio/runtime dependency. TestMigrateFrontend asserts that frontend/wailsjs does not exist.

♻️ Proposed fix
-	// Frontend: copy, regenerate wailsjs as a compatibility layer, add the
-	// `@wailsio/runtime` dependency.
+	// Frontend: copy the sources (the v2 wailsjs output is dropped) and add
+	// the `@wailsio/runtime` dependency.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/commands/migrate.go` around lines 106 - 107, Update the frontend
migration comment near TestMigrateFrontend to remove the claim that wailsjs is
regenerated or retained as a compatibility layer; describe only copying the
frontend and adding the `@wailsio/runtime` dependency, matching the asserted
absence of frontend/wailsjs.

309-315: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Report the icon copy only when the write succeeds.

Line 312 discards the os.WriteFile error. The note at Line 313 then tells the user that build/appicon.png was copied even when the write failed. Check the error and warn instead.

♻️ Proposed fix
 	if _, err := os.Stat(srcIcon); err == nil {
 		data, err := os.ReadFile(srcIcon)
 		if err == nil {
-			_ = os.WriteFile(filepath.Join(outDir, "build", "appicon.png"), data, 0o644)
-			proj.Report.Note("Copied build/appicon.png from the v2 project. Run `wails3 task common:generate:icons` to regenerate the platform icon files (icons.icns / icon.ico) from it.")
+			if werr := os.WriteFile(filepath.Join(outDir, "build", "appicon.png"), data, 0o644); werr != nil {
+				term.Warningf("Could not copy build/appicon.png (%v) - copy it manually.\n", werr)
+			} else {
+				proj.Report.Note("Copied build/appicon.png from the v2 project. Run `wails3 task common:generate:icons` to regenerate the platform icon files (icons.icns / icon.ico) from it.")
+			}
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/commands/migrate.go` around lines 309 - 315, Update the icon-copy
block in the migration flow to check the error returned by os.WriteFile before
reporting success. Only call proj.Report.Note after writing build/appicon.png
succeeds; when the write fails, issue a warning through the existing reporting
mechanism instead.

190-203: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Restore stdout and the working directory with defer.

templates.Install mutates process-global state. The restore of os.Stdout at Line 197 and the os.Chdir(wd) at Line 201 only run on the normal path. If templates.Install panics, the process keeps writing to /dev/null and stays in the deleted scratch directory. defer makes the restore unconditional.

♻️ Proposed fix
 	term.DisableOutput()
-	stdout := os.Stdout
-	if devnull, dnErr := os.OpenFile(os.DevNull, os.O_WRONLY, 0); dnErr == nil {
-		os.Stdout = devnull
-		defer devnull.Close()
-	}
-	err = templates.Install(initFlags)
-	os.Stdout = stdout
-	if !quiet {
-		term.EnableOutput()
-	}
-	if chdirErr := os.Chdir(wd); chdirErr != nil {
-		return chdirErr
-	}
+	stdout := os.Stdout
+	if devnull, dnErr := os.OpenFile(os.DevNull, os.O_WRONLY, 0); dnErr == nil {
+		os.Stdout = devnull
+		defer devnull.Close()
+	}
+	defer func() {
+		os.Stdout = stdout
+		if !quiet {
+			term.EnableOutput()
+		}
+		_ = os.Chdir(wd)
+	}()
+	err = templates.Install(initFlags)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/commands/migrate.go` around lines 190 - 203, In the migration
flow surrounding templates.Install, defer restoration of os.Stdout immediately
after redirecting it and defer returning to wd before invoking the installer, so
both process-global state and the working directory are restored during panics
or early returns. Remove the corresponding normal-path restoration calls while
preserving quiet-mode output handling and existing error propagation.
v3/internal/migrate/migrate_test.go (1)

370-383: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the dead sentinel in the assertion loop.

Line 375 adds "`src/main.js` should not appear" to the want list. Lines 377-379 then skip that exact entry. The loop asserts nothing for it. Either drop the entry and the guard, or write the intended negative assertion explicitly.

♻️ Proposed fix
 	for _, want := range []string{
 		"## Port these to the v3 API",
 		"`app.go:", // Go call site location
 		"`runtime.WindowSetTitle`",
 		"window.SetTitle(title)",
-		"`src/main.js` should not appear",
 	} {
-		if want == "`src/main.js` should not appear" {
-			continue
-		}
 		if !strings.Contains(md, want) {
 			t.Errorf("report missing %q\n---\n%s", want, md)
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/migrate_test.go` around lines 370 - 383, Remove the
unused "`src/main.js` should not appear" entry and its special-case guard from
the assertion loop in the migration test, or replace them with an explicit
negative assertion that verifies `md` does not contain that string.
docs/src/content/docs/guides/cli.mdx (1)

56-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a #### Flags table for consistency.

Every other command on this page documents its flags in a table (init at Line 23, dev at Line 87, build at Line 107). The migrate section points at --help instead. The flag set is small and stable in v3/internal/flags/migrate.go.

♻️ Proposed addition
 ```bash
 wails3 migrate -d /path/to/v2-project -o /path/to/v3-project

+#### Flags
+| Flag | Description | Default |
+|-------------------|------------------------------------------------|---------|
+| -d | Path to the Wails v2 project to migrate | . |
+| -o | Directory to write the migrated v3 project to | |
+| -f | Write into a non-empty output directory | false |
+| -q | Suppress output | false |
+| -skipgomodtidy | Skip go mod tidy | false |

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/src/content/docs/guides/cli.mdx around lines 56 - 77, Add a “####
Flags” table to the migrate documentation section, listing -d, -o, -f,
-q, and -skipgomodtidy with their descriptions and defaults consistent with
v3/internal/flags/migrate.go; retain the existing migration guidance and
--help reference.


</details>

<!-- cr-comment:v1:ccbb6802bc24f525259c197d -->

</blockquote></details>
<details>
<summary>v3/internal/commands/migrate_test.go (1)</summary><blockquote>

`223-235`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_

**Add the reverse containment case.**

`TestMigrateRefusesNestedOutput` covers only `outputDir` inside `v2Dir`. Add a case where `outputDir` is the parent of `v2Dir`. That case currently passes the path check and can overwrite the v2 sources. See the related comment on `v3/internal/commands/migrate.go` Line 54.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @v3/internal/commands/migrate_test.go around lines 223 - 235, Add a
reverse-containment test alongside TestMigrateRefusesNestedOutput that sets
OutputDir to a parent of v2Dir, invokes Migrate with the same options, and
asserts the same “must not be inside” error. Ensure the test verifies Migrate
rejects output paths that contain V2Dir and protects the existing v2 sources.


</details>

<!-- cr-comment:v1:70ed96ad24765bbf30bcaad5 -->

</blockquote></details>
<details>
<summary>v3/internal/migrate/report.go (1)</summary><blockquote>

`72-82`: _📐 Maintainability & Code Quality_ | _🔵 Trivial_ | _⚡ Quick win_

**Render call sites in a stable order.**

`callSites` is appended during `adviseGoRuntimeCalls` from `range files`, and `Markdown()` renders them in that order. Copy and sort the slice before rendering to keep `MIGRATION.md` deterministic for equivalent inputs.

<details>
<summary>Proposed change</summary>

```diff
-		for _, cs := range r.callSites {
+		callSites := append([]string(nil), r.callSites...)
+		sort.Strings(callSites)
+		for _, cs := range callSites {
 			sb.WriteString(cs + "\n")
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/report.go` around lines 72 - 82, Update Markdown() to
copy r.callSites into a separate slice, sort the copy before iterating, and
render the sorted values. Preserve r.callSites itself unchanged while ensuring
equivalent inputs produce deterministic MIGRATION.md output.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/src/content/docs/guides/cli.mdx`:
- Around line 75-76: Update the Wails Enhancement Proposal link in the migration
guidance near generated MIGRATION.md so it points to the existing
/tree/master/v3/wep path, preserving the surrounding text and link purpose.

In `@v3/internal/commands/migrate.go`:
- Around line 54-59: The output containment validation only rejects outputs
nested inside the source; make it reject both directions so the source project
remains unchanged. In v3/internal/commands/migrate.go lines 54-59, update the
guards around isSubPath to also reject isSubPath(outDir, proj.Dir) with an
appropriate error. In v3/internal/commands/migrate_test.go lines 223-235, extend
TestMigrateRefusesNestedOutput with a case using the parent of v2Dir as
OutputDir and assert the new error.
- Around line 358-387: Update the YAML generation in the file-associations and
protocols migration blocks to serialize every emitted scalar safely, including
Ext, Name, Description, IconName, Role, and Scheme; use the project’s YAML
marshaller or quote each value so characters such as :, #, leading -, and quotes
remain parseable. Omit optional empty descriptions instead of emitting
null-valued fields, and preserve the existing migration notes and structure.

In `@v3/internal/migrate/advisor.go`:
- Line 155: Update wailsjsImportRe and the associated FindStringSubmatch
scanning loop to recognize side-effect static imports and dynamic import(...)
expressions in addition to existing from/require forms. Iterate through each
line’s matches so every wailsjs import, including multiple imports on one line,
is reported in MIGRATION.md before continuing to the next line.
- Around line 193-209: Update the scanner setup in adviseFrontendImports to
configure an explicit buffer limit appropriate for supported frontend files,
allowing valid long source lines beyond bufio.MaxScanTokenSize while retaining a
bounded maximum. Add a regression fixture containing an overlong frontend line
and verify migration advice completes without scanner.Err().
- Around line 111-151: Extend adviseGoRuntimeCalls to detect dot-imported
V2RuntimeImport entries, retaining an *ast.Ident reference for the v2 runtime
package and recording runtime.Updates when such an import is present. During AST
inspection, also match unqualified ast.CallExpr calls against the known runtime
advice names and report them using the v2 runtime reference, while preserving
existing SelectorExpr handling for aliased or named imports.

In `@v3/internal/migrate/frontend.go`:
- Around line 69-92: The addRuntimeDependency function must update package.json
as structured JSON instead of using regex or global text checks, which can
create trailing commas or skip required changes. Unmarshal the document, inspect
only the root dependencies object for `@wailsio/runtime`, add the latest entry
when absent, and marshal it back while preserving the existing missing-file
behavior; add tests covering empty root and empty dependencies objects.

In `@v3/internal/migrate/gomod.go`:
- Around line 10-12: Update the minGoVersion constant to 1.25 so generated Wails
v3 projects declare the supported minimum Go version, and update the
corresponding expectation in the migration test covering the Go directive to
assert 1.25.

In `@v3/internal/migrate/maingen.go`:
- Around line 89-93: Deduplicate field names in MapOptions before they reach
generation, ensuring repeated keys such as BackgroundType and Assets are emitted
only once. Also update writeFields to track already-written f.Name values and
skip duplicates, while preserving the existing field order and formatting for
the first occurrence.

In `@v3/internal/migrate/mapping.go`:
- Around line 296-304: Update the Assets mapping cases and mapAssetServer to use
set semantics like BackgroundType, ensuring only one application.Options.Assets
field remains when multiple v2 asset fields are configured. When a later asset
value replaces an earlier one, record the corresponding overwrite in
m.proj.Report.
- Around line 460-469: Update v3/internal/migrate/mapping.go lines 460-469 to
replace existing BackgroundType fields when mapping WebviewIsTransparent and
WindowIsTranslucent, and apply the same replacement behavior to
Mac.WebviewIsTransparent at line 547; update v3/internal/migrate/mapping.go
lines 296-304 so Assets, AssetsHandler, and mapAssetServer at line 429 replace
the Assets field and record a report note when overwriting; update
v3/internal/migrate/maingen.go lines 89-93 in writeFields to skip field names
already written as a defensive duplicate-key guard.
- Around line 280-286: Update the Fullscreen handling branch in the migration
mapping logic to distinguish the literal false identifier from other non-true
values. Skip m.manual for Fullscreen: false, while preserving the existing
fullscreen mapping for true and manual guidance for genuinely non-constant
values.

In `@v3/internal/migrate/migrate_test.go`:
- Around line 359-361: Align the v3 migration minimum Go version with the
toolchain by changing minGoVersion in gomod.go to 1.25, updating the
migrate_test.go expectation from “go 1.24” to “go 1.25”, and synchronizing
IMPLEMENTATION.md with the same v3 Go version decision.

In `@v3/internal/migrate/parse.go`:
- Around line 148-239: Update findRunCall to collect every wails.Run candidate
instead of stopping at the first match, preserving each call’s enclosing
statement context; reject multiple candidates and unsupported contexts before
GenerateMain writes output. Detect the documented if err := wails.Run(...); err
!= nil form and set RunStmt to the complete *ast.IfStmt only when its error
behavior can be preserved, otherwise reject it, so GenerateMain replaces a valid
rewrite range. Add fixtures covering multiple calls and both assignment/if
forms.
- Around line 309-448: Update resolveElementType, constructorReturnType, and
collectMethods to resolve each Bind entry from the wails.Run file’s lexical
scope and retain its package identity. Handle package-level and local var
declarations while selecting the nearest valid declaration, and restrict
constructor and receiver-method lookup to the resolved package instead of
matching names across all parsed files. Add fixtures covering package-level
vars, local shadowing, and duplicate symbols in a subpackage.
- Around line 58-90: Update the source-file selection in the filepath.WalkDir
callback to honor Go build constraints using a defined build context, excluding
files and directories such as testdata that are not part of the selected project
build. If migration supports multiple platforms, process each applicable
constrained entrypoint; otherwise reject or omit non-selected variants before
populating files and writing output. Add fixtures covering mutually exclusive
platform files and testdata.

In `@v3/internal/migrate/report.go`:
- Around line 60-70: Update the next-steps rendering in the report builder
around r.callSites so the call-site migration step and its continuation lines
are emitted only when callSites is non-empty. Renumber the subsequent go mod
tidy, dev, and manual-steps items based on whether that conditional step was
included, while preserving the existing guidance text.

---

Nitpick comments:
In `@docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md`:
- Around line 137-141: Update the migration assistant paragraph to use lowercase
v3 and v2 consistently, including the project description, checklist reference,
and API calls.

In `@docs/src/content/docs/guides/cli.mdx`:
- Around line 56-77: Add a “#### Flags” table to the `migrate` documentation
section, listing `-d`, `-o`, `-f`, `-q`, and `-skipgomodtidy` with their
descriptions and defaults consistent with `v3/internal/flags/migrate.go`; retain
the existing migration guidance and `--help` reference.

In `@v3/internal/commands/migrate_test.go`:
- Around line 223-235: Add a reverse-containment test alongside
TestMigrateRefusesNestedOutput that sets OutputDir to a parent of v2Dir, invokes
Migrate with the same options, and asserts the same “must not be inside” error.
Ensure the test verifies Migrate rejects output paths that contain V2Dir and
protects the existing v2 sources.

In `@v3/internal/commands/migrate.go`:
- Around line 106-107: Update the frontend migration comment near
TestMigrateFrontend to remove the claim that wailsjs is regenerated or retained
as a compatibility layer; describe only copying the frontend and adding the
`@wailsio/runtime` dependency, matching the asserted absence of frontend/wailsjs.
- Around line 309-315: Update the icon-copy block in the migration flow to check
the error returned by os.WriteFile before reporting success. Only call
proj.Report.Note after writing build/appicon.png succeeds; when the write fails,
issue a warning through the existing reporting mechanism instead.
- Around line 190-203: In the migration flow surrounding templates.Install,
defer restoration of os.Stdout immediately after redirecting it and defer
returning to wd before invoking the installer, so both process-global state and
the working directory are restored during panics or early returns. Remove the
corresponding normal-path restoration calls while preserving quiet-mode output
handling and existing error propagation.

In `@v3/internal/migrate/maingen.go`:
- Around line 271-293: Update the import-pruning logic in the loop over
gen.Specs to include comments attached to each ast.ImportSpec when calculating
the removal span. Extend the start and end positions using imp.Doc and
imp.Comment so associated comments are removed with the unused import, while
preserving the existing handling for used and side-effect imports.

In `@v3/internal/migrate/mapping.go`:
- Around line 336-341: Update the “Debug” handling around compositeLit and
litFields so a manual migration step is recorded when Debug is not a composite
literal or lacks OpenInspectorOnStartup. Preserve the existing m.carry call when
that key is present, and add m.manual("Debug", ...) only for the unsupported or
missing-key path.

In `@v3/internal/migrate/migrate_test.go`:
- Around line 370-383: Remove the unused "`src/main.js` should not appear" entry
and its special-case guard from the assertion loop in the migration test, or
replace them with an explicit negative assertion that verifies `md` does not
contain that string.

In `@v3/internal/migrate/report.go`:
- Around line 72-82: Update Markdown() to copy r.callSites into a separate
slice, sort the copy before iterating, and render the sorted values. Preserve
r.callSites itself unchanged while ensuring equivalent inputs produce
deterministic MIGRATION.md output.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: a7a7af04-289f-4fb2-981b-2693cce22560

📥 Commits

Reviewing files that changed from the base of the PR and between 2014eb6 and 3495d3a.

📒 Files selected for processing (19)
  • docs/src/content/docs/blog/2026-08-02-wails-v3-beta.md
  • docs/src/content/docs/guides/cli.mdx
  • docs/src/content/docs/migration/v2-to-v3.mdx
  • docs/src/content/docs/reference/cli.mdx
  • v3/cmd/wails3/main.go
  • v3/internal/commands/migrate.go
  • v3/internal/commands/migrate_test.go
  • v3/internal/flags/migrate.go
  • v3/internal/migrate/advisor.go
  • v3/internal/migrate/copy.go
  • v3/internal/migrate/frontend.go
  • v3/internal/migrate/gomod.go
  • v3/internal/migrate/maingen.go
  • v3/internal/migrate/mapping.go
  • v3/internal/migrate/migrate.go
  • v3/internal/migrate/migrate_test.go
  • v3/internal/migrate/parse.go
  • v3/internal/migrate/report.go
  • v3/internal/migrate/wailsjson.go

Comment on lines +75 to +76
generated `MIGRATION.md`. Proposals to change migration behavior belong in a
[Wails Enhancement Proposal](https://github.com/wailsapp/wails/tree/master/wep).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check that the wep directory exists on the default branch.
gh api repos/wailsapp/wails/contents/wep --jq '.[0].name' || echo "wep directory not found"

Repository: wailsapp/wails

Length of output: 328


🌐 Web query:

Wails WEP GitHub wailsapp/wails Wails Enhancement Proposal

💡 Result:

The Wails Enhancement Proposal (WEP) is the formal process used by the Wails project to propose, discuss, and implement new features or changes to public behavior in Wails v3 [1][2][3]. This process is designed to ensure that major new capabilities are well-documented, reviewed by the community and maintainers, and integrated in a structured manner [1]. Key aspects of the WEP process include: Process Overview: Contributors who wish to propose a new capability or a change to public behavior should submit a draft pull request (PR) titled [WEP] instead of creating a standard feature-request issue [1][3][4]. The WEP PR serves as the canonical venue for discussion, allowing for feedback and community support to be gathered in a single, searchable record [5]. Workflow: 1. Submission: The proposer submits a draft PR containing the proposal documentation (often using a standard WEP template) and any necessary supporting materials [1][5]. 2. Discussion: The community and maintainers review the PR. Proposers are encouraged to share their proposal on platforms like the Wails Discord to gather feedback and support [1][5]. 3. Decision: Wails maintainers make a final decision based on the proposal's merits and community feedback. Accepted proposals are assigned a WEP number, and the PR is merged [1]. 4. Implementation: Once accepted, the focus shifts to the implementation phase, which involves developing the feature, conducting code reviews, and finalizing documentation before the feature is merged into the project [1]. Documentation: The official documentation and templates for the WEP process can be found within the Wails repository at v3/wep/README.md [1]. It includes details such as a WEP Index, status definitions, and guidelines for when a WEP is required [5]. This process is a key component of the Wails v3 ecosystem, helping the project manage growth while maintaining compatibility and clear governance [3].

Citations:


🌐 Web query:

github.com/wailsapp/wails/tree/master/v3/wep

💡 Result:

The directory github.com/wailsapp/wails/tree/master/v3/wep contains the documentation and framework for the Wails Enhancement Proposal (WEP) process [1][2]. This process is the official mechanism for proposing, discussing, and implementing new features or changes to public behavior in Wails v3 [2][3]. Key details of the WEP process include: Purpose: It replaces traditional feature-request issues for significant changes, ensuring a structured approach to evolving the Wails codebase [1][4]. Workflow: 1. Submission: Proposers document their idea by creating a directory in v3/wep/proposals/ and filling out the official WEP template (v3/wep/WEP_TEMPLATE.md) [2][5]. 2. Proposal Review: Proposers submit a draft pull request (PR) titled [WEP] <title> containing the proposal file to gather community feedback [2][4]. 3. Decision: The Wails maintainers evaluate the proposal based on community feedback and project merits [2]. If accepted, the proposal is assigned a WEP number and merged [2]. 4. Implementation: Once accepted, the focus shifts to development, which involves submitting PRs for the implementation and iterating based on reviews [2]. This process is designed to help maintain clarity and stability as Wails v3 evolves during its beta phase [3][4].

Citations:


Update the broken Wails Enhancement Proposal link.

https://github.com/wailsapp/wails/tree/master/wep returns a repository 404. The WEP documentation is under v3/wep, so update the links to point to the existing https://github.com/wailsapp/wails/tree/master/v3/wep path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/src/content/docs/guides/cli.mdx` around lines 75 - 76, Update the Wails
Enhancement Proposal link in the migration guidance near generated MIGRATION.md
so it points to the existing /tree/master/v3/wep path, preserving the
surrounding text and link purpose.

Comment on lines +54 to +59
if isSubPath(proj.Dir, outDir) {
return fmt.Errorf("the output directory must not be inside the v2 project directory")
}
if entries, err := os.ReadDir(outDir); err == nil && len(entries) > 0 && !options.Force {
return fmt.Errorf("output directory %s is not empty (use -f to write into it anyway)", outDir)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

One-directional output containment check. isSubPath(proj.Dir, outDir) rejects an output directory inside the v2 project, but it does not reject an output directory that contains the v2 project. With -f, the migration then writes over the v2 sources, which contradicts the documented promise that the source project stays unchanged.

  • v3/internal/commands/migrate.go#L54-L59: add a second guard, isSubPath(outDir, proj.Dir), that returns an error when the output directory contains the v2 project directory.
  • v3/internal/commands/migrate_test.go#L223-L235: add a case to TestMigrateRefusesNestedOutput that sets OutputDir to the parent of v2Dir and asserts the new error.
📍 Affects 2 files
  • v3/internal/commands/migrate.go#L54-L59 (this comment)
  • v3/internal/commands/migrate_test.go#L223-L235
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/commands/migrate.go` around lines 54 - 59, The output containment
validation only rejects outputs nested inside the source; make it reject both
directions so the source project remains unchanged. In
v3/internal/commands/migrate.go lines 54-59, update the guards around isSubPath
to also reject isSubPath(outDir, proj.Dir) with an appropriate error. In
v3/internal/commands/migrate_test.go lines 223-235, extend
TestMigrateRefusesNestedOutput with a case using the parent of v2Dir as
OutputDir and assert the new error.

Comment on lines +358 to +387
if fas := proj.Config.Info.FileAssociations; len(fas) > 0 {
var sb strings.Builder
sb.WriteString("fileAssociations:\n")
for _, fa := range fas {
sb.WriteString(fmt.Sprintf(" - ext: %s\n", fa.Ext))
sb.WriteString(fmt.Sprintf(" name: %s\n", fa.Name))
sb.WriteString(fmt.Sprintf(" description: %s\n", fa.Description))
sb.WriteString(fmt.Sprintf(" iconName: %s\n", fa.IconName))
sb.WriteString(fmt.Sprintf(" role: %s\n", fa.Role))
}
if fileAssociationsRe.MatchString(content) {
content = fileAssociationsRe.ReplaceAllString(content, strings.TrimSuffix(sb.String(), "\n"))
} else {
content += "\n" + sb.String()
}
proj.Report.Note("File associations were moved to `build/config.yml`. Copy their icons into `build/` (macOS: `<iconName>.icns`, Windows: `<iconName>.ico`).")
}

if protos := proj.Config.Info.Protocols; len(protos) > 0 {
var sb strings.Builder
sb.WriteString("\nprotocols:\n")
for _, p := range protos {
sb.WriteString(fmt.Sprintf(" - scheme: %s\n", p.Scheme))
if p.Description != "" {
sb.WriteString(fmt.Sprintf(" description: %s\n", p.Description))
}
}
content += sb.String()
proj.Report.Note("Custom protocol schemes were moved to `build/config.yml`.")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Quote the emitted YAML scalars.

appendAssociations writes v2 values into build/config.yml as bare YAML scalars. A description, name or scheme from wails.json can contain :, #, a leading -, or a quote character. The generated config.yml then fails to parse. UpdateBuildAssets at Line 302 reads that same file, so the migration aborts on a valid v2 project. Empty optional fields also emit description: with a null value instead of being omitted.

Emit the block with a YAML marshaller, or quote each scalar.

🐛 Proposed fix (minimal: quote each value)
+// yamlString renders s as a double-quoted YAML scalar.
+func yamlString(s string) string {
+	b, err := yaml.Marshal(s)
+	if err != nil {
+		return strconv.Quote(s)
+	}
+	return strings.TrimSpace(string(b))
+}
 		for _, fa := range fas {
-			sb.WriteString(fmt.Sprintf("  - ext: %s\n", fa.Ext))
-			sb.WriteString(fmt.Sprintf("    name: %s\n", fa.Name))
-			sb.WriteString(fmt.Sprintf("    description: %s\n", fa.Description))
-			sb.WriteString(fmt.Sprintf("    iconName: %s\n", fa.IconName))
-			sb.WriteString(fmt.Sprintf("    role: %s\n", fa.Role))
+			sb.WriteString(fmt.Sprintf("  - ext: %s\n", yamlString(fa.Ext)))
+			sb.WriteString(fmt.Sprintf("    name: %s\n", yamlString(fa.Name)))
+			sb.WriteString(fmt.Sprintf("    description: %s\n", yamlString(fa.Description)))
+			sb.WriteString(fmt.Sprintf("    iconName: %s\n", yamlString(fa.IconName)))
+			sb.WriteString(fmt.Sprintf("    role: %s\n", yamlString(fa.Role)))
 		}
 		for _, p := range protos {
-			sb.WriteString(fmt.Sprintf("  - scheme: %s\n", p.Scheme))
+			sb.WriteString(fmt.Sprintf("  - scheme: %s\n", yamlString(p.Scheme)))
 			if p.Description != "" {
-				sb.WriteString(fmt.Sprintf("    description: %s\n", p.Description))
+				sb.WriteString(fmt.Sprintf("    description: %s\n", yamlString(p.Description)))
 			}
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/commands/migrate.go` around lines 358 - 387, Update the YAML
generation in the file-associations and protocols migration blocks to serialize
every emitted scalar safely, including Ext, Name, Description, IconName, Role,
and Scheme; use the project’s YAML marshaller or quote each value so characters
such as :, #, leading -, and quotes remain parseable. Omit optional empty
descriptions instead of emitting null-valued fields, and preserve the existing
migration notes and structure.

Comment on lines +111 to +151
func adviseGoRuntimeCalls(fset *token.FileSet, files map[string]*ast.File, proj *V2Project) {
for path, file := range files {
localName := ""
for name, ipath := range importMap(file) {
if ipath == V2RuntimeImport {
localName = name
}
}
if localName == "" {
continue
}
rel, err := filepath.Rel(proj.Dir, path)
if err != nil {
rel = path
}
ast.Inspect(file, func(n ast.Node) bool {
sel, ok := n.(*ast.SelectorExpr)
if !ok {
return true
}
ident, ok := sel.X.(*ast.Ident)
if !ok || ident.Name != localName {
return true
}
name := sel.Sel.Name
advice, ok := goRuntimeAdvice[name]
if !ok {
advice, ok = windowRuntimeAdvice[name]
if ok {
advice += " - get the window with `app.Window.Current()` or keep a reference to the one you create"
}
}
if !ok {
// Type references (runtime.OpenDialogOptions{...}) and
// anything unknown.
advice = "see the v3 application API and https://v3.wails.io/migration/v2-to-v3/"
}
pos := fset.Position(sel.Pos())
proj.Report.CallSite(fmt.Sprintf("%s:%d", rel, pos.Line), "`runtime."+name+"`", advice)
return true
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the current selector-only implementation and dot-import coverage.
rg -n -C 8 --type go \
  'adviseGoRuntimeCalls|SelectorExpr|CallExpr|V2RuntimeImport' \
  v3/internal/migrate/advisor.go

rg -n --type go \
  '(import\s+\.\s*|^\s*\.\s*)"github\.com/wailsapp/wails/v2/pkg/runtime"' \
  v3 || true

Repository: wailsapp/wails

Length of output: 1647


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect full advisor.go and related import-map behavior.
wc -l v3/internal/migrate/advisor.go
sed -n '1,220p' v3/internal/migrate/advisor.go

# Probe Go AST for dot-imported calls and selector calls in an isolated Go source.
if command -v go >/dev/null 2>&1; then
  tmp="$(mktemp -d)"
  cat > "$tmp/main.go" <<'EOF'
package main

import . "github.com/wailsapp/wails/v2/pkg/runtime"

func f(ctx Context) {
    Quit(ctx)
    Quit(ctx)
}
EOF
  go parser "$tmp/main.go" 2>/tmp/parser_err.log || { echo "go parser unavailable"; cat /tmp/parser_err.log; }
  # AST dump without installing repo package; if parser unavailable, use parser package source if present.
  cat >/tmp/astprobe.go <<'EOF'
package main
import (
	"fmt"
	"go/parser"
	"go/token"
)
func main() {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "m.go", `package main

import . "github.com/wailsapp/wails/v2/pkg/runtime"

func f(ctx Context) {
	Quit(ctx)
	runtime.Quit(ctx)
}`, parser.ParseComments)
	if err != nil {
		panic(err)
	}
	ast.Inspect(f, func(n interface{}) bool {
		if call, ok := n.(*ast.CallExpr); ok {
			fmt.Printf("CALL pos=%s callee=%T: ", fset.Position(call.Pos()), call.Fun)
			switch fn := call.Fun.(type) {
			case *ast.Ident:
				fmt.Printf("Ident.Name=%s\n", fn.Name)
			case *ast.SelectorExpr:
				fmt.Printf("Ident.Name=%s Sel.Name=%s\n", fn.X.(*ast.Ident).Name, fn.Sel.Name)
			default:
				fmt.Printf("%#v\n", fn)
			}
		}
		return true
	})
}
EOF
  cat >/tmp/astprobe_m.go <<'EOF'
package main
import "github.com/wailsapp/wails/v2/pkg/runtime"
func f(ctx runtime.Context) {
    runtime.Quit(ctx)
}
EOF
  cd "$tmp"
  go run astprobe.go
fi

# Search for importMap and report implementation.
rg -n -C 5 'func importMap|MIGRATION|Report|CallSite' v3/internal/migrate

Repository: wailsapp/wails

Length of output: 10219


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and inspect the import-map implementation and any dot-import handling.
rg -n -C 10 'func importMap|importMap\s*\(' --type go v3 || true

# Locate migration guidance files that advise dot-import handling if present.
rg -n -C 8 'dot import|dot imported|import \.|"github\.com/wailsapp/wails/v2/pkg/runtime"|runtime\.' --type go . || true

# Read-only Go parser probe without running repo code, using vendor std only if present.
tmp="$(mktemp -d)"
cat > "$tmp/astprobe.go" <<'EOF'
package main

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
)

func main() {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "m.go", `package main

import . "github.com/wailsapp/wails/v2/pkg/runtime"

func f(ctx Context) {
	Quit(ctx)
	runtime.Quit(ctx)
}

const _ = "github.com/wailsapp/wails/v2/pkg/runtime".(string)
var _ = "github.com/wailsapp/wails/v2/pkg/runtime".(Context)
`, 0)
	if err != nil {
		panic(err)
	}
	ast.Inspect(f, func(n ast.Node) bool {
		switch node := n.(type) {
		case *ast.CallExpr:
			switch fn := node.Fun.(type) {
			case *ast.Ident:
				fmt.Printf("call identifier: %s\n", fn.Name)
			case *ast.SelectorExpr:
				fmt.Printf("call selector: package=%s function=%s\n", fn.X.(*ast.Ident).Name, fn.Sel.Name)
			}
		case *ast.CompositeLit:
			switch typ := node.Type.(type) {
			case *ast.SelectorExpr:
				fmt.Printf("type selector: package=%s type=%s\n", typ.X.(*ast.Ident).Name, typ.Sel.Name)
			case *ast.Ident:
				fmt.Printf("type identifier: %s\n", typ.Name)
			}
		}
		return true
	})
}
EOF
if [ -f "$tmp/astprobe.go" ]; then
  cd "$tmp"
  GOPATH=/tmp/gopath GOFLAGS="-mod=vendor" go run astprobe.go || true
fi

Repository: wailsapp/wails

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the targeted advisor output for selector-only AST nodes.
rg -n -C 4 'func adviseGoRuntimeCalls|func reportCall|CallSite|SelectorExpr' v3/internal/migrate/advisor.go

# Check tracked migration docs for dot-import coverage in GTK/WebKit contexts.
for f in IMPLEMENTATION.md m3/IMPLEMENTATION.md v3/IMPLEMENTATION.md MIGRATION.md README.md; do
  if [ -f "$f" ]; then
    echo "== $f =="
    rg -n -C 4 'dot import|dot imported|import \.|"github\.com/wailsapp/wails/v2/pkg/runtime"|Quit\(ctx\)|runtime\.' "$f" || true
  fi
done

# Precise search under migrate tests/fixtures for dot imports if present.
rg -n -C 3 --type go 'import\s+\.\s*"github\.com/wailsapp/wails/v2/pkg/runtime"|dot import|CallExpr\(' v3/internal/migrate || true

Repository: wailsapp/wails

Length of output: 1793


Handle dot-imported v2 runtime calls.

With import . "github.com/wailsapp/wails/v2/pkg/runtime", calls such as Quit(ctx) appear as unqualified ast.CallExpr nodes, but adviseGoRuntimeCalls only records ast.SelectorExpr. Add dot-import handling and track matching unqualified calls so generated migration guidance does not omit those sites. Include runtime.Updates when a dot import is present and build an *ast.Ident reference to the v2 runtime package.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/advisor.go` around lines 111 - 151, Extend
adviseGoRuntimeCalls to detect dot-imported V2RuntimeImport entries, retaining
an *ast.Ident reference for the v2 runtime package and recording runtime.Updates
when such an import is present. During AST inspection, also match unqualified
ast.CallExpr calls against the known runtime advice names and report them using
the v2 runtime reference, while preserving existing SelectorExpr handling for
aliased or named imports.

}
}

var wailsjsImportRe = regexp.MustCompile(`(?:from\s*|require\s*\(\s*)['"]([^'"]*wailsjs/(runtime|go)/[^'"]*)['"]`)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect extractor behavior and locate frontend import fixtures.
rg -n -C 8 --type go \
  'wailsjsImportRe|FindStringSubmatch|FindAllStringSubmatch|adviseFrontendImports' \
  v3/internal/migrate/advisor.go

rg -n \
  -g '*.js' -g '*.jsx' -g '*.ts' -g '*.tsx' -g '*.svelte' -g '*.vue' \
  -g '*.html' -g '*.mjs' -g '*.cjs' \
  'wailsjs/(runtime|go)/' v3 || true

Repository: wailsapp/wails

Length of output: 2087


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import re

wailsjsImportRe = re.compile(r"(?:from\s*|require\s*\(\s*)['"]([^'"]*wailsjs/(runtime|go)/[^'"]*)['"]")

cases = {
    "named_static": "import foo from 'wailsjs/go/my/svc';",
    "side_effect": "import 'wailsjs/go/my/svc';",
    "dynamic": "import('wailsjs/go/my/svc')",
    "require_static": "require('wailsjs/runtime/foo');",
    "require_side_effect": "require('wailsjs/go/my/svc');",
    "two_line_imports": "import 'a';from 'b';",
    "two_matching_static": "import a from 'wailsjs/go/svc'; import b from 'wailsjs/go/svc2';",
    "two_matching_sideeffect": "import 'wailsjs/runtime/svc'; import 'wailsjs/go/svc2';",
    "single_named": "from 'wailsjs/runtime/foo';",
}
for name, line in cases.items():
    m = wailsjsImportRe.match(line)
    allm = wailsjsImportRe.findall(line)
    print(f"{name}")
    print("  line:", line)
    print("  match:", m.groups() if m else None)
    print("  all:", allm)
PY

printf '\nRelevant advisor.go slice:\n'
sed -n '155,220p' v3/internal/migrate/advisor.go

Repository: wailsapp/wails

Length of output: 428


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Relevant advisor.go slice:\n'
sed -n '155,220p' v3/internal/migrate/advisor.go

printf '\nBehavioral probe for the exact regex and reporter logic:\n'
python3 - <<'PY'
from typing import Tuple
import re

# Exact form found in source, with matching quote class as in the Go source.
wailsjsImportRe = re.compile(r"(?:from\s*|require\s*\(\s*)['"'"'"]([^'"'"'"]*wailsjs/(runtime|go)/[^'"'"'"]*)['"'"'"]")

cases = {
    "static_named": "import foo from 'wailsjs/go/my/svc';",
    "side_effect_static": "import 'wailsjs/go/my/svc';",
    "dynamic": "import('wailsjs/go/my/svc')",
    "require_static": "require('wailsjs/runtime/foo');",
    "require_side_effect": "require('wailsjs/go/my/svc');",
    "two_static_same_line": "import a from 'wailsjs/go/svc'; import b from 'wailsjs/go/svc2';",
    "two_side_effect_same_line": "import 'wailsjs/runtime/svc'; import 'wailsjs/go/svc2';",
    "single_static_like": "from 'wailsjs/runtime/foo';",
}

for name, line in cases.items():
    first = wailsjsImportRe.search(line)
    all_matches = wailsjsImportRe.findall(line)
    reports = []
    m = first
    while True:
        if m is None:
            break
        reports.append(m.group(1))
        m = wailsjsImportRe.search(line, pos=m.end())
    print(f"{name}:")
    print(f"  line: {line}")
    print(f"  first_report: {first.group(1) if first else None}")
    print(f"  find_all_capture_groups: {all_matches}")
    print(f"  all_reports_after_end_search: {reports}")
PY

Repository: wailsapp/wails

Length of output: 4060


Scan every wailsjs import.

The regex only sees from ... and require(...) imports. Side-effect static imports like import 'wailsjs/go/svc'; and dynamic imports like import('wailsjs/go/svc') are skipped. Also, FindStringSubmatch + the current loop reports only the first match on a line, so later same-line imports are left out of MIGRATION.md. Use the required import predicates and report every match before scanning further.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/advisor.go` at line 155, Update wailsjsImportRe and the
associated FindStringSubmatch scanning loop to recognize side-effect static
imports and dynamic import(...) expressions in addition to existing from/require
forms. Iterate through each line’s matches so every wailsjs import, including
multiple imports on one line, is reported in MIGRATION.md before continuing to
the next line.

Comment on lines +359 to +361
if !strings.Contains(src, "go 1.24") {
t.Errorf("go directive not raised:\n%s", src)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show minGoVersion and the v3 module's own go directive.
rg -n -C 3 'minGoVersion' --glob '*.go'
fd -H -t f '^go.mod$' | while IFS= read -r f; do
  echo "=== $f ==="
  rg -n '^(go|toolchain) ' "$f"
done

Repository: wailsapp/wails

Length of output: 152


🏁 Script executed:

#!/bin/bash
set -u

echo "Repository tracked files containing migrate_test.go or migrate/gomod.go:"
git ls-files | rg '(^|/)(migrate_test\.go|gomod\.go)$|(^|/)v3/internal/migrate/' || true

echo
echo "Search for minGoVersion and go directive text:"
rg -n -C 3 'minGoVersion|go 1\.2[0-9]|go \d+\.\d+|toolchain' . --glob '*.go' --glob '*.md' --glob '*.txt' || true

echo
echo "go.mod files and directives:"
git ls-files | rg '(^|/)go\.mod$' | while IFS= read -r f; do
  echo "=== $f ==="
  rg -n '^(go|toolchain) ' "$f" || true
done

echo
echo "IMPLEMENTATION.md references to Go:"
if git ls-files | rg -q '(^|/)IMPLEMENTATION\.md$'; then
  rg -n -C 2 -i 'go|wails v3|minimum|toolchain' IMPLEMENTATION.md
fi

Repository: wailsapp/wails

Length of output: 48708


Align migration minimum Go version with v3 toolchain.

Set v3/internal/migrate/gomod.go minGoVersion to 1.25, update v3/internal/migrate/migrate_test.go, and keep IMPLEMENTATION.md synchronized with the v3 Go decision.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/migrate_test.go` around lines 359 - 361, Align the v3
migration minimum Go version with the toolchain by changing minGoVersion in
gomod.go to 1.25, updating the migrate_test.go expectation from “go 1.24” to “go
1.25”, and synchronizing IMPLEMENTATION.md with the same v3 Go version decision.

Source: Learnings

Comment on lines +58 to +90
// Parse all Go files in the module (excluding frontend, build dir and
// hidden/vendor directories).
fset := token.NewFileSet()
files := map[string]*ast.File{} // abs path -> file
err = filepath.WalkDir(absDir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
name := d.Name()
if path != absDir && (strings.HasPrefix(name, ".") || name == "vendor" || name == "node_modules") {
return filepath.SkipDir
}
if path == proj.FrontendDir || path == filepath.Join(absDir, cfg.BuildDir) {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
return nil
}
file, perr := parser.ParseFile(fset, path, nil, parser.ParseComments|parser.SkipObjectResolution)
if perr != nil {
return fmt.Errorf("could not parse %s: %w", path, perr)
}
for _, imp := range file.Imports {
if imp.Path.Value == strconv.Quote(V2RuntimeImport) {
proj.UsesV2Runtime = true
}
}
files[path] = file
return nil
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Respect build constraints when selecting project files.

Line 62 walks every .go file in the tree. Line 76 only skips _test.go. This includes files disabled by //go:build and fixture trees such as testdata. A project with mutually exclusive platform entrypoints can trigger the false “more than one wails.Run call” failure. It can also resolve methods from files that cannot coexist in one target build.

Build the parsed source set from a defined Go build context. If cross-platform migration is supported, migrate each build-constrained entrypoint. If it is not supported, reject non-selected variants before writing output. Add fixtures for mutually exclusive platform files and testdata.

Based on learnings, platform-specific behavior must be controlled by build tags.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/parse.go` around lines 58 - 90, Update the source-file
selection in the filepath.WalkDir callback to honor Go build constraints using a
defined build context, excluding files and directories such as testdata that are
not part of the selected project build. If migration supports multiple
platforms, process each applicable constrained entrypoint; otherwise reject or
omit non-selected variants before populating files and writing output. Add
fixtures covering mutually exclusive platform files and testdata.

Source: Learnings

Comment on lines +148 to +239
// findRunCall looks for a statement of the form
//
// err := wails.Run(&options.App{...})
// err = wails.Run(...)
// wails.Run(...)
// if err := wails.Run(...); err != nil { ... }
//
// where "wails" is the local name of the github.com/wailsapp/wails/v2 import.
func findRunCall(fset *token.FileSet, path string, file *ast.File) *MainInfo {
imports := importMap(file)
wailsName := ""
for name, ipath := range imports {
if ipath == "github.com/wailsapp/wails/v2" {
wailsName = name
}
}
if wailsName == "" {
return nil
}

info := &MainInfo{Path: path, File: file, Fset: fset, Imports: imports}

isRunCall := func(n ast.Node) *ast.CallExpr {
call, ok := n.(*ast.CallExpr)
if !ok {
return nil
}
sel, ok := call.Fun.(*ast.SelectorExpr)
if !ok || sel.Sel.Name != "Run" {
return nil
}
ident, ok := sel.X.(*ast.Ident)
if !ok || ident.Name != wailsName {
return nil
}
return call
}

ast.Inspect(file, func(n ast.Node) bool {
if info.RunCall != nil {
return false
}
stmt, ok := n.(ast.Stmt)
if !ok {
return true
}
switch s := stmt.(type) {
case *ast.AssignStmt:
if len(s.Rhs) != 1 {
return true
}
call := isRunCall(s.Rhs[0])
if call == nil {
return true
}
info.RunStmt = s
info.RunCall = call
if len(s.Lhs) == 1 {
if ident, ok := s.Lhs[0].(*ast.Ident); ok {
info.ErrIdent = ident.Name
info.AssignTok = s.Tok
}
}
return false
case *ast.ExprStmt:
call := isRunCall(s.X)
if call == nil {
return true
}
info.RunStmt = s
info.RunCall = call
return false
}
return true
})

if info.RunCall == nil {
return nil
}

// Extract the &options.App{...} literal.
if len(info.RunCall.Args) == 1 {
arg := info.RunCall.Args[0]
if unary, ok := arg.(*ast.UnaryExpr); ok && unary.Op == token.AND {
arg = unary.X
}
if lit, ok := arg.(*ast.CompositeLit); ok {
info.AppLit = lit
}
}
return info
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Collect a single rewriteable wails.Run site.

Line 187 stops traversal after the first match. The parser therefore hides a second wails.Run in the same file, so the multiple-call guard at Line 104 cannot reject it. It also records the inner assignment of the documented if err := wails.Run(...); err != nil form as RunStmt. GenerateMain replaces that inner range with a v3 application/window block, but an if initializer accepts only one simple statement. The output is invalid Go.

Collect all candidates with their enclosing statement context. Reject multiple or unsupported contexts before writing output. For the if form, rewrite the complete *ast.IfStmt only when its error behavior can be preserved. Add fixtures for both forms.

Based on learnings from the supplied downstream code, GenerateMain replaces the exact MainInfo.RunStmt range.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/parse.go` around lines 148 - 239, Update findRunCall to
collect every wails.Run candidate instead of stopping at the first match,
preserving each call’s enclosing statement context; reject multiple candidates
and unsupported contexts before GenerateMain writes output. Detect the
documented if err := wails.Run(...); err != nil form and set RunStmt to the
complete *ast.IfStmt only when its error behavior can be preserved, otherwise
reject it, so GenerateMain replaces a valid rewrite range. Add fixtures covering
multiple calls and both assignment/if forms.

Comment on lines +309 to +448
// resolveBoundTypes maps each element of the Bind slice literal to its struct
// type and collects the exported methods of that type from the parsed files.
func resolveBoundTypes(fset *token.FileSet, files map[string]*ast.File, proj *V2Project, bind ast.Expr) []*BoundType {
lit, ok := bind.(*ast.CompositeLit)
if !ok {
proj.Report.Manual("Bind", "The Bind value is not a slice literal, so bound structs could not be discovered. The generated frontend/wailsjs shims may be incomplete; check frontend imports against the v3 bindings generated into frontend/bindings.")
return nil
}

mainFile := files[proj.Main.Path]
var result []*BoundType
for _, elt := range lit.Elts {
expr := printExpr(fset, elt)
typeName := resolveElementType(fset, files, mainFile, elt)
bt := &BoundType{Expr: expr, Name: typeName}
if typeName == "" {
proj.Report.Manual("Bind: "+expr,
"Could not statically determine the struct type of this Bind entry. It is still registered as a v3 service, but no frontend/wailsjs shim was generated for it.")
result = append(result, bt)
continue
}
pkgName, pkgPath, methods := collectMethods(fset, files, proj, typeName)
bt.PkgName = pkgName
bt.PkgPath = pkgPath
bt.Methods = methods
if len(methods) == 0 {
proj.Report.Note("Bind: " + expr + " (" + typeName + ") has no exported methods that could be discovered; no frontend/wailsjs shim was generated for it.")
}
result = append(result, bt)
}
return result
}

// resolveElementType attempts to resolve a Bind element expression to a
// struct type name using purely syntactic information:
//
// &App{...} -> App
// NewApp() -> return type of func NewApp
// app -> declaration of app in the same file (app := NewApp(),
// app := &App{}, var app = ...)
func resolveElementType(fset *token.FileSet, files map[string]*ast.File, mainFile *ast.File, expr ast.Expr) string {
switch e := expr.(type) {
case *ast.UnaryExpr:
if e.Op == token.AND {
if lit, ok := e.X.(*ast.CompositeLit); ok {
if ident, ok := lit.Type.(*ast.Ident); ok {
return ident.Name
}
}
}
case *ast.CallExpr:
if ident, ok := e.Fun.(*ast.Ident); ok {
return constructorReturnType(files, ident.Name)
}
case *ast.Ident:
var typeName string
ast.Inspect(mainFile, func(n ast.Node) bool {
if typeName != "" {
return false
}
assign, ok := n.(*ast.AssignStmt)
if !ok || len(assign.Lhs) != 1 || len(assign.Rhs) != 1 {
return true
}
lhs, ok := assign.Lhs[0].(*ast.Ident)
if !ok || lhs.Name != e.Name {
return true
}
typeName = resolveElementType(fset, files, mainFile, assign.Rhs[0])
return false
})
return typeName
}
return ""
}

// constructorReturnType finds `func Name(...) *T` in the parsed files and
// returns T.
func constructorReturnType(files map[string]*ast.File, name string) string {
for _, file := range files {
for _, decl := range file.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok || fn.Recv != nil || fn.Name.Name != name {
continue
}
if fn.Type.Results == nil || len(fn.Type.Results.List) == 0 {
return ""
}
t := fn.Type.Results.List[0].Type
if star, ok := t.(*ast.StarExpr); ok {
t = star.X
}
if ident, ok := t.(*ast.Ident); ok {
return ident.Name
}
}
}
return ""
}

// collectMethods gathers the exported methods declared on *typeName or
// typeName across the parsed files.
func collectMethods(fset *token.FileSet, files map[string]*ast.File, proj *V2Project, typeName string) (pkgName, pkgPath string, methods []*BoundMethod) {
// Sort file paths for deterministic method order across map iteration.
paths := make([]string, 0, len(files))
for path := range files {
paths = append(paths, path)
}
sort.Strings(paths)

for _, path := range paths {
file := files[path]
for _, decl := range file.Decls {
fn, ok := decl.(*ast.FuncDecl)
if !ok || fn.Recv == nil || len(fn.Recv.List) != 1 {
continue
}
recv := fn.Recv.List[0].Type
if star, ok := recv.(*ast.StarExpr); ok {
recv = star.X
}
ident, ok := recv.(*ast.Ident)
if !ok || ident.Name != typeName {
continue
}
if !fn.Name.IsExported() {
continue
}
if pkgName == "" {
pkgName = file.Name.Name
pkgPath = packagePath(proj, path, pkgName)
}
methods = append(methods, &BoundMethod{
Name: fn.Name.Name,
Params: fieldListParams(fset, fn.Type.Params),
Results: fieldListParams(fset, fn.Type.Results),
})
}
}
return pkgName, pkgPath, methods

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Resolve each Bind entry in lexical and package scope.

Line 364 searches assignments only by identifier text. An earlier app := ... in another function can be selected, and var app = ... is never inspected despite the documented form. Line 387 then searches every parsed package for NewApp. Lines 419-446 merge methods from every package with the same receiver spelling. Go identifiers and types are package-scoped. A duplicate NewApp or App in a subpackage can associate a Bind entry with an unrelated type and method list.

Resolve declarations relative to the wails.Run file and lexical scope. Preserve package identity with the resolved type. Restrict constructor and method lookup to that package. Add fixtures for a package-level var, a local shadow, and duplicate symbols in a subpackage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/parse.go` around lines 309 - 448, Update
resolveElementType, constructorReturnType, and collectMethods to resolve each
Bind entry from the wails.Run file’s lexical scope and retain its package
identity. Handle package-level and local var declarations while selecting the
nearest valid declaration, and restrict constructor and receiver-method lookup
to the resolved package instead of matching names across all parsed files. Add
fixtures covering package-level vars, local shadowing, and duplicate symbols in
a subpackage.

Comment on lines +60 to +70
sb.WriteString("## Next steps\n\n")
sb.WriteString("1. Run `wails3 doctor` to check your environment.\n")
sb.WriteString("2. Port the call sites listed below to the v3 API. The compiler will point at\n")
sb.WriteString(" them: the project intentionally does not build until they are ported.\n")
sb.WriteString(" Do not run `go mod tidy` before this is done - it would re-add the v2\n")
sb.WriteString(" dependency and make the old calls compile, but they cannot work inside a\n")
sb.WriteString(" v3 application and will fail at runtime.\n")
sb.WriteString("3. Run `go mod tidy`, then `wails3 generate bindings` for the frontend bindings.\n")
sb.WriteString("4. Run `wails3 dev` to build and run the migrated app.\n")
sb.WriteString("5. Work through the *Manual steps* below, if any.\n")
sb.WriteString(" See https://v3.wails.io/migration/v2-to-v3/ for the full guide.\n\n")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the call-site step conditional.

When r.callSites is empty, Lines 62-66 refer to call sites listed below and state that the project does not build. The report renders no call-site section in that case. Emit this step only when call sites exist, and renumber the remaining steps.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@v3/internal/migrate/report.go` around lines 60 - 70, Update the next-steps
rendering in the report builder around r.callSites so the call-site migration
step and its continuation lines are emitted only when callSites is non-empty.
Renumber the subsequent go mod tidy, dev, and manual-steps items based on
whether that conditional step was included, while preserving the existing
guidance text.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cli Documentation Improvements or additions to documentation v3-alpha

Projects

Status: Todo

Development

Successfully merging this pull request may close these issues.

2 participants