Skip to content

Commit fd8c1c7

Browse files
feat!: allow characters in header as per spec
allowed characters for type, scope and description as per spec test: add internal test case for parseHeader https://github.com/conventional-commits/parser#the-grammar
1 parent f03ee57 commit fd8c1c7

2 files changed

Lines changed: 64 additions & 4 deletions

File tree

parser.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ func newFooterNote(token, value string) FooterNote {
4444

4545
// Parse attempts to parse a commit message to a conventional commit
4646
func Parse(message string) (*Commit, error) {
47-
messageLines := strings.Split(strings.TrimRight(message, "\n\t "), "\n")
47+
message = strings.TrimRight(message, "\n\t ")
48+
messageLines := strings.Split(message, "\n")
4849

4950
commit := &Commit{
5051
FullCommit: message,
@@ -135,10 +136,14 @@ func parseLineAsFooter(line string) (key, value string) {
135136

136137
// parseHeader attempts to parse the commit description line and set the appropriate values in the the given commit
137138
func parseHeader(header string, commit *Commit) error {
138-
// allows /, \ in scope
139-
headerRegexp := regexp.MustCompile(`^(?P<type>[A-Za-z]+)(?:\((?P<scope>[A-Za-z\/\\]+)\))?(?P<breaking>!)?: (?P<description>[\w| ]+)(?:\n\s*\n(?P<body>(?:.|\n)*)(?:\n\s+\n(?P<footers>(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$`)
140-
// TODO: comma separated multiple scopes?
139+
// from https://github.com/conventional-commits/parser#the-grammar
140+
141+
// <header/summary> ::= <type>, "(", <scope>, ")", ["!"], ":", <whitespace>*, <text> <type>, ["!"], ":", <whitespace>*, <text>
142+
// <type> ::= <any UTF8-octets except newline or parens or ":" or "!:" or whitespace>+
143+
// <scope> ::= <any UTF8-octets except newline or parens>+
141144

145+
headerRegexp := regexp.MustCompile(`^(?P<type>[^\n\(\)(:|!:| )]+)(?:\((?P<scope>[^\n\(\)]+)\))?(?P<breaking>!)?: (?P<description>[^\n]+)(?:\n\s*\n(?P<body>(?:.|\n)*)(?:\n\s+\n(?P<footers>(?:[A-Za-z-]+: (?:.|\n)*)|(?:BREAKING CHANGE: (?:.|\n)*)|(?:[A-Za-z]+ \#(?:.|\n)*)))?)?$`)
146+
// TODO: comma separated multiple scopes?
142147
matches := headerRegexp.FindStringSubmatch(header)
143148
if matches == nil {
144149
return fmt.Errorf("unable to parse commit header: %s", header)

parser_internal_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package parser
2+
3+
import (
4+
"strconv"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestParseHeaderValid(t *testing.T) {
11+
var validCases = []string{
12+
`feat: description with name.txt`,
13+
`feat: description with question?`,
14+
`feat: description with numbers 1, 2, 3 and 4?`,
15+
"feat: !@#$%^&*() ??>?///||| /\\", // just characters. why ?
16+
"feat: 123 description \n\n body 1, 2, 3 and 4?",
17+
"feat: ?123 description \n\n body 1, 2, 3 and 4?",
18+
"feat: description with body 1, \n\n2, 3 and 4?",
19+
"feat1234(@scope/scope1,scope2): description, \n\n body 1 2, 3 and 4?",
20+
"1245#feat1234(@scope/scope1,scope2): description, \n\n body 1 2, 3 and 4?",
21+
}
22+
23+
for index, validCase := range validCases {
24+
testName := "case#" + strconv.Itoa(index+1)
25+
t.Run(testName, func(innerT *testing.T) {
26+
commit := &Commit{}
27+
err := parseHeader(validCase, commit)
28+
assert.NoError(innerT, err, validCase)
29+
})
30+
}
31+
}
32+
33+
func TestParseHeaderInvalid(t *testing.T) {
34+
var validCases = []string{
35+
`feat:() description with name.txt`,
36+
`feat:1 description with name.txt`,
37+
`feat:! description with name.txt`,
38+
`feat:A description with name.txt`,
39+
`feat123:A description with name.txt`,
40+
`feat!:A description with name.txt`,
41+
`feat())!:A description with name.txt`,
42+
`feat(scope1)!:A description with name.txt`,
43+
`!feat(scope1)!:A description with name.txt`,
44+
`feat(scope))!: A description with name.txt`,
45+
}
46+
47+
for index, validCase := range validCases {
48+
testName := "case#" + strconv.Itoa(index+1)
49+
t.Run(testName, func(innerT *testing.T) {
50+
commit := &Commit{}
51+
err := parseHeader(validCase, commit)
52+
assert.Error(innerT, err, validCase)
53+
})
54+
}
55+
}

0 commit comments

Comments
 (0)