diff --git a/formatter/builder.go b/formatter/builder.go index 30191e7..a4022ed 100644 --- a/formatter/builder.go +++ b/formatter/builder.go @@ -25,9 +25,10 @@ var ( warningStyle = color.New(color.FgHiYellow, color.Bold) ruleStyle = color.New(color.FgYellow, color.Bold) fileStyle = color.New(color.FgCyan, color.Bold) - lineStyle = color.New(color.FgBlue, color.Bold) + lineStyle = color.New(color.FgHiBlue, color.Bold) messageStyle = color.New(color.FgRed, color.Bold) suggestionStyle = color.New(color.FgGreen, color.Bold) + noStyle = color.New(color.FgWhite) ) // issueFormatter is the interface that wraps the Format method. @@ -136,7 +137,8 @@ func (b *issueFormatterBuilder) AddCodeSnippet() *issueFormatterBuilder { line = strings.TrimPrefix(line, b.commonIndent) lineNum := fmt.Sprintf("%*d", b.maxLineNumWidth, i) - b.writeStyledLine(lineStyle, "%s | %s\n", lineNum, line) + b.writeStyledLine(lineStyle, "%s | ", lineNum) + b.writeStyledLine(noStyle, "%s\n", line) } return b @@ -163,10 +165,15 @@ func (b *issueFormatterBuilder) AddUnderlineAndMessage() *issueFormatterBuilder underlineLength := underlineEnd - underlineStart + 1 b.result.WriteString(strings.Repeat(" ", underlineStart)) - b.writeStyledLine(messageStyle, "%s\n", strings.Repeat("~", underlineLength)) + b.writeStyledLine(messageStyle, "%s\n", strings.Repeat("^", underlineLength)) + b.writeStyledLine(lineStyle, "%s|\n", b.padding) b.writeStyledLine(lineStyle, "%s= ", b.padding) - b.writeStyledLine(messageStyle, "%s\n\n", b.issue.Message) + b.writeStyledLine(messageStyle, "%s\n", b.issue.Message) + + if b.issue.Note == "" { + b.result.WriteString("\n") + } return b } @@ -182,13 +189,14 @@ func (b *issueFormatterBuilder) AddSuggestion() *issueFormatterBuilder { return b } - b.writeStyledLine(suggestionStyle, "Suggestion:\n") + b.writeStyledLine(suggestionStyle, "suggestion:\n") b.writeStyledLine(lineStyle, "%s|\n", b.padding) suggestionLines := strings.Split(b.issue.Suggestion, "\n") for i, line := range suggestionLines { lineNum := fmt.Sprintf("%*d", b.maxLineNumWidth, b.issue.Start.Line+i) - b.writeStyledLine(lineStyle, "%s | %s\n", lineNum, line) + b.writeStyledLine(lineStyle, "%s | ", lineNum) + b.writeStyledLine(noStyle, "%s\n", line) } b.writeStyledLine(lineStyle, "%s|\n\n", b.padding) @@ -201,8 +209,13 @@ func (b *issueFormatterBuilder) AddNote() *issueFormatterBuilder { return b } - b.result.WriteString(suggestionStyle.Sprint("Note: ")) - b.writeStyledLine(lineStyle, "%s\n\n", b.issue.Note) + b.writeStyledLine(lineStyle, "%s= ", b.padding) + b.result.WriteString(noStyle.Sprint("note: ")) + + b.writeStyledLine(noStyle, "%s\n", b.issue.Note) + if b.issue.Suggestion == "" { + b.result.WriteString("\n") + } return b } diff --git a/formatter/cyclomatic_complexity.go b/formatter/cyclomatic_complexity.go index e516329..ba6de4b 100644 --- a/formatter/cyclomatic_complexity.go +++ b/formatter/cyclomatic_complexity.go @@ -16,8 +16,8 @@ func (f *CyclomaticComplexityFormatter) Format(issue tt.Issue, snippet *internal AddHeader(). AddCodeSnippet(). AddComplexityInfo(). - AddSuggestion(). AddNote(). + AddSuggestion(). Build() } diff --git a/formatter/formatter_test.go b/formatter/formatter_test.go index 8639077..4677962 100644 --- a/formatter/formatter_test.go +++ b/formatter/formatter_test.go @@ -43,14 +43,16 @@ func TestFormatIssuesWithArrows(t *testing.T) { --> test.go:4:5 | 4 | x := 1 - | ~~ + | ^^ + | = x declared but not used error: empty-if --> test.go:5:5 | 5 | if true {} - | ~~~~~~~~~ + | ^^^^^^^^^ + | = empty branch ` @@ -75,14 +77,16 @@ error: empty-if --> test.go:4:5 | 4 | x := 1 - | ~~ + | ^^ + | = x declared but not used error: empty-if --> test.go:5:5 | 5 | if true {} - | ~~~~~~~~~ + | ^^^^^^^^^ + | = empty branch ` @@ -137,21 +141,24 @@ func TestFormatIssuesWithArrows_MultipleDigitsLineNumbers(t *testing.T) { --> test.go:4:5 | 4 | x := 1 // unused variable - | ~~ + | ^^ + | = x declared but not used error: empty-if --> test.go:5:5 | 5 | if true {} // empty if statement - | ~~~~~~~~~ + | ^^^^^^^^^ + | = empty branch error: example --> test.go:10:5 | 10 | println("end") - | ~~~~~~~~ + | ^^^^^^^^ + | = example issue ` @@ -190,16 +197,15 @@ func TestUnnecessaryTypeConversionFormatter(t *testing.T) { --> test.go:5:10 | 5 | result := int(myInt) - | ~~~~~~~~~~~ + | ^^^^^^^^^^^ + | = unnecessary type conversion - -Suggestion: + = note: Unnecessary type conversions can make the code less readable and may slightly impact performance. They are safe to remove when the expression already has the desired type. +suggestion: | 5 | Remove the type conversion. Change ` + "`int(myInt)`" + ` to just ` + "`myInt`" + `. | -Note: Unnecessary type conversions can make the code less readable and may slightly impact performance. They are safe to remove when the expression already has the desired type. - ` result := formatter.Format(issue, snippet) diff --git a/formatter/general.go b/formatter/general.go index 7de030d..9f2e088 100644 --- a/formatter/general.go +++ b/formatter/general.go @@ -19,7 +19,7 @@ func (f *GeneralIssueFormatter) Format( AddHeader(). AddCodeSnippet(). AddUnderlineAndMessage(). - AddSuggestion(). AddNote(). + AddSuggestion(). Build() } diff --git a/internal/lints/const_error_decl.go b/internal/lints/const_error_decl.go index 9ecca65..faa0925 100644 --- a/internal/lints/const_error_decl.go +++ b/internal/lints/const_error_decl.go @@ -58,7 +58,7 @@ func DetectConstErrorDeclaration( Filename: filename, Start: fset.Position(genDecl.Pos()), End: fset.Position(genDecl.End()), - Message: "Avoid declaring constant errors", + Message: "avoid declaring constant errors", Suggestion: suggestion, Confidence: 1.0, Severity: severity, diff --git a/internal/lints/cyclomatic_complexity.go b/internal/lints/cyclomatic_complexity.go index 1972fb2..1093de8 100644 --- a/internal/lints/cyclomatic_complexity.go +++ b/internal/lints/cyclomatic_complexity.go @@ -41,8 +41,8 @@ func DetectHighCyclomaticComplexity(filename string, threshold int, severity tt. Start: fset.Position(funcNode.Pos()), End: fset.Position(funcNode.End()), Message: fmt.Sprintf("function %s has a cyclomatic complexity of %d (threshold %d)", stat.FuncName, stat.Complexity, threshold), - Suggestion: "Consider refactoring this function to reduce its complexity. You can split it into smaller functions or simplify the logic.\n", - Note: "High cyclomatic complexity can make the code harder to understand, test, and maintain. Aim for a complexity score of 10 or less for most functions.\n", + Suggestion: "consider refactoring this function to reduce its complexity. you can split it into smaller functions or simplify the logic.\n", + Note: "high cyclomatic complexity can make the code harder to understand, test, and maintain. aim for a complexity score of 10 or less for most functions.\n", Severity: severity, } issues = append(issues, issue) diff --git a/internal/lints/defers.go b/internal/lints/defers.go index 6ec233a..f9eb051 100644 --- a/internal/lints/defers.go +++ b/internal/lints/defers.go @@ -43,9 +43,9 @@ func (dc *DeferChecker) checkDeferPanic(stmt *ast.DeferStmt) { if call, ok := n.(*ast.CallExpr); ok { if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" { dc.addIssue("defer-panic", stmt.Pos(), stmt.End(), - "Avoid calling panic inside a defer statement", - "Consider removing the panic call from the defer statement. "+ - "If error handling is needed, use a separate error check before the defer.") + "avoid calling panic inside a defer statement", + "consider removing the panic call from the defer statement. "+ + "if error handling is needed, use a separate error check before the defer.") } } return true @@ -56,8 +56,8 @@ func (dc *DeferChecker) checkDeferNilFunc(stmt *ast.DeferStmt) { if ident, ok := stmt.Call.Fun.(*ast.Ident); ok { if ident.Name == "nil" || (ident.Obj != nil && ident.Obj.Kind == ast.Var) { dc.addIssue("defer-nil-func", stmt.Pos(), stmt.End(), - "Avoid deferring a potentially nil function", - "Deferring a nil function will cause a panic at runtime. Ensure the function is not nil before deferring.") + "avoid deferring a potentially nil function", + "deferring a nil function will cause a panic at runtime. ensure the function is not nil before deferring.") } } } @@ -68,8 +68,8 @@ func (dc *DeferChecker) checkReturnInDefer(stmt *ast.DeferStmt) { ast.Inspect(funcLit.Body, func(n ast.Node) bool { if _, ok := n.(*ast.ReturnStmt); ok { dc.addIssue("return-in-defer", n.Pos(), n.End(), - "Avoid using return statement inside a defer function", - "The return statement in a deferred function doesn't affect the returned value of the surrounding function. Consider removing it or refactoring your code.") + "avoid using return statement inside a defer function", + "the return statement in a deferred function doesn't affect the returned value of the surrounding function. consider removing it or refactoring your code.") return false } return true @@ -87,23 +87,23 @@ func (dc *DeferChecker) checkDeferInLoop(n ast.Node) { ast.Inspect(n, func(inner ast.Node) bool { if defer_, ok := inner.(*ast.DeferStmt); ok { dc.addIssue("defer-in-loop", defer_.Pos(), defer_.End(), - "Avoid using defer inside a loop", - "Consider moving the defer statement outside the loop to avoid potential performance issues.") + "avoid using defer inside a loop", + "consider moving the defer statement outside the loop to avoid potential performance issues.") } return true }) } } -func (dc *DeferChecker) addIssue(rule string, start, end token.Pos, message, suggestion string) { +func (dc *DeferChecker) addIssue(rule string, start, end token.Pos, message, note string) { dc.issues = append(dc.issues, tt.Issue{ - Rule: rule, - Filename: dc.filename, - Start: dc.fset.Position(start), - End: dc.fset.Position(end), - Message: message, - Suggestion: suggestion, - Severity: dc.severity, + Rule: rule, + Filename: dc.filename, + Start: dc.fset.Position(start), + End: dc.fset.Position(end), + Message: message, + Note: note, + Severity: dc.severity, }) } diff --git a/internal/lints/deprecate_func.go b/internal/lints/deprecate_func.go index 1e502b8..8b15d85 100644 --- a/internal/lints/deprecate_func.go +++ b/internal/lints/deprecate_func.go @@ -68,10 +68,10 @@ func DetectDeprecatedFunctions( func createDeprecationMessage(df checker.DeprecatedFunc) string { msg := "Use of deprecated function" if df.Alternative != "" { - msg = fmt.Sprintf("%s. Please use %s instead.", msg, df.Alternative) + msg = fmt.Sprintf("%s. please use %s instead.", msg, df.Alternative) return msg } - msg = fmt.Sprintf("%s. Please remove it.", msg) + msg = fmt.Sprintf("%s. please remove it.", msg) return msg } diff --git a/internal/lints/detect_cycles.go b/internal/lints/detect_cycles.go index 403ac0c..32de92d 100644 --- a/internal/lints/detect_cycles.go +++ b/internal/lints/detect_cycles.go @@ -19,7 +19,7 @@ func DetectCycle(filename string, node *ast.File, fset *token.FileSet, severity Filename: filename, Start: fset.Position(node.Pos()), End: fset.Position(node.End()), - Message: "Detected cycle in function call: " + cycle, + Message: "detected cycle in function call: " + cycle, Severity: severity, } issues = append(issues, issue) diff --git a/internal/lints/early_return.go b/internal/lints/early_return.go index a63bb89..f49eae1 100644 --- a/internal/lints/early_return.go +++ b/internal/lints/early_return.go @@ -45,7 +45,7 @@ func DetectEarlyReturnOpportunities(filename string, node *ast.File, fset *token Filename: filename, Start: fset.Position(ifStmt.Pos()), End: fset.Position(ifStmt.End()), - Message: "This if-else chain can be simplified using early returns", + Message: "this if-else chain can be simplified using early returns", Suggestion: suggestion, Confidence: 0.8, Severity: severity, diff --git a/internal/lints/early_return_test.go b/internal/lints/early_return_test.go index b917aa5..80a7c49 100644 --- a/internal/lints/early_return_test.go +++ b/internal/lints/early_return_test.go @@ -153,7 +153,6 @@ func example(x int) { issues, err := DetectEarlyReturnOpportunities(tmpfile, node, fset, types.SeverityError) require.NoError(t, err) - // assert.Equal(t, tt.expected, len(issues), "Number of detected early return opportunities doesn't match expected") if len(issues) != tt.expected { for _, issue := range issues { t.Logf("Issue: %v", issue) diff --git a/internal/lints/format_emit.go b/internal/lints/format_emit.go index b30a488..e075811 100644 --- a/internal/lints/format_emit.go +++ b/internal/lints/format_emit.go @@ -32,7 +32,7 @@ func DetectEmitFormat(filename string, node *ast.File, fset *token.FileSet, seve Filename: filename, Start: fset.Position(call.Pos()), End: fset.Position(call.End()), - Message: "Consider formatting std.Emit call for better readability", + Message: "consider formatting std.Emit call for better readability", Suggestion: formatEmitCall(call), Confidence: 1.0, Severity: severity, diff --git a/internal/lints/lint_test.go b/internal/lints/lint_test.go index 8fdfc7c..3635339 100644 --- a/internal/lints/lint_test.go +++ b/internal/lints/lint_test.go @@ -313,7 +313,7 @@ func main() { assert.Equal(t, tt.expected, len(issues), "Number of issues does not match expected") for _, issue := range issues { - assert.Contains(t, issue.Message, "Potential unnecessary allocation inside loop") + assert.Contains(t, issue.Message, "potential unnecessary allocation inside loop") } }) } @@ -383,7 +383,7 @@ func TestDetectEmitFormat(t *testing.T) { if len(issues) > 0 { assert.Equal(t, "emit-format", issues[0].Rule) - assert.Contains(t, issues[0].Message, "Consider formatting std.Emit call for better readability") + assert.Contains(t, issues[0].Message, "consider formatting std.Emit call for better readability") } }) } @@ -721,7 +721,7 @@ var err = errors.New("error") for _, issue := range issues { assert.Equal(t, "const-error-declaration", issue.Rule) - assert.Contains(t, issue.Message, "Avoid declaring constant errors") + assert.Contains(t, issue.Message, "avoid declaring constant errors") assert.Contains(t, issue.Suggestion, "var") } }) diff --git a/internal/lints/loop_allocation.go b/internal/lints/loop_allocation.go index 9005f51..07a7324 100644 --- a/internal/lints/loop_allocation.go +++ b/internal/lints/loop_allocation.go @@ -19,7 +19,7 @@ func DetectLoopAllocation(filename string, node *ast.File, fset *token.FileSet, if isAllocationFunction(innerNode) { issues = append(issues, tt.Issue{ Rule: "loop-allocation", - Message: "Potential unnecessary allocation inside loop", + Message: "potential unnecessary allocation inside loop", Start: fset.Position(innerNode.Pos()), End: fset.Position(innerNode.End()), Severity: severity, diff --git a/internal/lints/missing_package_mod.go b/internal/lints/missing_package_mod.go index 4fd45d9..9e34bea 100644 --- a/internal/lints/missing_package_mod.go +++ b/internal/lints/missing_package_mod.go @@ -42,7 +42,7 @@ func DetectMissingModPackage(filename string, node *ast.File, fset *token.FileSe Filename: modFile, Start: token.Position{Filename: modFile}, End: token.Position{Filename: modFile}, - Message: fmt.Sprintf("Packages %s are declared in gno.mod file but not imported.\nRun `gno mod tidy`", strings.Join(unusedPackages, ", ")), + Message: fmt.Sprintf("packages %s are declared in gno.mod file but not imported.\nrun `gno mod tidy`", strings.Join(unusedPackages, ", ")), Severity: severity, } issues = append(issues, issue) @@ -55,7 +55,7 @@ func DetectMissingModPackage(filename string, node *ast.File, fset *token.FileSe Filename: modFile, Start: token.Position{Filename: modFile}, End: token.Position{Filename: modFile}, - Message: fmt.Sprintf("Package %s is imported but not declared in gno.mod file. Please consider to remove.\nRun `gno mod tidy`", pkg), + Message: fmt.Sprintf("package %s is imported but not declared in gno.mod file. please consider to remove.\nrun `gno mod tidy`", pkg), Severity: severity, } issues = append(issues, issue) diff --git a/internal/lints/simplify_slice_expr.go b/internal/lints/simplify_slice_expr.go index 17461ba..d503c11 100644 --- a/internal/lints/simplify_slice_expr.go +++ b/internal/lints/simplify_slice_expr.go @@ -27,18 +27,18 @@ func DetectUnnecessarySliceLength(filename string, node *ast.File, fset *token.F if sliceExpr.Low == nil { suggestion = fmt.Sprintf("%s[:]", arg.Name) detailedMessage = fmt.Sprintf( - "%s\nIn this case, `%s[:len(%s)]` is equivalent to `%s[:]`. "+ - "The full length of the slice is already implied when omitting both start and end indices.", + "%s\nin this case, `%s[:len(%s)]` is equivalent to `%s[:]`. "+ + "the full length of the slice is already implied when omitting both start and end indices.", baseMessage, arg.Name, arg.Name, arg.Name) } else if basicLit, ok := sliceExpr.Low.(*ast.BasicLit); ok { suggestion = fmt.Sprintf("%s[%s:]", arg.Name, basicLit.Value) - detailedMessage = fmt.Sprintf("%s\nHere, `%s[%s:len(%s)]` can be simplified to `%s[%s:]`. "+ - "When slicing to the end of a slice, using len() is unnecessary.", + detailedMessage = fmt.Sprintf("%s\nhere, `%s[%s:len(%s)]` can be simplified to `%s[%s:]`. "+ + "when slicing to the end of a slice, using len() is unnecessary.", baseMessage, arg.Name, basicLit.Value, arg.Name, arg.Name, basicLit.Value) } else if lowIdent, ok := sliceExpr.Low.(*ast.Ident); ok { suggestion = fmt.Sprintf("%s[%s:]", arg.Name, lowIdent.Name) - detailedMessage = fmt.Sprintf("%s\nIn this instance, `%s[%s:len(%s)]` can be written as `%s[%s:]`. "+ - "The len() function is redundant when slicing to the end, regardless of the start index.", + detailedMessage = fmt.Sprintf("%s\nin this instance, `%s[%s:len(%s)]` can be written as `%s[%s:]`. "+ + "the len() function is redundant when slicing to the end, regardless of the start index.", baseMessage, arg.Name, lowIdent.Name, arg.Name, arg.Name, lowIdent.Name) } diff --git a/internal/lints/slice_bound.go b/internal/lints/slice_bound.go index 32a6ae3..f0dd062 100644 --- a/internal/lints/slice_bound.go +++ b/internal/lints/slice_bound.go @@ -55,14 +55,14 @@ func createIssue(node ast.Node, ident *ast.Ident, filename string, fset *token.F return tt.Issue{} } category = "index-access" - message = "Potential out of bounds array/slice index access" + message = "potential out of bounds array/slice index access" suggestion = fmt.Sprintf("if i < len(%s) { value := %s[i] }", ident.Name, ident.Name) - note = "Always check the length of the array/slice before accessing an index to prevent runtime panics." + note = "always check the length of the array/slice before accessing an index to prevent runtime panics." case *ast.SliceExpr: category = "slice-expression" - message = "Potential out of bounds slice expression" + message = "potential out of bounds slice expression" suggestion = fmt.Sprintf("%s = append(%s, newElement)", ident.Name, ident.Name) - note = "Consider using append() for slices to automatically handle capacity and prevent out of bounds errors." + note = "consider using append() for slices to automatically handle capacity and prevent out of bounds errors." } return tt.Issue{ diff --git a/internal/lints/unncessary_type_conversion.go b/internal/lints/unncessary_type_conversion.go index 19ef2cb..5bdff9d 100644 --- a/internal/lints/unncessary_type_conversion.go +++ b/internal/lints/unncessary_type_conversion.go @@ -102,7 +102,7 @@ func DetectUnnecessaryConversions(filename string, node *ast.File, fset *token.F if _, exists := varDecls[obj]; exists { declType := obj.Type().String() memo = fmt.Sprintf( - "The variable '%s' is declared as type '%s'. This type conversion appears unnecessary.", + "the variable '%s' is declared as type '%s'. this type conversion appears unnecessary.", id.Name, declType, ) }