Skip to content

Commit d66ff11

Browse files
committed
chore: rewrite the configuration loading
1 parent 7646196 commit d66ff11

15 files changed

+272
-116
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package fakeloader
2+
3+
// Config implements [config.BaseConfig].
4+
// This only the stub for the real file loader.
5+
type Config struct {
6+
Version string `mapstructure:"version"`
7+
8+
cfgDir string // Path to the directory containing golangci-lint config file.
9+
}
10+
11+
func NewConfig() *Config {
12+
return &Config{}
13+
}
14+
15+
// SetConfigDir sets the path to directory that contains golangci-lint config file.
16+
func (c *Config) SetConfigDir(dir string) {
17+
c.cfgDir = dir
18+
}
19+
20+
func (*Config) IsInternalTest() bool {
21+
return false
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package fakeloader
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/go-viper/mapstructure/v2"
8+
9+
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/parser"
10+
"github.com/golangci/golangci-lint/pkg/config"
11+
)
12+
13+
// Load is used to keep case of configuration.
14+
// Viper serialize raw map keys in lowercase, this is a problem with the configuration of some linters.
15+
func Load(srcPath string, old any) error {
16+
file, err := os.Open(srcPath)
17+
if err != nil {
18+
return fmt.Errorf("open file: %w", err)
19+
}
20+
21+
defer func() { _ = file.Close() }()
22+
23+
raw := map[string]any{}
24+
25+
err = parser.Decode(file, raw)
26+
if err != nil {
27+
return err
28+
}
29+
30+
// NOTE: this is inspired by viper internals.
31+
cc := &mapstructure.DecoderConfig{
32+
Result: old,
33+
WeaklyTypedInput: true,
34+
DecodeHook: config.DecodeHookFunc(),
35+
}
36+
37+
decoder, err := mapstructure.NewDecoder(cc)
38+
if err != nil {
39+
return fmt.Errorf("constructing mapstructure decoder: %w", err)
40+
}
41+
42+
err = decoder.Decode(raw)
43+
if err != nil {
44+
return fmt.Errorf("decoding configuration file: %w", err)
45+
}
46+
47+
return nil
48+
}

pkg/commands/internal/migrate/migrate_test.go

+33-22
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,27 @@ import (
88
"strings"
99
"testing"
1010

11-
"github.com/spf13/viper"
1211
"github.com/stretchr/testify/assert"
1312
"github.com/stretchr/testify/require"
14-
"gopkg.in/yaml.v3"
1513

14+
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/fakeloader"
15+
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/parser"
1616
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versionone"
17-
"github.com/golangci/golangci-lint/pkg/config"
18-
"github.com/golangci/golangci-lint/pkg/logutils"
1917
)
2018

19+
type fakeFile struct {
20+
bytes.Buffer
21+
name string
22+
}
23+
24+
func newFakeFile(name string) *fakeFile {
25+
return &fakeFile{name: name}
26+
}
27+
28+
func (f *fakeFile) Name() string {
29+
return f.name
30+
}
31+
2132
func TestToConfig(t *testing.T) {
2233
var testFiles []string
2334

@@ -30,7 +41,7 @@ func TestToConfig(t *testing.T) {
3041
return nil
3142
}
3243

33-
if strings.HasSuffix(path, ".golden.yml") {
44+
if strings.Contains(path, ".golden.") {
3445
return nil
3546
}
3647

@@ -46,7 +57,7 @@ func TestToConfig(t *testing.T) {
4657
t.Parallel()
4758

4859
ext := filepath.Ext(fileIn)
49-
fileGolden := strings.TrimSuffix(fileIn, ext) + ".golden.yml"
60+
fileGolden := strings.TrimSuffix(fileIn, ext) + ".golden" + ext
5061

5162
testFile(t, fileIn, fileGolden, false)
5263
})
@@ -58,30 +69,33 @@ func testFile(t *testing.T, in, golden string, update bool) {
5869

5970
old := versionone.NewConfig()
6071

61-
options := config.LoaderOptions{Config: in}
62-
6372
// Fake load of the configuration.
6473
// IMPORTANT: The default values from flags are not set.
65-
loader := config.NewBaseLoader(logutils.NewStderrLog("skip"), viper.New(), options, old, nil)
66-
67-
err := loader.Load()
74+
err := fakeloader.Load(in, old)
6875
require.NoError(t, err)
6976

7077
if update {
7178
updateGolden(t, golden, old)
7279
}
7380

74-
expected, err := os.ReadFile(golden)
75-
require.NoError(t, err)
81+
buf := newFakeFile("test" + filepath.Ext(golden))
7682

77-
var buf bytes.Buffer
78-
encoder := yaml.NewEncoder(&buf)
79-
encoder.SetIndent(2)
83+
err = parser.Encode(ToConfig(old), buf)
84+
require.NoError(t, err)
8085

81-
err = encoder.Encode(ToConfig(old))
86+
expected, err := os.ReadFile(golden)
8287
require.NoError(t, err)
8388

84-
assert.YAMLEq(t, string(expected), buf.String())
89+
switch filepath.Ext(golden) {
90+
case ".yml":
91+
assert.YAMLEq(t, string(expected), buf.String())
92+
case ".json":
93+
assert.JSONEq(t, string(expected), buf.String())
94+
case ".toml":
95+
assert.Equal(t, string(expected), buf.String())
96+
default:
97+
require.Failf(t, "unsupported extension: %s", golden)
98+
}
8599
}
86100

87101
func updateGolden(t *testing.T, golden string, old *versionone.Config) {
@@ -94,9 +108,6 @@ func updateGolden(t *testing.T, golden string, old *versionone.Config) {
94108
_ = fileOut.Close()
95109
}()
96110

97-
encoder := yaml.NewEncoder(fileOut)
98-
encoder.SetIndent(2)
99-
100-
err = encoder.Encode(ToConfig(old))
111+
err = parser.Encode(ToConfig(old), fileOut)
101112
require.NoError(t, err)
102113
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package parser
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"path/filepath"
10+
"strings"
11+
12+
"github.com/pelletier/go-toml/v2"
13+
"gopkg.in/yaml.v3"
14+
)
15+
16+
type File interface {
17+
io.ReadWriter
18+
Name() string
19+
}
20+
21+
// Decode decodes a file into data.
22+
// The choice of the decoder is based on the file extension.
23+
func Decode(file File, data any) error {
24+
ext := filepath.Ext(file.Name())
25+
26+
switch strings.ToLower(ext) {
27+
case ".yaml", ".yml", ".json":
28+
err := yaml.NewDecoder(file).Decode(data)
29+
if err != nil && !errors.Is(err, io.EOF) {
30+
return fmt.Errorf("YAML decode file %s: %w", file.Name(), err)
31+
}
32+
33+
case ".toml":
34+
err := toml.NewDecoder(file).Decode(&data)
35+
if err != nil {
36+
return fmt.Errorf("TOML decode file %s: %w", file.Name(), err)
37+
}
38+
39+
default:
40+
return fmt.Errorf("unsupported file type: %s", ext)
41+
}
42+
43+
return nil
44+
}
45+
46+
// Encode encodes data into a file.
47+
// The choice of the encoder is based on the file extension.
48+
func Encode(data any, dstFile File) error {
49+
ext := filepath.Ext(dstFile.Name())
50+
51+
switch strings.ToLower(ext) {
52+
case ".yml", ".yaml":
53+
encoder := yaml.NewEncoder(dstFile)
54+
encoder.SetIndent(2)
55+
56+
return encoder.Encode(data)
57+
58+
case ".toml":
59+
encoder := toml.NewEncoder(dstFile)
60+
61+
return encoder.Encode(data)
62+
63+
case ".json":
64+
// The JSON encoder converts empty struct to `{}` instead of nothing (even with omitempty JSON struct tags).
65+
// So we need to use the YAML encoder as bridge to create JSON file.
66+
67+
var buf bytes.Buffer
68+
err := yaml.NewEncoder(&buf).Encode(data)
69+
if err != nil {
70+
return err
71+
}
72+
73+
raw := map[string]any{}
74+
err = yaml.NewDecoder(&buf).Decode(raw)
75+
if err != nil {
76+
return err
77+
}
78+
79+
encoder := json.NewEncoder(dstFile)
80+
encoder.SetIndent("", " ")
81+
82+
return encoder.Encode(raw)
83+
84+
default:
85+
return fmt.Errorf("unsupported file type: %s", ext)
86+
}
87+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"formatters": {
3+
"exclusions": {
4+
"generated": "lax"
5+
}
6+
},
7+
"linters": {
8+
"exclusions": {
9+
"generated": "lax",
10+
"paths": [
11+
"third_party$",
12+
"builtin$",
13+
"examples$"
14+
]
15+
}
16+
},
17+
"version": "2"
18+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
version = '2'
2+
3+
[linters]
4+
[linters.exclusions]
5+
generated = 'lax'
6+
paths = ['third_party$', 'builtin$', 'examples$']
7+
8+
[formatters]
9+
[formatters.exclusions]
10+
generated = 'lax'

pkg/commands/internal/migrate/testdata/empty.json

Whitespace-only changes.

pkg/commands/internal/migrate/testdata/empty.toml

Whitespace-only changes.

pkg/commands/internal/migrate/testdata/linters-settings_gocritic.golden.yml

+25-25
Original file line numberDiff line numberDiff line change
@@ -231,35 +231,35 @@ linters:
231231
- experimental
232232
- opinionated
233233
settings:
234-
captlocal:
235-
paramsonly: false
236-
commentedoutcode:
237-
minlength: 50
234+
captLocal:
235+
paramsOnly: false
236+
commentedOutCode:
237+
minLength: 50
238238
elseif:
239-
skipbalanced: false
240-
hugeparam:
241-
sizethreshold: 70
242-
ifelsechain:
243-
minthreshold: 4
244-
nestingreduce:
245-
bodywidth: 4
246-
rangeexprcopy:
247-
sizethreshold: 516
248-
skiptestfuncs: false
249-
rangevalcopy:
250-
sizethreshold: 32
251-
skiptestfuncs: false
239+
skipBalanced: false
240+
hugeParam:
241+
sizeThreshold: 70
242+
ifElseChain:
243+
minThreshold: 4
244+
nestingReduce:
245+
bodyWidth: 4
246+
rangeExprCopy:
247+
sizeThreshold: 516
248+
skipTestFuncs: false
249+
rangeValCopy:
250+
sizeThreshold: 32
251+
skipTestFuncs: false
252252
ruleguard:
253253
debug: emptyDecl
254254
disable: myGroupName,#myTagName
255255
enable: myGroupName,#myTagName
256-
failon: dsl,import
256+
failOn: dsl,import
257257
rules: ${base-path}/ruleguard/rules-*.go,${base-path}/myrule1.go
258-
toomanyresultschecker:
259-
maxresults: 10
260-
truncatecmp:
261-
skiparchdependent: false
258+
tooManyResultsChecker:
259+
maxResults: 10
260+
truncateCmp:
261+
skipArchDependent: false
262262
underef:
263-
skiprecvderef: false
264-
unnamedresult:
265-
checkexported: true
263+
skipRecvDeref: false
264+
unnamedResult:
265+
checkExported: true

pkg/commands/internal/migrate/testdata/linters-settings_goheader.golden.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ linters:
44
goheader:
55
values:
66
const:
7-
company: MY COMPANY
7+
COMPANY: MY COMPANY
88
regexp:
9-
author: .*@mycompany\.com
9+
AUTHOR: .*@mycompany\.com
1010
template: |-
1111
Put here copyright header template for source code files
1212
For example:

pkg/commands/internal/migrate/testdata/linters-settings_gosec.golden.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -87,20 +87,20 @@ linters:
8787
severity: medium
8888
confidence: medium
8989
config:
90-
g101:
90+
G101:
9191
entropy_threshold: "80.0"
9292
ignore_entropy: false
9393
pattern: (?i)example
9494
per_char_threshold: "3.0"
9595
truncate: "32"
96-
g104:
96+
G104:
9797
fmt:
9898
- Fscanf
99-
g111:
99+
G111:
100100
pattern: custom\.Dir\(\)
101-
g301: "0750"
102-
g302: "0600"
103-
g306: "0600"
101+
G301: "0750"
102+
G302: "0600"
103+
G306: "0600"
104104
global:
105105
'#nosec': '#my-custom-nosec'
106106
audit: true

0 commit comments

Comments
 (0)