Skip to content

Commit 2a7e60a

Browse files
author
chavacava
committed
adds comments-density rule
1 parent f88f60d commit 2a7e60a

8 files changed

+205
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,7 @@ List of all available rules. The rules ported from `golint` are left unchanged a
539539
| [`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 |
540540
| [`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 |
541541
| [`max-control-nesting`](./RULES_DESCRIPTIONS.md#max-control-nesting) | int (defaults to 5) | Sets restriction for maximum nesting of control structures. | no | no |
542+
| [`comments-density`](./RULES_DESCRIPTIONS.md#comments-density) | int (defaults to 0) | Enforces a minumum comment / code relation | no | no |
542543

543544

544545
## Configurable rules

RULES_DESCRIPTIONS.md

+14
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ List of all available rules.
1414
- [call-to-gc](#call-to-gc)
1515
- [cognitive-complexity](#cognitive-complexity)
1616
- [comment-spacings](#comment-spacings)
17+
- [comments-density](#comment-spacings)
1718
- [confusing-naming](#confusing-naming)
1819
- [confusing-results](#confusing-results)
1920
- [constant-logical-expr](#constant-logical-expr)
@@ -192,6 +193,19 @@ Example:
192193
[rule.comment-spacings]
193194
arguments =["mypragma","otherpragma"]
194195
```
196+
## comments-density
197+
198+
_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_
199+
200+
_Configuration_: (int) the minimum expected comments lines density.
201+
202+
Example:
203+
204+
```toml
205+
[rule.comments-density]
206+
arguments =[15]
207+
```
208+
195209
## confusing-naming
196210

197211
_Description_: Methods or fields of `struct` that have names different only by capitalization could be confusing.

config/config.go

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ var allRules = append([]lint.Rule{
9595
&rule.EnforceRepeatedArgTypeStyleRule{},
9696
&rule.EnforceSliceStyleRule{},
9797
&rule.MaxControlNestingRule{},
98+
&rule.CommentsDensityRule{},
9899
}, defaultRules...)
99100

100101
var allFormatters = []lint.Formatter{

rule/comments-density.go

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package rule
2+
3+
import (
4+
"fmt"
5+
"go/ast"
6+
"strings"
7+
"sync"
8+
9+
"github.com/mgechev/revive/lint"
10+
)
11+
12+
// CommentsDensityRule lints given else constructs.
13+
type CommentsDensityRule struct {
14+
minumumCommentsDensity int64
15+
configured bool
16+
sync.Mutex
17+
}
18+
19+
const defaultMinimumCommentsPercentage = 0
20+
21+
func (r *CommentsDensityRule) configure(arguments lint.Arguments) {
22+
r.Lock()
23+
defer r.Unlock()
24+
25+
if !r.configured {
26+
r.configured = true
27+
if len(arguments) < 1 {
28+
r.minumumCommentsDensity = defaultMinimumCommentsPercentage
29+
return
30+
}
31+
32+
var ok bool
33+
r.minumumCommentsDensity, ok = arguments[0].(int64)
34+
if !ok {
35+
panic(fmt.Sprintf("invalid argument for %q rule: argument should be an int, got %T", r.Name(), arguments[0]))
36+
}
37+
}
38+
}
39+
40+
// Apply applies the rule to given file.
41+
func (r *CommentsDensityRule) Apply(file *lint.File, arguments lint.Arguments) []lint.Failure {
42+
r.configure(arguments)
43+
44+
commentsLines := countDocLines(file.AST.Comments)
45+
statementsCount := countStatements(file.AST)
46+
density := (float32(commentsLines) / float32(statementsCount+commentsLines)) * 100
47+
48+
if density < float32(r.minumumCommentsDensity) {
49+
return []lint.Failure{
50+
{
51+
Node: file.AST,
52+
Confidence: 1,
53+
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),
54+
},
55+
}
56+
}
57+
58+
return nil
59+
}
60+
61+
// Name returns the rule name.
62+
func (*CommentsDensityRule) Name() string {
63+
return "comments-density"
64+
}
65+
66+
// countStatements counts the number of program statements in the given AST.
67+
func countStatements(node ast.Node) int {
68+
counter := 0
69+
70+
ast.Inspect(node, func(n ast.Node) bool {
71+
switch n.(type) {
72+
case *ast.ExprStmt, *ast.AssignStmt, *ast.ReturnStmt, *ast.GoStmt, *ast.DeferStmt,
73+
*ast.BranchStmt, *ast.IfStmt, *ast.SwitchStmt, *ast.TypeSwitchStmt,
74+
*ast.SelectStmt, *ast.ForStmt, *ast.RangeStmt, *ast.CaseClause, *ast.CommClause,
75+
*ast.DeclStmt, *ast.FuncDecl:
76+
counter++
77+
}
78+
return true
79+
})
80+
81+
return counter
82+
}
83+
84+
func countDocLines(comments []*ast.CommentGroup) int {
85+
acc := 0
86+
for _, c := range comments {
87+
lines := strings.Split(c.Text(), "\n")
88+
acc += len(lines) - 1
89+
}
90+
91+
return acc
92+
}

test/comments-density_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mgechev/revive/lint"
7+
"github.com/mgechev/revive/rule"
8+
)
9+
10+
func TestCommentsDensity(t *testing.T) {
11+
testRule(t, "comments-density-1", &rule.CommentsDensityRule{}, &lint.RuleConfig{
12+
Arguments: []any{int64(60)},
13+
})
14+
15+
testRule(t, "comments-density-2", &rule.CommentsDensityRule{}, &lint.RuleConfig{
16+
Arguments: []any{int64(90)},
17+
})
18+
19+
testRule(t, "comments-density-3", &rule.CommentsDensityRule{}, &lint.RuleConfig{})
20+
}

testdata/comments-density-1.go

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

testdata/comments-density-2.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package fixtures // MATCH /the file has a comment density of 19% (5 comment lines for 21 code lines) but expected a minimum of 90%/
2+
3+
// datarace is function
4+
func datarace() (r int, c char) {
5+
for _, p := range []int{1, 2} {
6+
go func() {
7+
print(r)
8+
print(p)
9+
}()
10+
for i, p1 := range []int{1, 2} {
11+
a := p1
12+
go func() {
13+
print(r)
14+
print(p)
15+
print(p1)
16+
print(a)
17+
print(i)
18+
}()
19+
print(i)
20+
print(p)
21+
go func() {
22+
_ = c
23+
}()
24+
}
25+
print(p1)
26+
}
27+
/* Goroutines
28+
are
29+
awesome */
30+
go func() {
31+
print(r)
32+
}()
33+
print(r)
34+
}

testdata/comments-density-3.go

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package fixtures
2+
3+
// datarace is function
4+
func datarace() (r int, c char) {
5+
for _, p := range []int{1, 2} {
6+
go func() {
7+
print(r)
8+
print(p)
9+
}()
10+
for i, p1 := range []int{1, 2} {
11+
a := p1
12+
go func() {
13+
print(r)
14+
print(p)
15+
print(p1)
16+
print(a)
17+
print(i)
18+
}()
19+
print(i)
20+
print(p)
21+
go func() {
22+
_ = c
23+
}()
24+
}
25+
print(p1)
26+
}
27+
/* Goroutines
28+
are
29+
awesome */
30+
go func() {
31+
print(r)
32+
}()
33+
print(r)
34+
}

0 commit comments

Comments
 (0)