wip: improve header parsing and error handling

This commit is contained in:
2020-11-01 15:57:19 +00:00
parent 068477c1e5
commit 883292995a
2 changed files with 159 additions and 20 deletions

View File

@@ -3,6 +3,7 @@ package commit
import (
"bytes"
"errors"
"fmt"
"regexp"
)
@@ -10,33 +11,64 @@ const (
cr = 13
lf = 10
crlf = "\r\n"
typeMatch = `^[\w-]+$`
scopeMatch = `^[\w\$\.\/\-\* ]+$`
)
var (
rHeader = regexp.MustCompile(
`^([\w\-]*)(?:\(([\w\$\.\/\-\* ]*)\))?(!)?\: (.*)$`,
`^([^\(\)]*?)(\((.*?)\))?(!)?\:\s+(.*)$`,
)
rFooter = regexp.MustCompile(
`^([\w-]+)\s+(#.*)|([\w-]+|BREAKING CHANGE):\s\s*(.*)$`,
`^([\w-]+)\s+(#.*)|([\w-]+|BREAKING CHANGE):\s+(.*)$`,
)
rType = regexp.MustCompile(typeMatch)
rScope = regexp.MustCompile(scopeMatch)
Err = errors.New("")
ErrFormat = fmt.Errorf("%winvalid format", Err)
ErrMultiLineHeader = fmt.Errorf("%w: header has multiple lines", ErrFormat)
ErrType = fmt.Errorf("%wtype", Err)
ErrTypeFormat = fmt.Errorf("%w must match: %s", ErrType, typeMatch)
ErrTypeMissing = fmt.Errorf("%w is missing", ErrType)
ErrScope = fmt.Errorf("%wscope", Err)
ErrScopeFormat = fmt.Errorf("%w must match: %s", ErrScope, scopeMatch)
)
func parseHeader(header []byte) (*Commit, error) {
commit := &Commit{}
if bytes.ContainsAny(header, crlf) {
return nil, errors.New("header cannot span multiple lines")
return commit, ErrMultiLineHeader
}
result := rHeader.FindSubmatch(header)
if result == nil {
return &Commit{Subject: string(header)}, nil
commit = &Commit{Subject: string(header)}
} else {
commit = &Commit{
Type: string(bytes.TrimSpace(result[1])),
Scope: string(bytes.TrimSpace(result[3])),
Subject: string(bytes.TrimSpace(result[5])),
IsBreaking: string(result[4]) == "!",
}
}
return &Commit{
Type: string(result[1]),
Scope: string(result[2]),
Subject: string(result[4]),
IsBreaking: (string(result[3]) == "!"),
}, nil
if commit.Type == "" {
return commit, ErrTypeMissing
} else if !rType.MatchString(commit.Type) {
return commit, ErrTypeFormat
}
if len(commit.Scope) > 0 && !rScope.MatchString(commit.Scope) {
return commit, ErrScopeFormat
}
return commit, nil
}
func footers(paragraph []byte) []*Footer {

View File

@@ -1,6 +1,7 @@
package commit
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
@@ -15,11 +16,22 @@ func Test_parseHeader(t *testing.T) {
args args
want *Commit
errStr string
errIs []error
}{
{
name: "non-convention commit",
args: args{header: []byte("add user sorting option")},
want: &Commit{Subject: "add user sorting option"},
name: "missing type",
args: args{header: []byte("add user sorting option")},
want: &Commit{Subject: "add user sorting option"},
errIs: []error{Err, ErrTypeMissing},
},
{
name: "missing type with scope",
args: args{
header: []byte("(user): add user sorting option"),
},
want: &Commit{Scope: "user", Subject: "add user sorting option"},
errStr: `type is missing`,
errIs: []error{Err, ErrType, ErrTypeMissing},
},
{
name: "type only",
@@ -147,19 +159,104 @@ func Test_parseHeader(t *testing.T) {
Subject: "add user sorting option",
},
},
{
name: "excess whitespace in type with scope",
args: args{
header: []byte(" feat (user sort): add user sorting option"),
},
want: &Commit{
Type: "feat",
Scope: "user sort",
Subject: "add user sorting option",
},
},
{
name: "excess whitespace in scope",
args: args{
header: []byte("feat( user sort ): add user sorting option"),
},
want: &Commit{
Type: "feat",
Scope: "user sort",
Subject: "add user sorting option",
},
},
{
name: "excess whitespace in subject",
args: args{
header: []byte("feat(user): add user sorting option "),
},
want: &Commit{
Type: "feat",
Scope: "user",
Subject: "add user sorting option",
},
},
{
name: "empty scope",
args: args{
header: []byte("feat(): add user sorting option"),
},
want: &Commit{
Type: "feat",
Subject: "add user sorting option",
},
},
{
name: "multi-line header (LF)",
args: args{
header: []byte("feat(user)!: add usersorting\noption"),
header: []byte("feat(user)!: add user sorting\nnoption"),
},
errStr: "header cannot span multiple lines",
want: &Commit{},
errStr: "invalid format: header has multiple lines",
errIs: []error{ErrFormat, ErrMultiLineHeader},
},
{
name: "multi-line header (CR)",
args: args{
header: []byte("feat(user)!: add usersorting\roption"),
header: []byte("feat(user)!: add user sorting\roption"),
},
errStr: "header cannot span multiple lines",
want: &Commit{},
errStr: "invalid format: header has multiple lines",
errIs: []error{Err, ErrFormat, ErrMultiLineHeader},
},
{
name: "invalid type character",
args: args{
header: []byte("feat/internal: add user sorting option"),
},
want: &Commit{
Type: "feat/internal",
Subject: "add user sorting option",
},
errStr: `type must match: ^[\w-]+$`,
errIs: []error{Err, ErrType, ErrTypeFormat},
},
{
name: "invalid type character with scope",
args: args{
header: []byte("feat/internal(user): add user sorting option"),
},
want: &Commit{
Type: "feat/internal",
Scope: "user",
Subject: "add user sorting option",
},
errStr: `type must match: ^[\w-]+$`,
errIs: []error{Err, ErrType, ErrTypeFormat},
},
{
name: "invalid scope character",
args: args{
header: []byte("feat(user#sort): add user sorting option"),
},
want: &Commit{
Type: "feat",
Scope: "user#sort",
Subject: "add user sorting option",
},
errStr: `scope must match: ^[\w\$\.\/\-\* ]+$`,
errIs: []error{Err, ErrScope, ErrScopeFormat},
},
}
for _, tt := range tests {
@@ -167,10 +264,20 @@ func Test_parseHeader(t *testing.T) {
got, err := parseHeader(tt.args.header)
if tt.errStr != "" {
assert.Error(t, err, tt.errStr)
} else {
assert.Equal(t, tt.want, got)
assert.EqualError(t, err, tt.errStr)
}
if len(tt.errIs) > 0 {
for _, errIs := range tt.errIs {
assert.True(t, errors.Is(err, errIs))
}
}
if len(tt.errIs) == 0 && tt.errStr == "" {
assert.NoError(t, err)
}
assert.Equal(t, tt.want, got)
})
}
}