From 3e5fe69f6547772c27f9fa129cfcbe99cb111c6a Mon Sep 17 00:00:00 2001 From: RW Date: Sun, 24 Aug 2025 14:31:16 +0200 Subject: [PATCH 1/3] Handle commented fields across migrations --- .../migrations/v3/basicauth_config.go | 4 +- .../migrations/v3/basicauth_config_test.go | 38 +++++++++++++++++++ .../migrations/v3/config_listener_fields.go | 4 +- .../v3/config_listener_fields_test.go | 28 ++++++++++++++ cmd/internal/migrations/v3/csrfconfig.go | 2 +- cmd/internal/migrations/v3/csrfconfig_test.go | 25 ++++++++++++ .../migrations/v3/healthcheck_config.go | 4 +- .../migrations/v3/healthcheck_config_test.go | 31 +++++++++++++++ .../migrations/v3/middleware_locals.go | 2 +- .../migrations/v3/middleware_locals_test.go | 26 +++++++++++++ 10 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 cmd/internal/migrations/v3/basicauth_config_test.go diff --git a/cmd/internal/migrations/v3/basicauth_config.go b/cmd/internal/migrations/v3/basicauth_config.go index 2d4fd3e..ff881ca 100644 --- a/cmd/internal/migrations/v3/basicauth_config.go +++ b/cmd/internal/migrations/v3/basicauth_config.go @@ -14,8 +14,8 @@ import ( ) func MigrateBasicauthConfig(cmd *cobra.Command, cwd string, _, _ *semver.Version) error { - reCtxUser := regexp.MustCompile(`\s*ContextUsername:\s*[^,]+,?\n`) - reCtxPass := regexp.MustCompile(`\s*ContextPassword:\s*[^,]+,?\n`) + reCtxUser := regexp.MustCompile(`(?m)\s*ContextUsername:\s*[^,\n]+,?\s*(//[^\n]*)?\n`) + reCtxPass := regexp.MustCompile(`(?m)\s*ContextPassword:\s*[^,\n]+,?\s*(//[^\n]*)?\n`) reUsers := regexp.MustCompile(`Users:\s*map\[string\]string{([^}]*)}`) reEntry := regexp.MustCompile(`("[^"]+")\s*:\s*"((?:[^"\\]|\\.)*)"`) diff --git a/cmd/internal/migrations/v3/basicauth_config_test.go b/cmd/internal/migrations/v3/basicauth_config_test.go new file mode 100644 index 0000000..1c1746e --- /dev/null +++ b/cmd/internal/migrations/v3/basicauth_config_test.go @@ -0,0 +1,38 @@ +package v3_test + +import ( + "bytes" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/gofiber/cli/cmd/internal/migrations/v3" +) + +func Test_MigrateBasicauthConfig_WithComments(t *testing.T) { + t.Parallel() + + dir, err := os.MkdirTemp("", "mbasiccfg") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + file := writeTempFile(t, dir, `package main +import "github.com/gofiber/fiber/v2/middleware/basicauth" +var _ = basicauth.New(basicauth.Config{ + ContextUsername: "user", // username comment + ContextPassword: "pass", // password comment +})`) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateBasicauthConfig(cmd, dir, nil, nil)) + + content := readFile(t, file) + assert.NotContains(t, content, "ContextUsername") + assert.NotContains(t, content, "ContextPassword") + assert.NotContains(t, content, "username comment") + assert.NotContains(t, content, "password comment") + assert.Contains(t, buf.String(), "Migrating basicauth configs") +} diff --git a/cmd/internal/migrations/v3/config_listener_fields.go b/cmd/internal/migrations/v3/config_listener_fields.go index 80b9710..1951b65 100644 --- a/cmd/internal/migrations/v3/config_listener_fields.go +++ b/cmd/internal/migrations/v3/config_listener_fields.go @@ -17,14 +17,14 @@ func MigrateConfigListenerFields(cmd *cobra.Command, cwd string, _, _ *semver.Ve var disableStartup string var enablePrint string - reField := regexp.MustCompile(`\s*(Prefork|Network|DisableStartupMessage|EnablePrintRoutes):\s*([^,}]+),?`) + reField := regexp.MustCompile(`\s*(Prefork|Network|DisableStartupMessage|EnablePrintRoutes):\s*([^,}]+),?\s*(//[^\n]*)?`) changed1, err := internal.ChangeFileContent(cwd, func(content string) string { reConfig := regexp.MustCompile(`fiber\.Config\{[^}]*\}`) return reConfig.ReplaceAllStringFunc(content, func(cfg string) string { inner := cfg[len("fiber.Config{") : len(cfg)-1] inner = reField.ReplaceAllStringFunc(inner, func(f string) string { sub := reField.FindStringSubmatch(f) - if len(sub) == 3 { + if len(sub) >= 3 { name := sub[1] val := strings.TrimSpace(sub[2]) switch name { diff --git a/cmd/internal/migrations/v3/config_listener_fields_test.go b/cmd/internal/migrations/v3/config_listener_fields_test.go index 5978c8d..b1cf8c8 100644 --- a/cmd/internal/migrations/v3/config_listener_fields_test.go +++ b/cmd/internal/migrations/v3/config_listener_fields_test.go @@ -255,3 +255,31 @@ func main() { assert.Contains(t, content, `go app.Listen(":3000", fiber.ListenConfig{DisableStartupMessage: true})`) assert.Contains(t, buf.String(), "Migrating listener related config fields") } + +func Test_MigrateConfigListenerFields_WithComment(t *testing.T) { + t.Parallel() + + dir, err := os.MkdirTemp("", "mconf_comment") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + file := writeTempFile(t, dir, `package main +import "github.com/gofiber/fiber/v2" +var prod bool +func main() { + app := fiber.New(fiber.Config{ + Prefork: prod, // comment + }) + app.Listen(":3000") +}`) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateConfigListenerFields(cmd, dir, nil, nil)) + + content := readFile(t, file) + assert.Contains(t, content, "fiber.New(fiber.Config{})") + assert.NotContains(t, content, "// comment") + assert.Contains(t, content, `app.Listen(":3000", fiber.ListenConfig{EnablePrefork: prod})`) + assert.Contains(t, buf.String(), "Migrating listener related config fields") +} diff --git a/cmd/internal/migrations/v3/csrfconfig.go b/cmd/internal/migrations/v3/csrfconfig.go index 0558238..59ebc5e 100644 --- a/cmd/internal/migrations/v3/csrfconfig.go +++ b/cmd/internal/migrations/v3/csrfconfig.go @@ -13,7 +13,7 @@ import ( func MigrateCSRFConfig(cmd *cobra.Command, cwd string, _, _ *semver.Version) error { reConfig := regexp.MustCompile(`csrf\.Config{`) - reSession := regexp.MustCompile(`(?m)\s*SessionKey:\s*[^,]+,?\n`) + 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 { diff --git a/cmd/internal/migrations/v3/csrfconfig_test.go b/cmd/internal/migrations/v3/csrfconfig_test.go index 7d3e378..6f7c209 100644 --- a/cmd/internal/migrations/v3/csrfconfig_test.go +++ b/cmd/internal/migrations/v3/csrfconfig_test.go @@ -39,6 +39,31 @@ var _ = csrf.New(csrf.Config{ assert.Contains(t, buf.String(), "Migrating CSRF middleware configs") } +func Test_MigrateCSRFConfig_SessionKeyWithComment(t *testing.T) { + t.Parallel() + + dir, err := os.MkdirTemp("", "mcsrfskc") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + file := writeTempFile(t, dir, `package main +import ( + "github.com/gofiber/fiber/v2/middleware/csrf" +) +var _ = csrf.New(csrf.Config{ + SessionKey: "csrf", // session comment +})`) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateCSRFConfig(cmd, dir, nil, nil)) + + content := readFile(t, file) + assert.NotContains(t, content, "SessionKey") + assert.NotContains(t, content, "session comment") + assert.Contains(t, buf.String(), "Migrating CSRF middleware configs") +} + func Test_MigrateCSRFConfig_KeyLookup(t *testing.T) { t.Parallel() diff --git a/cmd/internal/migrations/v3/healthcheck_config.go b/cmd/internal/migrations/v3/healthcheck_config.go index 9ffae98..0a297b5 100644 --- a/cmd/internal/migrations/v3/healthcheck_config.go +++ b/cmd/internal/migrations/v3/healthcheck_config.go @@ -34,13 +34,13 @@ func MigrateHealthcheckConfig(cmd *cobra.Command, cwd string, _, _ *semver.Versi body = bodyMatch[1] } - reLE := regexp.MustCompile(`(?m)\s*LivenessEndpoint:\s*([^,\n}]+),?`) + reLE := regexp.MustCompile(`(?m)\s*LivenessEndpoint:\s*([^,\n}]+),?\s*(//[^\n]*)?\n?`) if m := reLE.FindStringSubmatch(body); len(m) > 1 { lEndpoint = strings.TrimSpace(m[1]) } body = reLE.ReplaceAllString(body, "") - reRE := regexp.MustCompile(`(?m)\s*ReadinessEndpoint:\s*([^,\n}]+),?`) + reRE := regexp.MustCompile(`(?m)\s*ReadinessEndpoint:\s*([^,\n}]+),?\s*(//[^\n]*)?\n?`) if m := reRE.FindStringSubmatch(body); len(m) > 1 { rEndpoint = strings.TrimSpace(m[1]) } diff --git a/cmd/internal/migrations/v3/healthcheck_config_test.go b/cmd/internal/migrations/v3/healthcheck_config_test.go index 61d8116..31dd703 100644 --- a/cmd/internal/migrations/v3/healthcheck_config_test.go +++ b/cmd/internal/migrations/v3/healthcheck_config_test.go @@ -181,3 +181,34 @@ var cfg = healthcheck.Config{ assert.NotContains(t, content, "ReadinessEndpoint") assert.Contains(t, buf.String(), "Migrating healthcheck middleware configs") } + +func Test_MigrateHealthcheckConfig_WithComments(t *testing.T) { + t.Parallel() + + dir, err := os.MkdirTemp("", "mhealthc") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + file := writeTempFile(t, dir, `package main +import ( + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/healthcheck" +) +func main() { + app := fiber.New() + app.Use(healthcheck.New(healthcheck.Config{ + LivenessEndpoint: "/live", // live comment + ReadinessEndpoint: "/ready", // ready comment + })) +}`) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateHealthcheckConfig(cmd, dir, nil, nil)) + + content := readFile(t, file) + assert.Contains(t, content, `app.Get("/live"`) + assert.Contains(t, content, `app.Get("/ready"`) + assert.NotContains(t, content, "live comment") + assert.NotContains(t, content, "ready comment") +} diff --git a/cmd/internal/migrations/v3/middleware_locals.go b/cmd/internal/migrations/v3/middleware_locals.go index 75b2fa5..f9207a3 100644 --- a/cmd/internal/migrations/v3/middleware_locals.go +++ b/cmd/internal/migrations/v3/middleware_locals.go @@ -59,7 +59,7 @@ func MigrateMiddlewareLocals(cmd *cobra.Command, cwd string, _, _ *semver.Versio reComma := regexp.MustCompile(`(\w+)\s*,\s*(\w+)\s*:=\s*([\w\.]+FromContext\([^\)]+\))`) content = reComma.ReplaceAllString(content, "$1, $2 := $3, true") - reCtxKey := regexp.MustCompile(`\s*Context(?:Username|Password|Key):\s*[^,}\n]+,?`) + reCtxKey := regexp.MustCompile(`(?m)\s*Context(?:Username|Password|Key):\s*[^,\n}]+,?\s*(//[^\n]*)?\n`) content = reCtxKey.ReplaceAllString(content, "") return content diff --git a/cmd/internal/migrations/v3/middleware_locals_test.go b/cmd/internal/migrations/v3/middleware_locals_test.go index f8703e8..fa1471a 100644 --- a/cmd/internal/migrations/v3/middleware_locals_test.go +++ b/cmd/internal/migrations/v3/middleware_locals_test.go @@ -71,6 +71,32 @@ func main() { assert.NotContains(t, content, "ContextKey") } +func Test_MigrateMiddlewareLocals_ContextKeyWithComment(t *testing.T) { + t.Parallel() + + dir, err := os.MkdirTemp("", "mctxkeyc") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + file := writeTempFile(t, dir, `package main +import ( + "github.com/gofiber/fiber/v2/middleware/keyauth" +) + +func main() { + _ = keyauth.New(keyauth.Config{ContextKey: "token", // key comment + }) +}`) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateMiddlewareLocals(cmd, dir, nil, nil)) + + content := readFile(t, file) + assert.NotContains(t, content, "ContextKey") + assert.NotContains(t, content, "key comment") +} + func Test_MigrateMiddlewareLocals_CustomContextKey(t *testing.T) { t.Parallel() From b65ddc479f5f5f0e47519c59847b29b46bdbf4d2 Mon Sep 17 00:00:00 2001 From: RW Date: Sun, 24 Aug 2025 14:47:09 +0200 Subject: [PATCH 2/3] Handle inline config fields in migrations --- cmd/internal/migrations/v3/common.go | 56 ++++++++++++++++++- .../migrations/v3/middleware_locals.go | 5 +- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/cmd/internal/migrations/v3/common.go b/cmd/internal/migrations/v3/common.go index fa04f70..2b708dc 100644 --- a/cmd/internal/migrations/v3/common.go +++ b/cmd/internal/migrations/v3/common.go @@ -13,13 +13,38 @@ var ( ) // skipCommaSuffix advances the index past a comma and any trailing -// whitespace or newline characters. +// whitespace, comments, or newline characters. func skipCommaSuffix(src string, i int) int { i++ for i < len(src) { switch src[i] { case ' ', '\t': i++ + case '/': + if i+1 < len(src) { + if src[i+1] == '/' { // line comment + i += 2 + for i < len(src) && src[i] != '\n' { + i++ + } + if i < len(src) { + return i + 1 + } + return i + } + if src[i+1] == '*' { // block comment + i += 2 + for i+1 < len(src) && !(src[i] == '*' && src[i+1] == '/') { + i++ + } + if i+1 < len(src) { + i += 2 + continue + } + return i + } + } + return i case '\n': return i + 1 default: @@ -34,13 +59,37 @@ func skipCommaSuffix(src string, i int) int { // multi-line function literals or function calls with arguments that contain // commas. func removeConfigField(src, field string) string { - re := regexp.MustCompile(`(?m)^\s*` + field + `:\s*`) + re := regexp.MustCompile(field + `:\s*`) for { loc := re.FindStringIndex(src) if loc == nil { break } + start := loc[0] + // include any leading whitespace and comma + for start > 0 { + switch src[start-1] { + case ' ', '\t': + start-- + case ',': + start-- + for start > 0 && (src[start-1] == ' ' || src[start-1] == '\t') { + start-- + } + goto leadDone + case '\n': + start-- + for start > 0 && (src[start-1] == ' ' || src[start-1] == '\t') { + start-- + } + goto leadDone + default: + goto leadDone + } + } + leadDone: + i := loc[1] depth := 0 inString := false @@ -63,6 +112,9 @@ func removeConfigField(src, field string) string { case ')', '}', ']': if depth > 0 { depth-- + } else if ch == '}' { + src = src[:start] + src[i:] + goto nextField } case ',': if depth == 0 { diff --git a/cmd/internal/migrations/v3/middleware_locals.go b/cmd/internal/migrations/v3/middleware_locals.go index f9207a3..b9c47d1 100644 --- a/cmd/internal/migrations/v3/middleware_locals.go +++ b/cmd/internal/migrations/v3/middleware_locals.go @@ -59,8 +59,9 @@ func MigrateMiddlewareLocals(cmd *cobra.Command, cwd string, _, _ *semver.Versio reComma := regexp.MustCompile(`(\w+)\s*,\s*(\w+)\s*:=\s*([\w\.]+FromContext\([^\)]+\))`) content = reComma.ReplaceAllString(content, "$1, $2 := $3, true") - reCtxKey := regexp.MustCompile(`(?m)\s*Context(?:Username|Password|Key):\s*[^,\n}]+,?\s*(//[^\n]*)?\n`) - content = reCtxKey.ReplaceAllString(content, "") + content = removeConfigField(content, "ContextKey") + content = removeConfigField(content, "ContextUsername") + content = removeConfigField(content, "ContextPassword") return content }) From 1d2ea08aa76221f9ca93befdc6c55cb0b33767dc Mon Sep 17 00:00:00 2001 From: RW Date: Sun, 24 Aug 2025 15:04:22 +0200 Subject: [PATCH 3/3] Handle inline basicauth context fields and reduce remover nesting --- .../migrations/v3/basicauth_config.go | 20 +++++-- .../migrations/v3/basicauth_config_test.go | 22 +++++++ cmd/internal/migrations/v3/common.go | 59 +++++++++++-------- 3 files changed, 72 insertions(+), 29 deletions(-) diff --git a/cmd/internal/migrations/v3/basicauth_config.go b/cmd/internal/migrations/v3/basicauth_config.go index ff881ca..67ac421 100644 --- a/cmd/internal/migrations/v3/basicauth_config.go +++ b/cmd/internal/migrations/v3/basicauth_config.go @@ -14,14 +14,26 @@ import ( ) func MigrateBasicauthConfig(cmd *cobra.Command, cwd string, _, _ *semver.Version) error { - reCtxUser := regexp.MustCompile(`(?m)\s*ContextUsername:\s*[^,\n]+,?\s*(//[^\n]*)?\n`) - reCtxPass := regexp.MustCompile(`(?m)\s*ContextPassword:\s*[^,\n]+,?\s*(//[^\n]*)?\n`) + // Remove ContextKey/Username/Password when they are on their own line (with optional trailing comment). + reCtxKeyLine := regexp.MustCompile(`(?m)^\s*Context(?:Username|Password|Key):\s*[^,\n}]+,?\s*(//[^\n]*)?\n`) + + // Also remove inline occurrences immediately before a closing '}' (with optional comma/comment), + // e.g. basicauth.Config{ContextUsername: "user"}) or {..., ContextPassword: "pass"}) + // Keep the closing brace via a capture group. + reCtxKeyInline := regexp.MustCompile(`(?m)\s*Context(?:Username|Password|Key):\s*[^,\n}]+(?:,\s*)?(?://[^\n]*)?(\s*})`) + reUsers := regexp.MustCompile(`Users:\s*map\[string\]string{([^}]*)}`) reEntry := regexp.MustCompile(`("[^"]+")\s*:\s*"((?:[^"\\]|\\.)*)"`) changed, err := internal.ChangeFileContent(cwd, func(content string) string { - content = reCtxUser.ReplaceAllString(content, "") - content = reCtxPass.ReplaceAllString(content, "") + content = reCtxKeyLine.ReplaceAllString(content, "") + for { + newContent := reCtxKeyInline.ReplaceAllString(content, "$1") + if newContent == content { + break + } + content = newContent + } content = reUsers.ReplaceAllStringFunc(content, func(m string) string { sub := reUsers.FindStringSubmatch(m) diff --git a/cmd/internal/migrations/v3/basicauth_config_test.go b/cmd/internal/migrations/v3/basicauth_config_test.go index 1c1746e..364f5a2 100644 --- a/cmd/internal/migrations/v3/basicauth_config_test.go +++ b/cmd/internal/migrations/v3/basicauth_config_test.go @@ -36,3 +36,25 @@ var _ = basicauth.New(basicauth.Config{ assert.NotContains(t, content, "password comment") assert.Contains(t, buf.String(), "Migrating basicauth configs") } + +func Test_MigrateBasicauthConfig_Inline(t *testing.T) { + t.Parallel() + + dir, err := os.MkdirTemp("", "mbasiccfg_inline") + require.NoError(t, err) + defer func() { require.NoError(t, os.RemoveAll(dir)) }() + + file := writeTempFile(t, dir, `package main +import "github.com/gofiber/fiber/v2/middleware/basicauth" +var _ = basicauth.New(basicauth.Config{ContextUsername: "user", ContextPassword: "pass"})`) + + var buf bytes.Buffer + cmd := newCmd(&buf) + require.NoError(t, v3.MigrateBasicauthConfig(cmd, dir, nil, nil)) + + content := readFile(t, file) + assert.NotContains(t, content, "ContextUsername") + assert.NotContains(t, content, "ContextPassword") + assert.Contains(t, content, "basicauth.Config{}") + assert.Contains(t, buf.String(), "Migrating basicauth configs") +} diff --git a/cmd/internal/migrations/v3/common.go b/cmd/internal/migrations/v3/common.go index 2b708dc..b87be89 100644 --- a/cmd/internal/migrations/v3/common.go +++ b/cmd/internal/migrations/v3/common.go @@ -103,32 +103,41 @@ func removeConfigField(src, field string) string { if ch == '"' { inString = false } - } else { - switch ch { - case '"': - inString = true - case '(', '{', '[': - depth++ - case ')', '}', ']': - if depth > 0 { - depth-- - } else if ch == '}' { - src = src[:start] + src[i:] - goto nextField - } - case ',': - if depth == 0 { - i = skipCommaSuffix(src, i) - src = src[:start] + src[i:] - goto nextField - } - case '\n': - if depth == 0 { - i++ - src = src[:start] + src[i:] - goto nextField - } + i++ + continue + } + + switch ch { + case '"': + inString = true + case '(', '{', '[': + depth++ + case ')', '}', ']': + if depth > 0 { + depth-- + i++ + continue } + if ch == '}' { + src = src[:start] + src[i:] + goto nextField + } + case ',': + if depth > 0 { + i++ + continue + } + i = skipCommaSuffix(src, i) + src = src[:start] + src[i:] + goto nextField + case '\n': + if depth > 0 { + i++ + continue + } + i++ + src = src[:start] + src[i:] + goto nextField } i++ }