diff --git a/cmd/internal/migrations/v3/cache_config.go b/cmd/internal/migrations/v3/cache_config.go index bba708e..3ecd7bf 100644 --- a/cmd/internal/migrations/v3/cache_config.go +++ b/cmd/internal/migrations/v3/cache_config.go @@ -35,17 +35,10 @@ func MigrateCacheConfig(cmd *cobra.Command, cwd string, _, _ *semver.Version) er s = strings.ReplaceAll(s, "Store:", "Storage:") s = strings.ReplaceAll(s, "Key:", "KeyGenerator:") s = reCacheControl.ReplaceAllStringFunc(s, func(match string) string { - value := strings.TrimPrefix(match, "CacheControl:") - value = strings.TrimSpace(value) + rawValue := strings.TrimPrefix(match, "CacheControl:") + rawValue = strings.TrimSpace(rawValue) - comment := "" - if idx := strings.Index(value, "//"); idx != -1 { - comment = strings.TrimSpace(value[idx:]) - value = value[:idx] - } else if idx := strings.Index(value, "/*"); idx != -1 { - comment = strings.TrimSpace(value[idx:]) - value = value[:idx] - } + value, comment := ExtractCommentAndValue(rawValue) hasComma := strings.HasSuffix(strings.TrimRight(value, " \t"), ",") if hasComma { diff --git a/cmd/internal/migrations/v3/common.go b/cmd/internal/migrations/v3/common.go index 3fb3deb..dd0771e 100644 --- a/cmd/internal/migrations/v3/common.go +++ b/cmd/internal/migrations/v3/common.go @@ -2,6 +2,7 @@ package v3 import ( "fmt" + "go/ast" "regexp" "sort" "strconv" @@ -480,3 +481,89 @@ func addImport(content, path string) string { return content } + +// GetBaseIdent recursively resolves an expression to its base identifier. +// It handles selector expressions, call expressions, and identifiers. +// Returns nil if the expression cannot be resolved to a simple identifier. +func GetBaseIdent(expr ast.Expr) *ast.Ident { + for { + switch e := expr.(type) { + case *ast.Ident: + return e + case *ast.SelectorExpr: + expr = e.X + case *ast.CallExpr: + expr = e.Fun + default: + return nil + } + } +} + +// ExtractCommentAndValue separates a value from its trailing comment. +// It handles both line comments (//) and block comments (/* */). +// Returns the value (trimmed) and the comment (with original delimiters). +func ExtractCommentAndValue(line string) (value, comment string) { + value = line + if idx := strings.Index(line, "//"); idx >= 0 { + comment = strings.TrimSpace(line[idx:]) + value = strings.TrimSpace(line[:idx]) + } else if idx := strings.Index(line, "/*"); idx >= 0 { + comment = strings.TrimSpace(line[idx:]) + value = strings.TrimSpace(line[:idx]) + } + return value, comment +} + +// FormatFieldWithComment formats a field assignment with consistent spacing +// for indentation, value, comma, comment, and newline. +func FormatFieldWithComment(indent, fieldName, value, comma, comment, newline string) string { + if comment != "" { + comment = " " + comment + } + return fmt.Sprintf("%s%s: %s%s%s%s", indent, fieldName, value, comma, comment, newline) +} + +// IterateConfigBlocks finds all occurrences matching the given regex pattern, +// extracts their config blocks using braces, processes each block with the +// provided function, and reconstructs the content. +func IterateConfigBlocks(content string, pattern *regexp.Regexp, processor func(string) string) string { + matches := pattern.FindAllStringIndex(content, -1) + if len(matches) == 0 { + return content + } + + var b strings.Builder + last := 0 + for _, m := range matches { + if m[0] < last { + // Skip matches that fall inside a block we've already processed. + continue + } + b.WriteString(content[last:m[0]]) //nolint:errcheck // WriteString never returns an error + start := m[0] + end := extractBlock(content, m[1], '{', '}') + cfg := content[start:end] + + // Process the config block + cfg = processor(cfg) + + b.WriteString(cfg) //nolint:errcheck // WriteString never returns an error + last = end + } + b.WriteString(content[last:]) //nolint:errcheck // WriteString never returns an error + return b.String() +} + +// BuildExtractorChain builds an extractor expression from a slice of extractors. +// Returns a single extractor for one element, Chain() for multiple, or empty for none. +func BuildExtractorChain(extractors []string) string { + switch len(extractors) { + case 0: + return "" + case 1: + return extractors[0] + default: + return fmt.Sprintf("extractors.Chain(%s)", strings.Join(extractors, ", ")) + } +} diff --git a/cmd/internal/migrations/v3/context_methods.go b/cmd/internal/migrations/v3/context_methods.go index ad793eb..f3f0dde 100644 --- a/cmd/internal/migrations/v3/context_methods.go +++ b/cmd/internal/migrations/v3/context_methods.go @@ -26,20 +26,6 @@ func MigrateContextMethods(cmd *cobra.Command, cwd string, _, _ *semver.Version) file, err := parser.ParseFile(fset, "", content, parser.ParseComments) if err == nil { modified := false - baseIdent := func(expr ast.Expr) *ast.Ident { - for { - switch e := expr.(type) { - case *ast.Ident: - return e - case *ast.SelectorExpr: - expr = e.X - case *ast.CallExpr: - expr = e.Fun - default: - return nil - } - } - } ast.Inspect(file, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { @@ -49,7 +35,7 @@ func MigrateContextMethods(cmd *cobra.Command, cwd string, _, _ *semver.Version) if !ok || sel.Sel.Name != "Context" || len(call.Args) != 0 { return true } - if ident := baseIdent(sel.X); ident != nil && isFiberCtx(orig, ident.Name) { + if ident := GetBaseIdent(sel.X); ident != nil && isFiberCtx(orig, ident.Name) { sel.Sel.Name = "RequestCtx" modified = true } diff --git a/cmd/internal/migrations/v3/csrfconfig.go b/cmd/internal/migrations/v3/csrfconfig.go index abf9838..b6f212b 100644 --- a/cmd/internal/migrations/v3/csrfconfig.go +++ b/cmd/internal/migrations/v3/csrfconfig.go @@ -15,55 +15,27 @@ func MigrateCSRFConfig(cmd *cobra.Command, cwd string, _, _ *semver.Version) err reConfig := regexp.MustCompile(`csrf\.Config{`) reSession := regexp.MustCompile(`(?m)\s*SessionKey:\s*[^,\n]+,?\s*(//[^\n]*)?\n`) changed, err := internal.ChangeFileContent(cwd, func(content string) string { - matches := reConfig.FindAllStringIndex(content, -1) - if len(matches) == 0 { - return content - } - - var b strings.Builder - last := 0 - for _, m := range matches { - if _, err := b.WriteString(content[last:m[0]]); err != nil { - return content - } - start := m[0] - end := extractBlock(content, m[1], '{', '}') - cfg := content[start:end] + updated := IterateConfigBlocks(content, reConfig, func(cfg string) string { cfg = strings.ReplaceAll(cfg, "Expiration:", "IdleTimeout:") cfg = reSession.ReplaceAllString(cfg, "") - cfg = replaceKeyLookup(cfg, func(indent, val, comma, comment, newline string) string { + return replaceKeyLookup(cfg, func(indent, val, comma, comment, newline string) string { var extractor string switch { case strings.HasPrefix(val, "header:"): - extractor = fmt.Sprintf("Extractor: extractors.FromHeader(%q)", strings.TrimPrefix(val, "header:")) + extractor = fmt.Sprintf("extractors.FromHeader(%q)", strings.TrimPrefix(val, "header:")) case strings.HasPrefix(val, "form:"): - extractor = fmt.Sprintf("Extractor: extractors.FromForm(%q)", strings.TrimPrefix(val, "form:")) + extractor = fmt.Sprintf("extractors.FromForm(%q)", strings.TrimPrefix(val, "form:")) case strings.HasPrefix(val, "query:"): - extractor = fmt.Sprintf("Extractor: extractors.FromQuery(%q)", strings.TrimPrefix(val, "query:")) + extractor = fmt.Sprintf("extractors.FromQuery(%q)", strings.TrimPrefix(val, "query:")) default: - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s%s%s", indent, val, comment, newline) + return FormatFieldWithComment(indent, "// TODO: migrate KeyLookup", val, "", comment, newline) } - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s%s%s%s%s", indent, extractor, comma, comment, newline) + return FormatFieldWithComment(indent, "Extractor", extractor, comma, comment, newline) }) + }) - if _, err := b.WriteString(cfg); err != nil { - return content - } - last = end - } - if _, err := b.WriteString(content[last:]); err != nil { - return content - } - - updated := b.String() if updated != content { updated = addImport(updated, "github.com/gofiber/fiber/v3/extractors") } diff --git a/cmd/internal/migrations/v3/encryptcookie_config.go b/cmd/internal/migrations/v3/encryptcookie_config.go index 3824a41..b399de7 100644 --- a/cmd/internal/migrations/v3/encryptcookie_config.go +++ b/cmd/internal/migrations/v3/encryptcookie_config.go @@ -15,34 +15,11 @@ func MigrateEncryptcookieConfig(cmd *cobra.Command, cwd string, _, _ *semver.Ver reConfig := regexp.MustCompile(`encryptcookie\.Config{`) changed, err := internal.ChangeFileContent(cwd, func(content string) string { - matches := reConfig.FindAllStringIndex(content, -1) - if len(matches) == 0 { - return content - } - - var b strings.Builder - last := 0 - for _, m := range matches { - if _, err := b.WriteString(content[last:m[0]]); err != nil { - return content - } - - start := m[0] - end := extractBlock(content, m[1], '{', '}') - cfg := content[start:end] + return IterateConfigBlocks(content, reConfig, func(cfg string) string { cfg = addEncryptcookieParam(cfg, "Encryptor") cfg = addEncryptcookieParam(cfg, "Decryptor") - - if _, err := b.WriteString(cfg); err != nil { - return content - } - last = end - } - - if _, err := b.WriteString(content[last:]); err != nil { - return content - } - return b.String() + return cfg + }) }) if err != nil { return fmt.Errorf("failed to migrate encryptcookie configs: %w", err) diff --git a/cmd/internal/migrations/v3/jwt_extractor.go b/cmd/internal/migrations/v3/jwt_extractor.go index ddea3e4..4d8889b 100644 --- a/cmd/internal/migrations/v3/jwt_extractor.go +++ b/cmd/internal/migrations/v3/jwt_extractor.go @@ -60,33 +60,16 @@ func MigrateJWTExtractor(cmd *cobra.Command, cwd string, _, _ *semver.Version) e case strings.HasPrefix(p, "form:"): extractors = append(extractors, fmt.Sprintf("extractors.FromForm(%q)", strings.TrimPrefix(p, "form:"))) default: - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s// TODO: migrate TokenLookup: %s%s%s", indent, val, comment, newline) + return FormatFieldWithComment(indent, "// TODO: migrate TokenLookup", val, "", comment, newline) } } - extractor := "" - switch len(extractors) { - case 1: - extractor = extractors[0] - case 0: - default: - extractor = fmt.Sprintf("extractors.Chain(%s)", strings.Join(extractors, ", ")) - } - + extractor := BuildExtractorChain(extractors) if extractor == "" { - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s// TODO: migrate TokenLookup: %s%s%s", indent, val, comment, newline) + return FormatFieldWithComment(indent, "// TODO: migrate TokenLookup", val, "", comment, newline) } - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%sExtractor: %s%s%s%s", indent, extractor, comma, comment, newline) + return FormatFieldWithComment(indent, "Extractor", extractor, comma, comment, newline) }) cfg = reAuthLine.ReplaceAllString(cfg, "") diff --git a/cmd/internal/migrations/v3/key_auth_config.go b/cmd/internal/migrations/v3/key_auth_config.go index f8be944..64bfc1b 100644 --- a/cmd/internal/migrations/v3/key_auth_config.go +++ b/cmd/internal/migrations/v3/key_auth_config.go @@ -48,30 +48,16 @@ func MigrateKeyAuthConfig(cmd *cobra.Command, cwd string, _, _ *semver.Version) case strings.HasPrefix(p, "cookie:"): extractors = append(extractors, fmt.Sprintf("extractors.FromCookie(%q)", strings.TrimPrefix(p, "cookie:"))) default: - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s%s%s", indent, val, comment, newline) + return FormatFieldWithComment(indent, "// TODO: migrate KeyLookup", val, "", comment, newline) } } - extractor := "" - if len(extractors) == 1 { - extractor = extractors[0] - } else if len(extractors) > 1 { - extractor = fmt.Sprintf("extractors.Chain(%s)", strings.Join(extractors, ", ")) - } + extractor := BuildExtractorChain(extractors) if extractor == "" { - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s%s%s", indent, val, comment, newline) + return FormatFieldWithComment(indent, "// TODO: migrate KeyLookup", val, "", comment, newline) } - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%sExtractor: %s%s%s%s", indent, extractor, comma, comment, newline) + return FormatFieldWithComment(indent, "Extractor", extractor, comma, comment, newline) }) cfg = removeConfigField(cfg, "AuthScheme") diff --git a/cmd/internal/migrations/v3/paseto_extractor.go b/cmd/internal/migrations/v3/paseto_extractor.go index d19478d..17cc4ed 100644 --- a/cmd/internal/migrations/v3/paseto_extractor.go +++ b/cmd/internal/migrations/v3/paseto_extractor.go @@ -45,10 +45,7 @@ func MigratePasetoExtractor(cmd *cobra.Command, cwd string, _, _ *semver.Version lookup = strings.TrimSuffix(lookup, "}") parts := splitArgs(lookup) if len(parts) < 2 { - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s// TODO: migrate TokenLookup: %s%s%s", indent, val, comment, newline) + return FormatFieldWithComment(indent, "// TODO: migrate TokenLookup", val, "", comment, newline) } source := strings.TrimSpace(parts[0]) @@ -99,16 +96,10 @@ func MigratePasetoExtractor(cmd *cobra.Command, cwd string, _, _ *semver.Version } if extractor == "" { - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s// TODO: migrate TokenLookup: %s%s%s", indent, val, comment, newline) + return FormatFieldWithComment(indent, "// TODO: migrate TokenLookup", val, "", comment, newline) } - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%sExtractor: %s%s%s%s", indent, extractor, comma, comment, newline) + return FormatFieldWithComment(indent, "Extractor", extractor, comma, comment, newline) }) cfg = removeConfigField(cfg, "TokenPrefix") diff --git a/cmd/internal/migrations/v3/redirect_methods.go b/cmd/internal/migrations/v3/redirect_methods.go index 8ad23c8..8b823ea 100644 --- a/cmd/internal/migrations/v3/redirect_methods.go +++ b/cmd/internal/migrations/v3/redirect_methods.go @@ -27,21 +27,6 @@ func MigrateRedirectMethods(cmd *cobra.Command, cwd string, _, _ *semver.Version modified := false - baseIdent := func(expr ast.Expr) *ast.Ident { - for { - switch e := expr.(type) { - case *ast.Ident: - return e - case *ast.SelectorExpr: - expr = e.X - case *ast.CallExpr: - expr = e.Fun - default: - return nil - } - } - } - ast.Inspect(file, func(n ast.Node) bool { call, ok := n.(*ast.CallExpr) if !ok { @@ -53,7 +38,7 @@ func MigrateRedirectMethods(cmd *cobra.Command, cwd string, _, _ *semver.Version return true } - ident := baseIdent(sel.X) + ident := GetBaseIdent(sel.X) if ident == nil || !isFiberCtx(orig, ident.Name) { return true } diff --git a/cmd/internal/migrations/v3/session_config.go b/cmd/internal/migrations/v3/session_config.go index 6decadc..7677201 100644 --- a/cmd/internal/migrations/v3/session_config.go +++ b/cmd/internal/migrations/v3/session_config.go @@ -14,30 +14,9 @@ import ( func MigrateSessionConfig(cmd *cobra.Command, cwd string, _, _ *semver.Version) error { reConfig := regexp.MustCompile(`session\.Config{`) changed, err := internal.ChangeFileContent(cwd, func(content string) string { - matches := reConfig.FindAllStringIndex(content, -1) - if len(matches) == 0 { - return content - } - - var b strings.Builder - last := 0 - for _, m := range matches { - if _, err := b.WriteString(content[last:m[0]]); err != nil { - return content - } - start := m[0] - end := extractBlock(content, m[1], '{', '}') - cfg := content[start:end] - cfg = strings.ReplaceAll(cfg, "Expiration:", "IdleTimeout:") - if _, err := b.WriteString(cfg); err != nil { - return content - } - last = end - } - if _, err := b.WriteString(content[last:]); err != nil { - return content - } - return b.String() + return IterateConfigBlocks(content, reConfig, func(cfg string) string { + return strings.ReplaceAll(cfg, "Expiration:", "IdleTimeout:") + }) }) if err != nil { return fmt.Errorf("failed to migrate session configs: %w", err) diff --git a/cmd/internal/migrations/v3/session_extractor.go b/cmd/internal/migrations/v3/session_extractor.go index 7bdcf21..72bdb93 100644 --- a/cmd/internal/migrations/v3/session_extractor.go +++ b/cmd/internal/migrations/v3/session_extractor.go @@ -14,21 +14,8 @@ import ( func MigrateSessionExtractor(cmd *cobra.Command, cwd string, _, _ *semver.Version) error { reConfig := regexp.MustCompile(`session\.Config{`) changed, err := internal.ChangeFileContent(cwd, func(content string) string { - matches := reConfig.FindAllStringIndex(content, -1) - if len(matches) == 0 { - return content - } - - var b strings.Builder - last := 0 - for _, m := range matches { - if _, err := b.WriteString(content[last:m[0]]); err != nil { - return content - } - start := m[0] - end := extractBlock(content, m[1], '{', '}') - cfg := content[start:end] - cfg = replaceKeyLookup(cfg, func(indent, val, comma, comment, newline string) string { + updated := IterateConfigBlocks(content, reConfig, func(cfg string) string { + return replaceKeyLookup(cfg, func(indent, val, comma, comment, newline string) string { parts := strings.Split(val, ",") var extractors []string for _, p := range parts { @@ -41,40 +28,19 @@ func MigrateSessionExtractor(cmd *cobra.Command, cwd string, _, _ *semver.Versio case strings.HasPrefix(p, "query:"): extractors = append(extractors, fmt.Sprintf("extractors.FromQuery(%q)", strings.TrimPrefix(p, "query:"))) default: - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s%s%s", indent, val, comment, newline) - } - } - - if len(extractors) == 0 { - if comment != "" { - comment = " " + comment + return FormatFieldWithComment(indent, "// TODO: migrate KeyLookup", val, "", comment, newline) } - return fmt.Sprintf("%s// TODO: migrate KeyLookup: %s%s%s", indent, val, comment, newline) } - extractor := extractors[0] - if len(extractors) > 1 { - extractor = fmt.Sprintf("extractors.Chain(%s)", strings.Join(extractors, ", ")) + extractor := BuildExtractorChain(extractors) + if extractor == "" { + return FormatFieldWithComment(indent, "// TODO: migrate KeyLookup", val, "", comment, newline) } - if comment != "" { - comment = " " + comment - } - return fmt.Sprintf("%sExtractor: %s%s%s%s", indent, extractor, comma, comment, newline) + return FormatFieldWithComment(indent, "Extractor", extractor, comma, comment, newline) }) - if _, err := b.WriteString(cfg); err != nil { - return content - } - last = end - } - if _, err := b.WriteString(content[last:]); err != nil { - return content - } + }) - updated := b.String() if updated != content { updated = addImport(updated, "github.com/gofiber/fiber/v3/extractors") }