mirror of
https://github.com/romdo/go-conver.git
synced 2026-02-19 08:16:40 +00:00
wip: improve header parsing and error handling
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user