mirror of
https://github.com/scratchfoundation/golangci-lint.git
synced 2025-07-30 08:00:36 -04:00
output: add colored-tab (#3729)
This commit is contained in:
parent
9c46d7d7c0
commit
00d17cc8d1
13 changed files with 157 additions and 39 deletions
|
@ -76,7 +76,7 @@ run:
|
||||||
|
|
||||||
# output configuration options
|
# output configuration options
|
||||||
output:
|
output:
|
||||||
# Format: colored-line-number|line-number|json|tab|checkstyle|code-climate|junit-xml|github-actions|teamcity
|
# Format: colored-line-number|line-number|json|colored-tab|tab|checkstyle|code-climate|junit-xml|github-actions|teamcity
|
||||||
#
|
#
|
||||||
# Multiple can be specified by separating them by comma, output can be provided
|
# Multiple can be specified by separating them by comma, output can be provided
|
||||||
# for each of them by separating format name and path by colon symbol.
|
# for each of them by separating format name and path by colon symbol.
|
||||||
|
|
|
@ -10,7 +10,7 @@ linters-settings:
|
||||||
dupl:
|
dupl:
|
||||||
threshold: 100
|
threshold: 100
|
||||||
funlen:
|
funlen:
|
||||||
lines: 100
|
lines: -1 # the number of lines (code + empty lines) is not a right metric and leads to code without empty line or one-liner.
|
||||||
statements: 50
|
statements: 50
|
||||||
goconst:
|
goconst:
|
||||||
min-len: 2
|
min-len: 2
|
||||||
|
|
|
@ -475,8 +475,10 @@ func (e *Executor) createPrinter(format string, w io.Writer) (printers.Printer,
|
||||||
p = printers.NewText(e.cfg.Output.PrintIssuedLine,
|
p = printers.NewText(e.cfg.Output.PrintIssuedLine,
|
||||||
format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName,
|
format == config.OutFormatColoredLineNumber, e.cfg.Output.PrintLinterName,
|
||||||
e.log.Child(logutils.DebugKeyTextPrinter), w)
|
e.log.Child(logutils.DebugKeyTextPrinter), w)
|
||||||
case config.OutFormatTab:
|
case config.OutFormatTab, config.OutFormatColoredTab:
|
||||||
p = printers.NewTab(e.cfg.Output.PrintLinterName, e.log.Child(logutils.DebugKeyTabPrinter), w)
|
p = printers.NewTab(e.cfg.Output.PrintLinterName,
|
||||||
|
format == config.OutFormatColoredTab,
|
||||||
|
e.log.Child(logutils.DebugKeyTabPrinter), w)
|
||||||
case config.OutFormatCheckstyle:
|
case config.OutFormatCheckstyle:
|
||||||
p = printers.NewCheckstyle(w)
|
p = printers.NewCheckstyle(w)
|
||||||
case config.OutFormatCodeClimate:
|
case config.OutFormatCodeClimate:
|
||||||
|
|
|
@ -5,6 +5,7 @@ const (
|
||||||
OutFormatLineNumber = "line-number"
|
OutFormatLineNumber = "line-number"
|
||||||
OutFormatColoredLineNumber = "colored-line-number"
|
OutFormatColoredLineNumber = "colored-line-number"
|
||||||
OutFormatTab = "tab"
|
OutFormatTab = "tab"
|
||||||
|
OutFormatColoredTab = "colored-tab"
|
||||||
OutFormatCheckstyle = "checkstyle"
|
OutFormatCheckstyle = "checkstyle"
|
||||||
OutFormatCodeClimate = "code-climate"
|
OutFormatCodeClimate = "code-climate"
|
||||||
OutFormatHTML = "html"
|
OutFormatHTML = "html"
|
||||||
|
@ -28,11 +29,13 @@ var OutFormats = []string{
|
||||||
|
|
||||||
type Output struct {
|
type Output struct {
|
||||||
Format string
|
Format string
|
||||||
Color string
|
|
||||||
PrintIssuedLine bool `mapstructure:"print-issued-lines"`
|
PrintIssuedLine bool `mapstructure:"print-issued-lines"`
|
||||||
PrintLinterName bool `mapstructure:"print-linter-name"`
|
PrintLinterName bool `mapstructure:"print-linter-name"`
|
||||||
UniqByLine bool `mapstructure:"uniq-by-line"`
|
UniqByLine bool `mapstructure:"uniq-by-line"`
|
||||||
SortResults bool `mapstructure:"sort-results"`
|
SortResults bool `mapstructure:"sort-results"`
|
||||||
PrintWelcomeMessage bool `mapstructure:"print-welcome"`
|
PrintWelcomeMessage bool `mapstructure:"print-welcome"`
|
||||||
PathPrefix string `mapstructure:"path-prefix"`
|
PathPrefix string `mapstructure:"path-prefix"`
|
||||||
|
|
||||||
|
// only work with CLI flags because the setup of logs is done before the config file parsing.
|
||||||
|
Color string
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import (
|
||||||
"github.com/golangci/golangci-lint/pkg/result"
|
"github.com/golangci/golangci-lint/pkg/result"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen
|
|
||||||
func TestLinter_Run(t *testing.T) {
|
func TestLinter_Run(t *testing.T) {
|
||||||
type issueWithReplacement struct {
|
type issueWithReplacement struct {
|
||||||
issue string
|
issue string
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen
|
|
||||||
func TestGetEnabledLintersSet(t *testing.T) {
|
func TestGetEnabledLintersSet(t *testing.T) {
|
||||||
type cs struct {
|
type cs struct {
|
||||||
cfg config.Linters
|
cfg config.Linters
|
||||||
|
|
|
@ -14,13 +14,16 @@ import (
|
||||||
|
|
||||||
type Tab struct {
|
type Tab struct {
|
||||||
printLinterName bool
|
printLinterName bool
|
||||||
log logutils.Log
|
useColors bool
|
||||||
w io.Writer
|
|
||||||
|
log logutils.Log
|
||||||
|
w io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTab(printLinterName bool, log logutils.Log, w io.Writer) *Tab {
|
func NewTab(printLinterName, useColors bool, log logutils.Log, w io.Writer) *Tab {
|
||||||
return &Tab{
|
return &Tab{
|
||||||
printLinterName: printLinterName,
|
printLinterName: printLinterName,
|
||||||
|
useColors: useColors,
|
||||||
log: log,
|
log: log,
|
||||||
w: w,
|
w: w,
|
||||||
}
|
}
|
||||||
|
@ -28,6 +31,11 @@ func NewTab(printLinterName bool, log logutils.Log, w io.Writer) *Tab {
|
||||||
|
|
||||||
func (p *Tab) SprintfColored(ca color.Attribute, format string, args ...any) string {
|
func (p *Tab) SprintfColored(ca color.Attribute, format string, args ...any) string {
|
||||||
c := color.New(ca)
|
c := color.New(ca)
|
||||||
|
|
||||||
|
if !p.useColors {
|
||||||
|
c.DisableColor()
|
||||||
|
}
|
||||||
|
|
||||||
return c.Sprintf(format, args...)
|
return c.Sprintf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -14,6 +15,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTab_Print(t *testing.T) {
|
func TestTab_Print(t *testing.T) {
|
||||||
|
// force color globally
|
||||||
|
backup := color.NoColor
|
||||||
|
t.Cleanup(func() {
|
||||||
|
color.NoColor = backup
|
||||||
|
})
|
||||||
|
color.NoColor = false
|
||||||
|
|
||||||
issues := []result.Issue{
|
issues := []result.Issue{
|
||||||
{
|
{
|
||||||
FromLinter: "linter-a",
|
FromLinter: "linter-a",
|
||||||
|
@ -44,16 +52,50 @@ func TestTab_Print(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
printer := NewTab(true, logutils.NewStderrLog(logutils.DebugKeyEmpty), buf)
|
printLinterName bool
|
||||||
|
useColors bool
|
||||||
err := printer.Print(context.Background(), issues)
|
expected string
|
||||||
require.NoError(t, err)
|
}{
|
||||||
|
{
|
||||||
expected := `path/to/filea.go:10:4 linter-a some issue
|
desc: "with linter name",
|
||||||
|
printLinterName: true,
|
||||||
|
useColors: false,
|
||||||
|
expected: `path/to/filea.go:10:4 linter-a some issue
|
||||||
path/to/fileb.go:300:9 linter-b another issue
|
path/to/fileb.go:300:9 linter-b another issue
|
||||||
`
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "disable all options",
|
||||||
|
printLinterName: false,
|
||||||
|
useColors: false,
|
||||||
|
expected: `path/to/filea.go:10:4 some issue
|
||||||
|
path/to/fileb.go:300:9 another issue
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "enable all options",
|
||||||
|
printLinterName: true,
|
||||||
|
useColors: true,
|
||||||
|
//nolint:lll // color characters must be in a simple string.
|
||||||
|
expected: "\x1b[1mpath/to/filea.go:10\x1b[0m:4 linter-a \x1b[31msome issue\x1b[0m\n\x1b[1mpath/to/fileb.go:300\x1b[0m:9 linter-b \x1b[31manother issue\x1b[0m\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
assert.Equal(t, expected, buf.String())
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
printer := NewTab(test.printLinterName, test.useColors, logutils.NewStderrLog(logutils.DebugKeyEmpty), buf)
|
||||||
|
|
||||||
|
err := printer.Print(context.Background(), issues)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, buf.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ import (
|
||||||
|
|
||||||
type Text struct {
|
type Text struct {
|
||||||
printIssuedLine bool
|
printIssuedLine bool
|
||||||
useColors bool
|
|
||||||
printLinterName bool
|
printLinterName bool
|
||||||
|
useColors bool
|
||||||
|
|
||||||
log logutils.Log
|
log logutils.Log
|
||||||
w io.Writer
|
w io.Writer
|
||||||
|
@ -24,19 +24,20 @@ type Text struct {
|
||||||
func NewText(printIssuedLine, useColors, printLinterName bool, log logutils.Log, w io.Writer) *Text {
|
func NewText(printIssuedLine, useColors, printLinterName bool, log logutils.Log, w io.Writer) *Text {
|
||||||
return &Text{
|
return &Text{
|
||||||
printIssuedLine: printIssuedLine,
|
printIssuedLine: printIssuedLine,
|
||||||
useColors: useColors,
|
|
||||||
printLinterName: printLinterName,
|
printLinterName: printLinterName,
|
||||||
|
useColors: useColors,
|
||||||
log: log,
|
log: log,
|
||||||
w: w,
|
w: w,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *Text) SprintfColored(ca color.Attribute, format string, args ...any) string {
|
func (p *Text) SprintfColored(ca color.Attribute, format string, args ...any) string {
|
||||||
|
c := color.New(ca)
|
||||||
|
|
||||||
if !p.useColors {
|
if !p.useColors {
|
||||||
return fmt.Sprintf(format, args...)
|
c.DisableColor()
|
||||||
}
|
}
|
||||||
|
|
||||||
c := color.New(ca)
|
|
||||||
return c.Sprintf(format, args...)
|
return c.Sprintf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ func (p *Text) printSourceCode(i *result.Issue) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p Text) printUnderLinePointer(i *result.Issue) {
|
func (p *Text) printUnderLinePointer(i *result.Issue) {
|
||||||
// if column == 0 it means column is unknown (e.g. for gosec)
|
// if column == 0 it means column is unknown (e.g. for gosec)
|
||||||
if len(i.SourceLines) != 1 || i.Pos.Column == 0 {
|
if len(i.SourceLines) != 1 || i.Pos.Column == 0 {
|
||||||
return
|
return
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/fatih/color"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
@ -14,6 +15,13 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestText_Print(t *testing.T) {
|
func TestText_Print(t *testing.T) {
|
||||||
|
// force color globally
|
||||||
|
backup := color.NoColor
|
||||||
|
t.Cleanup(func() {
|
||||||
|
color.NoColor = backup
|
||||||
|
})
|
||||||
|
color.NoColor = false
|
||||||
|
|
||||||
issues := []result.Issue{
|
issues := []result.Issue{
|
||||||
{
|
{
|
||||||
FromLinter: "linter-a",
|
FromLinter: "linter-a",
|
||||||
|
@ -44,19 +52,78 @@ func TestText_Print(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
printer := NewText(true, false, true, logutils.NewStderrLog(logutils.DebugKeyEmpty), buf)
|
printIssuedLine bool
|
||||||
|
printLinterName bool
|
||||||
err := printer.Print(context.Background(), issues)
|
useColors bool
|
||||||
require.NoError(t, err)
|
expected string
|
||||||
|
}{
|
||||||
expected := `path/to/filea.go:10:4: some issue (linter-a)
|
{
|
||||||
|
desc: "printIssuedLine and printLinterName",
|
||||||
|
printIssuedLine: true,
|
||||||
|
printLinterName: true,
|
||||||
|
useColors: false,
|
||||||
|
expected: `path/to/filea.go:10:4: some issue (linter-a)
|
||||||
path/to/fileb.go:300:9: another issue (linter-b)
|
path/to/fileb.go:300:9: another issue (linter-b)
|
||||||
func foo() {
|
func foo() {
|
||||||
fmt.Println("bar")
|
fmt.Println("bar")
|
||||||
}
|
}
|
||||||
`
|
`,
|
||||||
|
},
|
||||||
assert.Equal(t, expected, buf.String())
|
{
|
||||||
|
desc: "printLinterName only",
|
||||||
|
printIssuedLine: false,
|
||||||
|
printLinterName: true,
|
||||||
|
useColors: false,
|
||||||
|
expected: `path/to/filea.go:10:4: some issue (linter-a)
|
||||||
|
path/to/fileb.go:300:9: another issue (linter-b)
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "printIssuedLine only",
|
||||||
|
printIssuedLine: true,
|
||||||
|
printLinterName: false,
|
||||||
|
useColors: false,
|
||||||
|
expected: `path/to/filea.go:10:4: some issue
|
||||||
|
path/to/fileb.go:300:9: another issue
|
||||||
|
func foo() {
|
||||||
|
fmt.Println("bar")
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "enable all options",
|
||||||
|
printIssuedLine: true,
|
||||||
|
printLinterName: true,
|
||||||
|
useColors: true,
|
||||||
|
//nolint:lll // color characters must be in a simple string.
|
||||||
|
expected: "\x1b[1mpath/to/filea.go:10\x1b[0m:4: \x1b[31msome issue\x1b[0m (linter-a)\n\x1b[1mpath/to/fileb.go:300\x1b[0m:9: \x1b[31manother issue\x1b[0m (linter-b)\nfunc foo() {\n\tfmt.Println(\"bar\")\n}\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "disable all options",
|
||||||
|
printIssuedLine: false,
|
||||||
|
printLinterName: false,
|
||||||
|
useColors: false,
|
||||||
|
expected: `path/to/filea.go:10:4: some issue
|
||||||
|
path/to/fileb.go:300:9: another issue
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testCases {
|
||||||
|
test := test
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
|
||||||
|
printer := NewText(test.printIssuedLine, test.useColors, test.printLinterName, logutils.NewStderrLog(logutils.DebugKeyEmpty), buf)
|
||||||
|
|
||||||
|
err := printer.Print(context.Background(), issues)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
assert.Equal(t, test.expected, buf.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import (
|
||||||
"github.com/golangci/golangci-lint/test/testshared"
|
"github.com/golangci/golangci-lint/test/testshared"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen
|
|
||||||
func TestEnabledLinters(t *testing.T) {
|
func TestEnabledLinters(t *testing.T) {
|
||||||
// require to display the message "Active x linters: [x,y]"
|
// require to display the message "Active x linters: [x,y]"
|
||||||
t.Setenv(lintersdb.EnvTestRun, "1")
|
t.Setenv(lintersdb.EnvTestRun, "1")
|
||||||
|
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen
|
|
||||||
func Test_parseComments(t *testing.T) {
|
func Test_parseComments(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
filename string
|
filename string
|
||||||
|
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
"github.com/golangci/golangci-lint/pkg/exitcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
//nolint:funlen
|
|
||||||
func TestRunnerBuilder_Runner(t *testing.T) {
|
func TestRunnerBuilder_Runner(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
desc string
|
desc string
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue