-
Notifications
You must be signed in to change notification settings - Fork 460
Preserve expression precedence in stringsjoinone autofix replacements
#47482
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 |
|---|---|---|
|
|
@@ -69,10 +69,11 @@ func analyzeJoinOne(pass *analysis.Pass, n ast.Node, generatedFiles filecheck.Ge | |
| return | ||
| } | ||
|
|
||
| elemText, ok := matchSingleElementStringSlice(pass, joinCall.Args[0]) | ||
| elem, elemText, ok := matchSingleElementStringSlice(pass, joinCall.Args[0]) | ||
| if !ok { | ||
| return | ||
| } | ||
| replacementText := formatReplacementText(elem, elemText) | ||
| // Only flag when the separator is a compile-time constant so that the | ||
| // suggested fix does not silently drop observable side effects. For | ||
| // example, strings.Join([]string{s}, <-ch) receives from a channel before | ||
|
|
@@ -84,13 +85,13 @@ func analyzeJoinOne(pass *analysis.Pass, n ast.Node, generatedFiles filecheck.Ge | |
| pass.Report(analysis.Diagnostic{ | ||
| Pos: call.Pos(), | ||
| End: call.End(), | ||
| Message: fmt.Sprintf("strings.Join called with a single-element slice; use %s directly", elemText), | ||
| Message: fmt.Sprintf("strings.Join called with a single-element slice; use %s directly", replacementText), | ||
|
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. [/diagnosing-bugs] The diagnostic 💡 Suggested improvementConsider using the raw Message: fmt.Sprintf("strings.Join called with a single-element slice; use %s directly", elemText),
// ...
Message: "Replace strings.Join call with " + replacementText,
// ...
NewText: []byte(replacementText),This keeps the human-readable message clean ("use a + b directly") while the machine-applied fix still emits the correctly-parenthesized text. @copilot please address this.
Contributor
Author
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. Fixed in 37c8f45. The diagnostic |
||
| SuggestedFixes: []analysis.SuggestedFix{{ | ||
| Message: "Replace strings.Join call with " + elemText, | ||
| Message: "Replace strings.Join call with " + replacementText, | ||
| TextEdits: []analysis.TextEdit{{ | ||
| Pos: call.Pos(), | ||
| End: call.End(), | ||
| NewText: []byte(elemText), | ||
| NewText: []byte(replacementText), | ||
| }}, | ||
| }}, | ||
| }) | ||
|
|
@@ -107,30 +108,39 @@ func isSafeToDiscardSeparator(pass *analysis.Pass, sep ast.Expr) bool { | |
|
|
||
| // matchSingleElementStringSlice reports whether expr is a []string{...} composite | ||
| // literal with exactly one element and returns the text of that element. | ||
| func matchSingleElementStringSlice(pass *analysis.Pass, expr ast.Expr) (elemText string, ok bool) { | ||
| func matchSingleElementStringSlice(pass *analysis.Pass, expr ast.Expr) (elem ast.Expr, elemText string, ok bool) { | ||
| lit, ok := expr.(*ast.CompositeLit) | ||
| if !ok { | ||
| return "", false | ||
| return nil, "", false | ||
| } | ||
| // The type must be []string (array type with no length, element type "string"). | ||
| arrayType, ok := lit.Type.(*ast.ArrayType) | ||
| if !ok || arrayType.Len != nil { | ||
| return "", false | ||
| return nil, "", false | ||
| } | ||
| ident, ok := arrayType.Elt.(*ast.Ident) | ||
| if !ok || ident.Name != "string" { | ||
| return "", false | ||
| return nil, "", false | ||
| } | ||
| if len(lit.Elts) != 1 { | ||
| return "", false | ||
| return nil, "", false | ||
| } | ||
| elem := lit.Elts[0] | ||
| elem = lit.Elts[0] | ||
| if _, isKV := elem.(*ast.KeyValueExpr); isKV { | ||
| return "", false | ||
| return nil, "", false | ||
| } | ||
| text := astutil.NodeText(pass.Fset, elem) | ||
| if text == "" { | ||
| return "", false | ||
| return nil, "", false | ||
| } | ||
| return elem, text, true | ||
| } | ||
|
|
||
| func formatReplacementText(elem ast.Expr, elemText string) string { | ||
| switch elem.(type) { | ||
| case *ast.Ident, *ast.BasicLit, *ast.ParenExpr, *ast.SelectorExpr, *ast.IndexExpr, *ast.CallExpr: | ||
|
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. [/diagnosing-bugs] 💡 Suggested fixAdd case *ast.Ident, *ast.BasicLit, *ast.ParenExpr, *ast.SelectorExpr,
*ast.IndexExpr, *ast.SliceExpr, *ast.CallExpr, *ast.TypeAssertExpr:
return elemTextBoth slice expressions ( Companion test case to add: func joinOneSliceElem(s string) string {
return strings.Join([]string{s[1:]}, "") // want `strings\.Join called with a single-element slice`
}Golden: func joinOneSliceElem(s string) string {
return s[1:] // want `strings\.Join called with a single-element slice`
}@copilot please address this.
Contributor
Author
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. Fixed in 37c8f45. Added |
||
| return elemText | ||
| default: | ||
| return "(" + elemText + ")" | ||
| } | ||
| return text, true | ||
| } | ||
|
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. Missing 💡 Details and suggested fixThe allowlist in
Suggested fix: case *ast.Ident, *ast.BasicLit, *ast.ParenExpr, *ast.SelectorExpr,
*ast.IndexExpr, *ast.CallExpr, *ast.SliceExpr, *ast.TypeAssertExpr:
return elemTextThis is not a correctness regression (the wrapped form compiles identically), but it produces misleading suggested fixes for these common patterns. |
||
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.
Diagnostic message leaks implementation parens into user-visible output for compound expressions.
💡 Details
When the element is
a + b,replacementTextbecomes(a + b). The diagnostic message then reads:The parentheses are an autofix-internal detail and look confusing in the warning message. The message should use the raw
elemTextwhile theSuggestedFixusesreplacementText: