mirror of
https://github.com/romdo/go-conventionalcommit.git
synced 2026-02-19 08:06:41 +00:00
wip(parser): partly finished Message parser
This commit is contained in:
54
buffer.go
54
buffer.go
@@ -1,38 +1,5 @@
|
|||||||
package conventionalcommit
|
package conventionalcommit
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// footerToken will match against all variations of Conventional Commit footer
|
|
||||||
// formats.
|
|
||||||
//
|
|
||||||
// Examples of valid footer tokens:
|
|
||||||
//
|
|
||||||
// Approved-by: John Carter
|
|
||||||
// ReviewdBy: Noctis
|
|
||||||
// Fixes #49
|
|
||||||
// Reverts #SOL-42
|
|
||||||
// BREAKING CHANGE: Flux capacitor no longer exists.
|
|
||||||
// BREAKING-CHANGE: Time will flow backwads
|
|
||||||
//
|
|
||||||
// Examples of invalid footer tokens:
|
|
||||||
//
|
|
||||||
// Approved-by:
|
|
||||||
// Approved-by:John Carter
|
|
||||||
// Approved by: John Carter
|
|
||||||
// ReviewdBy: Noctis
|
|
||||||
// Fixes#49
|
|
||||||
// Fixes #
|
|
||||||
// Fixes 49
|
|
||||||
// BREAKING CHANGE:Flux capacitor no longer exists.
|
|
||||||
// Breaking Change: Flux capacitor no longer exists.
|
|
||||||
// Breaking-Change: Time will flow backwads
|
|
||||||
//
|
|
||||||
var footerToken = regexp.MustCompile(
|
|
||||||
`^(?:([\w-]+)\s+(#.+)|([\w-]+|BREAKING[\s-]CHANGE):\s+(.+))$`,
|
|
||||||
)
|
|
||||||
|
|
||||||
// Buffer represents a commit message in a more structured form than a simple
|
// Buffer represents a commit message in a more structured form than a simple
|
||||||
// string or byte slice. This makes it easier to process a message for the
|
// string or byte slice. This makes it easier to process a message for the
|
||||||
// purposes of extracting detailed information, linting, and formatting.
|
// purposes of extracting detailed information, linting, and formatting.
|
||||||
@@ -119,11 +86,11 @@ func NewBuffer(message []byte) *Buffer {
|
|||||||
lastLen++
|
lastLen++
|
||||||
}
|
}
|
||||||
|
|
||||||
// If last paragraph starts with a Convention Commit footer token, it is the
|
// If last paragraph starts with a Conventional Commit footer token, it is
|
||||||
// foot section, otherwise it is part of the body.
|
// the foot section, otherwise it is part of the body.
|
||||||
if lastLen > 0 {
|
if lastLen > 0 {
|
||||||
line := buf.lines[buf.lastLine-lastLen+1]
|
line := buf.lines[buf.lastLine-lastLen+1]
|
||||||
if footerToken.Match(line.Content) {
|
if FooterToken.Match(line.Content) {
|
||||||
buf.footLen = lastLen
|
buf.footLen = lastLen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -176,6 +143,15 @@ func (s *Buffer) Lines() Lines {
|
|||||||
return s.lines[s.firstLine : s.lastLine+1]
|
return s.lines[s.firstLine : s.lastLine+1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LinesRaw returns all lines of the buffer including any blank lines at the
|
||||||
|
// beginning and end of the buffer.
|
||||||
|
func (s *Buffer) LinesRaw() Lines {
|
||||||
|
return s.lines
|
||||||
|
}
|
||||||
|
|
||||||
|
// LineCount returns number of lines in the buffer after discarding blank lines
|
||||||
|
// from the beginning and end of the buffer. Effectively counting all lines from
|
||||||
|
// the first to the last line which contain any non-whitespace characters.
|
||||||
func (s *Buffer) LineCount() int {
|
func (s *Buffer) LineCount() int {
|
||||||
if s.headLen == 0 {
|
if s.headLen == 0 {
|
||||||
return 0
|
return 0
|
||||||
@@ -184,6 +160,12 @@ func (s *Buffer) LineCount() int {
|
|||||||
return (s.lastLine + 1) - s.firstLine
|
return (s.lastLine + 1) - s.firstLine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LineCountRaw returns the number of lines in the buffer including any blank
|
||||||
|
// lines at the beginning and end of the buffer.
|
||||||
|
func (s *Buffer) LineCountRaw() int {
|
||||||
|
return len(s.lines)
|
||||||
|
}
|
||||||
|
|
||||||
// Bytes renders the Buffer back into a byte slice, without any leading or
|
// Bytes renders the Buffer back into a byte slice, without any leading or
|
||||||
// trailing whitespace lines. Leading whitespace on the first line which
|
// trailing whitespace lines. Leading whitespace on the first line which
|
||||||
// contains non-whitespace characters is retained. It is only whole lines
|
// contains non-whitespace characters is retained. It is only whole lines
|
||||||
|
|||||||
@@ -994,6 +994,55 @@ func BenchmarkBuffer_Lines(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBuffer_LinesRaw(t *testing.T) {
|
||||||
|
for _, tt := range bufferTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
want := tt.wantBuffer.lines[0:]
|
||||||
|
|
||||||
|
got := tt.wantBuffer.LinesRaw()
|
||||||
|
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuffer_LineCount(t *testing.T) {
|
||||||
|
for _, tt := range bufferTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
want := tt.wantLines[1]
|
||||||
|
|
||||||
|
got := tt.wantBuffer.LineCount()
|
||||||
|
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkBuffer_LineCount(b *testing.B) {
|
||||||
|
for _, tt := range bufferTestCases {
|
||||||
|
if tt.bytes == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
b.Run(tt.name, func(b *testing.B) {
|
||||||
|
for n := 0; n < b.N; n++ {
|
||||||
|
_ = tt.wantBuffer.LineCount()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuffer_LineCountRaw(t *testing.T) {
|
||||||
|
for _, tt := range bufferTestCases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
want := len(tt.wantBuffer.lines)
|
||||||
|
|
||||||
|
got := tt.wantBuffer.LineCountRaw()
|
||||||
|
|
||||||
|
assert.Equal(t, want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBuffer_Bytes(t *testing.T) {
|
func TestBuffer_Bytes(t *testing.T) {
|
||||||
for _, tt := range bufferTestCases {
|
for _, tt := range bufferTestCases {
|
||||||
if tt.bytes == nil {
|
if tt.bytes == nil {
|
||||||
|
|||||||
8
line.go
8
line.go
@@ -58,9 +58,9 @@ type Lines []*Line
|
|||||||
// basis.
|
// basis.
|
||||||
func NewLines(content []byte) Lines {
|
func NewLines(content []byte) Lines {
|
||||||
r := Lines{}
|
r := Lines{}
|
||||||
cLen := len(content)
|
length := len(content)
|
||||||
|
|
||||||
if cLen == 0 {
|
if length == 0 {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,13 +68,13 @@ func NewLines(content []byte) Lines {
|
|||||||
var breaks [][]int
|
var breaks [][]int
|
||||||
|
|
||||||
// Locate each line break within content.
|
// Locate each line break within content.
|
||||||
for i := 0; i < cLen; i++ {
|
for i := 0; i < length; i++ {
|
||||||
switch content[i] {
|
switch content[i] {
|
||||||
case lf:
|
case lf:
|
||||||
breaks = append(breaks, []int{i, i + 1})
|
breaks = append(breaks, []int{i, i + 1})
|
||||||
case cr:
|
case cr:
|
||||||
b := []int{i, i + 1}
|
b := []int{i, i + 1}
|
||||||
if i+1 < cLen && content[i+1] == lf {
|
if i+1 < length && content[i+1] == lf {
|
||||||
b[1]++
|
b[1]++
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|||||||
178
message.go
Normal file
178
message.go
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
package conventionalcommit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Err = errors.New("conventionalcommit")
|
||||||
|
ErrEmptyMessage = fmt.Errorf("%w: empty message", Err)
|
||||||
|
)
|
||||||
|
|
||||||
|
// HeaderToken will match a Conventional Commit formatted subject line, to
|
||||||
|
// extract type, scope, breaking change (bool), and description.
|
||||||
|
//
|
||||||
|
// It is intentionally VERY forgiving so as to be able to extract the various
|
||||||
|
// parts even when things aren't quite right.
|
||||||
|
var HeaderToken = regexp.MustCompile(
|
||||||
|
`^([^\(\)\r\n]*?)(\((.*?)\)\s*)?(!)?(\s*\:)\s(.*)$`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// FooterToken will match against all variations of Conventional Commit footer
|
||||||
|
// formats.
|
||||||
|
//
|
||||||
|
// Examples of valid footer tokens:
|
||||||
|
//
|
||||||
|
// Approved-by: John Carter
|
||||||
|
// ReviewdBy: Noctis
|
||||||
|
// Fixes #49
|
||||||
|
// Reverts #SOL-42
|
||||||
|
// BREAKING CHANGE: Flux capacitor no longer exists.
|
||||||
|
// BREAKING-CHANGE: Time will flow backwads
|
||||||
|
//
|
||||||
|
// Examples of invalid footer tokens:
|
||||||
|
//
|
||||||
|
// Approved-by:
|
||||||
|
// Approved-by:John Carter
|
||||||
|
// Approved by: John Carter
|
||||||
|
// ReviewdBy: Noctis
|
||||||
|
// Fixes#49
|
||||||
|
// Fixes #
|
||||||
|
// Fixes 49
|
||||||
|
// BREAKING CHANGE:Flux capacitor no longer exists.
|
||||||
|
// Breaking Change: Flux capacitor no longer exists.
|
||||||
|
// Breaking-Change: Time will flow backwads
|
||||||
|
//
|
||||||
|
var FooterToken = regexp.MustCompile(
|
||||||
|
`^([\w-]+|BREAKING[\s-]CHANGE)(?:\s*(:)\s+|\s+(#))(.+)$`,
|
||||||
|
)
|
||||||
|
|
||||||
|
// Message represents a Conventional Commit message in a structured way.
|
||||||
|
type Message struct {
|
||||||
|
// Type indicates what kind of a change the commit message describes.
|
||||||
|
Type string
|
||||||
|
|
||||||
|
// Scope indicates the context/component/area that the change affects.
|
||||||
|
Scope string
|
||||||
|
|
||||||
|
// Description is the primary description for the commit.
|
||||||
|
Description string
|
||||||
|
|
||||||
|
// Body is the main text body of the commit message. Effectively all text
|
||||||
|
// between the subject line, and any footers if present.
|
||||||
|
Body string
|
||||||
|
|
||||||
|
// Footers are all footers which are not references or breaking changes.
|
||||||
|
Footers []*Footer
|
||||||
|
|
||||||
|
// References are all footers defined with a reference style token, for
|
||||||
|
// example:
|
||||||
|
//
|
||||||
|
// Fixes #42
|
||||||
|
References []*Reference
|
||||||
|
|
||||||
|
// Breaking is set to true if the message subject included the "!" breaking
|
||||||
|
// change indicator.
|
||||||
|
Breaking bool
|
||||||
|
|
||||||
|
// BreakingChanges includes the descriptions from all BREAKING CHANGE
|
||||||
|
// footers.
|
||||||
|
BreakingChanges []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMessage(buf *Buffer) (*Message, error) {
|
||||||
|
msg := &Message{}
|
||||||
|
count := buf.LineCount()
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
return nil, ErrEmptyMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Description = buf.Head().Join("\n")
|
||||||
|
if m := HeaderToken.FindStringSubmatch(msg.Description); len(m) > 0 {
|
||||||
|
msg.Type = strings.TrimSpace(m[1])
|
||||||
|
msg.Scope = strings.TrimSpace(m[3])
|
||||||
|
msg.Breaking = m[4] == "!"
|
||||||
|
msg.Description = m[6]
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.Body = buf.Body().Join("\n")
|
||||||
|
|
||||||
|
if foot := buf.Foot(); len(foot) > 0 {
|
||||||
|
footers := parseFooters(foot)
|
||||||
|
|
||||||
|
for _, f := range footers {
|
||||||
|
name := string(f.name)
|
||||||
|
value := string(f.value)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case f.ref:
|
||||||
|
msg.References = append(msg.References, &Reference{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
case name == "BREAKING CHANGE" || name == "BREAKING-CHANGE":
|
||||||
|
msg.BreakingChanges = append(msg.BreakingChanges, value)
|
||||||
|
default:
|
||||||
|
msg.Footers = append(msg.Footers, &Footer{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Message) IsBreakingChange() bool {
|
||||||
|
return s.Breaking || len(s.BreakingChanges) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFooters(lines Lines) []*rawFooter {
|
||||||
|
var footers []*rawFooter
|
||||||
|
footer := &rawFooter{}
|
||||||
|
for _, line := range lines {
|
||||||
|
if m := FooterToken.FindSubmatch(line.Content); m != nil {
|
||||||
|
if len(footer.name) > 0 {
|
||||||
|
footers = append(footers, footer)
|
||||||
|
}
|
||||||
|
|
||||||
|
footer = &rawFooter{}
|
||||||
|
if len(m[3]) > 0 {
|
||||||
|
footer.ref = true
|
||||||
|
footer.value = []byte{hash}
|
||||||
|
}
|
||||||
|
footer.name = m[1]
|
||||||
|
footer.value = append(footer.value, m[4]...)
|
||||||
|
} else if len(footer.name) > 0 {
|
||||||
|
footer.value = append(footer.value, lf)
|
||||||
|
footer.value = append(footer.value, line.Content...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(footer.name) > 0 {
|
||||||
|
footers = append(footers, footer)
|
||||||
|
}
|
||||||
|
|
||||||
|
return footers
|
||||||
|
}
|
||||||
|
|
||||||
|
type rawFooter struct {
|
||||||
|
name []byte
|
||||||
|
value []byte
|
||||||
|
ref bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type Footer struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Reference struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
80
message_test.go
Normal file
80
message_test.go
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
package conventionalcommit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMessage_IsBreakingChange(t *testing.T) {
|
||||||
|
type fields struct {
|
||||||
|
Breaking bool
|
||||||
|
BreakingChanges []string
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
fields fields
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "false breaking flag, no change texts",
|
||||||
|
fields: fields{
|
||||||
|
Breaking: false,
|
||||||
|
BreakingChanges: []string{},
|
||||||
|
},
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "true breaking flag, no change texts",
|
||||||
|
fields: fields{
|
||||||
|
Breaking: true,
|
||||||
|
BreakingChanges: []string{},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false breaking flag, 1 change texts",
|
||||||
|
fields: fields{
|
||||||
|
Breaking: false,
|
||||||
|
BreakingChanges: []string{"be careful"},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "true breaking flag, 1 change texts",
|
||||||
|
fields: fields{
|
||||||
|
Breaking: true,
|
||||||
|
BreakingChanges: []string{"be careful"},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "false breaking flag, 3 change texts",
|
||||||
|
fields: fields{
|
||||||
|
Breaking: false,
|
||||||
|
BreakingChanges: []string{"be careful", "oops", "ouch"},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "true breaking flag, 3 change texts",
|
||||||
|
fields: fields{
|
||||||
|
Breaking: true,
|
||||||
|
BreakingChanges: []string{"be careful", "oops", "ouch"},
|
||||||
|
},
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
msg := &Message{
|
||||||
|
Breaking: tt.fields.Breaking,
|
||||||
|
BreakingChanges: tt.fields.BreakingChanges,
|
||||||
|
}
|
||||||
|
|
||||||
|
got := msg.IsBreakingChange()
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
9
parse.go
Normal file
9
parse.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package conventionalcommit
|
||||||
|
|
||||||
|
// Parse parses a conventional commit message and returns it as a *Message
|
||||||
|
// struct.
|
||||||
|
func Parse(message []byte) (*Message, error) {
|
||||||
|
buffer := NewBuffer(message)
|
||||||
|
|
||||||
|
return NewMessage(buffer)
|
||||||
|
}
|
||||||
421
parse_test.go
Normal file
421
parse_test.go
Normal file
@@ -0,0 +1,421 @@
|
|||||||
|
package conventionalcommit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
message []byte
|
||||||
|
want *Message
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
message: []byte{},
|
||||||
|
wantErr: "conventionalcommit: empty message",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description only",
|
||||||
|
message: []byte("change a thing"),
|
||||||
|
want: &Message{
|
||||||
|
Description: "change a thing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description and body",
|
||||||
|
message: []byte(`change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "type and description",
|
||||||
|
message: []byte("feat: change a thing"),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "type, description and body",
|
||||||
|
message: []byte(
|
||||||
|
"feat: change a thing\n\nmore stuff\nand more",
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "type, scope and description",
|
||||||
|
message: []byte("feat(token): change a thing"),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "token",
|
||||||
|
Description: "change a thing",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "type, scope, description and body",
|
||||||
|
message: []byte(
|
||||||
|
`feat(token): change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "token",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "breaking change in subject line",
|
||||||
|
message: []byte(
|
||||||
|
`feat!: change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
Breaking: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "breaking change in subject line with scope",
|
||||||
|
message: []byte(
|
||||||
|
`feat(token)!: change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "token",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
Breaking: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "BREAKING CHANGE footer",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
BREAKING CHANGE: will blow up
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
BreakingChanges: []string{"will blow up"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BREAKING-CHANGE footer",
|
||||||
|
message: []byte(
|
||||||
|
`feat(token): change a thing
|
||||||
|
|
||||||
|
BREAKING-CHANGE: maybe not
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "token",
|
||||||
|
Description: "change a thing",
|
||||||
|
BreakingChanges: []string{"maybe not"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reference footer",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
Fixes #349
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
References: []*Reference{
|
||||||
|
{Name: "Fixes", Value: "#349"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reference (alt) footer",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
Reverts #SOL-934
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
References: []*Reference{
|
||||||
|
{Name: "Reverts", Value: "#SOL-934"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token footer",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
Approved-by: John Carter
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Footers: []*Footer{
|
||||||
|
{Name: "Approved-by", Value: "John Carter"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token (alt) footer",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
ReviewedBy: Noctis
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Footers: []*Footer{
|
||||||
|
{Name: "ReviewedBy", Value: "Noctis"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: "BREAKING CHANGE footer with body",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more
|
||||||
|
|
||||||
|
BREAKING CHANGE: will blow up
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
BreakingChanges: []string{"will blow up"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "BREAKING-CHANGE footer with body",
|
||||||
|
message: []byte(
|
||||||
|
`feat(token): change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more
|
||||||
|
|
||||||
|
BREAKING-CHANGE: maybe not
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "token",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
BreakingChanges: []string{"maybe not"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reference footer with body",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more
|
||||||
|
|
||||||
|
Fixes #349
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
References: []*Reference{
|
||||||
|
{Name: "Fixes", Value: "#349"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reference (alt) footer with body",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more
|
||||||
|
|
||||||
|
Reverts #SOL-934
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
References: []*Reference{
|
||||||
|
{Name: "Reverts", Value: "#SOL-934"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token footer with body",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more
|
||||||
|
|
||||||
|
Approved-by: John Carter
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
Footers: []*Footer{
|
||||||
|
{Name: "Approved-by", Value: "John Carter"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "token (alt) footer with body",
|
||||||
|
message: []byte(
|
||||||
|
`feat: change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more
|
||||||
|
|
||||||
|
ReviewedBy: Noctis
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
Footers: []*Footer{
|
||||||
|
{Name: "ReviewedBy", Value: "Noctis"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "type, scope, description, body and footers",
|
||||||
|
message: []byte(
|
||||||
|
`feat(token): change a thing
|
||||||
|
|
||||||
|
more stuff
|
||||||
|
and more
|
||||||
|
|
||||||
|
BREAKING CHANGE: will blow up
|
||||||
|
BREAKING-CHANGE: maybe not
|
||||||
|
Fixes #349
|
||||||
|
Reverts #SOL-934
|
||||||
|
Approved-by: John Carter
|
||||||
|
ReviewedBy: Noctis
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "token",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "more stuff\nand more",
|
||||||
|
Footers: []*Footer{
|
||||||
|
{Name: "Approved-by", Value: "John Carter"},
|
||||||
|
{Name: "ReviewedBy", Value: "Noctis"},
|
||||||
|
},
|
||||||
|
References: []*Reference{
|
||||||
|
{Name: "Fixes", Value: "#349"},
|
||||||
|
{Name: "Reverts", Value: "#SOL-934"},
|
||||||
|
},
|
||||||
|
BreakingChanges: []string{"will blow up", "maybe not"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multi-line footers",
|
||||||
|
message: []byte(
|
||||||
|
`feat(token): change a thing
|
||||||
|
|
||||||
|
Some stuff
|
||||||
|
|
||||||
|
BREAKING CHANGE: Nam euismod tellus id erat. Cum sociis natoque penatibus
|
||||||
|
et magnis dis parturient montes, nascetur ridiculous mus.
|
||||||
|
Approved-by: John Carter
|
||||||
|
and Noctis
|
||||||
|
Fixes #SOL-349 and also
|
||||||
|
#SOL-9440
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "token",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "Some stuff",
|
||||||
|
Footers: []*Footer{
|
||||||
|
{Name: "Approved-by", Value: "John Carter\nand Noctis"},
|
||||||
|
},
|
||||||
|
References: []*Reference{
|
||||||
|
{Name: "Fixes", Value: "#SOL-349 and also\n#SOL-9440"},
|
||||||
|
},
|
||||||
|
BreakingChanges: []string{
|
||||||
|
`Nam euismod tellus id erat. Cum sociis natoque penatibus
|
||||||
|
et magnis dis parturient montes, nascetur ridiculous mus.`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "indented footer",
|
||||||
|
message: []byte(
|
||||||
|
`feat(token): change a thing
|
||||||
|
|
||||||
|
Some stuff
|
||||||
|
|
||||||
|
Approved-by: John Carter
|
||||||
|
`,
|
||||||
|
),
|
||||||
|
want: &Message{
|
||||||
|
Type: "feat",
|
||||||
|
Scope: "token",
|
||||||
|
Description: "change a thing",
|
||||||
|
Body: "Some stuff\n\n Approved-by: John Carter",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := Parse(tt.message)
|
||||||
|
|
||||||
|
if tt.wantErr != "" {
|
||||||
|
assert.EqualError(t, err, tt.wantErr)
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, tt.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user