From 010b80034307616bcd81712c292a67a08faaa89e Mon Sep 17 00:00:00 2001 From: George Shaw Date: Fri, 16 Jul 2021 09:20:40 -0700 Subject: [PATCH] feat: TODO linter (#15) --- cmd/lintroller/main.go | 3 +- internal/copyright/copyright.go | 3 +- internal/todo/todo.go | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 internal/todo/todo.go diff --git a/cmd/lintroller/main.go b/cmd/lintroller/main.go index 1b7a4ce..701c18f 100644 --- a/cmd/lintroller/main.go +++ b/cmd/lintroller/main.go @@ -4,6 +4,7 @@ import ( "github.com/getoutreach/lintroller/internal/copyright" "github.com/getoutreach/lintroller/internal/doculint" "github.com/getoutreach/lintroller/internal/header" + "github.com/getoutreach/lintroller/internal/todo" "golang.org/x/tools/go/analysis/unitchecker" ) @@ -12,6 +13,6 @@ func main() { &doculint.Analyzer, &header.Analyzer, ©right.Analyzer, - // Add more *analysis.Analyzer's here. + &todo.Analyzer, ) } diff --git a/internal/copyright/copyright.go b/internal/copyright/copyright.go index f0828d2..d47587a 100644 --- a/internal/copyright/copyright.go +++ b/internal/copyright/copyright.go @@ -1,3 +1,4 @@ +// Package copyright contains the necessary logic for the copyright linter. package copyright import ( @@ -9,7 +10,7 @@ import ( ) // Here is an example regular expression that can be used to test this linter: -// ^Copyright 20[2-9][0-9] Outreach Corporation\. All Rights Reserved\.$ +// ^Copyright 20[2-9][0-9] Outreach Corporation\. All Rights Reserved\.$ // doc defines the help text for the copyright linter. const doc = `Ensures each .go file has a comment at the top of the file containing the diff --git a/internal/todo/todo.go b/internal/todo/todo.go new file mode 100644 index 0000000..e386e60 --- /dev/null +++ b/internal/todo/todo.go @@ -0,0 +1,54 @@ +// Package todo contains the necessary logic for the todo linter. +package todo + +import ( + "regexp" + "strings" + + "golang.org/x/tools/go/analysis" +) + +// doc defines the help text for the todo linter. +const doc = `Ensures that each TODO comment defined in the codebase conforms to one of the +following formats: TODO()[]: or TODO[]: ` + +// Analyzer exports the todo analyzer (linter). +var Analyzer = analysis.Analyzer{ + Name: "todo", + Doc: doc, + Run: todo, +} + +// Regular expression variable block for the todo linter. +var ( + // reTodo is the regular expression that matches the required TODO format by this + // linter. + // + // For examples, see: https://regex101.com/r/vsbdEm/1 + reTodo = regexp.MustCompile(`^TODO(\((?P[\w-]+)\))?\[(?P[A-Z]+-\d+)\]: .+$`) + + // These subexpression indexes are placeholders just incase they're ever needed to + // be used for whatever reason in the future. + _ = reTodo.SubexpIndex("ghUser") + _ = reTodo.SubexpIndex("jiraTicket") +) + +// todo is the function that gets passed to the Analyzer which runs the actual +// analysis for the todo linter on a set of files. +func todo(pass *analysis.Pass) (interface{}, error) { + for _, file := range pass.Files { + for _, commentGroup := range file.Comments { + for _, comment := range commentGroup.List { + text := strings.TrimSpace(strings.TrimPrefix(comment.Text, "//")) + + if strings.HasPrefix(text, "TODO") { + if !reTodo.MatchString(text) { + pass.Reportf(comment.Pos(), "TODO comment must match one of the required formats: TODO()[]: or TODO[]: ") + } + } + } + } + } + + return nil, nil +}