11package v3
22
33import (
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
1418func 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+ }
0 commit comments