aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/tetafro
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2021-02-22 20:37:25 +0100
committerDmitry Vyukov <dvyukov@google.com>2021-02-22 21:02:12 +0100
commitfcc6d71be2c3ce7d9305c04fc2e87af554571bac (patch)
treeb01dbb3d1e2988e28ea158d2d543d603ec0b9569 /vendor/github.com/tetafro
parent8f23c528ad5a943b9ffec5dcaf332fd0f614006e (diff)
go.mod: update golangci-lint to v1.37
Diffstat (limited to 'vendor/github.com/tetafro')
-rw-r--r--vendor/github.com/tetafro/godot/.gitignore2
-rw-r--r--vendor/github.com/tetafro/godot/Makefile4
-rw-r--r--vendor/github.com/tetafro/godot/README.md32
-rw-r--r--vendor/github.com/tetafro/godot/checks.go260
-rw-r--r--vendor/github.com/tetafro/godot/config.yaml14
-rw-r--r--vendor/github.com/tetafro/godot/getters.go260
-rw-r--r--vendor/github.com/tetafro/godot/go.mod4
-rw-r--r--vendor/github.com/tetafro/godot/go.sum4
-rw-r--r--vendor/github.com/tetafro/godot/godot.go264
-rw-r--r--vendor/github.com/tetafro/godot/settings.go29
10 files changed, 641 insertions, 232 deletions
diff --git a/vendor/github.com/tetafro/godot/.gitignore b/vendor/github.com/tetafro/godot/.gitignore
index 2b87e3915..db77fd15d 100644
--- a/vendor/github.com/tetafro/godot/.gitignore
+++ b/vendor/github.com/tetafro/godot/.gitignore
@@ -1,2 +1,4 @@
/dist/
+/vendor/
/godot
+/profile.out
diff --git a/vendor/github.com/tetafro/godot/Makefile b/vendor/github.com/tetafro/godot/Makefile
index 98a691d78..f167051e8 100644
--- a/vendor/github.com/tetafro/godot/Makefile
+++ b/vendor/github.com/tetafro/godot/Makefile
@@ -1,3 +1,7 @@
+.PHONY: dep
+dep:
+ go mod tidy && go mod verify
+
.PHONY: test
test:
go test ./...
diff --git a/vendor/github.com/tetafro/godot/README.md b/vendor/github.com/tetafro/godot/README.md
index 864767e3c..902678f54 100644
--- a/vendor/github.com/tetafro/godot/README.md
+++ b/vendor/github.com/tetafro/godot/README.md
@@ -26,6 +26,28 @@ go get -u github.com/tetafro/godot/cmd/godot
or download binary from [releases page](https://github.com/tetafro/godot/releases).
+## Config
+
+You can specify options using config file. If no config provided the following
+defaults are used:
+
+```yaml
+# Which comments to check:
+# declarations - for top level declaration comments (default);
+# toplevel - for top level comments;
+# all - for all comments.
+scope: toplevel
+
+# List pf regexps for excluding particular comment lines from check.
+exclude:
+
+# Check periods at the end of sentences.
+period: true
+
+# Check that first letter of each sentence is capital.
+capital: false
+```
+
## Run
```sh
@@ -39,7 +61,9 @@ godot -f ./myproject # fix issues and print the result
godot -w ./myproject # fix issues and replace the original file
```
-## Examples
+See all flags with `godot -h`.
+
+## Example
Code
@@ -55,9 +79,5 @@ func Sum(a, b int) int {
Output
```sh
-Top level comment should end in a period: math/math.go:3:1
+Comment should end in a period: math/math.go:3:1
```
-
-See more examples in test files:
-- [for default mode](testdata/default/in/main.go)
-- [for using --all flag](testdata/checkall/in/main.go)
diff --git a/vendor/github.com/tetafro/godot/checks.go b/vendor/github.com/tetafro/godot/checks.go
new file mode 100644
index 000000000..0e66add1d
--- /dev/null
+++ b/vendor/github.com/tetafro/godot/checks.go
@@ -0,0 +1,260 @@
+package godot
+
+import (
+ "go/token"
+ "regexp"
+ "strings"
+ "unicode"
+)
+
+// Error messages.
+const (
+ noPeriodMessage = "Comment should end in a period"
+ noCapitalMessage = "Sentence should start with a capital letter"
+)
+
+var (
+ // List of valid sentence ending.
+ // A sentence can be inside parenthesis, and therefore ends with parenthesis.
+ lastChars = []string{".", "?", "!", ".)", "?)", "!)", specialReplacer}
+
+ // Special tags in comments like "// nolint:", or "// +k8s:".
+ tags = regexp.MustCompile(`^\+?[a-z0-9]+:`)
+
+ // Special hashtags in comments like "// #nosec".
+ hashtags = regexp.MustCompile(`^#[a-z]+($|\s)`)
+
+ // URL at the end of the line.
+ endURL = regexp.MustCompile(`[a-z]+://[^\s]+$`)
+)
+
+// checkComments checks every comment accordings to the rules from
+// `settings` argument.
+func checkComments(comments []comment, settings Settings) []Issue {
+ var issues []Issue // nolint: prealloc
+ for _, c := range comments {
+ if settings.Period {
+ if iss := checkCommentForPeriod(c); iss != nil {
+ issues = append(issues, *iss)
+ }
+ }
+ if settings.Capital {
+ if iss := checkCommentForCapital(c); len(iss) > 0 {
+ issues = append(issues, iss...)
+ }
+ }
+ }
+ return issues
+}
+
+// checkCommentForPeriod checks that the last sentense of the comment ends
+// in a period.
+func checkCommentForPeriod(c comment) *Issue {
+ pos, ok := checkPeriod(c.text)
+ if ok {
+ return nil
+ }
+
+ // Shift position by the length of comment's special symbols: /* or //
+ isBlock := strings.HasPrefix(c.lines[0], "/*")
+ if (isBlock && pos.line == 1) || !isBlock {
+ pos.column += 2
+ }
+
+ iss := Issue{
+ Pos: token.Position{
+ Filename: c.start.Filename,
+ Offset: c.start.Offset,
+ Line: pos.line + c.start.Line - 1,
+ Column: pos.column + c.start.Column - 1,
+ },
+ Message: noPeriodMessage,
+ }
+
+ // Make a replacement. Use `pos.line` to get an original line from
+ // attached lines. Use `iss.Pos.Column` because it's a position in
+ // the original line.
+ original := []rune(c.lines[pos.line-1])
+ iss.Replacement = string(original[:iss.Pos.Column-1]) + "." +
+ string(original[iss.Pos.Column-1:])
+
+ // Save replacement to raw lines to be able to combine it with
+ // further replacements
+ c.lines[pos.line-1] = iss.Replacement
+
+ return &iss
+}
+
+// checkCommentForCapital checks that the each sentense of the comment starts with
+// a capital letter.
+// nolint: unparam
+func checkCommentForCapital(c comment) []Issue {
+ pp := checkCapital(c.text, c.decl)
+ if len(pp) == 0 {
+ return nil
+ }
+
+ issues := make([]Issue, len(pp))
+ for i, pos := range pp {
+ // Shift position by the length of comment's special symbols: /* or //
+ isBlock := strings.HasPrefix(c.lines[0], "/*")
+ if (isBlock && pos.line == 1) || !isBlock {
+ pos.column += 2
+ }
+
+ iss := Issue{
+ Pos: token.Position{
+ Filename: c.start.Filename,
+ Offset: c.start.Offset,
+ Line: pos.line + c.start.Line - 1,
+ Column: pos.column + c.start.Column - 1,
+ },
+ Message: noCapitalMessage,
+ }
+
+ // Make a replacement. Use `pos.line` to get an original line from
+ // attached lines. Use `iss.Pos.Column` because it's a position in
+ // the original line.
+ rep := []rune(c.lines[pos.line-1])
+ rep[iss.Pos.Column-1] = unicode.ToTitle(rep[iss.Pos.Column-1])
+ iss.Replacement = string(rep)
+
+ // Save replacement to raw lines to be able to combine it with
+ // further replacements
+ c.lines[pos.line-1] = iss.Replacement
+
+ issues[i] = iss
+ }
+
+ return issues
+}
+
+// checkPeriod checks that the last sentense of the text ends in a period.
+// NOTE: Returned position is a position inside given text, not in the
+// original file.
+func checkPeriod(comment string) (pos position, ok bool) {
+ // Check last non-empty line
+ var found bool
+ var line string
+ lines := strings.Split(comment, "\n")
+ for i := len(lines) - 1; i >= 0; i-- {
+ line = strings.TrimRightFunc(lines[i], unicode.IsSpace)
+ if line == "" {
+ continue
+ }
+ found = true
+ pos.line = i + 1
+ break
+ }
+ // All lines are empty
+ if !found {
+ return position{}, true
+ }
+ // Correct line
+ if hasSuffix(line, lastChars) {
+ return position{}, true
+ }
+
+ pos.column = len([]rune(line)) + 1
+ return pos, false
+}
+
+// checkCapital checks that the each sentense of the text starts with
+// a capital letter.
+// NOTE: First letter is not checked in declaration comments, because they
+// can describe unexported functions, which start from small letter.
+func checkCapital(comment string, skipFirst bool) (pp []position) {
+ // List of states during the scan: `empty` - nothing special,
+ // `endChar` - found one of sentence ending chars (.!?),
+ // `endOfSentence` - found `endChar`, and then space or newline.
+ const empty, endChar, endOfSentence = 1, 2, 3
+
+ pos := position{line: 1}
+ state := endOfSentence
+ if skipFirst {
+ state = empty
+ }
+ for _, r := range comment {
+ s := string(r)
+
+ pos.column++
+ if s == "\n" {
+ pos.line++
+ pos.column = 0
+ if state == endChar {
+ state = endOfSentence
+ }
+ continue
+ }
+ if s == "." || s == "!" || s == "?" {
+ state = endChar
+ continue
+ }
+ if s == ")" && state == endChar {
+ continue
+ }
+ if s == " " {
+ if state == endChar {
+ state = endOfSentence
+ }
+ continue
+ }
+ if state == endOfSentence && unicode.IsLower(r) {
+ pp = append(pp, position{line: pos.line, column: pos.column})
+ }
+ state = empty
+ }
+ return pp
+}
+
+// isSpecialBlock checks that given block of comment lines is special and
+// shouldn't be checked as a regular sentence.
+func isSpecialBlock(comment string) bool {
+ // Skip cgo code blocks
+ // TODO: Find a better way to detect cgo code
+ if strings.HasPrefix(comment, "/*") && (strings.Contains(comment, "#include") ||
+ strings.Contains(comment, "#define")) {
+ return true
+ }
+ return false
+}
+
+// isSpecialBlock checks that given comment line is special and
+// shouldn't be checked as a regular sentence.
+func isSpecialLine(comment string) bool {
+ // Skip cgo export tags: https://golang.org/cmd/cgo/#hdr-C_references_to_Go
+ if strings.HasPrefix(comment, "//export ") {
+ return true
+ }
+
+ comment = strings.TrimPrefix(comment, "//")
+ comment = strings.TrimPrefix(comment, "/*")
+
+ // Don't check comments starting with space indentation - they may
+ // contain code examples, which shouldn't end with period
+ if strings.HasPrefix(comment, " ") ||
+ strings.HasPrefix(comment, " \t") ||
+ strings.HasPrefix(comment, "\t") {
+ return true
+ }
+
+ // Skip tags and URLs
+ comment = strings.TrimSpace(comment)
+ if tags.MatchString(comment) ||
+ hashtags.MatchString(comment) ||
+ endURL.MatchString(comment) ||
+ strings.HasPrefix(comment, "+build") {
+ return true
+ }
+
+ return false
+}
+
+func hasSuffix(s string, suffixes []string) bool {
+ for _, suffix := range suffixes {
+ if strings.HasSuffix(s, suffix) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/tetafro/godot/config.yaml b/vendor/github.com/tetafro/godot/config.yaml
new file mode 100644
index 000000000..c459ca323
--- /dev/null
+++ b/vendor/github.com/tetafro/godot/config.yaml
@@ -0,0 +1,14 @@
+# Which comments to check:
+# declarations - for top level declaration comments (default);
+# toplevel - for top level comments;
+# all - for all comments.
+scope: toplevel
+
+# List pf regexps for excluding particular comment lines from check.
+exclude:
+
+# Check periods at the end of sentences.
+period: true
+
+# Check that first letter of each sentence is capital.
+capital: false
diff --git a/vendor/github.com/tetafro/godot/getters.go b/vendor/github.com/tetafro/godot/getters.go
new file mode 100644
index 000000000..02b8acec7
--- /dev/null
+++ b/vendor/github.com/tetafro/godot/getters.go
@@ -0,0 +1,260 @@
+package godot
+
+import (
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/token"
+ "io/ioutil"
+ "regexp"
+ "strings"
+)
+
+var errEmptyInput = errors.New("empty input")
+
+// specialReplacer is a replacer for some types of special lines in comments,
+// which shouldn't be checked. For example, if comment ends with a block of
+// code it should not necessarily have a period at the end.
+const specialReplacer = "<godotSpecialReplacer>"
+
+type parsedFile struct {
+ fset *token.FileSet
+ file *ast.File
+ lines []string
+}
+
+func newParsedFile(file *ast.File, fset *token.FileSet) (*parsedFile, error) {
+ if file == nil || fset == nil || len(file.Comments) == 0 {
+ return nil, errEmptyInput
+ }
+
+ pf := parsedFile{
+ fset: fset,
+ file: file,
+ }
+
+ var err error
+
+ // Read original file. This is necessary for making a replacements for
+ // inline comments. I couldn't find a better way to get original line
+ // with code and comment without reading the file. Function `Format`
+ // from "go/format" won't help here if the original file is not gofmt-ed.
+ pf.lines, err = readFile(file, fset)
+ if err != nil {
+ return nil, fmt.Errorf("read file: %v", err)
+ }
+
+ // Check consistency to avoid checking slice indexes in each function
+ lastComment := pf.file.Comments[len(pf.file.Comments)-1]
+ if p := pf.fset.Position(lastComment.End()); len(pf.lines) < p.Line {
+ return nil, fmt.Errorf("inconsistence between file and AST: %s", p.Filename)
+ }
+
+ return &pf, nil
+}
+
+// getComments extracts comments from a file.
+func (pf *parsedFile) getComments(scope Scope, exclude []*regexp.Regexp) []comment {
+ var comments []comment
+ decl := pf.getDeclarationComments(exclude)
+ switch scope {
+ case AllScope:
+ // All comments
+ comments = pf.getAllComments(exclude)
+ case TopLevelScope:
+ // All top level comments and comments from the inside
+ // of top level blocks
+ comments = append(
+ pf.getBlockComments(exclude),
+ pf.getTopLevelComments(exclude)...,
+ )
+ default:
+ // Top level declaration comments and comments from the inside
+ // of top level blocks
+ comments = append(pf.getBlockComments(exclude), decl...)
+ }
+
+ // Set `decl` flag
+ setDecl(comments, decl)
+
+ return comments
+}
+
+// getBlockComments gets comments from the inside of top level blocks:
+// var (...), const (...).
+func (pf *parsedFile) getBlockComments(exclude []*regexp.Regexp) []comment {
+ var comments []comment
+ for _, decl := range pf.file.Decls {
+ d, ok := decl.(*ast.GenDecl)
+ if !ok {
+ continue
+ }
+ // No parenthesis == no block
+ if d.Lparen == 0 {
+ continue
+ }
+ for _, c := range pf.file.Comments {
+ if c == nil || len(c.List) == 0 {
+ continue
+ }
+ // Skip comments outside this block
+ if d.Lparen > c.Pos() || c.Pos() > d.Rparen {
+ continue
+ }
+ // Skip comments that are not top-level for this block
+ // (the block itself is top level, so comments inside this block
+ // would be on column 2)
+ // nolint: gomnd
+ if pf.fset.Position(c.Pos()).Column != 2 {
+ continue
+ }
+ firstLine := pf.fset.Position(c.Pos()).Line
+ lastLine := pf.fset.Position(c.End()).Line
+ comments = append(comments, comment{
+ lines: pf.lines[firstLine-1 : lastLine],
+ text: getText(c, exclude),
+ start: pf.fset.Position(c.List[0].Slash),
+ })
+ }
+ }
+ return comments
+}
+
+// getTopLevelComments gets all top level comments.
+func (pf *parsedFile) getTopLevelComments(exclude []*regexp.Regexp) []comment {
+ var comments []comment // nolint: prealloc
+ for _, c := range pf.file.Comments {
+ if c == nil || len(c.List) == 0 {
+ continue
+ }
+ if pf.fset.Position(c.Pos()).Column != 1 {
+ continue
+ }
+ firstLine := pf.fset.Position(c.Pos()).Line
+ lastLine := pf.fset.Position(c.End()).Line
+ comments = append(comments, comment{
+ lines: pf.lines[firstLine-1 : lastLine],
+ text: getText(c, exclude),
+ start: pf.fset.Position(c.List[0].Slash),
+ })
+ }
+ return comments
+}
+
+// getDeclarationComments gets top level declaration comments.
+func (pf *parsedFile) getDeclarationComments(exclude []*regexp.Regexp) []comment {
+ var comments []comment // nolint: prealloc
+ for _, decl := range pf.file.Decls {
+ var cg *ast.CommentGroup
+ switch d := decl.(type) {
+ case *ast.GenDecl:
+ cg = d.Doc
+ case *ast.FuncDecl:
+ cg = d.Doc
+ }
+
+ if cg == nil || len(cg.List) == 0 {
+ continue
+ }
+
+ firstLine := pf.fset.Position(cg.Pos()).Line
+ lastLine := pf.fset.Position(cg.End()).Line
+ comments = append(comments, comment{
+ lines: pf.lines[firstLine-1 : lastLine],
+ text: getText(cg, exclude),
+ start: pf.fset.Position(cg.List[0].Slash),
+ })
+ }
+ return comments
+}
+
+// getAllComments gets every single comment from the file.
+func (pf *parsedFile) getAllComments(exclude []*regexp.Regexp) []comment {
+ var comments []comment //nolint: prealloc
+ for _, c := range pf.file.Comments {
+ if c == nil || len(c.List) == 0 {
+ continue
+ }
+ firstLine := pf.fset.Position(c.Pos()).Line
+ lastLine := pf.fset.Position(c.End()).Line
+ comments = append(comments, comment{
+ lines: pf.lines[firstLine-1 : lastLine],
+ start: pf.fset.Position(c.List[0].Slash),
+ text: getText(c, exclude),
+ })
+ }
+ return comments
+}
+
+// getText extracts text from comment. If comment is a special block
+// (e.g., CGO code), a block of empty lines is returned. If comment contains
+// special lines (e.g., tags or indented code examples), they are replaced
+// with `specialReplacer` to skip checks for it.
+// The result can be multiline.
+func getText(comment *ast.CommentGroup, exclude []*regexp.Regexp) (s string) {
+ if len(comment.List) == 1 &&
+ strings.HasPrefix(comment.List[0].Text, "/*") &&
+ isSpecialBlock(comment.List[0].Text) {
+ return ""
+ }
+
+ for _, c := range comment.List {
+ text := c.Text
+ isBlock := false
+ if strings.HasPrefix(c.Text, "/*") {
+ isBlock = true
+ text = strings.TrimPrefix(text, "/*")
+ text = strings.TrimSuffix(text, "*/")
+ }
+ for _, line := range strings.Split(text, "\n") {
+ if isSpecialLine(line) {
+ s += specialReplacer + "\n"
+ continue
+ }
+ if !isBlock {
+ line = strings.TrimPrefix(line, "//")
+ }
+ if matchAny(line, exclude) {
+ s += specialReplacer + "\n"
+ continue
+ }
+ s += line + "\n"
+ }
+ }
+ if len(s) == 0 {
+ return ""
+ }
+ return s[:len(s)-1] // trim last "\n"
+}
+
+// readFile reads file and returns it's lines as strings.
+func readFile(file *ast.File, fset *token.FileSet) ([]string, error) {
+ fname := fset.File(file.Package)
+ f, err := ioutil.ReadFile(fname.Name())
+ if err != nil {
+ return nil, err
+ }
+ return strings.Split(string(f), "\n"), nil
+}
+
+// setDecl sets `decl` flag to comments which are declaration comments.
+func setDecl(comments, decl []comment) {
+ for _, d := range decl {
+ for i, c := range comments {
+ if d.start == c.start {
+ comments[i].decl = true
+ break
+ }
+ }
+ }
+}
+
+// matchAny checks if string matches any of given regexps.
+func matchAny(s string, rr []*regexp.Regexp) bool {
+ for _, re := range rr {
+ if re.MatchString(s) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/tetafro/godot/go.mod b/vendor/github.com/tetafro/godot/go.mod
index d86531c42..d0fa675b6 100644
--- a/vendor/github.com/tetafro/godot/go.mod
+++ b/vendor/github.com/tetafro/godot/go.mod
@@ -1,3 +1,5 @@
module github.com/tetafro/godot
-go 1.14
+go 1.15
+
+require gopkg.in/yaml.v2 v2.4.0
diff --git a/vendor/github.com/tetafro/godot/go.sum b/vendor/github.com/tetafro/godot/go.sum
new file mode 100644
index 000000000..dd0bc19f1
--- /dev/null
+++ b/vendor/github.com/tetafro/godot/go.sum
@@ -0,0 +1,4 @@
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
diff --git a/vendor/github.com/tetafro/godot/godot.go b/vendor/github.com/tetafro/godot/godot.go
index 81211d725..526a5f7d1 100644
--- a/vendor/github.com/tetafro/godot/godot.go
+++ b/vendor/github.com/tetafro/godot/godot.go
@@ -1,5 +1,5 @@
-// Package godot checks if all top-level comments contain a period at the
-// end of the last sentence if needed.
+// Package godot checks if comments contain a period at the end of the last
+// sentence if needed.
package godot
import (
@@ -13,67 +13,60 @@ import (
"strings"
)
-const (
- // noPeriodMessage is an error message to return.
- noPeriodMessage = "Top level comment should end in a period"
- // topLevelColumn is just the most left column of the file.
- topLevelColumn = 1
- // topLevelGroupColumn is the most left column inside a group declaration
- // on the top level.
- topLevelGroupColumn = 2
-)
+// NOTE: Line and column indexes are 1-based.
-// Settings contains linter settings.
-type Settings struct {
- // Check all top-level comments, not only declarations
- CheckAll bool
-}
+// NOTE: Errors `invalid line number inside comment...` should never happen.
+// Their goal is to prevent panic, if there's a bug with array indexes.
-// Issue contains a description of linting error and a possible replacement.
+// Issue contains a description of linting error and a recommended replacement.
type Issue struct {
Pos token.Position
Message string
Replacement string
}
-// position is an position inside a comment (might be multiline comment).
+// position is a position inside a comment (might be multiline comment).
type position struct {
line int
column int
}
-var (
- // List of valid last characters.
- lastChars = []string{".", "?", "!"}
-
- // Special tags in comments like "// nolint:", or "// +k8s:".
- tags = regexp.MustCompile(`^\+?[a-z0-9]+:`)
-
- // Special hashtags in comments like "#nosec".
- hashtags = regexp.MustCompile("^#[a-z]+ ")
-
- // URL at the end of the line.
- endURL = regexp.MustCompile(`[a-z]+://[^\s]+$`)
-)
+// comment is an internal representation of AST comment entity with additional
+// data attached. The latter is used for creating a full replacement for
+// the line with issues.
+type comment struct {
+ lines []string // unmodified lines from file
+ text string // concatenated `lines` with special parts excluded
+ start token.Position // position of the first symbol in comment
+ decl bool // whether comment is a special one (should not be checked)
+}
// Run runs this linter on the provided code.
-func Run(file *ast.File, fset *token.FileSet, settings Settings) []Issue {
- issues := checkBlocks(file, fset)
+func Run(file *ast.File, fset *token.FileSet, settings Settings) ([]Issue, error) {
+ pf, err := newParsedFile(file, fset)
+ if err == errEmptyInput {
+ return nil, nil
+ }
+ if err != nil {
+ return nil, fmt.Errorf("parse input file: %v", err)
+ }
- // Check all top-level comments
- if settings.CheckAll {
- issues = append(issues, checkTopLevel(file, fset)...)
- sortIssues(issues)
- return issues
+ exclude := make([]*regexp.Regexp, len(settings.Exclude))
+ for i := 0; i < len(settings.Exclude); i++ {
+ exclude[i], err = regexp.Compile(settings.Exclude[i])
+ if err != nil {
+ return nil, fmt.Errorf("invalid regexp: %v", err)
+ }
}
- // Check only declaration comments
- issues = append(issues, checkDeclarations(file, fset)...)
+ comments := pf.getComments(settings.Scope, exclude)
+ issues := checkComments(comments, settings)
sortIssues(issues)
- return issues
+
+ return issues, nil
}
-// Fix fixes all issues and return new version of file content.
+// Fix fixes all issues and returns new version of file content.
func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([]byte, error) {
// Read file
content, err := ioutil.ReadFile(path) // nolint: gosec
@@ -84,7 +77,10 @@ func Fix(path string, file *ast.File, fset *token.FileSet, settings Settings) ([
return nil, nil
}
- issues := Run(file, fset, settings)
+ issues, err := Run(file, fset, settings)
+ if err != nil {
+ return nil, fmt.Errorf("run linter: %v", err)
+ }
// slice -> map
m := map[int]Issue{}
@@ -137,185 +133,3 @@ func sortIssues(iss []Issue) {
return iss[i].Pos.Column < iss[j].Pos.Column
})
}
-
-// checkTopLevel checks all top-level comments.
-func checkTopLevel(file *ast.File, fset *token.FileSet) (issues []Issue) {
- for _, group := range file.Comments {
- if iss, ok := check(fset, group, topLevelColumn); !ok {
- issues = append(issues, iss)
- }
- }
- return issues
-}
-
-// checkDeclarations checks top level declaration comments.
-func checkDeclarations(file *ast.File, fset *token.FileSet) (issues []Issue) {
- for _, decl := range file.Decls {
- switch d := decl.(type) {
- case *ast.GenDecl:
- if iss, ok := check(fset, d.Doc, topLevelColumn); !ok {
- issues = append(issues, iss)
- }
- case *ast.FuncDecl:
- if iss, ok := check(fset, d.Doc, topLevelColumn); !ok {
- issues = append(issues, iss)
- }
- }
- }
- return issues
-}
-
-// checkBlocks checks comments inside top level blocks (var (...), const (...), etc).
-func checkBlocks(file *ast.File, fset *token.FileSet) (issues []Issue) {
- for _, decl := range file.Decls {
- d, ok := decl.(*ast.GenDecl)
- if !ok {
- continue
- }
- // No parenthesis == no block
- if d.Lparen == 0 {
- continue
- }
- for _, group := range file.Comments {
- // Skip comments outside this block
- if d.Lparen > group.Pos() || group.Pos() > d.Rparen {
- continue
- }
- // Skip comments that are not top-level for this block
- if fset.Position(group.Pos()).Column != topLevelGroupColumn {
- continue
- }
- if iss, ok := check(fset, group, topLevelGroupColumn); !ok {
- issues = append(issues, iss)
- }
- }
- }
- return issues
-}
-
-func check(fset *token.FileSet, group *ast.CommentGroup, level int) (iss Issue, ok bool) {
- if group == nil || len(group.List) == 0 {
- return Issue{}, true
- }
-
- // Check only top-level comments
- if fset.Position(group.Pos()).Column > level {
- return Issue{}, true
- }
-
- // Get last element from comment group - it can be either
- // last (or single) line for "//"-comment, or multiline string
- // for "/*"-comment
- last := group.List[len(group.List)-1]
-
- p, ok := checkComment(last.Text)
- if ok {
- return Issue{}, true
- }
-
- pos := fset.Position(last.Slash)
- pos.Line += p.line
- pos.Column = p.column + level - 1
-
- indent := strings.Repeat("\t", level-1)
-
- iss = Issue{
- Pos: pos,
- Message: noPeriodMessage,
- Replacement: indent + makeReplacement(last.Text, p),
- }
- return iss, false
-}
-
-func checkComment(comment string) (pos position, ok bool) {
- // Check last line of "//"-comment
- if strings.HasPrefix(comment, "//") {
- pos.column = len([]rune(comment)) // runes for non-latin chars
- comment = strings.TrimPrefix(comment, "//")
- if checkLastChar(comment) {
- return position{}, true
- }
- return pos, false
- }
-
- // Skip cgo code blocks
- // TODO: Find a better way to detect cgo code
- if strings.Contains(comment, "#include") || strings.Contains(comment, "#define") {
- return position{}, true
- }
-
- // Check last non-empty line in multiline "/*"-comment block
- lines := strings.Split(comment, "\n")
- var i int
- for i = len(lines) - 1; i >= 0; i-- {
- if s := strings.TrimSpace(lines[i]); s == "*/" || s == "" {
- continue
- }
- break
- }
- pos.line = i
- comment = lines[i]
- comment = strings.TrimSuffix(comment, "*/")
- comment = strings.TrimRight(comment, " ")
- // Get position of the last non-space char in comment line, use runes
- // in case of non-latin chars
- pos.column = len([]rune(comment))
- comment = strings.TrimPrefix(comment, "/*")
-
- if checkLastChar(comment) {
- return position{}, true
- }
- return pos, false
-}
-
-func checkLastChar(s string) bool {
- // Don't check comments starting with space indentation - they may
- // contain code examples, which shouldn't end with period
- if strings.HasPrefix(s, " ") || strings.HasPrefix(s, " \t") || strings.HasPrefix(s, "\t") {
- return true
- }
- // Skip cgo export tags: https://golang.org/cmd/cgo/#hdr-C_references_to_Go
- if strings.HasPrefix(s, "export") {
- return true
- }
- s = strings.TrimSpace(s)
- if tags.MatchString(s) ||
- hashtags.MatchString(s) ||
- endURL.MatchString(s) ||
- strings.HasPrefix(s, "+build") {
- return true
- }
- // Don't check empty lines
- if s == "" {
- return true
- }
- // Trim parenthesis for cases when the whole sentence is inside parenthesis
- s = strings.TrimRight(s, ")")
- for _, ch := range lastChars {
- if string(s[len(s)-1]) == ch {
- return true
- }
- }
- return false
-}
-
-// makeReplacement basically just inserts a period into comment on
-// the given position.
-func makeReplacement(s string, pos position) string {
- lines := strings.Split(s, "\n")
- if len(lines) < pos.line {
- // This should never happen
- return s
- }
- line := []rune(lines[pos.line])
- if len(line) < pos.column {
- // This should never happen
- return s
- }
- // Insert a period
- newline := append(
- line[:pos.column],
- append([]rune{'.'}, line[pos.column:]...)...,
- )
- return string(newline)
-}
diff --git a/vendor/github.com/tetafro/godot/settings.go b/vendor/github.com/tetafro/godot/settings.go
new file mode 100644
index 000000000..b71bf5d58
--- /dev/null
+++ b/vendor/github.com/tetafro/godot/settings.go
@@ -0,0 +1,29 @@
+package godot
+
+// Settings contains linter settings.
+type Settings struct {
+ // Which comments to check (top level declarations, top level, all).
+ Scope Scope
+
+ // Regexp for excluding particular comment lines from check.
+ Exclude []string
+
+ // Check periods at the end of sentences.
+ Period bool
+
+ // Check that first letter of each sentence is capital.
+ Capital bool
+}
+
+// Scope sets which comments should be checked.
+type Scope string
+
+// List of available check scopes.
+const (
+ // DeclScope is for top level declaration comments.
+ DeclScope Scope = "declarations"
+ // TopLevelScope is for all top level comments.
+ TopLevelScope Scope = "toplevel"
+ // AllScope is for all comments.
+ AllScope Scope = "all"
+)