diff --git a/README.md b/README.md
index 9f94294..e7cafaf 100644
--- a/README.md
+++ b/README.md
@@ -232,7 +232,8 @@ Based on this, here are the supported languages:
| R | `*.R` extension. Supports single-line `//` comments and multi-line `/* */` comments |
| Rust | `*.rs` extension. Supports single-line `//` comments and multi-line `/* */` comments |
| Scala | `*.scala`, `*.sc` extensions. Supports single-line `//` comments and multi-line `/* */` comments |
-| Swift | `*.swift` extension. Supports single-line `//` comments and multi-line `/* */` comments |
+| Swift | `*.swift` extension. Supports single-line `//` comments and multi-line `/* */` comments
+| Vue | `*.vue` extension. Supports single-line `//` comments, multi-line `/* */` comments and multi-line `` HTML comments |
If you don't see your favorite language in this table, but it does use one of the supported comment formats, submit an issue [here](https://github.com/preslavmihaylov/todocheck/issues/new)
diff --git a/matchers/matchers.go b/matchers/matchers.go
index d59f153..66c238a 100644
--- a/matchers/matchers.go
+++ b/matchers/matchers.go
@@ -10,6 +10,7 @@ import (
"github.com/preslavmihaylov/todocheck/matchers/scripts"
"github.com/preslavmihaylov/todocheck/matchers/standard"
"github.com/preslavmihaylov/todocheck/matchers/state"
+ "github.com/preslavmihaylov/todocheck/matchers/vue"
)
// TodoMatcher for todo comments
@@ -124,6 +125,23 @@ var (
return groovy.NewCommentMatcher(callback)
},
}
+
+ vueMatcherFactory = &matcherFactory{
+ func() func([]string) TodoMatcher {
+ var once sync.Once
+ var matcher TodoMatcher
+
+ return func(customTodos []string) TodoMatcher {
+ once.Do(func() {
+ matcher = vue.NewTodoMatcher(customTodos)
+ })
+ return matcher
+ }
+ }(),
+ func(callback state.CommentCallback) CommentMatcher {
+ return vue.NewCommentMatcher(callback)
+ },
+ }
)
var supportedMatchers = map[string]*matcherFactory{
@@ -159,6 +177,9 @@ var supportedMatchers = map[string]*matcherFactory{
// file types, supporting python comments
".py": pythonMatcherFactory,
+
+ // file types, supporting js, html and css comments
+ ".vue": vueMatcherFactory,
}
// TodoMatcherForFile gets the correct todo matcher for the given filename
diff --git a/matchers/vue/comments.go b/matchers/vue/comments.go
new file mode 100644
index 0000000..64e18d0
--- /dev/null
+++ b/matchers/vue/comments.go
@@ -0,0 +1,144 @@
+package vue
+
+import (
+ "github.com/preslavmihaylov/todocheck/matchers/state"
+)
+
+// NewCommentMatcher for vue comments
+func NewCommentMatcher(callback state.CommentCallback) *CommentMatcher {
+ return &CommentMatcher{
+ callback: callback,
+ }
+}
+
+// CommentMatcher for vue comments
+type CommentMatcher struct {
+ callback state.CommentCallback
+ buffer string
+ lines []string
+ linecnt int
+ stringToken rune
+ isExitingMultilineComment bool
+ commentType string
+ isStartingHTML bool
+}
+
+// NonCommentState for vue comments
+func (m *CommentMatcher) NonCommentState(
+ filename, line string, linecnt int, prevToken, currToken, nextToken rune,
+) (state.CommentState, error) {
+ if prevToken == '/' && currToken == '/' {
+ m.buffer += string(currToken)
+
+ return state.SingleLineComment, nil
+ } else if currToken == '"' || currToken == '\'' {
+ m.stringToken = currToken
+
+ return state.String, nil
+ } else if prevToken == '/' && currToken == '*' {
+ m.buffer += "/*"
+ m.lines = []string{line}
+ m.linecnt = linecnt
+ m.commentType = "CSS"
+
+ return state.MultiLineComment, nil
+ } else if prevToken == '<' && currToken == '!' && nextToken == '-' {
+ m.isStartingHTML = true
+
+ return state.NonComment, nil
+ } else if m.isStartingHTML && nextToken == '-' {
+ m.buffer += "' {
+ return true
+ }
+ }
+ return false
+}
diff --git a/matchers/vue/doc.go b/matchers/vue/doc.go
new file mode 100644
index 0000000..c0991da
--- /dev/null
+++ b/matchers/vue/doc.go
@@ -0,0 +1,3 @@
+// Package vue contains a todo matcher & comments matcher for vue comments.
+// Vue single-line comments use the '//' literal, and the multi-line comments use the CS&JS /**/ or the HTML literals.
+package vue
diff --git a/matchers/vue/todomatcher.go b/matchers/vue/todomatcher.go
new file mode 100644
index 0000000..e25bd63
--- /dev/null
+++ b/matchers/vue/todomatcher.go
@@ -0,0 +1,62 @@
+package vue
+
+import (
+ "regexp"
+
+ "github.com/preslavmihaylov/todocheck/common"
+ "github.com/preslavmihaylov/todocheck/matchers/errors"
+)
+
+// NewTodoMatcher for vue comments
+func NewTodoMatcher(todos []string) *TodoMatcher {
+ pattern := common.ArrayAsRegexAnyMatchExpression(todos)
+
+ singleLineTodoPattern := regexp.MustCompile(`^\s*//.*` + pattern)
+ singleLineValidTodoPattern := regexp.MustCompile(`^\s*// ` + pattern + ` (#?[a-zA-Z0-9\-]+):.*`)
+
+ multiLineTodoPattern := regexp.MustCompile(`(?s)^\s*(<\!--|/*).*` + pattern)
+ multiLineValidTodoPattern := regexp.MustCompile(`(?s)^\s*(<\!--|/*).*` + pattern + ` (#?[a-zA-Z0-9\-]+):.*`)
+
+ return &TodoMatcher{
+ singleLineTodoPattern: singleLineTodoPattern,
+ singleLineValidTodoPattern: singleLineValidTodoPattern,
+ multiLineTodoPattern: multiLineTodoPattern,
+ multiLineValidTodoPattern: multiLineValidTodoPattern,
+ }
+}
+
+// TodoMatcher for vue comments
+type TodoMatcher struct {
+ singleLineTodoPattern *regexp.Regexp
+ singleLineValidTodoPattern *regexp.Regexp
+ multiLineTodoPattern *regexp.Regexp
+ multiLineValidTodoPattern *regexp.Regexp
+}
+
+// IsMatch checks if the current expression matches a vue comment
+func (m *TodoMatcher) IsMatch(expr string) bool {
+ return m.singleLineTodoPattern.Match([]byte(expr)) || m.multiLineTodoPattern.Match([]byte(expr))
+}
+
+// IsValid checks if the expression is a valid todo comment
+func (m *TodoMatcher) IsValid(expr string) bool {
+ return m.singleLineValidTodoPattern.Match([]byte(expr)) || m.multiLineValidTodoPattern.Match([]byte(expr))
+}
+
+// ExtractIssueRef from the given expression.
+// If the expression is invalid, an ErrInvalidTODO is returned
+func (m *TodoMatcher) ExtractIssueRef(expr string) (string, error) {
+ if !m.IsValid(expr) {
+ return "", errors.ErrInvalidTODO
+ }
+
+ singleLineRes := m.singleLineValidTodoPattern.FindStringSubmatch(expr)
+ multiLineRes := m.multiLineValidTodoPattern.FindStringSubmatch(expr)
+ if len(singleLineRes) >= 2 {
+ return singleLineRes[1], nil
+ } else if len(multiLineRes) >= 3 {
+ return multiLineRes[2], nil
+ }
+
+ panic("Invariant violated. No issue reference found in valid TODO")
+}
diff --git a/testing/scenarios/vue/main.vue b/testing/scenarios/vue/main.vue
new file mode 100644
index 0000000..93a5b28
--- /dev/null
+++ b/testing/scenarios/vue/main.vue
@@ -0,0 +1,55 @@
+// oneline comment, malformed TODO
+// TODO 1: oneline comment wellformed
+// TODO 2: oneline comment with Issue closed
+
+
+
+
+
+/* TODO 1: wellformed CS/JS multiline entry */
+/* TODO 2: wellformed CS/JS multiline entry, BUT issue closed */
+/*
+wellformed CS/JS multline entry
+*/
+
+/*
+TODO: malformed CS/JS multline entry, missing number
+*/
+
+
+
+
+"this is a // TODO 2: valid comment in a string with issue closed"
+"this is a /* TODO 2: valid multiline comment in a string with issue closed */"
+'this is a '
+
+
+
+ {{ message }}
+
+ /* TODO 1: valid multiline comment in code */
+
+
+
+
+
+
diff --git a/testing/todocheck_test.go b/testing/todocheck_test.go
index 594844f..decc24a 100644
--- a/testing/todocheck_test.go
+++ b/testing/todocheck_test.go
@@ -742,6 +742,66 @@ func TestPrintingVersionFlagStopsProgram(t *testing.T) {
}
}
+func TestVueTodos(t *testing.T) {
+ err := scenariobuilder.NewScenario().
+ WithBinary("../todocheck").
+ WithBasepath("./scenarios/vue").
+ WithConfig("./test_configs/no_issue_tracker.yaml").
+ WithIssueTracker(issuetracker.Jira).
+ WithIssue("1", issuetracker.StatusOpen).
+ WithIssue("2", issuetracker.StatusClosed).
+ WithIssue("3", issuetracker.StatusOpen).
+ WithIssue("4", issuetracker.StatusOpen).
+ ExpectTodoErr(
+ scenariobuilder.NewTodoErr().
+ WithType(errors.TODOErrTypeMalformed).
+ WithLocation("scenarios/vue/main.vue", 1).
+ ExpectLine("// oneline comment, malformed TODO")).
+ ExpectTodoErr(
+ scenariobuilder.NewTodoErr().
+ WithType(errors.TODOErrTypeIssueClosed).
+ WithLocation("scenarios/vue/main.vue", 3).
+ ExpectLine("// TODO 2: oneline comment with Issue closed")).
+ ExpectTodoErr(
+ scenariobuilder.NewTodoErr().
+ WithType(errors.TODOErrTypeIssueClosed).
+ WithLocation("scenarios/vue/main.vue", 6).
+ ExpectLine("")).
+ ExpectTodoErr(
+ scenariobuilder.NewTodoErr().
+ WithType(errors.TODOErrTypeMalformed).
+ WithLocation("scenarios/vue/main.vue", 7).
+ ExpectLine("")).
+ ExpectTodoErr(
+ scenariobuilder.NewTodoErr().
+ WithType(errors.TODOErrTypeIssueClosed).
+ WithLocation("scenarios/vue/main.vue", 10).
+ ExpectLine("/* TODO 2: wellformed CS/JS multiline entry, BUT issue closed */")).
+ ExpectTodoErr(
+ scenariobuilder.NewTodoErr().
+ WithType(errors.TODOErrTypeMalformed).
+ WithLocation("scenarios/vue/main.vue", 15).
+ ExpectLine("/*").
+ ExpectLine("TODO: malformed CS/JS multline entry, missing number").
+ ExpectLine("*/")).
+ ExpectTodoErr(
+ scenariobuilder.NewTodoErr().
+ WithType(errors.TODOErrTypeIssueClosed).
+ WithLocation("scenarios/vue/main.vue", 49).
+ ExpectLine(" color: blue; // TODO 2: online comment wellformed in code, BUT issue closed")).
+ ExpectTodoErr(
+ scenariobuilder.NewTodoErr().
+ WithType(errors.TODOErrTypeMalformed).
+ WithLocation("scenarios/vue/main.vue", 53).
+ ExpectLine("")).
+ Run()
+ if err != nil {
+ t.Errorf("%s", err)
+ }
+}
+
// Testing multiple todo matchers created for different file types
func TestMultipleTodoMatchers(t *testing.T) {
err := scenariobuilder.NewScenario().