Skip to content

Conversation

kakkoyun
Copy link
Contributor

@kakkoyun kakkoyun commented Aug 6, 2025

  • Implement conflict resolution when both type assertion and error comparison linting rules apply to the same line
  • Add support for else-if statement transformations using errors.As and errors.Is
  • Generate combined diagnostics with unified suggested fixes

Signed-off-by: Kemal Akkoyun [email protected]

fixes polyfloyd#100)

- Implement conflict resolution when both type assertion and error comparison linting rules apply to the same line
- Add support for else-if statement transformations using errors.As and errors.Is
- Generate combined diagnostics with unified suggested fixes

Signed-off-by: Kemal Akkoyun <[email protected]>
@kakkoyun kakkoyun mentioned this pull request Aug 6, 2025
@rockdaboot
Copy link

Testing this PR on the same code as in the error description, the comment lines were dropped.

-               } else if e, ok := err.(*os.PathError); ok && e.Err == syscall.ESRCH {
-                       // If the process exits while reading its /proc/$PID/maps, the kernel will
-                       // return ESRCH. Handle it as if the process did not exist.
-                       pm.mappingStats.errProcESRCH.Add(1)
+               } else {
+                       e := &os.PathError{}
+                       if errors.As(err, &e) && errors.Is(e.Err, syscall.ESRCH) {
+                               pm.mappingStats.errProcESRCH.Add(1)
+                       }
                }

Also (maybe a different issue), with the following change, the errors import is missing

-       if n == 0 || (err != nil && err != io.EOF) {
+       if n == 0 || (err != nil && !errors.Is(err, io.EOF)) {

Let me know if this is unrelated and I'll open another issue.

Comment on lines +93 to +95
if strings.HasPrefix(targetTypeStr, "*") {
baseType, _ := strings.CutPrefix(targetTypeStr, "*")
return fmt.Sprintf("%s := &%s{}", assertion.varName, baseType)

Choose a reason for hiding this comment

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

What about this?

Suggested change
if strings.HasPrefix(targetTypeStr, "*") {
baseType, _ := strings.CutPrefix(targetTypeStr, "*")
return fmt.Sprintf("%s := &%s{}", assertion.varName, baseType)
if baseType, found := strings.CutPrefix(targetTypeStr, "*") ; found {
return fmt.Sprintf("%s := &%s{}", assertion.varName, baseType)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I should have remembered this one from your comments from the first PR 🙈 Muscle memory!

Comment on lines +1064 to +1084
// nodeContainsPos checks if a node contains the given position
func nodeContainsPos(node ast.Node, pos token.Pos) bool {
return node.Pos() <= pos && pos < node.End()
}

// containingIf finds the if statement that contains the given node
// by walking up the AST parent chain.
func containingIf(extInfo *TypesInfoExt, node ast.Node) *ast.IfStmt {
current := node
for current != nil {
if ifStmt, ok := current.(*ast.IfStmt); ok {
return ifStmt
}
parent := extInfo.NodeParent[current]
if parent == nil {
break
}
current = parent
}
return nil
}

Choose a reason for hiding this comment

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

Maybe this to be consistent

Suggested change
// nodeContainsPos checks if a node contains the given position
func nodeContainsPos(node ast.Node, pos token.Pos) bool {
return node.Pos() <= pos && pos < node.End()
}
// containingIf finds the if statement that contains the given node
// by walking up the AST parent chain.
func containingIf(extInfo *TypesInfoExt, node ast.Node) *ast.IfStmt {
current := node
for current != nil {
if ifStmt, ok := current.(*ast.IfStmt); ok {
return ifStmt
}
parent := extInfo.NodeParent[current]
if parent == nil {
break
}
current = parent
}
return nil
}
// nodeContainsPos checks if a node contains the given position
func nodeContainsPos(node ast.Node, pos token.Pos) bool {
return node.Pos() <= pos && pos < node.End()
}
// statementContainsIf finds the if statement that contains the given node
// by walking up the AST parent chain.
func statementContainsIf(extInfo *TypesInfoExt, node ast.Node) *ast.IfStmt {
current := node
for current != nil {
if ifStmt, ok := current.(*ast.IfStmt); ok {
return ifStmt
}
parent := extInfo.NodeParent[current]
if parent == nil {
break
}
current = parent
}
return nil
}

Comment on lines +1181 to +1185
// context holds if statement context information
type context struct {
isElseIf bool
bodyStmts []ast.Stmt
}

Choose a reason for hiding this comment

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

It's strange to call it context, especially because it shadows context package

ifInfo maybe?

Comment on lines +24 to +39
const (
typeAssertionPattern = "type assertion on error"
errorComparisonPattern = "comparing with"
)

// classifyDiagnostic determines the type of diagnostic based on its message
func classifyDiagnostic(diagnostic analysis.Diagnostic) diagnosticType {
msg := diagnostic.Message
if strings.Contains(msg, typeAssertionPattern) {
return typeAssertionDiag
}
if strings.Contains(msg, errorComparisonPattern) {
return errorComparisonDiag
}
return otherDiag
}

Choose a reason for hiding this comment

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

I'm surprised the detection is made on the message.

Should we use this constant when raising the diagnostic? Maybe I missed something 🤔

@kakkoyun
Copy link
Contributor Author

Testing this PR on the same code as in the error description, the comment lines were dropped.

-               } else if e, ok := err.(*os.PathError); ok && e.Err == syscall.ESRCH {
-                       // If the process exits while reading its /proc/$PID/maps, the kernel will
-                       // return ESRCH. Handle it as if the process did not exist.
-                       pm.mappingStats.errProcESRCH.Add(1)
+               } else {
+                       e := &os.PathError{}
+                       if errors.As(err, &e) && errors.Is(e.Err, syscall.ESRCH) {
+                               pm.mappingStats.errProcESRCH.Add(1)
+                       }
                }

Also (maybe a different issue), with the following change, the errors import is missing

-       if n == 0 || (err != nil && err != io.EOF) {
+       if n == 0 || (err != nil && !errors.Is(err, io.EOF)) {

Let me know if this is unrelated and I'll open another issue.

Good catch. I have to make sure we preserve the comments (hard with stdlib).

I will think ways to simplify the implementation. It is hard to rewrite control flow statements.

@kakkoyun kakkoyun marked this pull request as draft August 11, 2025 11:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants