diff --git a/task_test.go b/task_test.go index 540e7ba8de..2f180235c5 100644 --- a/task_test.go +++ b/task_test.go @@ -3417,6 +3417,26 @@ func TestWildcard(t *testing.T) { call: "wildcard-foo-bar", expectedOutput: "Hello foo-bar\n", }, + { + name: "regex metacharacters are matched literally", + call: "c++", + expectedOutput: "Building c++\n", + }, + { + name: "regex metacharacters do not match as a pattern", + call: "cxx", + wantErr: true, + }, + { + name: "a dot matches itself", + call: "deploy.prod", + expectedOutput: "Deploying prod\n", + }, + { + name: "a dot is not a wildcard", + call: "deploy-prod", + wantErr: true, + }, } for _, test := range tests { diff --git a/taskfile/ast/task.go b/taskfile/ast/task.go index 9465c77770..75b2815154 100644 --- a/taskfile/ast/task.go +++ b/taskfile/ast/task.go @@ -87,7 +87,10 @@ func (t *Task) WildcardMatch(name string) (bool, []string) { names := append([]string{t.Task}, t.Aliases...) for _, taskName := range names { - regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(taskName, "*", "(.*)")) + // Escape the task name so a name like "c++" or "a.b" is matched literally + // and does not panic in MustCompile, then turn the escaped "*" back into + // the wildcard group + regexStr := fmt.Sprintf("^%s$", strings.ReplaceAll(regexp.QuoteMeta(taskName), `\*`, "(.*)")) regex := regexp.MustCompile(regexStr) wildcards := regex.FindStringSubmatch(name) diff --git a/testdata/wildcards/Taskfile.yml b/testdata/wildcards/Taskfile.yml index 0ec2ae2895..e152876c01 100644 --- a/testdata/wildcards/Taskfile.yml +++ b/testdata/wildcards/Taskfile.yml @@ -25,3 +25,12 @@ tasks: SERVICE: "{{index .MATCH 0}}" cmds: - echo "Starting {{.SERVICE}}" + + # Regex metacharacters in a task name must be matched literally + c++: + cmds: + - echo "Building c++" + + deploy.prod: + cmds: + - echo "Deploying prod"