-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
feat: migration command #5506
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
feat: migration command #5506
Changes from 24 commits
Commits
Show all changes
30 commits
Select commit
Hold shift + click to select a range
973f628
chore: neutral configuration loader
ldez e24f69c
chore: copy v1 configuration structures
ldez 519820a
chore: clean configuration v2
ldez 450752d
chore: configuration v2 cloner
ldez 1a74a2a
feat: migration command
ldez 4ef8f4c
tests: configuration flles
ldez 9bea57a
review: add comment on generated files
ldez 101ac2a
review: add comment about v1 configuration files
ldez b7ca75f
review: rephrase
ldez e102282
review: log level
ldez ca11e3e
review: remove a switch
ldez d7d4986
fix: add a missing linter names migration case
ldez 6e989eb
docs: improve v1 configuration files
ldez c95ec05
review: rename configuration packages
ldez 8a3fbff
review
ldez 064f43f
review: add log about run.timeout
ldez 5ec43d0
review
ldez 345e674
review
ldez 1ab0066
review
ldez 7df6432
review
ldez fe69114
feat: missing case
ldez a7e11f8
review
ldez fdf27fc
review
ldez e809f59
review
ldez 7646196
chore: reduce log verbosity
ldez d66ff11
chore: rewrite the configuration loading
ldez 0a90047
fix: toml multiline string
ldez 8883832
chore: split test files by extensions
ldez d35e0d4
fix: exclude-use-default is true by default
ldez 6cbdf61
review
ldez File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"go/ast" | ||
"go/parser" | ||
"go/printer" | ||
"go/token" | ||
"log" | ||
"os" | ||
"path/filepath" | ||
"reflect" | ||
"strings" | ||
|
||
"golang.org/x/tools/imports" | ||
) | ||
|
||
const newPkgName = "versiontwo" | ||
|
||
const ( | ||
srcDir = "./pkg/config" | ||
dstDir = "./pkg/commands/internal/migrate/versiontwo" | ||
) | ||
|
||
func main() { | ||
stat, err := os.Stat(srcDir) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
if !stat.IsDir() { | ||
log.Fatalf("%s is not a directory", srcDir) | ||
} | ||
|
||
_ = os.RemoveAll(dstDir) | ||
|
||
err = processPackage(srcDir, dstDir) | ||
if err != nil { | ||
log.Fatalf("Processing package error: %v", err) | ||
} | ||
} | ||
|
||
func processPackage(srcDir, dstDir string) error { | ||
return filepath.Walk(srcDir, func(srcPath string, _ os.FileInfo, err error) error { | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if skipFile(srcPath) { | ||
return nil | ||
} | ||
|
||
fset := token.NewFileSet() | ||
|
||
file, err := parser.ParseFile(fset, srcPath, nil, parser.AllErrors) | ||
if err != nil { | ||
return fmt.Errorf("parsing %s: %w", srcPath, err) | ||
} | ||
|
||
processFile(file) | ||
|
||
return writeNewFile(fset, file, srcPath, dstDir) | ||
}) | ||
} | ||
|
||
func skipFile(path string) bool { | ||
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { | ||
return true | ||
} | ||
|
||
switch filepath.Base(path) { | ||
case "base_loader.go", "loader.go": | ||
return true | ||
default: | ||
return false | ||
} | ||
} | ||
|
||
func processFile(file *ast.File) { | ||
file.Name.Name = newPkgName | ||
|
||
var newDecls []ast.Decl | ||
for _, decl := range file.Decls { | ||
d, ok := decl.(*ast.GenDecl) | ||
if !ok { | ||
continue | ||
} | ||
|
||
switch d.Tok { | ||
case token.CONST, token.VAR: | ||
continue | ||
case token.TYPE: | ||
for _, spec := range d.Specs { | ||
typeSpec, ok := spec.(*ast.TypeSpec) | ||
if !ok { | ||
continue | ||
} | ||
|
||
structType, ok := typeSpec.Type.(*ast.StructType) | ||
if !ok { | ||
continue | ||
} | ||
|
||
processStructFields(structType) | ||
} | ||
default: | ||
// noop | ||
} | ||
|
||
newDecls = append(newDecls, decl) | ||
} | ||
|
||
file.Decls = newDecls | ||
} | ||
|
||
func processStructFields(structType *ast.StructType) { | ||
var newFields []*ast.Field | ||
|
||
for _, field := range structType.Fields.List { | ||
if len(field.Names) > 0 && !field.Names[0].IsExported() { | ||
continue | ||
} | ||
|
||
if field.Tag == nil { | ||
continue | ||
} | ||
|
||
field.Type = convertType(field.Type) | ||
field.Tag.Value = convertStructTag(field.Tag.Value) | ||
|
||
newFields = append(newFields, field) | ||
} | ||
|
||
structType.Fields.List = newFields | ||
} | ||
|
||
func convertType(expr ast.Expr) ast.Expr { | ||
ident, ok := expr.(*ast.Ident) | ||
if !ok { | ||
return expr | ||
} | ||
|
||
switch ident.Name { | ||
case "bool", "string", "int", "int8", "int16", "int32", "int64", "float32", "float64": | ||
return &ast.StarExpr{X: ident} | ||
|
||
default: | ||
return expr | ||
} | ||
} | ||
|
||
func convertStructTag(value string) string { | ||
structTag := reflect.StructTag(strings.Trim(value, "`")) | ||
|
||
key := structTag.Get("mapstructure") | ||
|
||
if key == ",squash" { | ||
return wrapStructTag(`yaml:",inline"`) | ||
} | ||
|
||
return wrapStructTag(fmt.Sprintf(`yaml:"%[1]s,omitempty" toml:"%[1]s,omitempty"`, key)) | ||
} | ||
|
||
func wrapStructTag(s string) string { | ||
return "`" + s + "`" | ||
} | ||
|
||
func writeNewFile(fset *token.FileSet, file *ast.File, srcPath, dstDir string) error { | ||
var buf bytes.Buffer | ||
|
||
buf.WriteString("// Code generated by pkg/commands/internal/migrate/cloner/cloner.go. DO NOT EDIT.\n\n") | ||
|
||
err := printer.Fprint(&buf, fset, file) | ||
if err != nil { | ||
return fmt.Errorf("printing %s: %w", srcPath, err) | ||
} | ||
|
||
dstPath := filepath.Join(dstDir, filepath.Base(srcPath)) | ||
|
||
_ = os.MkdirAll(filepath.Dir(dstPath), os.ModePerm) | ||
|
||
formatted, err := imports.Process(dstPath, buf.Bytes(), nil) | ||
if err != nil { | ||
return fmt.Errorf("formatting %s: %w", dstPath, err) | ||
} | ||
|
||
//nolint:gosec,mnd // The permission is right. | ||
err = os.WriteFile(dstPath, formatted, 0o644) | ||
if err != nil { | ||
return fmt.Errorf("writing file %s: %w", dstPath, err) | ||
} | ||
|
||
return nil | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package migrate | ||
|
||
import ( | ||
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/ptr" | ||
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versionone" | ||
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versiontwo" | ||
) | ||
|
||
func ToConfig(old *versionone.Config) *versiontwo.Config { | ||
return &versiontwo.Config{ | ||
Version: ptr.Pointer("2"), | ||
Linters: toLinters(old), | ||
Formatters: toFormatters(old), | ||
Issues: toIssues(old), | ||
Output: toOutput(old), | ||
Severity: toSeverity(old), | ||
Run: toRun(old), | ||
} | ||
} |
ldez marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package migrate | ||
|
||
import ( | ||
"slices" | ||
"strings" | ||
|
||
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/ptr" | ||
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versionone" | ||
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versiontwo" | ||
) | ||
|
||
func toFormatters(old *versionone.Config) versiontwo.Formatters { | ||
enable := ProcessEffectiveFormatters(old.Linters) | ||
|
||
var paths []string | ||
if len(enable) > 0 { | ||
paths = slices.Concat(old.Issues.ExcludeFiles, old.Issues.ExcludeDirs) | ||
|
||
if old.Issues.UseDefaultExcludeDirs == nil || ptr.Deref(old.Issues.UseDefaultExcludeDirs) { | ||
paths = append(paths, "examples$") | ||
} | ||
} | ||
|
||
paths = append(paths, toFormattersPathsFromRules(old.Issues)...) | ||
|
||
return versiontwo.Formatters{ | ||
Enable: enable, | ||
Settings: versiontwo.FormatterSettings{ | ||
Gci: toGciSettings(old.LintersSettings.Gci), | ||
GoFmt: toGoFmtSettings(old.LintersSettings.GoFmt), | ||
GoFumpt: toGoFumptSettings(old.LintersSettings.GoFumpt), | ||
GoImports: toGoImportsSettings(old.LintersSettings.GoImports), | ||
}, | ||
Exclusions: versiontwo.FormatterExclusions{ | ||
Generated: toExclusionGenerated(old.Issues.ExcludeGenerated), | ||
Paths: paths, | ||
}, | ||
} | ||
} | ||
|
||
func toFormattersPathsFromRules(old versionone.Issues) []string { | ||
var results []string | ||
|
||
for _, rule := range old.ExcludeRules { | ||
allNames := convertStaticcheckLinterNames(convertAlternativeNames(rule.Linters)) | ||
|
||
names := onlyFormatterNames(allNames) | ||
if len(names) == 0 { | ||
continue | ||
} | ||
|
||
if ptr.Deref(rule.Path) == "" { | ||
continue | ||
} | ||
|
||
results = append(results, ptr.Deref(rule.Path)) | ||
ldez marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
return results | ||
} | ||
|
||
func toGciSettings(old versionone.GciSettings) versiontwo.GciSettings { | ||
return versiontwo.GciSettings{ | ||
Sections: old.Sections, | ||
NoInlineComments: old.NoInlineComments, | ||
NoPrefixComments: old.NoPrefixComments, | ||
CustomOrder: old.CustomOrder, | ||
NoLexOrder: old.NoLexOrder, | ||
} | ||
} | ||
|
||
func toGoFmtSettings(old versionone.GoFmtSettings) versiontwo.GoFmtSettings { | ||
settings := versiontwo.GoFmtSettings{ | ||
Simplify: old.Simplify, | ||
} | ||
|
||
for _, rule := range old.RewriteRules { | ||
settings.RewriteRules = append(settings.RewriteRules, versiontwo.GoFmtRewriteRule{ | ||
Pattern: rule.Pattern, | ||
Replacement: rule.Replacement, | ||
}) | ||
} | ||
|
||
return settings | ||
} | ||
|
||
func toGoFumptSettings(old versionone.GoFumptSettings) versiontwo.GoFumptSettings { | ||
return versiontwo.GoFumptSettings{ | ||
ModulePath: old.ModulePath, | ||
ExtraRules: old.ExtraRules, | ||
} | ||
} | ||
|
||
func toGoImportsSettings(old versionone.GoImportsSettings) versiontwo.GoImportsSettings { | ||
var localPrefixes []string | ||
|
||
prefixes := ptr.Deref(old.LocalPrefixes) | ||
if prefixes != "" { | ||
localPrefixes = strings.Split(prefixes, ",") | ||
} | ||
|
||
return versiontwo.GoImportsSettings{ | ||
LocalPrefixes: localPrefixes, | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package migrate | ||
|
||
import ( | ||
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versionone" | ||
"github.com/golangci/golangci-lint/pkg/commands/internal/migrate/versiontwo" | ||
) | ||
|
||
func toIssues(old *versionone.Config) versiontwo.Issues { | ||
return versiontwo.Issues{ | ||
MaxIssuesPerLinter: old.Issues.MaxIssuesPerLinter, | ||
MaxSameIssues: old.Issues.MaxSameIssues, | ||
UniqByLine: old.Issues.UniqByLine, | ||
DiffFromRevision: old.Issues.DiffFromRevision, | ||
DiffFromMergeBase: old.Issues.DiffFromMergeBase, | ||
DiffPatchFilePath: old.Issues.DiffPatchFilePath, | ||
WholeFiles: old.Issues.WholeFiles, | ||
Diff: old.Issues.Diff, | ||
NeedFix: old.Issues.NeedFix, | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.