output: add colored-tab ()

This commit is contained in:
Ludovic Fernandez 2023-04-04 01:57:22 +02:00 committed by GitHub
parent 9c46d7d7c0
commit 00d17cc8d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 157 additions and 39 deletions

View file

@ -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.

View file

@ -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

View file

@ -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:

View file

@ -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
} }

View file

@ -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

View file

@ -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

View file

@ -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...)
} }

View file

@ -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())
})
}
} }

View file

@ -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

View file

@ -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())
})
}
} }

View file

@ -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")

View file

@ -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

View file

@ -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