chore(ci): add golangci-lint action and apply linting changes (#120)

BREAKING CHANGE: `JiraIssueId` has been renamed to `JiraIssueID`. This impacts the value for `pattern_maps` in `config.yml`.

* chore(ci): add golangci-lint action

* chore(lint): address errcheck lint failures

* chore(lint): address misspell lint failures

* chore(lint): address gocritic lint failures

* chore(lint): address golint lint failures

* chore(lint): address structcheck lint failures

* chore(lint): address gosimple lint failures

* chore(lint): address gofmt lint failures

* chore(ci): port to official golangci-lint github action

* Update golangci configuration for better coverage

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>

* fix: file is not goimports-ed

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>

* fix: golint and exported functions comments

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>

* chore(lint): address gosec G304 warning

* chore(lint): address uparam warnings

* chore(lint): address scopelint lint failures

* fix: cyclomatic complexity

Signed-off-by: Khosrow Moossavi <khos2ow@gmail.com>

* chore(lint): address prealloc warning, noting that we are warning for now

* chore(lint): address govet and errorlint failures

* chore: clean up defer logic when checking errors

Co-authored-by: Khosrow Moossavi <khos2ow@gmail.com>
This commit is contained in:
Derek Smith 2021-03-16 21:24:36 -05:00 committed by GitHub
parent 2c3d3f400e
commit ae3382b7c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 391 additions and 177 deletions

18
.github/workflows/lint.yml vendored Normal file
View file

@ -0,0 +1,18 @@
name: lint
on:
pull_request:
types: ['opened', 'synchronize']
push:
branches:
- master
jobs:
golangci:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.38

155
.golangci.yml Normal file
View file

@ -0,0 +1,155 @@
# https://golangci-lint.run/usage/configuration/
run:
timeout: 10m
deadline: 5m
tests: true
output:
format: tab
linters-settings:
govet:
# report about shadowed variables
check-shadowing: true
golint:
# minimal confidence for issues, default is 0.8
min-confidence: 0.8
gofmt:
# simplify code: gofmt with `-s` option, true by default
simplify: true
goimports:
# put imports beginning with prefix after 3rd-party packages;
# it's a comma-separated list of prefixes
local-prefixes: github.com/git-chglog/git-chglog
gocyclo:
# minimal code complexity to report, 30 by default (but we recommend 10-20)
min-complexity: 10
maligned:
# print struct with more effective memory layout or not, false by default
suggest-new: true
dupl:
# tokens count to trigger issue, 150 by default
threshold: 100
goconst:
# minimal length of string constant, 3 by default
min-len: 3
# minimal occurrences count to trigger, 3 by default
min-occurrences: 5
lll:
# tab width in spaces. Default to 1.
tab-width: 1
unused:
# treat code as a program (not a library) and report unused exported identifiers; default is false.
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
unparam:
# Inspect exported functions, default is false. Set to true if no external program/library imports your code.
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors:
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations
# with golangci-lint call it on a directory with the changed file.
check-exported: false
nakedret:
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30
max-func-lines: 30
prealloc:
# XXX: we don't recommend using this linter before doing performance profiling.
# For most programs usage of prealloc will be a premature optimization.
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them.
# True by default.
simple: true
range-loops: true # Report preallocation suggestions on range loops, true by default
for-loops: false # Report preallocation suggestions on for loops, false by default
gocritic:
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks.
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags".
enabled-tags:
- performance
settings: # settings passed to gocritic
captLocal: # must be valid enabled check name
paramsOnly: true
rangeValCopy:
sizeThreshold: 32
misspell:
locale: US
linters:
enable:
- megacheck
- govet
- gocyclo
- gocritic
- interfacer
- goconst
- goimports
- gofmt # We enable this as well as goimports for its simplify mode.
- prealloc
- golint
- unconvert
- misspell
- nakedret
- dupl
- depguard
presets:
- bugs
- unused
fast: false
issues:
# Excluding configuration per-path and per-linter
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test(ing)?\.go
linters:
- gocyclo
- errcheck
- dupl
- gosec
- scopelint
- unparam
# These are performance optimisations rather than style issues per se.
# They warn when function arguments or range values copy a lot of memory
# rather than using a pointer.
- text: "(hugeParam|rangeValCopy):"
linters:
- gocritic
# Independently from option `exclude` we use default exclude patterns,
# it can be disabled by this option. To list all
# excluded by default patterns execute `golangci-lint run --help`.
# Default value for this option is true.
exclude-use-default: false
# Show only new issues: if there are unstaged changes or untracked files,
# only those changes are analyzed, else only changes in HEAD~ are analyzed.
# It's a super-useful option for integration of golangci-lint into existing
# large codebase. It's not practical to fix all existing issues at the moment
# of integration: much better don't allow issues in new code.
# Default is false.
new: false
# Maximum issues count per one linter. Set to 0 to disable. Default is 50.
max-per-linter: 0
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3.
max-same-issues: 0

View file

@ -20,3 +20,7 @@ install:
.PHONY: changelog
changelog:
@git-chglog --next-tag $(tag) $(tag)
.PHONY: lint
lint:
@golangci-lint run

View file

@ -522,7 +522,7 @@ The following is a sample pattern:
pattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$"
pattern_maps:
- Type
- JiraIssueId
- JiraIssueID
- Subject
```
@ -646,6 +646,8 @@ We alway welcome your contributions :clap:
1. Fork (https://github.com/git-chglog/git-chglog) :tada:
1. Create a feature branch :coffee:
1. Run test suite with the `$ make test` command and confirm that it passes :zap:
1. Run linters with the `$ make lint` command and confirm it passes :broom:
- The project uses [golangci-lint](https://golangci-lint.run/usage/install/#local-installation)
1. Commit your changes :memo:
1. Rebase your local changes against the `master` branch :bulb:
1. Create new Pull Request :love_letter:

View file

@ -5,13 +5,14 @@ import (
"errors"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"text/template"
"time"
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
"github.com/tsuyoshiwada/go-gitcmd"
)
// Options is an option used to process commits
@ -37,7 +38,7 @@ type Options struct {
NoteKeywords []string // Keyword list to find `Note`. A semicolon is a separator, like `<keyword>:` (e.g. `BREAKING CHANGE`)
JiraUsername string
JiraToken string
JiraUrl string
JiraURL string
JiraTypeMaps map[string]string
JiraIssueDescriptionPattern string
Paths []string // Path filter
@ -138,7 +139,11 @@ func (gen *Generator) Generate(w io.Writer, query string) error {
if err != nil {
return err
}
defer back()
defer func() {
if err = back(); err != nil {
log.Fatal(err)
}
}()
tags, first, err := gen.getTags(query)
if err != nil {
@ -269,7 +274,7 @@ func (gen *Generator) getTags(query string) ([]*Tag, string, error) {
// Assign the date with `readVersions()`
tags = append([]*Tag{
&Tag{
{
Name: next,
Subject: next,
Previous: previous,
@ -318,30 +323,18 @@ func (gen *Generator) render(w io.Writer, unreleased *Unreleased, versions []*Ve
"datetime": func(layout string, input time.Time) string {
return input.Format(layout)
},
// check whether substs is withing s
"contains": func(s, substr string) bool {
return strings.Contains(s, substr)
},
// check whether substr is within s
"contains": strings.Contains,
// check whether s begins with prefix
"hasPrefix": func(s, prefix string) bool {
return strings.HasPrefix(s, prefix)
},
"hasPrefix": strings.HasPrefix,
// check whether s ends with suffix
"hasSuffix": func(s, suffix string) bool {
return strings.HasSuffix(s, suffix)
},
"hasSuffix": strings.HasSuffix,
// replace the first n instances of old with new
"replace": func(s, old, new string, n int) string {
return strings.Replace(s, old, new, n)
},
"replace": strings.Replace,
// lower case a string
"lower": func(s string) string {
return strings.ToLower(s)
},
"lower": strings.ToLower,
// upper case a string
"upper": func(s string) string {
return strings.ToUpper(s)
},
"upper": strings.ToUpper,
// upper case the first character of a string
"upperFirst": func(s string) string {
if len(s) > 0 {

View file

@ -32,17 +32,17 @@ func TestMain(m *testing.M) {
func setup(dir string, setupRepo func(commitFunc, tagFunc, gitcmd.Client)) {
testDir := filepath.Join(cwd, testRepoRoot, dir)
os.RemoveAll(testDir)
os.MkdirAll(testDir, os.ModePerm)
os.Chdir(testDir)
_ = os.RemoveAll(testDir)
_ = os.MkdirAll(testDir, os.ModePerm)
_ = os.Chdir(testDir)
loc, _ := time.LoadLocation("UTC")
time.Local = loc
git := gitcmd.New(nil)
git.Exec("init")
git.Exec("config", "user.name", "test_user")
git.Exec("config", "user.email", "test@example.com")
_, _ = git.Exec("init")
_, _ = git.Exec("config", "user.name", "test_user")
_, _ = git.Exec("config", "user.email", "test@example.com")
var commit = func(date, subject, body string) {
msg := subject
@ -51,21 +51,21 @@ func setup(dir string, setupRepo func(commitFunc, tagFunc, gitcmd.Client)) {
}
t, _ := time.Parse(internalTimeFormat, date)
d := t.Format("Mon Jan 2 15:04:05 2006 +0000")
git.Exec("commit", "--allow-empty", "--date", d, "-m", msg)
_, _ = git.Exec("commit", "--allow-empty", "--date", d, "-m", msg)
}
var tag = func(name string) {
git.Exec("tag", name)
_, _ = git.Exec("tag", name)
}
setupRepo(commit, tag, git)
os.Chdir(cwd)
_ = os.Chdir(cwd)
}
func cleanup() {
os.Chdir(cwd)
os.RemoveAll(filepath.Join(cwd, testRepoRoot))
_ = os.Chdir(cwd)
_ = os.RemoveAll(filepath.Join(cwd, testRepoRoot))
}
func TestGeneratorNotFoundTags(t *testing.T) {
@ -193,7 +193,7 @@ func TestGeneratorWithTypeScopeSubject(t *testing.T) {
tag("1.1.0")
commit("2018-01-03 00:00:00", "feat(context): Online breaking change", "BREAKING CHANGE: Online breaking change message.")
commit("2018-01-03 00:01:00", "feat(router): Muliple breaking change", `This is body,
commit("2018-01-03 00:01:00", "feat(router): Multiple breaking change", `This is body,
BREAKING CHANGE:
Multiple
@ -216,7 +216,7 @@ change message.`)
},
Options: &Options{
CommitFilters: map[string][]string{
"Type": []string{
"Type": {
"feat",
"fix",
},
@ -256,7 +256,7 @@ change message.`)
buf := &bytes.Buffer{}
err := gen.Generate(buf, "")
output := strings.Replace(strings.TrimSpace(buf.String()), "\r\n", "\n", -1)
output := strings.ReplaceAll(strings.TrimSpace(buf.String()), "\r\n", "\n")
assert.Nil(err)
assert.Equal(`<a name="unreleased"></a>
@ -270,7 +270,7 @@ change message.`)
## [2.0.0-beta.0] - 2018-01-03
### Features
- **context:** Online breaking change
- **router:** Muliple breaking change
- **router:** Multiple breaking change
### BREAKING CHANGE
@ -331,7 +331,7 @@ func TestGeneratorWithNextTag(t *testing.T) {
Options: &Options{
NextTag: "3.0.0",
CommitFilters: map[string][]string{
"Type": []string{
"Type": {
"feat",
},
},
@ -352,7 +352,7 @@ func TestGeneratorWithNextTag(t *testing.T) {
buf := &bytes.Buffer{}
err := gen.Generate(buf, "")
output := strings.Replace(strings.TrimSpace(buf.String()), "\r\n", "\n", -1)
output := strings.ReplaceAll(strings.TrimSpace(buf.String()), "\r\n", "\n")
assert.Nil(err)
assert.Equal(`<a name="unreleased"></a>
@ -383,7 +383,7 @@ func TestGeneratorWithNextTag(t *testing.T) {
buf = &bytes.Buffer{}
err = gen.Generate(buf, "3.0.0")
output = strings.Replace(strings.TrimSpace(buf.String()), "\r\n", "\n", -1)
output = strings.ReplaceAll(strings.TrimSpace(buf.String()), "\r\n", "\n")
assert.Nil(err)
assert.Equal(`<a name="unreleased"></a>
@ -424,7 +424,7 @@ func TestGeneratorWithTagFiler(t *testing.T) {
Options: &Options{
TagFilterPattern: "^v",
CommitFilters: map[string][]string{
"Type": []string{
"Type": {
"feat",
},
},

View file

@ -7,6 +7,7 @@ import (
"time"
"github.com/fatih/color"
chglog "github.com/git-chglog/git-chglog"
)

View file

@ -8,8 +8,9 @@ import (
"regexp"
"testing"
chglog "github.com/git-chglog/git-chglog"
"github.com/stretchr/testify/assert"
chglog "github.com/git-chglog/git-chglog"
)
func TestCLIForStdout(t *testing.T) {
@ -36,7 +37,7 @@ func TestCLIForStdout(t *testing.T) {
if config.Bin != "/custom/bin/git" {
return errors.New("")
}
w.Write([]byte("success!!"))
_, _ = w.Write([]byte("success!!"))
return nil
},
}
@ -104,7 +105,7 @@ func TestCLIForFile(t *testing.T) {
if filepath.ToSlash(config.Bin) != "/custom/bin/git" {
return errors.New("")
}
w.Write([]byte("success!!"))
_, _ = w.Write([]byte("success!!"))
return nil
},
}

View file

@ -4,8 +4,9 @@ import (
"path/filepath"
"strings"
chglog "github.com/git-chglog/git-chglog"
"github.com/imdario/mergo"
chglog "github.com/git-chglog/git-chglog"
)
// Info ...
@ -49,6 +50,7 @@ type NoteOptions struct {
Keywords []string `yaml:"keywords"`
}
// JiraClientInfoOptions ...
type JiraClientInfoOptions struct {
Username string `yaml:"username"`
Token string `yaml:"token"`
@ -286,7 +288,7 @@ func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
Template: orValue(ctx.Template, config.Template),
Info: &chglog.Info{
Title: info.Title,
RepositoryURL: orValue(ctx.RepositoryUrl, info.RepositoryURL),
RepositoryURL: orValue(ctx.RepositoryURL, info.RepositoryURL),
},
Options: &chglog.Options{
NextTag: ctx.NextTag,
@ -309,7 +311,7 @@ func (config *Config) Convert(ctx *CLIContext) *chglog.Config {
NoteKeywords: opts.Notes.Keywords,
JiraUsername: orValue(ctx.JiraUsername, opts.Jira.ClintInfo.Username),
JiraToken: orValue(ctx.JiraToken, opts.Jira.ClintInfo.Token),
JiraUrl: orValue(ctx.JiraUrl, opts.Jira.ClintInfo.URL),
JiraURL: orValue(ctx.JiraURL, opts.Jira.ClintInfo.URL),
JiraTypeMaps: opts.Jira.Issue.TypeMaps,
JiraIssueDescriptionPattern: opts.Jira.Issue.DescriptionPattern,
},

View file

@ -2,8 +2,9 @@ package main
import (
"io/ioutil"
"path/filepath"
yaml "gopkg.in/yaml.v2"
"gopkg.in/yaml.v2"
)
// ConfigLoader ...
@ -20,7 +21,8 @@ func NewConfigLoader() ConfigLoader {
}
func (loader *configLoaderImpl) Load(path string) (*Config, error) {
bytes, err := ioutil.ReadFile(path)
fp := filepath.Clean(path)
bytes, err := ioutil.ReadFile(fp)
if err != nil {
return nil, err
}

View file

@ -11,7 +11,7 @@ type CLIContext struct {
Stderr io.Writer
ConfigPath string
Template string
RepositoryUrl string
RepositoryURL string
OutputPath string
Silent bool
NoColor bool
@ -22,7 +22,7 @@ type CLIContext struct {
TagFilterPattern string
JiraUsername string
JiraToken string
JiraUrl string
JiraURL string
Paths []string
}

View file

@ -18,7 +18,7 @@ func (t *customTemplateBuilderImpl) Build(ans *Answer) (string, error) {
tpl += t.versionHeader(ans.Style, ans.Template)
// commits
tpl += t.commits(ans.Template, ans.CommitMessageFormat)
tpl += t.commits(ans.CommitMessageFormat)
// revert
if ans.IncludeReverts {
@ -75,7 +75,7 @@ func (*customTemplateBuilderImpl) versionHeader(style, template string) string {
return tpl
}
func (*customTemplateBuilderImpl) commits(template, format string) string {
func (*customTemplateBuilderImpl) commits(format string) string {
var (
header string
body string

View file

@ -2,17 +2,16 @@ package main
import (
"fmt"
"github.com/git-chglog/git-chglog"
"path/filepath"
"github.com/fatih/color"
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
chglog "github.com/git-chglog/git-chglog"
)
// Initializer ...
type Initializer struct {
ctx *InitContext
client gitcmd.Client
fs FileSystem
logger *chglog.Logger
questioner Questioner

View file

@ -3,14 +3,17 @@ package main
import (
"fmt"
"io"
"log"
"os"
"github.com/fatih/color"
"github.com/mattn/go-colorable"
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
"github.com/tsuyoshiwada/go-gitcmd"
"github.com/urfave/cli/v2"
)
// CreateApp creates and initializes CLI application
// with description, flags, version, etc.
func CreateApp(actionFunc cli.ActionFunc) *cli.App {
ttl := color.New(color.FgYellow).SprintFunc()
@ -182,6 +185,8 @@ func CreateApp(actionFunc cli.ActionFunc) *cli.App {
return app
}
// AppAction is a callback function to create initializer
// and CLIContext and ultimately run the application.
func AppAction(c *cli.Context) error {
wd, err := os.Getwd()
if err != nil {
@ -219,7 +224,7 @@ func AppAction(c *cli.Context) error {
Stderr: colorable.NewColorableStderr(),
ConfigPath: c.String("config"),
Template: c.String("template"),
RepositoryUrl: c.String("repository-url"),
RepositoryURL: c.String("repository-url"),
OutputPath: c.String("output"),
Silent: c.Bool("silent"),
NoColor: c.Bool("no-color"),
@ -230,7 +235,7 @@ func AppAction(c *cli.Context) error {
TagFilterPattern: c.String("tag-filter-pattern"),
JiraUsername: c.String("jira-username"),
JiraToken: c.String("jira-token"),
JiraUrl: c.String("jira-url"),
JiraURL: c.String("jira-url"),
Paths: c.StringSlice("path"),
},
fs,
@ -245,5 +250,8 @@ func AppAction(c *cli.Context) error {
func main() {
app := CreateApp(AppAction)
app.Run(os.Args)
err := app.Run(os.Args)
if err != nil {
log.Fatal(err)
}
}

View file

@ -1,14 +1,16 @@
package main
import (
"log"
"testing"
"github.com/stretchr/testify/assert"
"github.com/urfave/cli/v2"
"testing"
)
var gAssert *assert.Assertions
func mock_app_action(c *cli.Context) error {
func mockAppAction(c *cli.Context) error {
assert := gAssert
assert.Equal("c.yml", c.String("config"))
assert.Equal("^v", c.String("tag-filter-pattern"))
@ -25,7 +27,7 @@ func TestCreateApp(t *testing.T) {
assert.True(true)
gAssert = assert
app := CreateApp(mock_app_action)
app := CreateApp(mockAppAction)
args := []string{
"git-chglog",
"--silent",
@ -36,5 +38,8 @@ func TestCreateApp(t *testing.T) {
"--next-tag", "v5",
"--tag-filter-pattern", "^v",
}
app.Run(args)
err := app.Run(args)
if err != nil {
log.Fatal(err)
}
}

View file

@ -3,8 +3,9 @@ package main
import (
"testing"
chglog "github.com/git-chglog/git-chglog"
"github.com/stretchr/testify/assert"
chglog "github.com/git-chglog/git-chglog"
)
func TestProcessorFactory(t *testing.T) {

View file

@ -6,8 +6,8 @@ import (
"path/filepath"
"strings"
gitcmd "github.com/tsuyoshiwada/go-gitcmd"
survey "github.com/AlecAivazis/survey/v2"
"github.com/AlecAivazis/survey/v2"
"github.com/tsuyoshiwada/go-gitcmd"
)
// Answer ...
@ -52,11 +52,12 @@ func (q *questionerImpl) Ask() (*Answer, error) {
t := q.fs.Exists(tpl)
msg := ""
if c && t {
switch {
case c && t:
msg = fmt.Sprintf("\"%s\" and \"%s\" already exists. Do you want to overwrite?", config, tpl)
} else if c {
case c:
msg = fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", config)
} else if t {
case t:
msg = fmt.Sprintf("\"%s\" already exists. Do you want to overwrite?", tpl)
}

View file

@ -7,7 +7,7 @@ import (
"strings"
)
var reSSH = regexp.MustCompile("^\\w+@([\\w\\.\\-]+):([\\w\\.\\-]+)\\/([\\w\\.\\-]+)$")
var reSSH = regexp.MustCompile(`^\w+@([\w\.\-]+):([\w\.\-]+)\/([\w\.\-]+)$`)
func remoteOriginURLToHTTP(rawurl string) string {
if rawurl == "" {

View file

@ -74,7 +74,7 @@ func (f *CommitMessageFormat) PatternMapString() string {
return fmt.Sprintf("\n%s", strings.Join(arr, "\n"))
}
// FilterTypeString ...
// FilterTypesString ...
func (f *CommitMessageFormat) FilterTypesString() string {
if len(f.typeSamples) == 0 {
return " []"

View file

@ -127,7 +127,11 @@ func (e *commitExtractor) commitGroupTitle(commit *Commit) (string, string) {
return raw, ttl
}
func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) {
func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) { //nolint:gocyclo
// NOTE(khos2ow): this function is over our cyclomatic complexity goal.
// Be wary when adding branches, and look for functionality that could
// be reasonably moved into an injected dependency.
order := make(map[string]int)
if e.opts.CommitGroupSortBy == "Custom" {
for i, t := range e.opts.CommitGroupTitleOrder {
@ -136,6 +140,9 @@ func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) {
}
// groups
// TODO(khos2ow): move the inline sort function to
// conceret implementation of sort.Interface in order
// to reduce cyclomatic complaxity.
sort.Slice(groups, func(i, j int) bool {
if e.opts.CommitGroupSortBy == "Custom" {
return order[groups[i].RawTitle] < order[groups[j].RawTitle]
@ -165,6 +172,11 @@ func (e *commitExtractor) sortCommitGroups(groups []*CommitGroup) {
// commits
for _, group := range groups {
group := group // pin group to avoid potential bugs with passing group to lower functions
// TODO(khos2ow): move the inline sort function to
// conceret implementation of sort.Interface in order
// to reduce cyclomatic complaxity.
sort.Slice(group.Commits, func(i, j int) bool {
var (
a, b interface{}
@ -198,6 +210,7 @@ func (e *commitExtractor) sortNoteGroups(groups []*NoteGroup) {
// notes
for _, group := range groups {
group := group // pin group to avoid potential bugs with passing group to lower functions
sort.Slice(group.Notes, func(i, j int) bool {
return strings.ToLower(group.Notes[i].Title) < strings.ToLower(group.Notes[j].Title)
})

View file

@ -20,14 +20,14 @@ func TestCommitExtractor(t *testing.T) {
fixtures := []*Commit{
// [0]
&Commit{
{
Type: "foo",
Scope: "c",
Header: "1",
Notes: []*Note{},
},
// [1]
&Commit{
{
Type: "foo",
Scope: "b",
Header: "2",
@ -37,7 +37,7 @@ func TestCommitExtractor(t *testing.T) {
},
},
// [2]
&Commit{
{
Type: "bar",
Scope: "d",
Header: "3",
@ -47,7 +47,7 @@ func TestCommitExtractor(t *testing.T) {
},
},
// [3]
&Commit{
{
Type: "foo",
Scope: "a",
Header: "4",
@ -56,7 +56,7 @@ func TestCommitExtractor(t *testing.T) {
},
},
// [4]
&Commit{
{
Type: "",
Scope: "",
Header: "Merge1",
@ -67,7 +67,7 @@ func TestCommitExtractor(t *testing.T) {
},
},
// [5]
&Commit{
{
Type: "",
Scope: "",
Header: "Revert1",
@ -81,14 +81,14 @@ func TestCommitExtractor(t *testing.T) {
commitGroups, mergeCommits, revertCommits, noteGroups := extractor.Extract(fixtures)
assert.Equal([]*CommitGroup{
&CommitGroup{
{
RawTitle: "bar",
Title: "BAR",
Commits: []*Commit{
fixtures[2],
},
},
&CommitGroup{
{
RawTitle: "foo",
Title: "Foo",
Commits: []*Commit{
@ -108,26 +108,26 @@ func TestCommitExtractor(t *testing.T) {
}, revertCommits)
assert.Equal([]*NoteGroup{
&NoteGroup{
{
Title: "note1-title",
Notes: []*Note{
fixtures[1].Notes[0],
fixtures[2].Notes[0],
},
},
&NoteGroup{
{
Title: "note2-title",
Notes: []*Note{
fixtures[1].Notes[1],
},
},
&NoteGroup{
{
Title: "note3-title",
Notes: []*Note{
fixtures[2].Notes[1],
},
},
&NoteGroup{
{
Title: "note4-title",
Notes: []*Note{
fixtures[3].Notes[0],

View file

@ -4,7 +4,11 @@ import (
"strings"
)
func commitFilter(commits []*Commit, filters map[string][]string, noCaseSensitive bool) []*Commit {
func commitFilter(commits []*Commit, filters map[string][]string, noCaseSensitive bool) []*Commit { //nolint:gocyclo
// NOTE(khos2ow): this function is over our cyclomatic complexity goal.
// Be wary when adding branches, and look for functionality that could
// be reasonably moved into an injected dependency.
res := []*Commit{}
for _, commit := range commits {

View file

@ -18,27 +18,27 @@ func TestCommitFilter(t *testing.T) {
}
fixtures := []*Commit{
&Commit{
{
Type: "foo",
Scope: "hoge",
Subject: "1",
},
&Commit{
{
Type: "foo",
Scope: "fuga",
Subject: "2",
},
&Commit{
{
Type: "bar",
Scope: "hoge",
Subject: "3",
},
&Commit{
{
Type: "bar",
Scope: "fuga",
Subject: "4",
},
&Commit{
{
Type: "Bar",
Scope: "hogera",
Subject: "5",

View file

@ -80,7 +80,7 @@ func newCommitParser(logger *Logger, client gitcmd.Client, jiraClient JiraClient
reRef: regexp.MustCompile("(?i)(" + joinedRefActions + ")\\s?([\\w/\\.\\-]+)?(?:" + joinedIssuePrefix + ")(\\d+)"),
reIssue: regexp.MustCompile("(?:" + joinedIssuePrefix + ")(\\d+)"),
reNotes: regexp.MustCompile("^(?i)\\s*(" + joinedNoteKeywords + ")[:\\s]+(.*)"),
reMention: regexp.MustCompile("@([\\w-]+)"),
reMention: regexp.MustCompile(`@([\w-]+)`),
reJiraIssueDescription: regexp.MustCompile(opts.JiraIssueDescriptionPattern),
}
}
@ -223,8 +223,8 @@ func (p *commitParser) processHeader(commit *Commit, input string) {
commit.Mentions = p.parseMentions(input)
// Jira
if commit.JiraIssueId != "" {
p.processJiraIssue(commit, commit.JiraIssueId)
if commit.JiraIssueID != "" {
p.processJiraIssue(commit, commit.JiraIssueID)
}
}
@ -364,10 +364,10 @@ func (p *commitParser) uniqMentions(mentions []string) []string {
return arr
}
func (p *commitParser) processJiraIssue(commit *Commit, issueId string) {
issue, err := p.jiraClient.GetJiraIssue(commit.JiraIssueId)
func (p *commitParser) processJiraIssue(commit *Commit, issueID string) {
issue, err := p.jiraClient.GetJiraIssue(commit.JiraIssueID)
if err != nil {
p.logger.Error(fmt.Sprintf("Failed to parse Jira story %s: %s\n", issueId, err))
p.logger.Error(fmt.Sprintf("Failed to parse Jira story %s: %s\n", issueID, err))
return
}
commit.Type = p.config.Options.JiraTypeMaps[issue.Fields.Type.Name]

View file

@ -33,7 +33,7 @@ func TestCommitParserParse(t *testing.T) {
mock, nil, &Config{
Options: &Options{
CommitFilters: map[string][]string{
"Type": []string{
"Type": {
"feat",
"fix",
"perf",
@ -79,7 +79,7 @@ func TestCommitParserParse(t *testing.T) {
commits, err := parser.Parse("HEAD")
assert.Nil(err)
assert.Equal([]*Commit{
&Commit{
{
Hash: &Hash{
Long: "65cf1add9735dcc4810dda3312b0792236c97c4e",
Short: "65cf1add",
@ -97,7 +97,7 @@ func TestCommitParserParse(t *testing.T) {
Merge: nil,
Revert: nil,
Refs: []*Ref{
&Ref{
{
Action: "",
Ref: "123",
Source: "",
@ -111,7 +111,7 @@ func TestCommitParserParse(t *testing.T) {
Subject: "Add new feature #123",
Body: "",
},
&Commit{
{
Hash: &Hash{
Long: "14ef0b6d386c5432af9292eab3c8314fa3001bc7",
Short: "14ef0b6d",
@ -132,24 +132,24 @@ func TestCommitParserParse(t *testing.T) {
},
Revert: nil,
Refs: []*Ref{
&Ref{
{
Action: "",
Ref: "3",
Source: "",
},
&Ref{
{
Action: "Fixes",
Ref: "3",
Source: "",
},
&Ref{
{
Action: "Closes",
Ref: "1",
Source: "",
},
},
Notes: []*Note{
&Note{
{
Title: "BREAKING CHANGE",
Body: "This is breaking point message.",
},
@ -167,7 +167,7 @@ Closes #1
BREAKING CHANGE: This is breaking point message.`,
},
&Commit{
{
Hash: &Hash{
Long: "809a8280ffd0dadb0f4e7ba9fc835e63c37d6af6",
Short: "809a8280",
@ -201,7 +201,7 @@ BREAKING CHANGE: This is breaking point message.`,
@hogefuga
@FooBarBaz`,
},
&Commit{
{
Hash: &Hash{
Long: "74824d6bd1470b901ec7123d13a76a1b8938d8d0",
Short: "74824d6b",
@ -219,19 +219,19 @@ BREAKING CHANGE: This is breaking point message.`,
Merge: nil,
Revert: nil,
Refs: []*Ref{
&Ref{
{
Action: "Fixes",
Ref: "123",
Source: "",
},
&Ref{
{
Action: "Closes",
Ref: "456",
Source: "username/repository",
},
},
Notes: []*Note{
&Note{
{
Title: "BREAKING CHANGE",
Body: fmt.Sprintf(`This is multiline breaking change note.
It is treated as the body of the Note until a mention or reference appears.
@ -281,7 +281,7 @@ class MyController extends Controller {
Fixes #123
Closes username/repository#456`, "```", "```"),
},
&Commit{
{
Hash: &Hash{
Long: "123456789735dcc4810dda3312b0792236c97c4e",
Short: "12345678",
@ -381,7 +381,7 @@ func TestCommitParserParseWithJira(t *testing.T) {
mock, mockJiraClient{}, &Config{
Options: &Options{
CommitFilters: map[string][]string{
"Type": []string{
"Type": {
"feat",
"fix",
"perf",
@ -391,7 +391,7 @@ func TestCommitParserParseWithJira(t *testing.T) {
HeaderPattern: "^(?:(\\w*)|(?:\\[(.*)\\])?)\\:\\s(.*)$",
HeaderPatternMaps: []string{
"Type",
"JiraIssueId",
"JiraIssueID",
"Subject",
},
JiraTypeMaps: map[string]string{
@ -403,7 +403,7 @@ func TestCommitParserParseWithJira(t *testing.T) {
commits, err := parser.Parse("HEAD")
assert.Nil(err)
commit := commits[0]
assert.Equal(commit.JiraIssueId, "JIRA-1111")
assert.Equal(commit.JiraIssueID, "JIRA-1111")
assert.Equal(commit.JiraIssue.Type, "Story")
assert.Equal(commit.JiraIssue.Summary, "summary of JIRA-1111")
assert.Equal(commit.JiraIssue.Description, "description of JIRA-1111")

View file

@ -19,7 +19,7 @@ func Example() {
},
Options: &Options{
CommitFilters: map[string][]string{
"Type": []string{
"Type": {
"feat",
"fix",
},

View file

@ -52,7 +52,7 @@ type NoteGroup struct {
Notes []*Note
}
// JiraIssue
// JiraIssue is information about a jira ticket (type, summary, description, and labels)
type JiraIssue struct {
Type string
Summary string
@ -69,13 +69,13 @@ type Commit struct {
Revert *Revert // If it is not a revert commit, `nil` is assigned
Refs []*Ref
Notes []*Note
Mentions []string // Name of the user included in the commit header or body
Mentions []string // Name of the user included in the commit header or body
JiraIssue *JiraIssue // If no issue id found in header, `nil` is assigned
Header string // (e.g. `feat(core)[RNWY-310]: Add new feature`)
Type string // (e.g. `feat`)
Scope string // (e.g. `core`)
Subject string // (e.g. `Add new feature`)
JiraIssueId string // (e.g. `RNWY-310`)
Header string // (e.g. `feat(core)[RNWY-310]: Add new feature`)
Type string // (e.g. `feat`)
Scope string // (e.g. `core`)
Subject string // (e.g. `Add new feature`)
JiraIssueID string // (e.g. `RNWY-310`)
Body string
}

View file

@ -4,6 +4,7 @@ import (
agjira "github.com/andygrunwald/go-jira"
)
// JiraClient is an HTTP client for Jira
type JiraClient interface {
GetJiraIssue(id string) (*agjira.Issue, error)
}
@ -14,11 +15,12 @@ type jiraClient struct {
url string
}
// NewJiraClient returns an instance of JiraClient
func NewJiraClient(config *Config) JiraClient {
return jiraClient{
username: config.Options.JiraUsername,
token: config.Options.JiraToken,
url: config.Options.JiraUrl,
token: config.Options.JiraToken,
url: config.Options.JiraURL,
}
}

View file

@ -1,14 +1,15 @@
package chglog
import (
"github.com/stretchr/testify/assert"
"testing"
"github.com/stretchr/testify/assert"
)
func TestJira(t *testing.T) {
assert := assert.New(t)
config := &Config {
config := &Config{
Options: &Options{
Processor: nil,
NextTag: "",
@ -29,7 +30,7 @@ func TestJira(t *testing.T) {
NoteKeywords: nil,
JiraUsername: "uuu",
JiraToken: "ppp",
JiraUrl: "http://jira.com",
JiraURL: "http://jira.com",
JiraTypeMaps: nil,
JiraIssueDescriptionPattern: "",
},

View file

@ -7,7 +7,7 @@ import (
"regexp"
"github.com/fatih/color"
emoji "github.com/kyokomi/emoji/v2"
"github.com/kyokomi/emoji/v2"
)
// Logger ...
@ -26,7 +26,7 @@ func NewLogger(stdout, stderr io.Writer, silent, noEmoji bool) *Logger {
stderr: stderr,
silent: silent,
noEmoji: noEmoji,
reEmoji: regexp.MustCompile(":[\\w\\+_\\-]+:\\s?"),
reEmoji: regexp.MustCompile(`:[\w\+_\-]+:\s?`),
}
}

View file

@ -6,8 +6,8 @@ import (
"testing"
"github.com/fatih/color"
"github.com/stretchr/testify/assert"
emoji "github.com/kyokomi/emoji/v2"
"github.com/stretchr/testify/assert"
)
func TestLoggerLogSilent(t *testing.T) {
@ -40,7 +40,7 @@ func TestLoggerLogNoEmoji(t *testing.T) {
stderr := &bytes.Buffer{}
logger := NewLogger(stdout, stderr, false, true)
logger.Log(":+1:Hello, World! :)")
assert.Equal(fmt.Sprint("Hello, World! :)\n"), stdout.String())
assert.Equal(fmt.Sprint("Hello, World! :)\n"), stdout.String()) //nolint:gosimple
}
func TestLoggerError(t *testing.T) {

View file

@ -33,8 +33,8 @@ func (p *GitHubProcessor) Bootstrap(config *Config) {
p.Host = strings.TrimRight(p.Host, "/")
}
p.reMention = regexp.MustCompile("@(\\w+)")
p.reIssue = regexp.MustCompile("(?i)(#|gh-)(\\d+)")
p.reMention = regexp.MustCompile(`@(\w+)`)
p.reIssue = regexp.MustCompile(`(?i)(#|gh-)(\d+)`)
}
// ProcessCommit ...
@ -88,8 +88,8 @@ func (p *GitLabProcessor) Bootstrap(config *Config) {
p.Host = strings.TrimRight(p.Host, "/")
}
p.reMention = regexp.MustCompile("@(\\w+)")
p.reIssue = regexp.MustCompile("(?i)#(\\d+)")
p.reMention = regexp.MustCompile(`@(\w+)`)
p.reIssue = regexp.MustCompile(`(?i)#(\d+)`)
}
// ProcessCommit ...
@ -143,8 +143,8 @@ func (p *BitbucketProcessor) Bootstrap(config *Config) {
p.Host = strings.TrimRight(p.Host, "/")
}
p.reMention = regexp.MustCompile("@(\\w+)")
p.reIssue = regexp.MustCompile("(?i)#(\\d+)")
p.reMention = regexp.MustCompile(`@(\w+)`)
p.reIssue = regexp.MustCompile(`(?i)#(\d+)`)
}
// ProcessCommit ...

View file

@ -27,7 +27,7 @@ func TestGitHubProcessor(t *testing.T) {
multiline [#789](https://example.com/issues/789)
[@foo](https://github.com/foo), [@bar](https://github.com/bar)`,
Notes: []*Note{
&Note{
{
Body: `issue1 [#11](https://example.com/issues/11)
issue2 [#22](https://example.com/issues/22)
[gh-56](https://example.com/issues/56) hoge fuga`,
@ -42,7 +42,7 @@ issue2 [#22](https://example.com/issues/22)
multiline #789
@foo, @bar`,
Notes: []*Note{
&Note{
{
Body: `issue1 #11
issue2 #22
gh-56 hoge fuga`,
@ -89,7 +89,7 @@ func TestGitLabProcessor(t *testing.T) {
multiline [#789](https://example.com/issues/789)
[@foo](https://gitlab.com/foo), [@bar](https://gitlab.com/bar)`,
Notes: []*Note{
&Note{
{
Body: `issue1 [#11](https://example.com/issues/11)
issue2 [#22](https://example.com/issues/22)
gh-56 hoge fuga`,
@ -104,7 +104,7 @@ gh-56 hoge fuga`,
multiline #789
@foo, @bar`,
Notes: []*Note{
&Note{
{
Body: `issue1 #11
issue2 #22
gh-56 hoge fuga`,
@ -151,7 +151,7 @@ func TestBitbucketProcessor(t *testing.T) {
multiline [#789](https://example.com/issues/789/)
[@foo](https://bitbucket.org/foo/), [@bar](https://bitbucket.org/bar/)`,
Notes: []*Note{
&Note{
{
Body: `issue1 [#11](https://example.com/issues/11/)
issue2 [#22](https://example.com/issues/22/)
gh-56 hoge fuga`,
@ -166,7 +166,7 @@ gh-56 hoge fuga`,
multiline #789
@foo, @bar`,
Notes: []*Note{
&Note{
{
Body: `issue1 #11
issue2 #22
gh-56 hoge fuga`,

View file

@ -12,7 +12,6 @@ import (
type tagReader struct {
client gitcmd.Client
format string
separator string
reFilter *regexp.Regexp
}
@ -36,7 +35,7 @@ func (r *tagReader) ReadAll() ([]*Tag, error) {
tags := []*Tag{}
if err != nil {
return tags, fmt.Errorf("failed to get git-tag: %s", err.Error())
return tags, fmt.Errorf("failed to get git-tag: %w", err)
}
lines := strings.Split(out, "\n")

View file

@ -33,7 +33,7 @@ func TestTagReader(t *testing.T) {
assert.Equal(
[]*Tag{
&Tag{
{
Name: "hoge_fuga",
Subject: "Invalid semver tag name",
Date: time.Date(2018, 3, 12, 12, 30, 10, 0, time.UTC),
@ -44,7 +44,7 @@ func TestTagReader(t *testing.T) {
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
},
},
&Tag{
{
Name: "5.0.0-rc.0",
Subject: "Release 5.0.0-rc.0",
Date: time.Date(2018, 2, 3, 12, 30, 10, 0, time.UTC),
@ -59,7 +59,7 @@ func TestTagReader(t *testing.T) {
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
},
},
&Tag{
{
Name: "4.4.4",
Subject: "Release 4.4.4",
Date: time.Date(2018, 2, 2, 10, 0, 40, 0, time.UTC),
@ -74,7 +74,7 @@ func TestTagReader(t *testing.T) {
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
},
},
&Tag{
{
Name: "4.4.3",
Subject: "This is tag subject",
Date: time.Date(2018, 2, 2, 0, 0, 0, 0, time.UTC),
@ -89,7 +89,7 @@ func TestTagReader(t *testing.T) {
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
},
},
&Tag{
{
Name: "v2.0.4-beta.1",
Subject: "Release v2.0.4-beta.1",
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
@ -104,11 +104,11 @@ func TestTagReader(t *testing.T) {
actual,
)
actual_filtered, err_filtered := newTagReader(client, "^v").ReadAll()
assert.Nil(err_filtered)
actualFiltered, errFiltered := newTagReader(client, "^v").ReadAll()
assert.Nil(errFiltered)
assert.Equal(
[]*Tag{
&Tag{
{
Name: "v2.0.4-beta.1",
Subject: "Release v2.0.4-beta.1",
Date: time.Date(2018, 2, 1, 0, 0, 0, 0, time.UTC),
@ -116,6 +116,6 @@ func TestTagReader(t *testing.T) {
Previous: nil,
},
},
actual_filtered,
actualFiltered,
)
}

View file

@ -19,14 +19,16 @@ func (s *tagSelector) Select(tags []*Tag, query string) ([]*Tag, string, error)
case 2:
old := tokens[0]
new := tokens[1]
if old == "" && new == "" {
switch {
case old == "" && new == "":
return nil, "", nil
} else if old == "" {
case old == "":
return s.selectBeforeTags(tags, new)
} else if new == "" {
case new == "":
return s.selectAfterTags(tags, old)
default:
return s.selectRangeTags(tags, tokens[0], tokens[1])
}
return s.selectRangeTags(tags, tokens[0], tokens[1])
}
return nil, "", errFailedQueryParse
@ -76,7 +78,8 @@ func (*tagSelector) selectBeforeTags(tags []*Tag, token string) ([]*Tag, string,
}
func (*tagSelector) selectAfterTags(tags []*Tag, token string) ([]*Tag, string, error) {
var (
// NOTE(clok): the res slice can range in size based on the token passed in.
var ( //nolint:prealloc
res []*Tag
from string
)

View file

@ -12,51 +12,51 @@ func TestTagSelector(t *testing.T) {
selector := newTagSelector()
fixtures := []*Tag{
&Tag{Name: "2.2.12-rc.12"},
&Tag{Name: "2.1.0"},
&Tag{Name: "v2.0.0-beta.1"},
&Tag{Name: "v1.2.9"},
&Tag{Name: "v1.0.0"},
{Name: "2.2.12-rc.12"},
{Name: "2.1.0"},
{Name: "v2.0.0-beta.1"},
{Name: "v1.2.9"},
{Name: "v1.0.0"},
}
table := map[string][]string{
// Single
"2.2.12-rc.12": []string{
"2.2.12-rc.12": {
"2.2.12-rc.12",
"2.1.0",
},
"v2.0.0-beta.1": []string{
"v2.0.0-beta.1": {
"v2.0.0-beta.1",
"v1.2.9",
},
"v1.0.0": []string{
"v1.0.0": {
"v1.0.0",
"",
},
// ~ <tag>
"..2.1.0": []string{
"..2.1.0": {
"2.1.0",
"v2.0.0-beta.1",
"v1.2.9",
"v1.0.0",
"",
},
"..v1.0.0": []string{
"..v1.0.0": {
"v1.0.0",
"",
},
// <tag> ~
"v2.0.0-beta.1..": []string{
"v2.0.0-beta.1..": {
"2.2.12-rc.12",
"2.1.0",
"v2.0.0-beta.1",
"v1.2.9",
},
"2.2.12-rc.12..": []string{
"2.2.12-rc.12..": {
"2.2.12-rc.12",
"2.1.0",
},
"v1.0.0..": []string{
"v1.0.0..": {
"2.2.12-rc.12",
"2.1.0",
"v2.0.0-beta.1",
@ -65,7 +65,7 @@ func TestTagSelector(t *testing.T) {
"",
},
// <tag> ~ <tag>
"v1.0.0..2.2.12-rc.12": []string{
"v1.0.0..2.2.12-rc.12": {
"2.2.12-rc.12",
"2.1.0",
"v2.0.0-beta.1",
@ -73,13 +73,13 @@ func TestTagSelector(t *testing.T) {
"v1.0.0",
"",
},
"v1.0.0..v2.0.0-beta.1": []string{
"v1.0.0..v2.0.0-beta.1": {
"v2.0.0-beta.1",
"v1.2.9",
"v1.0.0",
"",
},
"v1.2.9..2.1.0": []string{
"v1.2.9..2.1.0": {
"2.1.0",
"v2.0.0-beta.1",
"v1.2.9",