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:
		| @@ -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 | ||||
|   | ||||
| @@ -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. | ||||
|   | ||||
| @@ -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
									
								
							
							
						
						
									
										95
									
								
								rule/comments-density.go
									
									
									
									
									
										Normal 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 | ||||
| } | ||||
							
								
								
									
										20
									
								
								test/comments-density_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								test/comments-density_test.go
									
									
									
									
									
										Normal 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
									
								
							
							
						
						
									
										9
									
								
								testdata/comments-density-1.go
									
									
									
									
										vendored
									
									
										Normal 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
									
								
							
							
						
						
									
										34
									
								
								testdata/comments-density-2.go
									
									
									
									
										vendored
									
									
										Normal 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
									
								
							
							
						
						
									
										34
									
								
								testdata/comments-density-3.go
									
									
									
									
										vendored
									
									
										Normal 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) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user