Skip to content

Commit 6a37088

Browse files
authored
feat: migration command (#5506)
1 parent c3a7802 commit 6a37088

File tree

369 files changed

+13490
-263
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

369 files changed

+13490
-263
lines changed

.golangci.yml

+11
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,17 @@ linters:
171171
linters: [gosec]
172172
text: "G306: Expect WriteFile permissions to be 0600 or less"
173173

174+
# Related to migration command.
175+
- path: pkg/commands/internal/migrate/two/
176+
linters:
177+
- lll
178+
179+
# Related to migration command.
180+
- path: pkg/commands/internal/migrate/
181+
linters:
182+
- gocritic
183+
text: "hugeParam:"
184+
174185
formatters:
175186
enable:
176187
- gofmt

Makefile

+5
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ fast_check_generated:
6060
git checkout -- go.mod go.sum # can differ between go1.16 and go1.17
6161
git diff --exit-code # check no changes
6262

63+
# Migration
64+
65+
clone_config:
66+
go run ./pkg/commands/internal/migrate/cloner/
67+
6368
# Benchmark
6469

6570
# Benchmark with a local version
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"go/ast"
7+
"go/parser"
8+
"go/printer"
9+
"go/token"
10+
"log"
11+
"os"
12+
"path/filepath"
13+
"reflect"
14+
"strings"
15+
16+
"golang.org/x/tools/imports"
17+
)
18+
19+
const newPkgName = "versiontwo"
20+
21+
const (
22+
srcDir = "./pkg/config"
23+
dstDir = "./pkg/commands/internal/migrate/versiontwo"
24+
)
25+
26+
func main() {
27+
stat, err := os.Stat(srcDir)
28+
if err != nil {
29+
log.Fatal(err)
30+
}
31+
32+
if !stat.IsDir() {
33+
log.Fatalf("%s is not a directory", srcDir)
34+
}
35+
36+
_ = os.RemoveAll(dstDir)
37+
38+
err = processPackage(srcDir, dstDir)
39+
if err != nil {
40+
log.Fatalf("Processing package error: %v", err)
41+
}
42+
}
43+
44+
func processPackage(srcDir, dstDir string) error {
45+
return filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, err error) error {
46+
if err != nil {
47+
return err
48+
}
49+
50+
if skipFile(srcPath) {
51+
return nil
52+
}
53+
54+
fset := token.NewFileSet()
55+
56+
file, err := parser.ParseFile(fset, srcPath, nil, parser.AllErrors)
57+
if err != nil {
58+
return fmt.Errorf("parsing %s: %w", srcPath, err)
59+
}
60+
61+
processFile(file)
62+
63+
return writeNewFile(fset, file, srcPath, dstDir)
64+
})
65+
}
66+
67+
func skipFile(path string) bool {
68+
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
69+
return true
70+
}
71+
72+
switch filepath.Base(path) {
73+
case "base_loader.go", "loader.go":
74+
return true
75+
default:
76+
return false
77+
}
78+
}
79+
80+
func processFile(file *ast.File) {
81+
file.Name.Name = newPkgName
82+
83+
var newDecls []ast.Decl
84+
for _, decl := range file.Decls {
85+
d, ok := decl.(*ast.GenDecl)
86+
if !ok {
87+
continue
88+
}
89+
90+
switch d.Tok {
91+
case token.CONST, token.VAR:
92+
continue
93+
case token.TYPE:
94+
for _, spec := range d.Specs {
95+
typeSpec, ok := spec.(*ast.TypeSpec)
96+
if !ok {
97+
continue
98+
}
99+
100+
structType, ok := typeSpec.Type.(*ast.StructType)
101+
if !ok {
102+
continue
103+
}
104+
105+
processStructFields(structType)
106+
}
107+
default:
108+
// noop
109+
}
110+
111+
newDecls = append(newDecls, decl)
112+
}
113+
114+
file.Decls = newDecls
115+
}
116+
117+
func processStructFields(structType *ast.StructType) {
118+
var newFields []*ast.Field
119+
120+
for _, field := range structType.Fields.List {
121+
if len(field.Names) > 0 && !field.Names[0].IsExported() {
122+
continue
123+
}
124+
125+
if field.Tag == nil {
126+
continue
127+
}
128+
129+
field.Type = convertType(field.Type)
130+
field.Tag.Value = convertStructTag(field.Tag.Value)
131+
132+
newFields = append(newFields, field)
133+
}
134+
135+
structType.Fields.List = newFields
136+
}
137+
138+
func convertType(expr ast.Expr) ast.Expr {
139+
ident, ok := expr.(*ast.Ident)
140+
if !ok {
141+
return expr
142+
}
143+
144+
switch ident.Name {
145+
case "bool", "string", "int", "int8", "int16", "int32", "int64", "float32", "float64":
146+
return &ast.StarExpr{X: ident}
147+
148+
default:
149+
return expr
150+
}
151+
}
152+
153+
func convertStructTag(value string) string {
154+
structTag := reflect.StructTag(strings.Trim(value, "`"))
155+
156+
key := structTag.Get("mapstructure")
157+
158+
if key == ",squash" {
159+
return wrapStructTag(`yaml:",inline"`)
160+
}
161+
162+
return wrapStructTag(fmt.Sprintf(`yaml:"%[1]s,omitempty" toml:"%[1]s,multiline,omitempty"`, key))
163+
}
164+
165+
func wrapStructTag(s string) string {
166+
return "`" + s + "`"
167+
}
168+
169+
func writeNewFile(fset *token.FileSet, file *ast.File, srcPath, dstDir string) error {
170+
var buf bytes.Buffer
171+
172+
buf.WriteString("// Code generated by pkg/commands/internal/migrate/cloner/cloner.go. DO NOT EDIT.\n\n")
173+
174+
err := printer.Fprint(&buf, fset, file)
175+
if err != nil {
176+
return fmt.Errorf("printing %s: %w", srcPath, err)
177+
}
178+
179+
dstPath := filepath.Join(dstDir, filepath.Base(srcPath))
180+
181+
_ = os.MkdirAll(filepath.Dir(dstPath), os.ModePerm)
182+
183+
formatted, err := imports.Process(dstPath, buf.Bytes(), nil)
184+
if err != nil {
185+
return fmt.Errorf("formatting %s: %w", dstPath, err)
186+
}
187+
188+
//nolint:gosec,mnd // The permission is right.
189+
err = os.WriteFile(dstPath, formatted, 0o644)
190+
if err != nil {
191+
return fmt.Errorf("writing file %s: %w", dstPath, err)
192+
}
193+
194+
return nil
195+
}
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+
}
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package migrate
2+
3+
import (
4+
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/ptr"
5+
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versionone"
6+
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versiontwo"
7+
)
8+
9+
func ToConfig(old *versionone.Config) *versiontwo.Config {
10+
return &versiontwo.Config{
11+
Version: ptr.Pointer("2"),
12+
Linters: toLinters(old),
13+
Formatters: toFormatters(old),
14+
Issues: toIssues(old),
15+
Output: toOutput(old),
16+
Severity: toSeverity(old),
17+
Run: toRun(old),
18+
}
19+
}

0 commit comments

Comments
 (0)