1
0
mirror of https://github.com/mgechev/revive.git synced 2025-10-30 23:37:49 +02:00

adds comments-density rule (#979)

This commit is contained in:
chavacava
2024-04-20 10:20:56 +02:00
committed by GitHub
parent 0a77458f89
commit 9b297848d9
8 changed files with 208 additions and 0 deletions

View File

@@ -539,6 +539,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
| [`enforce-slice-style`](./RULES_DESCRIPTIONS.md#enforce-slice-style) | string (defaults to "any") | Enforces consistent usage of `make([]type, 0)` or `[]type{}` for slice initialization. Does not affect `make(map[type]type, non_zero_len, or_non_zero_cap)` constructions. | no | no |
| [`enforce-repeated-arg-type-style`](./RULES_DESCRIPTIONS.md#enforce-repeated-arg-type-style) | string (defaults to "any") | Enforces consistent style for repeated argument and/or return value types. | no | no |
| [`max-control-nesting`](./RULES_DESCRIPTIONS.md#max-control-nesting) | int (defaults to 5) | Sets restriction for maximum nesting of control structures. | no | no |
| [`comments-density`](./RULES_DESCRIPTIONS.md#comments-density) | int (defaults to 0) | Enforces a minumum comment / code relation | no | no |
## Configurable rules

View File

@@ -14,6 +14,7 @@ List of all available rules.
- [call-to-gc](#call-to-gc)
- [cognitive-complexity](#cognitive-complexity)
- [comment-spacings](#comment-spacings)
- [comments-density](#comment-spacings)
- [confusing-naming](#confusing-naming)
- [confusing-results](#confusing-results)
- [constant-logical-expr](#constant-logical-expr)
@@ -193,6 +194,19 @@ Example:
[rule.comment-spacings]
arguments =["mypragma:", "+optional"]
```
## comments-density
_Description_: Spots files not respecting a minimum value for the [_comments lines density_](https://docs.sonarsource.com/sonarqube/latest/user-guide/metric-definitions/) metric = _comment lines / (lines of code + comment lines) * 100_
_Configuration_: (int) the minimum expected comments lines density.
Example:
```toml
[rule.comments-density]
arguments =[15]
```
## confusing-naming
_Description_: Methods or fields of `struct` that have names different only by capitalization could be confusing.

View File

@@ -95,6 +95,7 @@ var allRules = append([]lint.Rule{
&rule.EnforceRepeatedArgTypeStyleRule{},
&rule.EnforceSliceStyleRule{},
&rule.MaxControlNestingRule{},
&rule.CommentsDensityRule{},
}, defaultRules...)
var allFormatters = []lint.Formatter{

95
rule/comments-density.go Normal file
View File

@@ -0,0 +1,95 @@
package rule
import (
"fmt"
"go/ast"
"strings"
"sync"
"github.com/mgechev/revive/lint"
)
// CommentsDensityRule lints given else constructs.
type CommentsDensityRule struct {
minumumCommentsDensity int64
configured bool
sync.Mutex
}
const defaultMinimumCommentsPercentage = 0
func (r *CommentsDensityRule) configure(arguments lint.Arguments) {
r.Lock()
defer r.Unlock()
if r.configured {
return
}
r.configured = true
if len(arguments) < 1 {
r.minumumCommentsDensity = defaultMinimumCommentsPercentage
return
}
var ok bool
r.minumumCommentsDensity, ok = arguments[0].(int64)
if !ok {
panic(fmt.Sprintf("invalid argument for %q rule: argument should be an int, got %T", r.Name(), arguments[0]))
}
}
// Apply applies the rule to given file.
func (r *CommentsDensityRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
r.configure(arguments)
commentsLines := countDocLines(file.AST.Comments)
statementsCount := countStatements(file.AST)
density := (float32(commentsLines) / float32(statementsCount+commentsLines)) * 100
if density < float32(r.minumumCommentsDensity) {
return []lint.Failure{
{
Node: file.AST,
Confidence: 1,
Failure: fmt.Sprintf("the file has a comment density of %2.f%% (%d comment lines for %d code lines) but expected a minimum of %d%%", density, commentsLines, statementsCount, r.minumumCommentsDensity),
},
}
}
return nil
}
// Name returns the rule name.
func (*CommentsDensityRule) Name() string {
return "comments-density"
}
// countStatements counts the number of program statements in the given AST.
func countStatements(node ast.Node) int {
counter := 0
ast.Inspect(node, func(n ast.Node) bool {
switch n.(type) {
case *ast.ExprStmt, *ast.AssignStmt, *ast.ReturnStmt, *ast.GoStmt, *ast.DeferStmt,
*ast.BranchStmt, *ast.IfStmt, *ast.SwitchStmt, *ast.TypeSwitchStmt,
*ast.SelectStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause,
*ast.DeclStmt, *ast.FuncDecl:
counter++
}
return true
})
return counter
}
func countDocLines(comments []*ast.CommentGroup) int {
acc := 0
for _, c := range comments {
lines := strings.Split(c.Text(), "\n")
acc += len(lines) - 1
}
return acc
}

View File

@@ -0,0 +1,20 @@
package test
import (
"testing"
"github.com/mgechev/revive/lint"
"github.com/mgechev/revive/rule"
)
func TestCommentsDensity(t *testing.T) {
testRule(t, "comments-density-1", &rule.CommentsDensityRule{}, &lint.RuleConfig{
Arguments: []any{int64(60)},
})
testRule(t, "comments-density-2", &rule.CommentsDensityRule{}, &lint.RuleConfig{
Arguments: []any{int64(90)},
})
testRule(t, "comments-density-3", &rule.CommentsDensityRule{}, &lint.RuleConfig{})
}

9
testdata/comments-density-1.go vendored Normal file
View File

@@ -0,0 +1,9 @@
package fixtures // MATCH /the file has a comment density of 57% (4 comment lines for 3 code lines) but expected a minimum of 60%/
// func contains banned characters Ω // authorized banned chars in comment
func cd1() error {
// the var
var charσhid string
/* the return */
return nil
}

34
testdata/comments-density-2.go vendored Normal file
View File

@@ -0,0 +1,34 @@
package fixtures // MATCH /the file has a comment density of 19% (5 comment lines for 21 code lines) but expected a minimum of 90%/
// datarace is function
func datarace() (r int, c char) {
for _, p := range []int{1, 2} {
go func() {
print(r)
print(p)
}()
for i, p1 := range []int{1, 2} {
a := p1
go func() {
print(r)
print(p)
print(p1)
print(a)
print(i)
}()
print(i)
print(p)
go func() {
_ = c
}()
}
print(p1)
}
/* Goroutines
are
awesome */
go func() {
print(r)
}()
print(r)
}

34
testdata/comments-density-3.go vendored Normal file
View File

@@ -0,0 +1,34 @@
package fixtures
// datarace is function
func datarace() (r int, c char) {
for _, p := range []int{1, 2} {
go func() {
print(r)
print(p)
}()
for i, p1 := range []int{1, 2} {
a := p1
go func() {
print(r)
print(p)
print(p1)
print(a)
print(i)
}()
print(i)
print(p)
go func() {
_ = c
}()
}
print(p1)
}
/* Goroutines
are
awesome */
go func() {
print(r)
}()
print(r)
}