-
Notifications
You must be signed in to change notification settings - Fork 433
[lint-monster] Migrate unsafe sort.Slice calls to type-safe slices.SortFunc #38014
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,7 +6,7 @@ import ( | |
| "io" | ||
| "net/http" | ||
| "os" | ||
| "sort" | ||
| "slices" | ||
| "strings" | ||
| "time" | ||
|
|
||
|
|
@@ -100,8 +100,15 @@ func DisplayOutdatedDependencies(outdated []OutdatedDependency, totalDeps int) { | |
| fmt.Fprintln(os.Stderr, "") | ||
|
|
||
| // Sort by module name | ||
| sort.Slice(outdated, func(i, j int) bool { | ||
| return outdated[i].Module < outdated[j].Module | ||
| slices.SortFunc(outdated, func(a, b OutdatedDependency) int { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/improve-codebase-architecture] This 7-line switch block reinvents 💡 Suggested simplificationslices.SortFunc(outdated, func(a, b OutdatedDependency) int {
return cmp.Compare(a.Module, b.Module)
})Applying |
||
| switch { | ||
| case a.Module < b.Module: | ||
| return -1 | ||
| case a.Module > b.Module: | ||
| return 1 | ||
| default: | ||
| return 0 | ||
| } | ||
| }) | ||
|
|
||
| // Display table | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ import ( | |
| "os" | ||
| "os/signal" | ||
| "path/filepath" | ||
| "slices" | ||
| "sort" | ||
| "strings" | ||
| "time" | ||
|
|
@@ -329,16 +330,22 @@ func RunForecast(config ForecastConfig) error { | |
| } | ||
|
|
||
| // Sort results by Monte Carlo P50 (or point estimate when MC unavailable) descending. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/tdd] This sort controls which workflow ranks first in forecast output — a sign flip would silently display the highest-cost workflow as the best option. No test was added to verify the comparator sign or the nil-guard branch on 💡 Minimal regression test shapefunc TestForecastResultSortOrder(t *testing.T) {
results := []ForecastWorkflowResult{
{WorkflowID: "low", ProjectedAIC: 10},
{WorkflowID: "high", ProjectedAIC: 100},
{WorkflowID: "mc", MonteCarlo: &MonteCarloResult{P50ProjectedAIC: 50}},
}
slices.SortFunc(results, compareForecastResults) // extracted helper
assert.Equal(t, "high", results[0].WorkflowID)
assert.Equal(t, "mc", results[1].WorkflowID)
}Extract the comparator to |
||
| sort.Slice(results, func(i, j int) bool { | ||
| pi := results[i].ProjectedAIC | ||
| if mc := results[i].MonteCarlo; mc != nil { | ||
| slices.SortFunc(results, func(a, b ForecastWorkflowResult) int { | ||
| pi := a.ProjectedAIC | ||
| if mc := a.MonteCarlo; mc != nil { | ||
| pi = mc.P50ProjectedAIC | ||
| } | ||
| pj := results[j].ProjectedAIC | ||
| if mc := results[j].MonteCarlo; mc != nil { | ||
| pj := b.ProjectedAIC | ||
| if mc := b.MonteCarlo; mc != nil { | ||
| pj = mc.P50ProjectedAIC | ||
| } | ||
| return pi > pj | ||
| if pi > pj { | ||
| return -1 | ||
| } | ||
| if pi < pj { | ||
| return 1 | ||
| } | ||
| return 0 | ||
| }) | ||
|
|
||
| output := ForecastResult{ | ||
|
|
@@ -837,11 +844,21 @@ func extractExperimentVariantStubs(cfg *workflow.FrontmatterConfig) []ForecastVa | |
| }) | ||
| } | ||
| } | ||
| sort.Slice(stubs, func(i, j int) bool { | ||
| if stubs[i].ExperimentName != stubs[j].ExperimentName { | ||
| return stubs[i].ExperimentName < stubs[j].ExperimentName | ||
| slices.SortFunc(stubs, func(a, b ForecastVariantResult) int { | ||
| if a.ExperimentName != b.ExperimentName { | ||
| if a.ExperimentName < b.ExperimentName { | ||
| return -1 | ||
| } | ||
| return 1 | ||
| } | ||
| switch { | ||
| case a.Variant < b.Variant: | ||
| return -1 | ||
| case a.Variant > b.Variant: | ||
| return 1 | ||
| default: | ||
| return 0 | ||
| } | ||
| return stubs[i].Variant < stubs[j].Variant | ||
| }) | ||
| return stubs | ||
| } | ||
|
|
@@ -1032,16 +1049,22 @@ func emitPartialForecastResults(results []ForecastWorkflowResult, config Forecas | |
| fmt.Sprintf("Forecast interrupted; emitting partial results for %d workflow(s) processed so far.", len(results)))) | ||
|
|
||
| // Sort partial results by Monte Carlo P50 descending (mirrors the full-results sort). | ||
| sort.Slice(results, func(i, j int) bool { | ||
| pi := results[i].ProjectedAIC | ||
| if mc := results[i].MonteCarlo; mc != nil { | ||
| slices.SortFunc(results, func(a, b ForecastWorkflowResult) int { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Duplicate Monte Carlo P50 comparator will diverge — this exact 12-line block also appears at line 333 in 💡 Suggested fixExtract to a package-level helper and call it from both sites: func compareForecastByP50(a, b ForecastWorkflowResult) int {
pa := a.ProjectedAIC
if mc := a.MonteCarlo; mc != nil {
pa = mc.P50ProjectedAIC
}
pb := b.ProjectedAIC
if mc := b.MonteCarlo; mc != nil {
pb = mc.P50ProjectedAIC
}
return cmp.Compare(pb, pa) // descending
}
// at both call sites:
slices.SortFunc(results, compareForecastByP50)With
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/improve-codebase-architecture] This comparator is byte-for-byte identical to the one in 💡 Extract to a shared helper// compareForecastResults orders results by Monte Carlo P50 (or ProjectedAIC fallback), descending.
func compareForecastResults(a, b ForecastWorkflowResult) int {
pi := a.ProjectedAIC
if mc := a.MonteCarlo; mc != nil {
pi = mc.P50ProjectedAIC
}
pj := b.ProjectedAIC
if mc := b.MonteCarlo; mc != nil {
pj = mc.P50ProjectedAIC
}
return cmp.Compare(pj, pi) // descending: higher cost first
}Both sites become |
||
| pi := a.ProjectedAIC | ||
| if mc := a.MonteCarlo; mc != nil { | ||
| pi = mc.P50ProjectedAIC | ||
| } | ||
| pj := results[j].ProjectedAIC | ||
| if mc := results[j].MonteCarlo; mc != nil { | ||
| pj := b.ProjectedAIC | ||
| if mc := b.MonteCarlo; mc != nil { | ||
| pj = mc.P50ProjectedAIC | ||
| } | ||
| return pi > pj | ||
| if pi > pj { | ||
| return -1 | ||
| } | ||
| if pi < pj { | ||
| return 1 | ||
| } | ||
| return 0 | ||
| }) | ||
|
|
||
| output := ForecastResult{ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10,7 +10,7 @@ import ( | |
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "sort" | ||
| "slices" | ||
| "strings" | ||
| "time" | ||
|
|
||
|
|
@@ -234,15 +234,32 @@ func buildMCPSummaryStats(gatewayMetrics *GatewayMetrics, mcpData *MCPToolUsageD | |
| } | ||
|
|
||
| // Sort summaries by server name, then tool name | ||
| sort.Slice(mcpData.Summary, func(i, j int) bool { | ||
| if mcpData.Summary[i].ServerName != mcpData.Summary[j].ServerName { | ||
| return mcpData.Summary[i].ServerName < mcpData.Summary[j].ServerName | ||
| slices.SortFunc(mcpData.Summary, func(a, b MCPToolSummary) int { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [/improve-codebase-architecture] This two-field sort (ServerName, then ToolName) is a perfect candidate for 💡 Match the existing patternslices.SortFunc(mcpData.Summary, func(a, b MCPToolSummary) int {
return cmp.Or(
cmp.Compare(a.ServerName, b.ServerName),
cmp.Compare(a.ToolName, b.ToolName),
)
})This keeps the two MCP-summary sort implementations consistent. |
||
| if a.ServerName != b.ServerName { | ||
| if a.ServerName < b.ServerName { | ||
| return -1 | ||
| } | ||
| return 1 | ||
| } | ||
| switch { | ||
| case a.ToolName < b.ToolName: | ||
| return -1 | ||
| case a.ToolName > b.ToolName: | ||
| return 1 | ||
| default: | ||
| return 0 | ||
| } | ||
| return mcpData.Summary[i].ToolName < mcpData.Summary[j].ToolName | ||
| }) | ||
|
|
||
| // Sort servers by name | ||
| sort.Slice(mcpData.Servers, func(i, j int) bool { | ||
| return mcpData.Servers[i].ServerName < mcpData.Servers[j].ServerName | ||
| slices.SortFunc(mcpData.Servers, func(a, b MCPServerStats) int { | ||
| switch { | ||
| case a.ServerName < b.ServerName: | ||
| return -1 | ||
| case a.ServerName > b.ServerName: | ||
| return 1 | ||
| default: | ||
| return 0 | ||
| } | ||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[/tdd]
candidates[0]is returned immediately after this sort — it is the audit comparison baseline. A sign inversion in either the Score or CreatedAt comparisons would silently select the lowest-scoring or oldest run as the baseline for every audit.No test was added to verify: (1) higher Score wins, (2) for equal scores, more recent CreatedAt wins, (3) stability is preserved.
💡 Minimal regression test shape