github.com/Johnny2210/revive@v1.0.8-0.20210625134200-febf37ccd0f5/rule/package-comments.go (about) 1 package rule 2 3 import ( 4 "fmt" 5 "go/ast" 6 "go/token" 7 "strings" 8 9 "github.com/mgechev/revive/lint" 10 ) 11 12 // PackageCommentsRule lints the package comments. It complains if 13 // there is no package comment, or if it is not of the right form. 14 // This has a notable false positive in that a package comment 15 // could rightfully appear in a different file of the same package, 16 // but that's not easy to fix since this linter is file-oriented. 17 type PackageCommentsRule struct{} 18 19 // Apply applies the rule to given file. 20 func (r *PackageCommentsRule) Apply(file *lint.File, _ lint.Arguments) []lint.Failure { 21 var failures []lint.Failure 22 23 if isTest(file) { 24 return failures 25 } 26 27 onFailure := func(failure lint.Failure) { 28 failures = append(failures, failure) 29 } 30 31 fileAst := file.AST 32 w := &lintPackageComments{fileAst, file, onFailure} 33 ast.Walk(w, fileAst) 34 return failures 35 } 36 37 // Name returns the rule name. 38 func (r *PackageCommentsRule) Name() string { 39 return "package-comments" 40 } 41 42 type lintPackageComments struct { 43 fileAst *ast.File 44 file *lint.File 45 onFailure func(lint.Failure) 46 } 47 48 func (l *lintPackageComments) Visit(_ ast.Node) ast.Visitor { 49 if l.file.IsTest() { 50 return nil 51 } 52 53 const ref = styleGuideBase + "#package-comments" 54 prefix := "Package " + l.fileAst.Name.Name + " " 55 56 // Look for a detached package comment. 57 // First, scan for the last comment that occurs before the "package" keyword. 58 var lastCG *ast.CommentGroup 59 for _, cg := range l.fileAst.Comments { 60 if cg.Pos() > l.fileAst.Package { 61 // Gone past "package" keyword. 62 break 63 } 64 lastCG = cg 65 } 66 if lastCG != nil && strings.HasPrefix(lastCG.Text(), prefix) { 67 endPos := l.file.ToPosition(lastCG.End()) 68 pkgPos := l.file.ToPosition(l.fileAst.Package) 69 if endPos.Line+1 < pkgPos.Line { 70 // There isn't a great place to anchor this error; 71 // the start of the blank lines between the doc and the package statement 72 // is at least pointing at the location of the problem. 73 pos := token.Position{ 74 Filename: endPos.Filename, 75 // Offset not set; it is non-trivial, and doesn't appear to be needed. 76 Line: endPos.Line + 1, 77 Column: 1, 78 } 79 l.onFailure(lint.Failure{ 80 Category: "comments", 81 Position: lint.FailurePosition{ 82 Start: pos, 83 End: pos, 84 }, 85 Confidence: 0.9, 86 Failure: "package comment is detached; there should be no blank lines between it and the package statement", 87 }) 88 return nil 89 } 90 } 91 92 if l.fileAst.Doc == nil { 93 l.onFailure(lint.Failure{ 94 Category: "comments", 95 Node: l.fileAst, 96 Confidence: 0.2, 97 Failure: "should have a package comment, unless it's in another file for this package", 98 }) 99 return nil 100 } 101 s := l.fileAst.Doc.Text() 102 if ts := strings.TrimLeft(s, " \t"); ts != s { 103 l.onFailure(lint.Failure{ 104 Category: "comments", 105 Node: l.fileAst.Doc, 106 Confidence: 1, 107 Failure: "package comment should not have leading space", 108 }) 109 s = ts 110 } 111 // Only non-main packages need to keep to this form. 112 if !l.file.Pkg.IsMain() && !strings.HasPrefix(s, prefix) { 113 l.onFailure(lint.Failure{ 114 Category: "comments", 115 Node: l.fileAst.Doc, 116 Confidence: 1, 117 Failure: fmt.Sprintf(`package comment should be of the form "%s..."`, prefix), 118 }) 119 } 120 return nil 121 }