5
5
"go/ast"
6
6
"go/token"
7
7
"strings"
8
+ "sync"
8
9
9
10
"github.com/mgechev/revive/lint"
10
11
)
@@ -14,10 +15,12 @@ import (
14
15
// This has a notable false positive in that a package comment
15
16
// could rightfully appear in a different file of the same package,
16
17
// but that's not easy to fix since this linter is file-oriented.
17
- type PackageCommentsRule struct {}
18
+ type PackageCommentsRule struct {
19
+ checkPackageCommentCache sync.Map
20
+ }
18
21
19
22
// Apply applies the rule to given file.
20
- func (* PackageCommentsRule ) Apply (file * lint.File , _ lint.Arguments ) []lint.Failure {
23
+ func (r * PackageCommentsRule ) Apply (file * lint.File , _ lint.Arguments ) []lint.Failure {
21
24
var failures []lint.Failure
22
25
23
26
if file .IsTest () {
@@ -29,7 +32,7 @@ func (*PackageCommentsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Fail
29
32
}
30
33
31
34
fileAst := file .AST
32
- w := & lintPackageComments {fileAst , file , onFailure }
35
+ w := & lintPackageComments {fileAst , file , onFailure , r }
33
36
ast .Walk (w , fileAst )
34
37
return failures
35
38
}
@@ -43,6 +46,49 @@ type lintPackageComments struct {
43
46
fileAst * ast.File
44
47
file * lint.File
45
48
onFailure func (lint.Failure )
49
+ rule * PackageCommentsRule
50
+ }
51
+
52
+ func (l * lintPackageComments ) checkPackageComment () []lint.Failure {
53
+ // deduplicate warnings in package
54
+ if _ , exists := l .rule .checkPackageCommentCache .LoadOrStore (l .file .Pkg , struct {}{}); exists {
55
+ return nil
56
+ }
57
+ var docFile * ast.File // which name is doc.go
58
+ var packageFile * ast.File // which name is $package.go
59
+ var firstFile * ast.File
60
+ var firstFileName string
61
+ for name , file := range l .file .Pkg .Files () {
62
+ if file .AST .Doc != nil {
63
+ return nil
64
+ }
65
+ if name == "doc.go" {
66
+ docFile = file .AST
67
+ }
68
+ if name == file .AST .Name .String ()+ ".go" {
69
+ packageFile = file .AST
70
+ }
71
+ if firstFileName == "" || firstFileName > name {
72
+ firstFile = file .AST
73
+ firstFileName = name
74
+ }
75
+ }
76
+ // prefer warning on doc.go, $package.go over first file
77
+ if docFile == nil {
78
+ docFile = packageFile
79
+ }
80
+ if docFile == nil {
81
+ docFile = firstFile
82
+ }
83
+ if docFile != nil {
84
+ return []lint.Failure {{
85
+ Category : "comments" ,
86
+ Node : docFile ,
87
+ Confidence : 1 ,
88
+ Failure : "should have a package comment" ,
89
+ }}
90
+ }
91
+ return nil
46
92
}
47
93
48
94
func (l * lintPackageComments ) Visit (_ ast.Node ) ast.Visitor {
@@ -89,12 +135,9 @@ func (l *lintPackageComments) Visit(_ ast.Node) ast.Visitor {
89
135
}
90
136
91
137
if l .fileAst .Doc == nil {
92
- l .onFailure (lint.Failure {
93
- Category : "comments" ,
94
- Node : l .fileAst ,
95
- Confidence : 0.2 ,
96
- Failure : "should have a package comment, unless it's in another file for this package" ,
97
- })
138
+ for _ , failure := range l .checkPackageComment () {
139
+ l .onFailure (failure )
140
+ }
98
141
return nil
99
142
}
100
143
s := l .fileAst .Doc .Text ()
0 commit comments