Skip to content

Commit 580cce6

Browse files
authored
Merge pull request #229 from gofiber/codex/2025-11-29-16-18-20
2 parents 10cbf24 + 493cdbf commit 580cce6

File tree

2 files changed

+200
-13
lines changed

2 files changed

+200
-13
lines changed
Lines changed: 180 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package v3
22

33
import (
4+
"bytes"
45
"fmt"
5-
"regexp"
6+
"go/ast"
7+
"go/format"
8+
"go/parser"
9+
"go/token"
610
"strings"
711

812
semver "github.com/Masterminds/semver/v3"
@@ -12,17 +16,89 @@ import (
1216
)
1317

1418
func MigrateRedirectMethods(cmd *cobra.Command, cwd string, _, _ *semver.Version) error {
15-
replacer := strings.NewReplacer(
16-
".RedirectBack(", ".Redirect().Back(",
17-
".RedirectToRoute(", ".Redirect().Route(",
18-
)
19-
2019
changed, err := internal.ChangeFileContent(cwd, func(content string) string {
21-
re := regexp.MustCompile(`\.Redirect\([^)]`)
22-
content = re.ReplaceAllStringFunc(content, func(s string) string {
23-
return strings.Replace(s, ".Redirect(", ".Redirect().To(", 1)
20+
orig := content
21+
22+
fset := token.NewFileSet()
23+
file, err := parser.ParseFile(fset, "", content, parser.ParseComments)
24+
if err != nil {
25+
return content
26+
}
27+
28+
modified := false
29+
30+
baseIdent := func(expr ast.Expr) *ast.Ident {
31+
for {
32+
switch e := expr.(type) {
33+
case *ast.Ident:
34+
return e
35+
case *ast.SelectorExpr:
36+
expr = e.X
37+
case *ast.CallExpr:
38+
expr = e.Fun
39+
default:
40+
return nil
41+
}
42+
}
43+
}
44+
45+
ast.Inspect(file, func(n ast.Node) bool {
46+
call, ok := n.(*ast.CallExpr)
47+
if !ok {
48+
return true
49+
}
50+
51+
sel, ok := call.Fun.(*ast.SelectorExpr)
52+
if !ok {
53+
return true
54+
}
55+
56+
ident := baseIdent(sel.X)
57+
if ident == nil || !isFiberCtx(orig, ident.Name) {
58+
return true
59+
}
60+
61+
switch sel.Sel.Name {
62+
case "Redirect":
63+
if len(call.Args) == 0 {
64+
return true
65+
}
66+
67+
if len(call.Args) > 1 {
68+
*call = wrapWithRedirectStatus(sel.X, call.Args[0], call.Args[1], "To")
69+
} else {
70+
redirectCall := &ast.CallExpr{Fun: &ast.SelectorExpr{X: sel.X, Sel: ast.NewIdent("Redirect")}}
71+
*call = ast.CallExpr{
72+
Fun: &ast.SelectorExpr{X: redirectCall, Sel: ast.NewIdent("To")},
73+
Args: []ast.Expr{call.Args[0]},
74+
}
75+
}
76+
modified = true
77+
case "RedirectBack":
78+
transformRedirectCall(call, sel.X, "Back", func(ctx ast.Expr, args []ast.Expr, status ast.Expr) ast.CallExpr {
79+
return wrapWithRedirectStatus(ctx, args[0], status, "Back")
80+
})
81+
modified = true
82+
case "RedirectToRoute":
83+
transformRedirectCall(call, sel.X, "Route", wrapRouteWithStatus)
84+
modified = true
85+
default:
86+
return true
87+
}
88+
89+
return true
2490
})
25-
return replacer.Replace(content)
91+
92+
if !modified {
93+
return content
94+
}
95+
96+
var buf bytes.Buffer
97+
if err := format.Node(&buf, fset, file); err == nil {
98+
content = buf.String()
99+
}
100+
101+
return content
26102
})
27103
if err != nil {
28104
return fmt.Errorf("failed to migrate redirect methods: %w", err)
@@ -34,3 +110,97 @@ func MigrateRedirectMethods(cmd *cobra.Command, cwd string, _, _ *semver.Version
34110
cmd.Println("Migrating redirect methods")
35111
return nil
36112
}
113+
114+
func transformRedirectCall(call *ast.CallExpr, ctx ast.Expr, method string, statusWrapper func(ast.Expr, []ast.Expr, ast.Expr) ast.CallExpr) {
115+
redirectCall := &ast.CallExpr{Fun: &ast.SelectorExpr{X: ctx, Sel: ast.NewIdent("Redirect")}}
116+
args := call.Args
117+
118+
if len(call.Args) > 1 && isStatusArg(call.Args[len(call.Args)-1]) {
119+
statusArg := call.Args[len(call.Args)-1]
120+
args = call.Args[:len(call.Args)-1]
121+
*call = statusWrapper(ctx, args, statusArg)
122+
return
123+
}
124+
125+
*call = ast.CallExpr{
126+
Fun: &ast.SelectorExpr{X: redirectCall, Sel: ast.NewIdent(method)},
127+
Args: args,
128+
}
129+
}
130+
131+
func wrapWithRedirectStatus(ctx, target, status ast.Expr, method string) ast.CallExpr {
132+
redirectCall := &ast.CallExpr{Fun: &ast.SelectorExpr{X: ctx, Sel: ast.NewIdent("Redirect")}}
133+
134+
targetIdent := ast.NewIdent("__fiberRedirectTarget")
135+
statusIdent := ast.NewIdent("__fiberRedirectStatus")
136+
137+
redirectStatus := &ast.CallExpr{
138+
Fun: &ast.SelectorExpr{X: redirectCall, Sel: ast.NewIdent("Status")},
139+
Args: []ast.Expr{statusIdent},
140+
}
141+
142+
redirectWithTarget := &ast.CallExpr{
143+
Fun: &ast.SelectorExpr{X: redirectStatus, Sel: ast.NewIdent(method)},
144+
Args: []ast.Expr{targetIdent},
145+
}
146+
147+
return ast.CallExpr{
148+
Fun: &ast.FuncLit{
149+
Type: &ast.FuncType{Params: &ast.FieldList{}},
150+
Body: &ast.BlockStmt{List: []ast.Stmt{
151+
&ast.AssignStmt{Lhs: []ast.Expr{targetIdent}, Tok: token.DEFINE, Rhs: []ast.Expr{target}},
152+
&ast.AssignStmt{Lhs: []ast.Expr{statusIdent}, Tok: token.DEFINE, Rhs: []ast.Expr{status}},
153+
&ast.ReturnStmt{Results: []ast.Expr{redirectWithTarget}},
154+
}},
155+
},
156+
}
157+
}
158+
159+
func wrapRouteWithStatus(ctx ast.Expr, args []ast.Expr, status ast.Expr) ast.CallExpr {
160+
redirectCall := &ast.CallExpr{Fun: &ast.SelectorExpr{X: ctx, Sel: ast.NewIdent("Redirect")}}
161+
statusIdent := ast.NewIdent("__fiberRedirectStatus")
162+
163+
var routeArgIdents []ast.Expr
164+
var stmts []ast.Stmt
165+
for i, arg := range args {
166+
ident := ast.NewIdent(fmt.Sprintf("__fiberRedirectRouteArg%d", i))
167+
routeArgIdents = append(routeArgIdents, ident)
168+
stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{ident}, Tok: token.DEFINE, Rhs: []ast.Expr{arg}})
169+
}
170+
171+
stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{statusIdent}, Tok: token.DEFINE, Rhs: []ast.Expr{status}})
172+
173+
redirectStatus := &ast.CallExpr{
174+
Fun: &ast.SelectorExpr{X: redirectCall, Sel: ast.NewIdent("Status")},
175+
Args: []ast.Expr{statusIdent},
176+
}
177+
178+
redirectRoute := &ast.CallExpr{
179+
Fun: &ast.SelectorExpr{X: redirectStatus, Sel: ast.NewIdent("Route")},
180+
Args: routeArgIdents,
181+
}
182+
183+
stmts = append(stmts, &ast.ReturnStmt{Results: []ast.Expr{redirectRoute}})
184+
185+
return ast.CallExpr{
186+
Fun: &ast.FuncLit{
187+
Type: &ast.FuncType{Params: &ast.FieldList{}},
188+
Body: &ast.BlockStmt{List: stmts},
189+
},
190+
}
191+
}
192+
193+
func isStatusArg(expr ast.Expr) bool {
194+
switch v := expr.(type) {
195+
case *ast.BasicLit:
196+
return v.Kind == token.INT
197+
case *ast.UnaryExpr:
198+
return (v.Op == token.ADD || v.Op == token.SUB) && isStatusArg(v.X)
199+
case *ast.SelectorExpr:
200+
if ident, ok := v.X.(*ast.Ident); ok {
201+
return (ident.Name == "fiber" || ident.Name == "http") && strings.HasPrefix(v.Sel.Name, "Status")
202+
}
203+
}
204+
205+
return false
206+
}

cmd/internal/migrations/v3/redirect_methods_test.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package v3_test
33
import (
44
"bytes"
55
"os"
6+
"strings"
67
"testing"
78

89
"github.com/stretchr/testify/assert"
@@ -22,8 +23,12 @@ func Test_MigrateRedirectMethods(t *testing.T) {
2223
import "github.com/gofiber/fiber/v2"
2324
func handler(c fiber.Ctx) error {
2425
c.Redirect("/foo")
26+
c.Redirect("/bar", fiber.StatusPermanentRedirect)
2527
c.RedirectBack()
28+
c.RedirectBack("/fallback", 301)
2629
c.RedirectToRoute("home")
30+
c.RedirectToRoute("home-redirect", 301)
31+
c.RedirectToRoute("dashboard", fiber.Map{}, 308)
2732
return nil
2833
}
2934
`)
@@ -34,8 +39,20 @@ func handler(c fiber.Ctx) error {
3439

3540
content := readFile(t, file)
3641
assert.Contains(t, content, ".Redirect().To(\"/foo\")")
42+
assert.Contains(t, content, "__fiberRedirectTarget := \"/bar\"")
43+
assert.Contains(t, content, "__fiberRedirectStatus := fiber.StatusPermanentRedirect")
44+
assert.Contains(t, content, "return c.Redirect().Status(__fiberRedirectStatus).To(__fiberRedirectTarget)")
3745
assert.Contains(t, content, ".Redirect().Back()")
46+
assert.Contains(t, content, "__fiberRedirectTarget := \"/fallback\"")
47+
assert.Contains(t, content, "__fiberRedirectStatus := 301")
48+
assert.Contains(t, content, "return c.Redirect().Status(__fiberRedirectStatus).Back(__fiberRedirectTarget)")
3849
assert.Contains(t, content, ".Redirect().Route(\"home\")")
50+
assert.Contains(t, content, "__fiberRedirectRouteArg0 := \"home-redirect\"")
51+
assert.Contains(t, content, "return c.Redirect().Status(__fiberRedirectStatus).Route(__fiberRedirectRouteArg0)")
52+
assert.Contains(t, content, "__fiberRedirectRouteArg1 := fiber.Map{}")
53+
assert.Contains(t, content, "return c.Redirect().Status(__fiberRedirectStatus).Route(__fiberRedirectRouteArg0, __fiberRedirectRouteArg1)")
54+
assert.Contains(t, content, "__fiberRedirectRouteArg0 := \"dashboard\"")
55+
assert.Contains(t, content, "__fiberRedirectStatus := 308")
3956
assert.Contains(t, buf.String(), "Migrating redirect methods")
4057
}
4158

@@ -49,7 +66,7 @@ func Test_MigrateRedirectMethodsTwice(t *testing.T) {
4966
file := writeTempFile(t, dir, `package main
5067
import "github.com/gofiber/fiber/v2"
5168
func handler(c fiber.Ctx) error {
52-
c.Redirect("/foo")
69+
c.Redirect("/foo", 302)
5370
return nil
5471
}
5572
`)
@@ -62,6 +79,6 @@ func handler(c fiber.Ctx) error {
6279
require.NoError(t, v3.MigrateRedirectMethods(cmd, dir, nil, nil))
6380

6481
content := readFile(t, file)
65-
assert.Contains(t, content, ".Redirect().To(\"/foo\")")
66-
assert.NotContains(t, content, ".Redirect().To().To(")
82+
assert.Equal(t, 1, strings.Count(content, "__fiberRedirectStatus := 302"))
83+
assert.Contains(t, content, "return c.Redirect().Status(__fiberRedirectStatus).To(__fiberRedirectTarget)")
6784
}

0 commit comments

Comments
 (0)