Skip to content

Reframe bytescomparestring as style/intent lint and fix single-import autofix output#44488

Open
pelikhan with Copilot wants to merge 6 commits into
mainfrom
copilot/bytescomparestring-fix-allocates-premise
Open

Reframe bytescomparestring as style/intent lint and fix single-import autofix output#44488
pelikhan with Copilot wants to merge 6 commits into
mainfrom
copilot/bytescomparestring-fix-allocates-premise

Conversation

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

The new bytescomparestring analyzer was functionally correct but described string(a) == string(b) / != as allocating, which is inaccurate under gc for non-escaping comparison temporaries. This change updates the linter rationale to readability/idiom and tightens its suggested-fix import behavior for single-import files.

  • Diagnostic + docs now match actual compiler behavior

    • Reworded analyzer diagnostics to remove allocation claims.
    • Repositioned guidance as “[]byte comparison written the long way; use bytes.Equal for clearer intent.”
    • Updated linter package doc text and the analyzer line in pkg/linters/doc.go to match.
  • Suggested-fix import insertion handles single non-grouped imports

    • Extended addBytesImportEdit to detect import "x" shape and rewrite it to a grouped import when adding bytes, instead of emitting a second top-level import declaration.
  • Testdata updated for message + import-shape coverage

    • Updated existing want expectations and goldens for the new diagnostic text.
    • Added a new fixture/golden pair for the import "fmt" (single non-grouped import) case.
// before
import "fmt"
return string(a) == string(b)

// after suggested fix
import (
	"fmt"
	"bytes"
)
return bytes.Equal(a, b)

Generated by 👨‍🍳 PR Sous Chef · 7.1 AIC · ⌖ 5.18 AIC · ⊞ 7.1K ·
Comment /souschef to run again


Generated by 👨‍🍳 PR Sous Chef · 10.4 AIC · ⌖ 5.15 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix inaccurate allocates premise for bytescomparestring linter Reframe bytescomparestring as style/intent lint and fix single-import autofix output Jul 9, 2026
Copilot AI requested a review from pelikhan July 9, 2026 05:41
@pelikhan pelikhan marked this pull request as ready for review July 9, 2026 05:46
Copilot AI review requested due to automatic review settings July 9, 2026 05:46
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ PR Code Quality Reviewer failed during code quality review.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR #44488 does not have the 'implementation' label and has only 54 new lines of code in business logic directories (threshold: 100).

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

No test files were added or modified in this PR. The changes affect the bytescomparestring linter implementation and its testdata fixtures (golden files and input fixtures), but no *_test.go or *.test.cjs files were touched. Test Quality Sentinel skipped.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request updates the bytescomparestring analyzer to frame string(a) == string(b) / != as a readability/intent issue (not an allocation claim), and improves the suggested-fix import insertion so it can rewrite import "x" into a grouped import when adding bytes.

Changes:

  • Reword diagnostics and package documentation to remove allocation claims and emphasize clearer intent via bytes.Equal.
  • Extend the suggested-fix import edit logic to handle single non-grouped imports by converting them to an import block when adding bytes.
  • Update/add testdata fixtures and goldens to cover the new diagnostic text and the single-import autofix shape.
Show a summary per file
File Description
pkg/linters/doc.go Updates the analyzer list entry to match the new “clearer intent” framing.
pkg/linters/bytescomparestring/bytescomparestring.go Rewords diagnostics/docs and updates import autofix behavior for import "x" files.
pkg/linters/bytescomparestring/testdata/src/bytescomparestring/bytescomparestring.go Updates want expectations for the new diagnostic message text.
pkg/linters/bytescomparestring/testdata/src/bytescomparestring/bytescomparestring.go.golden Updates golden output to match the new message text.
pkg/linters/bytescomparestring/testdata/src/bytescomparestring/noimport.go Updates want expectations for the new diagnostic message text.
pkg/linters/bytescomparestring/testdata/src/bytescomparestring/noimport.go.golden Updates golden output to match the new message text.
pkg/linters/bytescomparestring/testdata/src/bytescomparestring/singleimport.go Adds a new fixture covering import "fmt" (single non-grouped import) behavior.
pkg/linters/bytescomparestring/testdata/src/bytescomparestring/singleimport.go.golden Adds the golden output for the new single-import fixture.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 8/8 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment on lines +151 to +169
// If the file has a single non-grouped import declaration, convert it to a
// grouped import block while adding "bytes".
for _, decl := range file.Decls {
genDecl, ok := decl.(*ast.GenDecl)
if !ok || genDecl.Tok != token.IMPORT || genDecl.Lparen.IsValid() || len(genDecl.Specs) != 1 {
continue
}

specText := astutil.NodeText(pass.Fset, genDecl.Specs[0])
if specText == "" {
continue
}

return analysis.TextEdit{
Pos: genDecl.Pos(),
End: genDecl.End(),
NewText: []byte("import (\n\t" + specText + "\n\t\"" + bytesPkg + "\"\n)"),
}, true
}
@github-actions github-actions Bot mentioned this pull request Jul 9, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /codebase-design and /tdd — requesting changes on two focused issues.

📋 Key Themes & Highlights

Issues Found

  1. Import ordering (bytescomparestring.go:167) — The new single-import rewrite hardcodes the existing spec first, then appends "bytes". Since "bytes" < "fmt" alphabetically, this produces non-canonical import order that goimports would reorder. The golden file singleimport.go.golden already shows the non-sorted order, so the golden itself needs fixing too.

  2. Analyzer.Doc not updated (bytescomparestring.go:26) — The struct-level Doc field still says "that should use bytes.Equal instead" (the old phrasing), while every other surface (package doc, diagnostic messages, pkg/linters/doc.go) was updated to the new intent rationale. Small inconsistency visible to programmatic consumers.

  3. singleimport fixture only tests == — The != operator takes a separate code path; a singleImportNotEqual test case would make the fixture complete.

Positive Highlights

  • ✅ Correct technical rationale: the allocation claim was inaccurate under gc for non-escaping temporaries; the style/intent framing is more defensible.
  • ✅ The single-import autofix gap was a real correctness issue (would have emitted a second standalone import declaration); the fix is clean and well-placed.
  • ✅ New fixture + golden pair is a good pattern and matches the existing testdata conventions.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 52.9 AIC · ⌖ 8.15 AIC · ⊞ 6.6K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/linters/bytescomparestring/bytescomparestring.go:167

[/codebase-design] Import ordering: the new template appends &quot;bytes&quot; after the existing spec, producing &quot;fmt&quot;/&quot;bytes&quot; — but Go style (goimports) sorts alphabetically, so bytes should come before fmt.

<details>
<summary>💡 Suggestion</summary>

Insert &quot;bytes&quot; before the existing spec when it sorts earlier alphabetically. The current golden singleimport.go.golden already shows &quot;fmt&quot; before &quot;bytes&quot;, which goimports would reorder. Fix the emission order and update the golde…

pkg/linters/bytescomparestring/bytescomparestring.go:26

[/codebase-design] The Analyzer.Doc field (line 26) still says &quot;that should use bytes.Equal instead&quot; — it was not updated to match the new style/intent rationale used in the package doc, diagnostic messages, and pkg/linters/doc.go. This creates a small but observable inconsistency when users inspect the analyzer programmatically.

<details>
<summary>💡 Suggested fix</summary>

Doc: &quot;reports string(a) == string(b) and string(a) != string(b) comparisons where a and b are []byte va</details>

<details><summary>pkg/linters/bytescomparestring/testdata/src/bytescomparestring/singleimport.go:7</summary>

**[/tdd]** The `singleimport` fixture covers only the `==` (EQL) operator. The symmetrical `!=` (NEQ) path in the same single-import scenario is untestedboth operators produce separate code paths that call `buildFix`/`addBytesImportEdit`, and a future refactor could break one without the other.

&lt;details&gt;
&lt;summary&gt;💡 Suggestion&lt;/summary&gt;

Add a second function to `singleimport.go`:

```go
func singleImportNotEqual(a, b []byte) bool {
    fmt.Println(&quot;compare&quot;)
    return string(a) != string(…

</details>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

The PR is well-scoped and the core changes (reframing the diagnostic as style/intent, fixing single-import autofix) are correct. Two issues need addressing before merge:

Blocking:

  • Analyzer.Doc field not updatedbytescomparestring.go:26 still uses the old allocation-framing text while the package comment, diagnostic messages, and doc.go have all been updated. IDE integrations and go vet -json will surface this stale text.

Non-blocking:

  • Non-alphabetical import order in the singleimport golden — the autofix places "bytes" after "fmt". Since bytes sorts before fmt, inserting it first would produce already-canonical output and avoid a follow-up goimports churn for users.

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 54.4 AIC · ⌖ 5.93 AIC · ⊞ 4.8K

Comments that could not be inline-anchored

pkg/linters/bytescomparestring/bytescomparestring.go:26

The Analyzer.Doc field still says "that should use bytes.Equal instead" (the old allocation-framing wording), while the package comment, diagnostic messages, and doc.go have all been updated to the new "clearer intent" framing. This creates an inconsistency — tools that surface the Doc field (e.g. go vet -json, IDE integrations) will show the old text.

Suggested fix:

Doc: &quot;reports string(a) == string(b) and string(a) != string(b) comparisons where a and b are []byte values</details>

<details><summary>pkg/linters/bytescomparestring/testdata/src/bytescomparestring/singleimport.go.golden:5</summary>

The autofix emits `&quot;bytes&quot;` after `&quot;fmt&quot;`, producing non-alphabetical import order. Users who run `goimports` (or have editor formatters) after applying the fix will see a follow-up diff reordering the block.

Consider inserting alphabetically`&quot;bytes&quot;` sorts before `&quot;fmt&quot;`, so put it first:
```go
import (
\t&quot;bytes&quot;
\t&quot;fmt&quot;
)

This makes the autofix output already-canonical and avoids the double-edit.

@copilot please address this.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔎 Code quality review by PR Code Quality Reviewer · 191.6 AIC · ⌖ 6.8 AIC · ⊞ 5.4K
Comment /review to run again

return analysis.TextEdit{
Pos: genDecl.Pos(),
End: genDecl.End(),
NewText: []byte("import (\n\t" + specText + "\n\t\"" + bytesPkg + "\"\n)"),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Import sort order violation: "bytes" is always appended after the existing import, producing non-canonical order that goimports will immediately reformat.

💡 Suggested fix

The current NewText construction:

NewText: []byte("import (\n\t" + specText + "\n\t\"" + bytesPkg + "\"\n)"),

places the original import before "bytes" unconditionally. For import "fmt" this yields:

import (
	"fmt"
	"bytes"
)

goimports canonically sorts alphabetically within the stdlib group, so "bytes" must come before "fmt". The golden file at testdata/.../singleimport.go.golden confirms the wrong order is now checked in.

Minimal fix — sort the two specifiers before constructing the block:

newSpec := "\"" + bytesPkg + "\""
first, second := specText, newSpec
if newSpec < specText {
    first, second = newSpec, specText
}
NewText: []byte("import (\n\t" + first + "\n\t" + second + "\n)"),

Note: the existing grouped-import path (line ~147) also appends without sorting — that is a separate pre-existing issue.


// If the file has a single non-grouped import declaration, convert it to a
// grouped import block while adding "bytes".
for _, decl := range file.Decls {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate TextEdit conflict on multi-violation files: if a single-import file contains two or more flagged comparisons, both buildFix calls generate a TextEdit that replaces the identical range [genDecl.Pos(), genDecl.End()] with the same grouped-import block. The go/analysis framework may apply both edits or panic depending on the tooling, and no test covers this scenario.

💡 Explanation and fix

addBytesImportEdit is called once per flagged BinaryExpr. For a file like:

import "fmt"

func f(a, b []byte) bool { return string(a) == string(b) }
func g(a, b []byte) bool { return string(a) == string(b) }

Two diagnostics are reported and each SuggestedFix includes an independent TextEdit covering [genDecl.Pos(), genDecl.End()]. When both fixes are applied together (e.g. via go fix -fix=bytescomparestring), the overlapping TextEdits for the import rewrite can corrupt the file or cause the framework to emit "conflicting text edits" errors.

The standard pattern to avoid this is to make the import-rewrite edit idempotent at the source span: once the edit has been generated for a file, skip it for subsequent diagnostics in the same file. This can be done by tracking file.Pos() in a local map per pass.Files iteration, or — even simpler — relying on the existing "already imported?" check that runs at the top of addBytesImportEdit (which already handles the case where bytes is already imported).

The real gap is that after the first suggested fix is applied the import is present, but during a single analysis pass both diagnostics are generated simultaneously before any fix is applied, so both carry the import edit. Add a test file with two violations in a single-import context to make this concrete.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill and address the unresolved review feedback before rerunning checks.

Unresolved review feedback to address first:

  • github-actions: bytescomparestring.go Analyzer.Doc still uses the old wording; update it to match the new style/intent framing.
  • github-actions: single-import autofix/golden should keep imports in canonical alphabetical order (bytes before fmt).
  • github-actions: add single-import coverage for the != path as well.

Run: https://github.com/github/gh-aw/actions/runs/29000324183

Generated by 👨‍🍳 PR Sous Chef · 9.83 AIC · ⌖ 14.1 AIC · ⊞ 7.1K ·
Comment /souschef to run again

…t guard, and != coverage

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill and address the unresolved review feedback before rerunning checks.

Unresolved review feedback to address first:...

Done in the latest commit. Changes made:

  1. Analyzer.Doc — updated to "flags string(a) == string(b) and string(a) != string(b) as []byte comparisons written the long way; use bytes.Equal for clearer intent".
  2. Import orderaddBytesImportEdit now emits "bytes" before the existing spec so the grouped block is alphabetical (bytesfmt). Golden updated to match.
  3. Single-import guard — the non-grouped-to-grouped conversion is now wrapped in if len(file.Imports) == 1, so it won't misfire when a file has multiple standalone import "x" declarations.
  4. != coverage — added singleImportNotEqual to singleimport.go and the corresponding !bytes.Equal expectation to the golden file.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

Generated by 👨‍🍳 PR Sous Chef · 7.1 AIC · ⌖ 5.18 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address unresolved review comments, and rerun checks once the branch is up to date.

...

All review feedback was addressed in the previous commit (af87de5), and the branch was subsequently updated from main (d2046d2). Local validation (make fmt, make recompile, go test ./pkg/linters/bytescomparestring/...) is green. CI needs a human re-trigger to reflect the current HEAD.

@pelikhan

pelikhan commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

@copilot merge main and recompile

…tring-fix-allocates-premise

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot merge main and recompile

Done — merged main (b143f58) and recompiled all workflow lock files. Commit: 3e9fbdb.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address unresolved review feedback, and rerun checks once the branch update settles.

Unresolved review feedback to address first:

  • github-actions review feedback remains open.
  • copilot-pull-request-reviewer review feedback remains open.

Run: https://github.com/github/gh-aw/actions/runs/29016118662

Generated by 👨‍🍳 PR Sous Chef · 10.4 AIC · ⌖ 5.15 AIC · ⊞ 7.1K ·
Comment /souschef to run again

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address unresolved review feedback, and rerun checks once the branch update settles.

Unresolved review feedback to address first:...

All three unresolved threads are addressed in the current code (all are marked is_outdated: true):

  • copilot-pull-request-reviewer (single-import guard) — the len(file.Imports) == 1 guard is in place at line 153 of bytescomparestring.go.
  • github-actions (import sort order) — the NewText construction puts bytesPkg ("bytes") before specText, producing alphabetical order. The golden confirms "bytes" before "fmt".
  • github-actions (duplicate TextEdit)singleimport.go already exercises two violations in a single-import file (singleImportEqual + singleImportNotEqual). go test ./pkg/linters/bytescomparestring/... passes with the correct golden, confirming the framework handles the identical import-rewrite TextEdits correctly.

Local validation is green. No further code changes are needed; CI needs a human re-trigger to reflect the current HEAD.


  • ✅ Reviews — all three open threads are outdated and substantively addressed in prior commits.
  • ✅ Checks (local) — go test ./pkg/linters/bytescomparestring/... passes.
  • ❓ Checks (CI) — stale after the agent's last push; prior failures already root-caused and fixed. Needs human re-trigger (close/reopen or workflow_dispatch).
  • ✅ Mergeable — branch is up to date with main (merged at 3e9fbdb); blocked only by unresolved review threads, which are all outdated.

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

🤖 PR Triage — Run §29020837798

Field Value
Category refactor
Risk 🟢 Low
Score 52 / 100
Action 🔄 batch_review
Batch pr-batch:lint-fixes

Score breakdown: Impact 22 + Urgency 15 + Quality 15

Rationale: Linter correctness fix — reframes inaccurate allocation claims and fixes single-import autofix. Small (67 +/ 17 −) with updated test goldens. No user-facing behavior change. Fits naturally with other lint-fixes batch.

Generated by 🔧 PR Triage Agent · 77.6 AIC · ⌖ 14.7 AIC · ⊞ 5.4K ·

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is up to date.

Unresolved review feedback:

Generated by 👨‍🍳 PR Sous Chef · 12.1 AIC · ⌖ 6.75 AIC · ⊞ 4.7K ·
Comment /souschef to run again

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

@copilot please run the pr-finisher skill, address the unresolved review feedback below, and rerun checks once the branch is up to date.

Unresolved review feedback:...

All three threads are already addressed in the current code at 3e9fbdb (all three are marked is_outdated: true because the code changed since they were posted):

  • r3549194154 (single-import guard): len(file.Imports) == 1 guard is at line 153 of bytescomparestring.go.
  • r3549289266 (import sort order): Line 168 places bytesPkg ("bytes") before specText, producing alphabetical order ("bytes""fmt"). The golden confirms this.
  • r3549289271 (duplicate TextEdit): singleimport.go contains two violations (singleImportEqual and singleImportNotEqual) in a single-import file. analysistest.RunWithSuggestedFixes passes with the correct merged golden, demonstrating the framework handles the overlapping edits correctly.

Local validation: go test ./pkg/linters/bytescomparestring/... passes. No code changes are needed. CI needs a human re-trigger to reflect the current HEAD.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

pr-sous-chef
@copilot please address these unresolved review threads first:

Generated by 👨‍🍳 PR Sous Chef · 16.6 AIC · ⌖ 7.21 AIC · ⊞ 4.7K ·
Comment /souschef to run again

…same file

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>

Copilot AI commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

pr-sous-chef
@copilot please address these unresolved review threads first:

Fixed in c0b4778. Added a seenImportFiles map[token.Pos]bool in run(), threaded it through buildFix and addBytesImportEdit. Each file is now marked on the first import TextEdit emitted; subsequent violations in the same pass return (TextEdit{}, false) instead of a duplicate overlapping edit. All three threads are now addressed:

  • r3549194154len(file.Imports) == 1 guard already in place.
  • r3549289266"bytes" placed before existing spec (alphabetical).
  • r3549289271 — per-file deduplication map prevents duplicate TextEdits from multi-violation single-import files.

go test ./pkg/linters/bytescomparestring/... passes. CI needs a human re-trigger.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

4 participants