refactor(cmd): migrate sort.Slice to slices.SortFunc (bd-u2sc.2)
Modernize sorting code to use Go 1.21+ slices package: - Replace sort.Slice with slices.SortFunc across 16 files - Use cmp.Compare for orderable types (strings, ints) - Use time.Time.Compare for time comparisons - Use cmp.Or for multi-field sorting - Use slices.SortStableFunc where stability matters Benefits: cleaner 3-way comparison, slightly better performance, modern idiomatic Go. Part of GH#692 refactoring epic. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,7 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -218,8 +219,8 @@ func autoImportIfNewer() {
|
||||
for oldID, newID := range result.IDMapping {
|
||||
mappings = append(mappings, mapping{oldID, newID})
|
||||
}
|
||||
sort.Slice(mappings, func(i, j int) bool {
|
||||
return mappings[i].oldID < mappings[j].oldID
|
||||
slices.SortFunc(mappings, func(a, b mapping) int {
|
||||
return cmp.Compare(a.oldID, b.oldID)
|
||||
})
|
||||
|
||||
maxShow := 10
|
||||
@@ -442,8 +443,8 @@ func validateJSONLIntegrity(ctx context.Context, jsonlPath string) (bool, error)
|
||||
|
||||
func writeJSONLAtomic(jsonlPath string, issues []*types.Issue) ([]string, error) {
|
||||
// Sort issues by ID for consistent output
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].ID < issues[j].ID
|
||||
slices.SortFunc(issues, func(a, b *types.Issue) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
// Create temp file with PID suffix to avoid collisions (bd-306)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -205,8 +206,11 @@ Examples:
|
||||
outputJSON(result)
|
||||
} else {
|
||||
// Sort groups for consistent output
|
||||
sort.Slice(result.Groups, func(i, j int) bool {
|
||||
return result.Groups[i].Group < result.Groups[j].Group
|
||||
slices.SortFunc(result.Groups, func(a, b struct {
|
||||
Group string `json:"group"`
|
||||
Count int `json:"count"`
|
||||
}) int {
|
||||
return cmp.Compare(a.Group, b.Group)
|
||||
})
|
||||
|
||||
fmt.Printf("Total: %d\n\n", result.Total)
|
||||
@@ -397,8 +401,8 @@ Examples:
|
||||
}
|
||||
|
||||
// Sort for consistent output
|
||||
sort.Slice(groups, func(i, j int) bool {
|
||||
return groups[i].Group < groups[j].Group
|
||||
slices.SortFunc(groups, func(a, b GroupCount) int {
|
||||
return cmp.Compare(a.Group, b.Group)
|
||||
})
|
||||
|
||||
if jsonOutput {
|
||||
|
||||
@@ -2,12 +2,13 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -59,8 +60,8 @@ func exportToJSONLWithStore(ctx context.Context, store storage.Storage, jsonlPat
|
||||
}
|
||||
|
||||
// Sort by ID for consistent output
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].ID < issues[j].ID
|
||||
slices.SortFunc(issues, func(a, b *types.Issue) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
// Populate dependencies for all issues
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -895,15 +895,15 @@ func printDiagnostics(result doctorResult) {
|
||||
fmt.Println(ui.RenderWarn(ui.IconWarn + " WARNINGS"))
|
||||
|
||||
// Sort by severity: errors first, then warnings
|
||||
sort.Slice(warnings, func(i, j int) bool {
|
||||
slices.SortStableFunc(warnings, func(a, b doctorCheck) int {
|
||||
// Errors (statusError) come before warnings (statusWarning)
|
||||
if warnings[i].Status == statusError && warnings[j].Status != statusError {
|
||||
return true
|
||||
if a.Status == statusError && b.Status != statusError {
|
||||
return -1
|
||||
}
|
||||
if warnings[i].Status != statusError && warnings[j].Status == statusError {
|
||||
return false
|
||||
if a.Status != statusError && b.Status == statusError {
|
||||
return 1
|
||||
}
|
||||
return false // maintain original order within same severity
|
||||
return 0 // maintain original order within same severity
|
||||
})
|
||||
|
||||
for i, check := range warnings {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -316,7 +317,7 @@ Examples:
|
||||
len(jsonlIDs), len(issues), len(missingIDs))
|
||||
|
||||
if len(missingIDs) > 0 {
|
||||
sort.Strings(missingIDs)
|
||||
slices.Sort(missingIDs)
|
||||
fmt.Fprintf(os.Stderr, "Error: refusing to export stale database that would lose issues\n")
|
||||
fmt.Fprintf(os.Stderr, " Database has %d issues\n", len(issues))
|
||||
fmt.Fprintf(os.Stderr, " JSONL has %d issues\n", len(jsonlIDs))
|
||||
@@ -357,8 +358,8 @@ Examples:
|
||||
issues = filtered
|
||||
|
||||
// Sort by ID for consistent output
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].ID < issues[j].ID
|
||||
slices.SortFunc(issues, func(a, b *types.Issue) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
// Populate dependencies for all issues in one query (avoids N+1 problem)
|
||||
|
||||
@@ -3,12 +3,13 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"cmp"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -359,8 +360,8 @@ NOTE: Import requires direct database access and does not work with daemon mode.
|
||||
for oldID, newID := range result.IDMapping {
|
||||
mappings = append(mappings, mapping{oldID, newID})
|
||||
}
|
||||
sort.Slice(mappings, func(i, j int) bool {
|
||||
return mappings[i].oldID < mappings[j].oldID
|
||||
slices.SortFunc(mappings, func(a, b mapping) int {
|
||||
return cmp.Compare(a.oldID, b.oldID)
|
||||
})
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Remappings:\n")
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
@@ -10,7 +11,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/steveyegge/beads/internal/storage"
|
||||
@@ -431,8 +432,8 @@ func computeDBHash(ctx context.Context, store storage.Storage) (string, error) {
|
||||
}
|
||||
|
||||
// Sort by ID for consistent hash
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].ID < issues[j].ID
|
||||
slices.SortFunc(issues, func(a, b *types.Issue) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
// Populate dependencies
|
||||
|
||||
@@ -2,13 +2,14 @@ package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -479,8 +480,8 @@ func doPushToJira(ctx context.Context, dryRun bool, createOnly bool, updateRefs
|
||||
}
|
||||
|
||||
// Sort by ID for consistent output
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].ID < issues[j].ID
|
||||
slices.SortFunc(issues, func(a, b *types.Issue) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
// Generate JSONL for export
|
||||
|
||||
@@ -2,11 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
@@ -53,50 +54,50 @@ func sortIssues(issues []*types.Issue, sortBy string, reverse bool) {
|
||||
return
|
||||
}
|
||||
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
var less bool
|
||||
slices.SortFunc(issues, func(a, b *types.Issue) int {
|
||||
var result int
|
||||
|
||||
switch sortBy {
|
||||
case "priority":
|
||||
// Lower priority numbers come first (P0 > P1 > P2 > P3 > P4)
|
||||
less = issues[i].Priority < issues[j].Priority
|
||||
result = cmp.Compare(a.Priority, b.Priority)
|
||||
case "created":
|
||||
// Default: newest first (descending)
|
||||
less = issues[i].CreatedAt.After(issues[j].CreatedAt)
|
||||
result = b.CreatedAt.Compare(a.CreatedAt)
|
||||
case "updated":
|
||||
// Default: newest first (descending)
|
||||
less = issues[i].UpdatedAt.After(issues[j].UpdatedAt)
|
||||
result = b.UpdatedAt.Compare(a.UpdatedAt)
|
||||
case "closed":
|
||||
// Default: newest first (descending)
|
||||
// Handle nil ClosedAt values
|
||||
if issues[i].ClosedAt == nil && issues[j].ClosedAt == nil {
|
||||
less = false
|
||||
} else if issues[i].ClosedAt == nil {
|
||||
less = false // nil sorts last
|
||||
} else if issues[j].ClosedAt == nil {
|
||||
less = true // non-nil sorts before nil
|
||||
if a.ClosedAt == nil && b.ClosedAt == nil {
|
||||
result = 0
|
||||
} else if a.ClosedAt == nil {
|
||||
result = 1 // nil sorts last
|
||||
} else if b.ClosedAt == nil {
|
||||
result = -1 // non-nil sorts before nil
|
||||
} else {
|
||||
less = issues[i].ClosedAt.After(*issues[j].ClosedAt)
|
||||
result = b.ClosedAt.Compare(*a.ClosedAt)
|
||||
}
|
||||
case "status":
|
||||
less = issues[i].Status < issues[j].Status
|
||||
result = cmp.Compare(a.Status, b.Status)
|
||||
case "id":
|
||||
less = issues[i].ID < issues[j].ID
|
||||
result = cmp.Compare(a.ID, b.ID)
|
||||
case "title":
|
||||
less = strings.ToLower(issues[i].Title) < strings.ToLower(issues[j].Title)
|
||||
result = cmp.Compare(strings.ToLower(a.Title), strings.ToLower(b.Title))
|
||||
case "type":
|
||||
less = issues[i].IssueType < issues[j].IssueType
|
||||
result = cmp.Compare(a.IssueType, b.IssueType)
|
||||
case "assignee":
|
||||
less = issues[i].Assignee < issues[j].Assignee
|
||||
result = cmp.Compare(a.Assignee, b.Assignee)
|
||||
default:
|
||||
// Unknown sort field, no sorting
|
||||
less = false
|
||||
result = 0
|
||||
}
|
||||
|
||||
if reverse {
|
||||
return !less
|
||||
return -result
|
||||
}
|
||||
return less
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
@@ -9,7 +10,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -272,8 +273,8 @@ func migrateToHashIDs(ctx context.Context, store *sqlite.SQLiteStorage, issues [
|
||||
// We need to also update text references in descriptions, notes, design, acceptance criteria
|
||||
|
||||
// Sort issues by ID to process parents before children
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].ID < issues[j].ID
|
||||
slices.SortFunc(issues, func(a, b *types.Issue) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
// Update all issues
|
||||
@@ -394,8 +395,8 @@ func saveMappingFile(path string, mapping map[string]string) error {
|
||||
}
|
||||
|
||||
// Sort by old ID for readability
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return entries[i].OldID < entries[j].OldID
|
||||
slices.SortFunc(entries, func(a, b mappingEntry) int {
|
||||
return cmp.Compare(a.OldID, b.OldID)
|
||||
})
|
||||
|
||||
data, err := json.MarshalIndent(map[string]interface{}{
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -247,11 +248,11 @@ func repairPrefixes(ctx context.Context, st storage.Storage, actorName string, t
|
||||
}
|
||||
|
||||
// Sort incorrect issues: first by prefix lexicographically, then by number
|
||||
sort.Slice(incorrectIssues, func(i, j int) bool {
|
||||
if incorrectIssues[i].prefix != incorrectIssues[j].prefix {
|
||||
return incorrectIssues[i].prefix < incorrectIssues[j].prefix
|
||||
}
|
||||
return incorrectIssues[i].number < incorrectIssues[j].number
|
||||
slices.SortFunc(incorrectIssues, func(a, b issueSort) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.prefix, b.prefix),
|
||||
cmp.Compare(a.number, b.number),
|
||||
)
|
||||
})
|
||||
|
||||
// Get a database connection for ID generation
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -1204,8 +1204,8 @@ func showMessageThread(ctx context.Context, messageID string, jsonOutput bool) {
|
||||
}
|
||||
|
||||
// Sort by creation time
|
||||
sort.Slice(threadMessages, func(i, j int) bool {
|
||||
return threadMessages[i].CreatedAt.Before(threadMessages[j].CreatedAt)
|
||||
slices.SortFunc(threadMessages, func(a, b *types.Issue) int {
|
||||
return a.CreatedAt.Compare(b.CreatedAt)
|
||||
})
|
||||
|
||||
if jsonOutput {
|
||||
|
||||
@@ -3,13 +3,14 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -1346,8 +1347,8 @@ func exportToJSONL(ctx context.Context, jsonlPath string) error {
|
||||
}
|
||||
|
||||
// Sort by ID for consistent output
|
||||
sort.Slice(issues, func(i, j int) bool {
|
||||
return issues[i].ID < issues[j].ID
|
||||
slices.SortFunc(issues, func(a, b *types.Issue) int {
|
||||
return cmp.Compare(a.ID, b.ID)
|
||||
})
|
||||
|
||||
// Populate dependencies for all issues (avoid N+1)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -130,8 +131,8 @@ func getContributorsSorted() []string {
|
||||
for name, commits := range beadsContributors {
|
||||
sorted = append(sorted, kv{name, commits})
|
||||
}
|
||||
sort.Slice(sorted, func(i, j int) bool {
|
||||
return sorted[i].commits > sorted[j].commits
|
||||
slices.SortFunc(sorted, func(a, b kv) int {
|
||||
return cmp.Compare(b.commits, a.commits) // descending order
|
||||
})
|
||||
names := make([]string, len(sorted))
|
||||
for i, kv := range sorted {
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -116,8 +117,8 @@ func selectNextTip(store storage.Storage) *Tip {
|
||||
}
|
||||
|
||||
// Sort by priority (highest first)
|
||||
sort.Slice(eligibleTips, func(i, j int) bool {
|
||||
return eligibleTips[i].Priority > eligibleTips[j].Priority
|
||||
slices.SortFunc(eligibleTips, func(a, b Tip) int {
|
||||
return cmp.Compare(b.Priority, a.Priority) // descending order
|
||||
})
|
||||
|
||||
// Apply probability roll (in priority order)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -397,8 +397,8 @@ func runWispList(cmd *cobra.Command, args []string) {
|
||||
}
|
||||
|
||||
// Sort by updated_at descending (most recent first)
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return items[i].UpdatedAt.After(items[j].UpdatedAt)
|
||||
slices.SortFunc(items, func(a, b WispListItem) int {
|
||||
return b.UpdatedAt.Compare(a.UpdatedAt) // descending order
|
||||
})
|
||||
|
||||
result := WispListResult{
|
||||
|
||||
Reference in New Issue
Block a user